Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
932bcd0
Update README.md
myndaaa Aug 8, 2025
1c9788e
Update README.md
myndaaa Aug 8, 2025
52f2fde
added genre and implementations
myndaaa Aug 8, 2025
da0dc1f
changes deps location / file path
myndaaa Aug 8, 2025
7de0683
fixed imports
myndaaa Aug 8, 2025
5906821
creation of song crud and api endpoints
myndaaa Aug 8, 2025
6150156
admin seed files
myndaaa Aug 8, 2025
53ed52c
docker failed to read .env credentials
myndaaa Aug 8, 2025
94bb0aa
dockerization altered to multi stage to reduce image volume
myndaaa Aug 10, 2025
8b3c90e
deleted frotnend
myndaaa Aug 10, 2025
3398683
deletion of frontend
myndaaa Aug 10, 2025
ac002b9
like endpoint and routes
myndaaa Aug 10, 2025
6447fdb
implemented following feature
myndaaa Aug 11, 2025
cb71088
altered model to add field is_cleared, to make users capable of clear…
myndaaa Aug 11, 2025
13d4a51
history feature implementation
myndaaa Aug 11, 2025
5789326
playlist implementation
myndaaa Aug 11, 2025
7e74536
Playlist song api endpoints implementation
myndaaa Aug 12, 2025
347fc1c
added collaboration fields
myndaaa Aug 12, 2025
48cd7d5
added collaboration features and its endpoints
myndaaa Aug 12, 2025
49d058d
Merge branch 'sub-feat/crud-artists-bands' into sub-feat/crud-genre-s…
myndaaa Aug 19, 2025
26b6dc7
Merge branch 'sub-feat/crud-artists-bands' into sub-feat/crud-playlist
myndaaa Aug 19, 2025
28f39a2
Merge branch 'sub-feat/crud-artists-bands' into sub-feat/crud-genre-s…
myndaaa Aug 22, 2025
a2c2d9b
Merge branch 'sub-feat/crud-artists-bands' into sub-feat/crud-playlist
myndaaa Aug 22, 2025
269864f
Fix for PR issues:
myndaaa Aug 25, 2025
8fc2e54
linter issue fix
myndaaa Aug 25, 2025
40aad43
- added to do comment on usage of proper rest naming and using names …
myndaaa Aug 25, 2025
b0171b6
brought changes from parent changes caused by fixes due to PR feedbacks
myndaaa Aug 25, 2025
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
TEST_ADMIN_EMAIL=admin@test.com
TEST_ADMIN_PASSWORD=AdminPass123!
TEST_ADMIN_FIRST_NAME=Test
TEST_ADMIN_LAST_NAME=Admin

TEST_MUSICIAN_USERNAME=test_musician
TEST_MUSICIAN_EMAIL=musician@test.com
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
TEST_LISTENER_EMAIL=listener@test.com
TEST_LISTENER_PASSWORD=ListenerPass123!
TEST_LISTENER_FIRST_NAME=Test
TEST_LISTENER_LAST_NAME=Listener
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Add playlist sharing and collaboration fields

Revision ID: 51eb42f5babc
Revises: 95b5ebff5e7a
Create Date: 2025-08-12 03:31:03.437557

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '51eb42f5babc'
down_revision: Union[str, Sequence[str], None] = '95b5ebff5e7a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('playlists', sa.Column('share_token', sa.String(length=64), nullable=True))
op.add_column('playlists', sa.Column('allow_collaboration', sa.Boolean(), nullable=True))

# Set default value for existing records
op.execute("UPDATE playlists SET allow_collaboration = FALSE WHERE allow_collaboration IS NULL")

# Make the column NOT NULL after setting default values
op.alter_column('playlists', 'allow_collaboration', nullable=False)

op.create_unique_constraint(None, 'playlists', ['share_token'])
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'playlists', type_='unique')
op.drop_column('playlists', 'allow_collaboration')
op.drop_column('playlists', 'share_token')
# ### end Alembic commands ###
34 changes: 34 additions & 0 deletions backend/alembic/versions/95b5ebff5e7a_add_is_cleared_to_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""add_is_cleared_to_history

Revision ID: 95b5ebff5e7a
Revises: 407106d49b66
Create Date: 2025-08-11 06:37:39.301845

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '95b5ebff5e7a'
down_revision: Union[str, Sequence[str], None] = '407106d49b66'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('histories', sa.Column('is_cleared', sa.Boolean(), nullable=False))
op.create_index('idx_history_cleared', 'histories', ['is_cleared'], unique=False)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('idx_history_cleared', table_name='histories')
op.drop_column('histories', 'is_cleared')
# ### end Alembic commands ###
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
}
'''
38 changes: 10 additions & 28 deletions backend/app/api/v1/band.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
BandCreate, BandOut, BandUpdate, BandStats, BandWithRelations
)
from app.crud.band import (
create_band, get_band_by_id, get_band_by_name, get_all_bands,
create_band, get_band_by_id, get_all_bands,
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
)

router = APIRouter()
# TODO: add slug later on;
# resource: https://stackoverflow.com/questions/10018100/identify-item-by-either-an-id-or-a-slug-in-a-restful-api
"""
AUTHENTICATION LEVELS:
- None: Public endpoint, no authentication required
Expand Down Expand Up @@ -61,7 +63,7 @@ async def get_bands_public(
db: Session = Depends(get_db),
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(20, ge=1, le=100, description="Maximum number of records to return"),
search: Optional[str] = Query(None, min_length=1, description="Search bands by name"),
name: Optional[str] = Query(None, min_length=1, description="Filter bands by name (case-insensitive, partial)"),
active_only: bool = Query(True, description="Return only active bands")
):
"""
Expand All @@ -70,13 +72,13 @@ async def get_bands_public(
Query Parameters:
- skip: Number of records to skip (pagination)
- limit: Maximum number of records to return (pagination)
- search: Search bands by name
- name: Filter bands by name (case-insensitive, partial)
- active_only: Return only active bands (default: True)

Returns: 200 OK - List of bands
"""
if search:
bands = search_bands_by_name(db, search, skip=skip, limit=limit)
if name:
bands = search_bands_by_name(db, name, skip=skip, limit=limit)
elif active_only:
bands = get_active_bands(db, skip=skip, limit=limit)
else:
Expand Down Expand Up @@ -105,28 +107,8 @@ async def get_band_public(
)

return band


@router.get("/name/{name}", response_model=BandOut)
async def get_band_by_name_public(
name: str,
db: Session = Depends(get_db)
):
"""
Get public band profile by name.
Returns basic band information for public viewing.
Only active bands are returned.
Returns: 200 OK - Band profile found
Returns: 404 Not Found - Band not found or inactive
"""
band = get_band_by_name(db, name)
if not band or band.is_disabled:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Band not found"
)

return band
# removed {name} function as it can be fetched alrdy via query param in get_bands_public



@router.get("/me/bands", response_model=List[BandOut])
Expand Down
Loading