diff --git a/.env.example b/.env.example index 4950136..8cb46de 100644 --- a/.env.example +++ b/.env.example @@ -14,5 +14,11 @@ S3_BUCKET_NAME=enigfm S3_REGION=ru-1 # Limits -MAX_FILE_SIZE_MB=10 +MAX_FILE_SIZE_MB=100 MAX_STORAGE_GB=90 +MAX_ROOM_PARTICIPANTS=50 + +# Frontend (Vite) +# VITE_API_URL - оставляем пустым для использования относительных путей +# VITE_WS_URL - оставляем пустым для автоопределения +VITE_MAX_FILE_SIZE_MB=100 diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 847e307..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,17 +0,0 @@ -# Database -DATABASE_URL=postgresql://postgres:postgres@localhost:4002/enigfm - -# JWT -SECRET_KEY=your-secret-key-change-in-production - -# S3 (FirstVDS) -S3_ENDPOINT_URL=https://s3.firstvds.ru -S3_ACCESS_KEY=your-access-key -S3_SECRET_KEY=your-secret-key -S3_BUCKET_NAME=enigfm -S3_REGION=ru-1 - -# Limits -MAX_FILE_SIZE_MB=10 -MAX_STORAGE_GB=90 -MAX_ROOM_PARTICIPANTS=50 diff --git a/backend/app/config.py b/backend/app/config.py index d5a585d..0dc9952 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -24,7 +24,7 @@ class Settings(BaseSettings): max_room_participants: int = 50 class Config: - env_file = ".env" + # env_file не нужен - переменные передаются через docker-compose extra = "ignore" diff --git a/backend/app/routers/rooms.py b/backend/app/routers/rooms.py index 2033dbc..629949a 100644 --- a/backend/app/routers/rooms.py +++ b/backend/app/routers/rooms.py @@ -7,7 +7,7 @@ from ..database import get_db from ..models.user import User from ..models.room import Room, RoomParticipant from ..models.track import RoomQueue -from ..schemas.room import RoomCreate, RoomResponse, RoomDetailResponse, QueueAdd, QueueAddMultiple +from ..schemas.room import RoomCreate, RoomResponse, RoomDetailResponse, QueueAdd, QueueAddMultiple, QueueItemResponse from ..schemas.track import TrackResponse from ..schemas.user import UserResponse from ..services.auth import get_current_user @@ -178,7 +178,7 @@ async def leave_room( return {"status": "left"} -@router.get("/{room_id}/queue", response_model=list[TrackResponse]) +@router.get("/{room_id}/queue", response_model=list[QueueItemResponse]) async def get_queue(room_id: UUID, db: AsyncSession = Depends(get_db)): result = await db.execute( select(RoomQueue) @@ -187,7 +187,13 @@ async def get_queue(room_id: UUID, db: AsyncSession = Depends(get_db)): .order_by(RoomQueue.position) ) queue_items = result.scalars().all() - return [TrackResponse.model_validate(item.track) for item in queue_items] + return [ + QueueItemResponse( + track=TrackResponse.model_validate(item.track), + added_by=item.added_by + ) + for item in queue_items + ] @router.post("/{room_id}/queue") @@ -314,13 +320,25 @@ async def remove_from_queue( ) queue_item = result.scalar_one_or_none() - if queue_item: - await db.delete(queue_item) - - # Notify others - await manager.broadcast_to_room( - room_id, - {"type": "queue_updated"}, + if not queue_item: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Track not in queue" ) + # Check if user added this track to queue + if queue_item.added_by != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Can only remove tracks you added" + ) + + await db.delete(queue_item) + + # Notify others + await manager.broadcast_to_room( + room_id, + {"type": "queue_updated"}, + ) + return {"status": "removed"} diff --git a/backend/app/schemas/room.py b/backend/app/schemas/room.py index 069b49e..199a98e 100644 --- a/backend/app/schemas/room.py +++ b/backend/app/schemas/room.py @@ -49,3 +49,16 @@ class QueueAdd(BaseModel): class QueueAddMultiple(BaseModel): track_ids: list[UUID] + + +class QueueItemResponse(BaseModel): + track: "TrackResponse" + added_by: UUID + + class Config: + from_attributes = True + + +# Import at the end to avoid circular imports +from .track import TrackResponse +QueueItemResponse.model_rebuild() diff --git a/docker-compose.yml b/docker-compose.yml index af66655..9376af5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,10 @@ services: S3_ACCESS_KEY: ${S3_ACCESS_KEY} S3_SECRET_KEY: ${S3_SECRET_KEY} S3_BUCKET_NAME: ${S3_BUCKET_NAME:-enigfm} - S3_REGION: ${S3_REGION:-ru-1} + S3_REGION: ${S3_REGION:-default} + MAX_FILE_SIZE_MB: ${MAX_FILE_SIZE_MB:-10} + MAX_STORAGE_GB: ${MAX_STORAGE_GB:-90} + MAX_ROOM_PARTICIPANTS: ${MAX_ROOM_PARTICIPANTS:-50} ports: - "4001:8000" depends_on: @@ -40,6 +43,8 @@ services: build: context: ./frontend dockerfile: Dockerfile + args: + VITE_MAX_FILE_SIZE_MB: ${VITE_MAX_FILE_SIZE_MB:-100} ports: - "4000:80" depends_on: diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index f5dabbb..0000000 --- a/frontend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -VITE_API_URL=http://localhost:4001 -VITE_WS_URL=ws://localhost:4001 -VITE_MAX_FILE_SIZE_MB=10 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9d435a8..c308d7b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -3,6 +3,12 @@ FROM node:20-alpine as build WORKDIR /app +# Build arguments (from docker-compose) +ARG VITE_MAX_FILE_SIZE_MB + +# Set as env variables for Vite build +ENV VITE_MAX_FILE_SIZE_MB=${VITE_MAX_FILE_SIZE_MB} + COPY package*.json ./ RUN npm install diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 4237727..45a8c43 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -5,7 +5,7 @@ server { index index.html; # Max upload size - client_max_body_size 30M; + client_max_body_size 100M; # Gzip compression gzip on; @@ -27,6 +27,9 @@ server { proxy_buffering off; proxy_cache off; proxy_request_buffering off; + + # Max upload size for API + client_max_body_size 100M; } # WebSocket proxy diff --git a/frontend/package.json b/frontend/package.json index 8fa2f58..7c28355 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,10 +12,13 @@ "vue": "^3.4.15", "vue-router": "^4.2.5", "pinia": "^2.1.7", - "axios": "^1.6.5" + "axios": "^1.6.5", + "vuetify": "^3.5.0", + "@mdi/font": "^7.4.47" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.3", - "vite": "^5.0.11" + "vite": "^5.0.11", + "vite-plugin-vuetify": "^2.0.1" } } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 85c2c40..dc771fd 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,37 +1,187 @@ diff --git a/frontend/src/components/chat/ChatWindow.vue b/frontend/src/components/chat/ChatWindow.vue index 4ed240f..eb4541b 100644 --- a/frontend/src/components/chat/ChatWindow.vue +++ b/frontend/src/components/chat/ChatWindow.vue @@ -1,23 +1,38 @@ @@ -74,12 +89,7 @@ function scrollToBottom() { .chat { display: flex; flex-direction: column; - height: 400px; -} - -.chat h3 { - margin: 0 0 12px 0; - font-size: 16px; + height: 450px; } .messages { @@ -88,20 +98,19 @@ function scrollToBottom() { display: flex; flex-direction: column; gap: 8px; - padding-right: 8px; + padding: 16px; +} + +.empty-chat { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; } .chat-input { - display: flex; - gap: 8px; - margin-top: 12px; -} - -.chat-input input { - flex: 1; -} - -.chat-input button { - white-space: nowrap; + padding: 16px; } diff --git a/frontend/src/components/player/MiniPlayer.vue b/frontend/src/components/player/MiniPlayer.vue index aca00cb..ce32a57 100644 --- a/frontend/src/components/player/MiniPlayer.vue +++ b/frontend/src/components/player/MiniPlayer.vue @@ -1,76 +1,136 @@ diff --git a/frontend/src/components/room/Queue.vue b/frontend/src/components/room/Queue.vue index 803b80b..209dddf 100644 --- a/frontend/src/components/room/Queue.vue +++ b/frontend/src/components/room/Queue.vue @@ -1,36 +1,67 @@