initial commit
This commit is contained in:
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
55
backend/app/auth.py
Normal file
55
backend/app/auth.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from datetime import datetime, timedelta
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.models.models import User
|
||||
|
||||
SECRET_KEY = "your-secret-key-change-in-production-make-it-very-long-and-secure"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 часа
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth/login")
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
def get_password_hash(password):
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def create_access_token(data: dict):
|
||||
to_encode = data.copy()
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
except JWTError as e:
|
||||
print(f"JWT Error: {e}")
|
||||
raise credentials_exception
|
||||
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
if user.is_banned:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Ваш аккаунт заблокирован"
|
||||
)
|
||||
|
||||
return user
|
||||
16
backend/app/database.py
Normal file
16
backend/app/database.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./music_platform.db"
|
||||
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
31
backend/app/main.py
Normal file
31
backend/app/main.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import os
|
||||
|
||||
app = FastAPI(title="Music Platform API")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:5173"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
os.makedirs("uploads/music", exist_ok=True)
|
||||
os.makedirs("uploads/covers", exist_ok=True)
|
||||
|
||||
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|
||||
|
||||
from app.routes import auth, music, playlists, rooms, admin
|
||||
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
||||
app.include_router(music.router, prefix="/api/music", tags=["music"])
|
||||
app.include_router(playlists.router, prefix="/api/playlists", tags=["playlists"])
|
||||
app.include_router(rooms.router, prefix="/api/rooms", tags=["rooms"])
|
||||
app.include_router(admin.router)
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "Music Platform API"}
|
||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
62
backend/app/models/models.py
Normal file
62
backend/app/models/models.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Table, DateTime, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from app.database import Base
|
||||
|
||||
playlist_songs = Table('playlist_songs', Base.metadata,
|
||||
Column('playlist_id', Integer, ForeignKey('playlists.id')),
|
||||
Column('song_id', Integer, ForeignKey('songs.id'))
|
||||
)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String, unique=True, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
is_admin = Column(Boolean, default=False)
|
||||
is_owner = Column(Boolean, default=False)
|
||||
is_banned = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
songs = relationship("Song", back_populates="owner")
|
||||
playlists = relationship("Playlist", back_populates="owner")
|
||||
rooms = relationship("Room", back_populates="creator")
|
||||
|
||||
class Song(Base):
|
||||
__tablename__ = "songs"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String, index=True)
|
||||
artist = Column(String)
|
||||
file_path = Column(String)
|
||||
cover_path = Column(String, nullable=True)
|
||||
duration = Column(Integer)
|
||||
owner_id = Column(Integer, ForeignKey("users.id"))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
owner = relationship("User", back_populates="songs")
|
||||
playlists = relationship("Playlist", secondary=playlist_songs, back_populates="songs")
|
||||
|
||||
class Playlist(Base):
|
||||
__tablename__ = "playlists"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String)
|
||||
description = Column(String, nullable=True)
|
||||
is_public = Column(Boolean, default=False)
|
||||
owner_id = Column(Integer, ForeignKey("users.id"))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
owner = relationship("User", back_populates="playlists")
|
||||
songs = relationship("Song", secondary=playlist_songs, back_populates="playlists")
|
||||
|
||||
class Room(Base):
|
||||
__tablename__ = "rooms"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String)
|
||||
code = Column(String, unique=True, index=True)
|
||||
creator_id = Column(Integer, ForeignKey("users.id"))
|
||||
current_song_id = Column(Integer, ForeignKey("songs.id"), nullable=True)
|
||||
is_playing = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
creator = relationship("User", back_populates="rooms")
|
||||
0
backend/app/routes/__init__.py
Normal file
0
backend/app/routes/__init__.py
Normal file
174
backend/app/routes/admin.py
Normal file
174
backend/app/routes/admin.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.models.models import User, Song
|
||||
from app.auth import get_current_user
|
||||
from pydantic import BaseModel
|
||||
import os
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
||||
|
||||
def get_admin_user(current_user: User = Depends(get_current_user)):
|
||||
if not current_user.is_admin and not current_user.is_owner:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Доступ запрещен. Требуются права администратора"
|
||||
)
|
||||
return current_user
|
||||
|
||||
def get_owner_user(current_user: User = Depends(get_current_user)):
|
||||
if not current_user.is_owner:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Доступ запрещен. Требуются права создателя"
|
||||
)
|
||||
return current_user
|
||||
|
||||
class UpdateSongRequest(BaseModel):
|
||||
title: str
|
||||
artist: str
|
||||
|
||||
class BanUserRequest(BaseModel):
|
||||
user_id: int
|
||||
is_banned: bool
|
||||
|
||||
class PromoteUserRequest(BaseModel):
|
||||
user_id: int
|
||||
is_admin: bool
|
||||
|
||||
@router.get("/users")
|
||||
def get_all_users(
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
users = db.query(User).all()
|
||||
return [{
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
"is_admin": user.is_admin,
|
||||
"is_owner": user.is_owner,
|
||||
"is_banned": user.is_banned,
|
||||
"created_at": user.created_at
|
||||
} for user in users]
|
||||
|
||||
@router.get("/songs")
|
||||
def get_all_songs(
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
songs = db.query(Song).all()
|
||||
return [{
|
||||
"id": song.id,
|
||||
"title": song.title,
|
||||
"artist": song.artist,
|
||||
"file_path": song.file_path,
|
||||
"cover_path": song.cover_path,
|
||||
"owner_id": song.owner_id,
|
||||
"owner_username": song.owner.username if song.owner else None,
|
||||
"created_at": song.created_at
|
||||
} for song in songs]
|
||||
|
||||
@router.put("/songs/{song_id}")
|
||||
def update_song(
|
||||
song_id: int,
|
||||
request: UpdateSongRequest,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Песня не найдена")
|
||||
|
||||
song.title = request.title
|
||||
song.artist = request.artist
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
|
||||
return {"message": "Песня обновлена", "song": {
|
||||
"id": song.id,
|
||||
"title": song.title,
|
||||
"artist": song.artist
|
||||
}}
|
||||
|
||||
@router.delete("/songs/{song_id}")
|
||||
def delete_song(
|
||||
song_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Песня не найдена")
|
||||
|
||||
# Удаляем файлы
|
||||
if song.file_path and os.path.exists(song.file_path):
|
||||
os.remove(song.file_path)
|
||||
if song.cover_path and os.path.exists(song.cover_path):
|
||||
os.remove(song.cover_path)
|
||||
|
||||
db.delete(song)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Песня удалена"}
|
||||
|
||||
@router.post("/users/ban")
|
||||
def ban_user(
|
||||
request: BanUserRequest,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
user = db.query(User).filter(User.id == request.user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
if user.is_admin or user.is_owner:
|
||||
raise HTTPException(status_code=400, detail="Нельзя забанить администратора или создателя")
|
||||
|
||||
user.is_banned = request.is_banned
|
||||
db.commit()
|
||||
|
||||
return {"message": f"Пользователь {'забанен' if request.is_banned else 'разбанен'}"}
|
||||
|
||||
@router.post("/users/promote")
|
||||
def promote_user(
|
||||
request: PromoteUserRequest,
|
||||
db: Session = Depends(get_db),
|
||||
owner: User = Depends(get_owner_user)
|
||||
):
|
||||
user = db.query(User).filter(User.id == request.user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
if user.is_owner:
|
||||
raise HTTPException(status_code=400, detail="Нельзя изменить права создателя")
|
||||
|
||||
user.is_admin = request.is_admin
|
||||
db.commit()
|
||||
|
||||
return {"message": f"Пользователь {'повышен до администратора' if request.is_admin else 'понижен до пользователя'}"}
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
def delete_user(
|
||||
user_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
if user.is_admin or user.is_owner:
|
||||
raise HTTPException(status_code=400, detail="Нельзя удалить администратора или создателя")
|
||||
|
||||
# Удаляем все песни пользователя
|
||||
for song in user.songs:
|
||||
if song.file_path and os.path.exists(song.file_path):
|
||||
os.remove(song.file_path)
|
||||
if song.cover_path and os.path.exists(song.cover_path):
|
||||
os.remove(song.cover_path)
|
||||
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Пользователь удален"}
|
||||
57
backend/app/routes/auth.py
Normal file
57
backend/app/routes/auth.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from app.database import get_db
|
||||
from app.models.models import User
|
||||
from app.auth import get_password_hash, verify_password, create_access_token, get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
username: str
|
||||
email: str
|
||||
password: str
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
@router.post("/register")
|
||||
def register(user: UserCreate, db: Session = Depends(get_db)):
|
||||
if db.query(User).filter(User.username == user.username).first():
|
||||
raise HTTPException(status_code=400, detail="Username already exists")
|
||||
if db.query(User).filter(User.email == user.email).first():
|
||||
raise HTTPException(status_code=400, detail="Email already exists")
|
||||
|
||||
db_user = User(
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
hashed_password=get_password_hash(user.password)
|
||||
)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return {"message": "User created successfully"}
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
||||
user = db.query(User).filter(User.username == form_data.username).first()
|
||||
if not user or not verify_password(form_data.password, user.hashed_password):
|
||||
raise HTTPException(status_code=401, detail="Incorrect username or password")
|
||||
|
||||
if user.is_banned:
|
||||
raise HTTPException(status_code=403, detail="Ваш аккаунт заблокирован")
|
||||
|
||||
access_token = create_access_token(data={"sub": user.username})
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
@router.get("/me")
|
||||
def get_current_user_info(current_user: User = Depends(get_current_user)):
|
||||
return {
|
||||
"id": current_user.id,
|
||||
"username": current_user.username,
|
||||
"email": current_user.email,
|
||||
"is_admin": current_user.is_admin,
|
||||
"is_owner": current_user.is_owner
|
||||
}
|
||||
72
backend/app/routes/music.py
Normal file
72
backend/app/routes/music.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, Form, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
import shutil
|
||||
import os
|
||||
from app.database import get_db
|
||||
from app.models.models import Song, User
|
||||
from app.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class SongResponse(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
artist: str
|
||||
file_path: str
|
||||
cover_path: str | None
|
||||
duration: int
|
||||
owner_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_song(
|
||||
title: str = Form(...),
|
||||
artist: str = Form(...),
|
||||
duration: int = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
cover: UploadFile = File(None),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
file_path = f"uploads/music/{current_user.id}_{file.filename}"
|
||||
with open(file_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
cover_path = None
|
||||
if cover:
|
||||
cover_path = f"uploads/covers/{current_user.id}_{cover.filename}"
|
||||
with open(cover_path, "wb") as buffer:
|
||||
shutil.copyfileobj(cover.file, buffer)
|
||||
|
||||
song = Song(
|
||||
title=title,
|
||||
artist=artist,
|
||||
file_path=file_path,
|
||||
cover_path=cover_path,
|
||||
duration=duration,
|
||||
owner_id=current_user.id
|
||||
)
|
||||
db.add(song)
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
return song
|
||||
|
||||
@router.get("/songs", response_model=List[SongResponse])
|
||||
def get_songs(db: Session = Depends(get_db)):
|
||||
return db.query(Song).all()
|
||||
|
||||
@router.get("/my-songs", response_model=List[SongResponse])
|
||||
def get_my_songs(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
return db.query(Song).filter(Song.owner_id == current_user.id).all()
|
||||
|
||||
@router.get("/download/{song_id}")
|
||||
def download_song(song_id: int, db: Session = Depends(get_db)):
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
return FileResponse(song.file_path, filename=f"{song.title}.mp3")
|
||||
112
backend/app/routes/playlists.py
Normal file
112
backend/app/routes/playlists.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from app.database import get_db
|
||||
from app.models.models import Playlist, Song, User
|
||||
from app.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class PlaylistCreate(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
is_public: bool = False
|
||||
|
||||
class PlaylistResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str | None
|
||||
is_public: bool
|
||||
owner_id: int
|
||||
songs: List[dict]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@router.post("/create")
|
||||
def create_playlist(
|
||||
playlist: PlaylistCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
db_playlist = Playlist(
|
||||
name=playlist.name,
|
||||
description=playlist.description,
|
||||
is_public=playlist.is_public,
|
||||
owner_id=current_user.id
|
||||
)
|
||||
db.add(db_playlist)
|
||||
db.commit()
|
||||
db.refresh(db_playlist)
|
||||
return db_playlist
|
||||
|
||||
@router.get("/my-playlists")
|
||||
def get_my_playlists(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
return db.query(Playlist).filter(Playlist.owner_id == current_user.id).all()
|
||||
|
||||
@router.get("/public")
|
||||
def get_public_playlists(db: Session = Depends(get_db)):
|
||||
return db.query(Playlist).filter(Playlist.is_public == True).all()
|
||||
|
||||
@router.post("/{playlist_id}/add-song/{song_id}")
|
||||
def add_song_to_playlist(
|
||||
playlist_id: int,
|
||||
song_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
|
||||
if not playlist or playlist.owner_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
playlist.songs.append(song)
|
||||
db.commit()
|
||||
return {"message": "Song added to playlist"}
|
||||
|
||||
@router.get("/{playlist_id}")
|
||||
def get_playlist(playlist_id: int, db: Session = Depends(get_db)):
|
||||
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
|
||||
if not playlist:
|
||||
raise HTTPException(status_code=404, detail="Playlist not found")
|
||||
|
||||
songs_data = [{
|
||||
"id": song.id,
|
||||
"title": song.title,
|
||||
"artist": song.artist,
|
||||
"file_path": song.file_path,
|
||||
"cover_path": song.cover_path,
|
||||
"duration": song.duration,
|
||||
"owner_id": song.owner_id
|
||||
} for song in playlist.songs]
|
||||
|
||||
return {
|
||||
"id": playlist.id,
|
||||
"name": playlist.name,
|
||||
"description": playlist.description,
|
||||
"is_public": playlist.is_public,
|
||||
"owner_id": playlist.owner_id,
|
||||
"songs": songs_data
|
||||
}
|
||||
|
||||
@router.delete("/{playlist_id}/remove-song/{song_id}")
|
||||
def remove_song_from_playlist(
|
||||
playlist_id: int,
|
||||
song_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
|
||||
if not playlist or playlist.owner_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if song in playlist.songs:
|
||||
playlist.songs.remove(song)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Song removed from playlist"}
|
||||
128
backend/app/routes/rooms.py
Normal file
128
backend/app/routes/rooms.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, List
|
||||
from pydantic import BaseModel
|
||||
import random
|
||||
import string
|
||||
from app.database import get_db
|
||||
from app.models.models import Room, User, Song, Playlist
|
||||
from app.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class RoomCreate(BaseModel):
|
||||
name: str
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: Dict[str, List[dict]] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, room_code: str, username: str):
|
||||
await websocket.accept()
|
||||
if room_code not in self.active_connections:
|
||||
self.active_connections[room_code] = []
|
||||
|
||||
# Проверяем, не подключен ли уже этот пользователь
|
||||
existing = [conn for conn in self.active_connections[room_code] if conn["username"] == username]
|
||||
if not existing:
|
||||
self.active_connections[room_code].append({"ws": websocket, "username": username})
|
||||
|
||||
await self.broadcast({"type": "user_count", "count": len(self.active_connections[room_code])}, room_code)
|
||||
|
||||
def disconnect(self, websocket: WebSocket, room_code: str):
|
||||
if room_code in self.active_connections:
|
||||
self.active_connections[room_code] = [
|
||||
conn for conn in self.active_connections[room_code] if conn["ws"] != websocket
|
||||
]
|
||||
return len(self.active_connections[room_code])
|
||||
return 0
|
||||
|
||||
async def broadcast(self, message: dict, room_code: str):
|
||||
if room_code in self.active_connections:
|
||||
disconnected = []
|
||||
for connection in self.active_connections[room_code]:
|
||||
try:
|
||||
await connection["ws"].send_json(message)
|
||||
except:
|
||||
disconnected.append(connection)
|
||||
|
||||
# Удаляем отключенные соединения
|
||||
for conn in disconnected:
|
||||
if conn in self.active_connections[room_code]:
|
||||
self.active_connections[room_code].remove(conn)
|
||||
|
||||
def get_user_count(self, room_code: str):
|
||||
if room_code in self.active_connections:
|
||||
return len(self.active_connections[room_code])
|
||||
return 0
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
def generate_room_code():
|
||||
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
||||
|
||||
@router.post("/create")
|
||||
def create_room(
|
||||
room: RoomCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
code = generate_room_code()
|
||||
db_room = Room(name=room.name, code=code, creator_id=current_user.id)
|
||||
db.add(db_room)
|
||||
db.commit()
|
||||
db.refresh(db_room)
|
||||
return db_room
|
||||
|
||||
@router.get("/{room_code}")
|
||||
def get_room(room_code: str, db: Session = Depends(get_db)):
|
||||
room = db.query(Room).filter(Room.code == room_code).first()
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
user_count = manager.get_user_count(room_code)
|
||||
return {**room.__dict__, "user_count": user_count}
|
||||
|
||||
@router.post("/{room_code}/add-song/{song_id}")
|
||||
def add_song_to_room(
|
||||
room_code: str,
|
||||
song_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
room = db.query(Room).filter(Room.code == room_code).first()
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
|
||||
song = db.query(Song).filter(Song.id == song_id).first()
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
return {"message": "Song added to queue"}
|
||||
|
||||
@router.post("/{room_code}/add-playlist/{playlist_id}")
|
||||
def add_playlist_to_room(
|
||||
room_code: str,
|
||||
playlist_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
room = db.query(Room).filter(Room.code == room_code).first()
|
||||
if not room:
|
||||
raise HTTPException(status_code=404, detail="Room not found")
|
||||
|
||||
playlist = db.query(Playlist).filter(Playlist.id == playlist_id).first()
|
||||
if not playlist:
|
||||
raise HTTPException(status_code=404, detail="Playlist not found")
|
||||
|
||||
return {"message": "Playlist added to queue", "songs": [{"id": s.id, "title": s.title, "artist": s.artist} for s in playlist.songs]}
|
||||
|
||||
@router.websocket("/ws/{room_code}")
|
||||
async def websocket_endpoint(websocket: WebSocket, room_code: str, username: str = "User"):
|
||||
await manager.connect(websocket, room_code, username)
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
await manager.broadcast(data, room_code)
|
||||
except WebSocketDisconnect:
|
||||
count = manager.disconnect(websocket, room_code)
|
||||
await manager.broadcast({"type": "user_count", "count": count}, room_code)
|
||||
Reference in New Issue
Block a user