Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
# App Settings
app_name=
app_env=
app_version=

APP_NAME=musicstreamer
APP_ENV=development
APP_VERSION=1.0.0

# Database Settings
postgres_db=
postgres_user=
postgres_password=
postgres_server=
postgres_port=
POSTGRES_DB=music_stream_secure
POSTGRES_USER=music_admin
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432

# JWT Authentication
jwt_secret_key=
jwt_algorithm=
access_token_expire_minutes=
refresh_token_expire_minutes=
JWT_SECRET_KEY=your_jwt_secret_key_here
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=60
REFRESH_TOKEN_EXPIRE_MINUTES=43200

# Password Security
password_pepper=
PASSWORD_PEPPER=password_pepper_here

# Test User Credentials
TEST_ADMIN_USERNAME=test_admin
[email protected]
TEST_ADMIN_PASSWORD=AdminPass123!
TEST_ADMIN_FIRST_NAME=Test
TEST_ADMIN_LAST_NAME=Admin

TEST_MUSICIAN_USERNAME=test_musician
[email protected]
TEST_MUSICIAN_PASSWORD=MusicianPass123!
TEST_MUSICIAN_FIRST_NAME=Test
TEST_MUSICIAN_LAST_NAME=Musician
TEST_MUSICIAN_STAGE_NAME=Test Musician
TEST_MUSICIAN_BIO=A test musician for development

TEST_LISTENER_USERNAME=test_listener
[email protected]
TEST_LISTENER_PASSWORD=ListenerPass123!
TEST_LISTENER_FIRST_NAME=Test
TEST_LISTENER_LAST_NAME=Listener
2 changes: 1 addition & 1 deletion backend/app/api/v1/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
disable_artist, enable_artist, delete_artist, artist_exists,
get_artist_with_related_entities, get_artists_followed_by_user
)
from app.api.v1.deps import (
from app.core.deps import (
get_current_active_user, get_current_admin, get_current_musician
)

Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/v1/artist_band_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.api.v1.deps import get_db, get_current_musician, get_current_admin
from app.core.deps import get_db, get_current_musician, get_current_admin
from app.crud.artist_band_member import (
create_artist_band_member,
get_artist_band_member_by_id,
Expand Down
6 changes: 5 additions & 1 deletion backend/app/api/v1/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from app.schemas.user import UserLogin, UserOut
from app.schemas.token import TokenResponse, TokenRefresh
from app.services.auth import AuthService
from app.api.v1.deps import get_current_active_user, get_current_admin, get_auth_service
from app.core.deps import get_current_active_user, get_current_admin, get_auth_service

router = APIRouter()

Expand Down Expand Up @@ -119,6 +119,9 @@ async def get_current_user_info(
"""
return current_user

'''

TODO: use cron job -- refer to issues for assistance

@router.post("/cleanup-expired")
async def cleanup_expired_tokens(
Expand All @@ -136,3 +139,4 @@ async def cleanup_expired_tokens(
"message": f"Cleaned up {cleaned_count} expired tokens",
"tokens_removed": cleaned_count
}
'''
2 changes: 1 addition & 1 deletion backend/app/api/v1/band.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
get_active_bands, search_bands_by_name, update_band,
disable_band, enable_band, delete_band_permanently, get_band_statistics
)
from app.api.v1.deps import (
from app.core.deps import (
get_current_active_user, get_current_admin, get_current_musician
)

Expand Down
207 changes: 207 additions & 0 deletions backend/app/api/v1/genre.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.core.deps import get_current_admin
from app.schemas.genre import GenreCreate, GenreUpdate, GenreOut, GenreStats
from app.crud.genre import (
create_genre, get_genre_by_id, get_genre_by_name, get_all_genres,
get_all_active_genres, update_genre, disable_genre, enable_genre,
genre_exists, genre_name_taken, get_genre_statistics
)

router = APIRouter()


@router.get("/", response_model=List[GenreOut])
async def get_active_genres(db: Session = Depends(get_db)):
"""Get all active genres - public access"""
return get_all_active_genres(db)


@router.get("/{genre_id}", response_model=GenreOut)
async def get_genre(genre_id: int, db: Session = Depends(get_db)):
"""Get a specific genre by ID - public access"""
genre = get_genre_by_id(db, genre_id)
if not genre:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)
return genre


@router.get("/name/{name}", response_model=GenreOut)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this API. Use a query-param to the GET genre API

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed updates and changes

async def get_genre_by_name_endpoint(name: str, db: Session = Depends(get_db)):
"""Get a specific genre by name - public access"""
genre = get_genre_by_name(db, name)
if not genre:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)
return genre


@router.post("/", response_model=GenreOut, status_code=status.HTTP_201_CREATED)
async def create_new_genre(
genre_data: GenreCreate,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Create a new genre - admin only"""
if genre_name_taken(db, genre_data.name):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do the same thing by only one database call?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use integrity error to throw an exception, looked it up working similarly to how it works for CREATE ... IF NOT EXISTS in raw sql

raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Genre name already exists"
)

return create_genre(db, genre_data)


@router.put("/{genre_id}", response_model=GenreOut)
async def update_genre_endpoint(
genre_id: int,
genre_data: GenreUpdate,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Update a genre - admin only"""
if not genre_exists(db, genre_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

if genre_data.name and genre_name_taken(db, genre_data.name, exclude_genre_id=genre_id):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Genre name already exists"
)

updated_genre = update_genre(db, genre_id, genre_data)
if not updated_genre:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

return updated_genre


@router.patch("/{genre_id}", response_model=GenreOut)
async def partial_update_genre(
genre_id: int,
genre_data: GenreUpdate,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Partially update a genre - admin only"""
if not genre_exists(db, genre_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

if genre_data.name and genre_name_taken(db, genre_data.name, exclude_genre_id=genre_id):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Genre name already exists"
)

updated_genre = update_genre(db, genre_id, genre_data)
if not updated_genre:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

return updated_genre


@router.delete("/{genre_id}")
async def delete_genre(
genre_id: int,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Delete a genre - admin only"""
if not genre_exists(db, genre_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

success = disable_genre(db, genre_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

return {"message": "Genre disabled successfully"}


@router.post("/{genre_id}/disable")
async def disable_genre_endpoint(
genre_id: int,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Disable a genre - admin only"""
if not genre_exists(db, genre_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

success = disable_genre(db, genre_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

return {"message": "Genre disabled successfully"}


@router.post("/{genre_id}/enable")
async def enable_genre_endpoint(
genre_id: int,
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Enable a genre - admin only"""
if not genre_exists(db, genre_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

success = enable_genre(db, genre_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Genre not found"
)

return {"message": "Genre enabled successfully"}


@router.get("/admin/all", response_model=List[GenreOut])
async def get_all_genres_admin(
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Get all genres including inactive ones - admin only"""
return get_all_genres(db)


@router.get("/admin/statistics", response_model=GenreStats)
async def get_genre_statistics_endpoint(
db: Session = Depends(get_db),
current_admin: dict = Depends(get_current_admin)
):
"""Get genre statistics - admin only"""
stats = get_genre_statistics(db)
return GenreStats(**stats)
Loading