1822 lines
46 KiB
YAML
1822 lines
46 KiB
YAML
workflow_version: "v001"
|
|
feature: "Music platform for musicians to upload songs"
|
|
created_at: "2025-12-18T15:10:00Z"
|
|
status: draft
|
|
revision: 1
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LAYER 1: DATA MODELS
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
data_models:
|
|
- id: model_user
|
|
name: User
|
|
table_name: users
|
|
description: Base user entity with authentication
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: email
|
|
type: string
|
|
constraints: [unique, not_null]
|
|
- name: password_hash
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: name
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: role
|
|
type: enum
|
|
values: [musician, listener, label]
|
|
constraints: [not_null]
|
|
- name: email_verified
|
|
type: boolean
|
|
default: false
|
|
- name: avatar_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: has_one
|
|
to: model_artist
|
|
foreign_key: user_id
|
|
condition: "role = 'musician'"
|
|
- type: has_one
|
|
to: model_label
|
|
foreign_key: user_id
|
|
condition: "role = 'label'"
|
|
- type: has_many
|
|
to: model_playlist
|
|
foreign_key: user_id
|
|
indexes:
|
|
- fields: [email]
|
|
unique: true
|
|
- fields: [role]
|
|
timestamps: true
|
|
|
|
- id: model_artist
|
|
name: Artist
|
|
table_name: artists
|
|
description: Extended profile for musicians
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: user_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: users.id
|
|
- name: stage_name
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: bio
|
|
type: text
|
|
constraints: [nullable]
|
|
- name: cover_image_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: social_links
|
|
type: jsonb
|
|
description: "JSON object with {twitter, instagram, facebook, website}"
|
|
constraints: [nullable]
|
|
- name: verified
|
|
type: boolean
|
|
default: false
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_user
|
|
foreign_key: user_id
|
|
- type: has_many
|
|
to: model_song
|
|
foreign_key: artist_id
|
|
- type: has_many
|
|
to: model_album
|
|
foreign_key: artist_id
|
|
- type: belongs_to
|
|
to: model_label
|
|
foreign_key: label_id
|
|
optional: true
|
|
indexes:
|
|
- fields: [user_id]
|
|
unique: true
|
|
- fields: [stage_name]
|
|
timestamps: true
|
|
|
|
- id: model_label
|
|
name: Label
|
|
table_name: labels
|
|
description: Organization profile for labels
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: user_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: users.id
|
|
- name: label_name
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: description
|
|
type: text
|
|
constraints: [nullable]
|
|
- name: logo_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: website
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_user
|
|
foreign_key: user_id
|
|
- type: has_many
|
|
to: model_artist
|
|
foreign_key: label_id
|
|
indexes:
|
|
- fields: [user_id]
|
|
unique: true
|
|
timestamps: true
|
|
|
|
- id: model_genre
|
|
name: Genre
|
|
table_name: genres
|
|
description: Music category for discovery
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: name
|
|
type: string
|
|
constraints: [unique, not_null]
|
|
- name: slug
|
|
type: string
|
|
constraints: [unique, not_null]
|
|
- name: description
|
|
type: text
|
|
constraints: [nullable]
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: has_many
|
|
to: model_song
|
|
through: song_genres
|
|
foreign_key: genre_id
|
|
indexes:
|
|
- fields: [slug]
|
|
unique: true
|
|
timestamps: true
|
|
|
|
- id: model_album
|
|
name: Album
|
|
table_name: albums
|
|
description: Collection of songs
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: artist_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: artists.id
|
|
- name: title
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: description
|
|
type: text
|
|
constraints: [nullable]
|
|
- name: cover_art_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: release_date
|
|
type: date
|
|
constraints: [nullable]
|
|
- name: album_type
|
|
type: enum
|
|
values: [album, ep, single]
|
|
default: album
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_artist
|
|
foreign_key: artist_id
|
|
- type: has_many
|
|
to: model_song
|
|
foreign_key: album_id
|
|
indexes:
|
|
- fields: [artist_id]
|
|
- fields: [release_date]
|
|
timestamps: true
|
|
|
|
- id: model_song
|
|
name: Song
|
|
table_name: songs
|
|
description: Audio track with metadata
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: artist_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: artists.id
|
|
- name: album_id
|
|
type: uuid
|
|
constraints: [nullable, foreign_key]
|
|
references: albums.id
|
|
- name: title
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: duration
|
|
type: integer
|
|
description: Duration in seconds
|
|
constraints: [not_null]
|
|
- name: file_url
|
|
type: string
|
|
description: Cloud storage URL for audio file
|
|
constraints: [not_null]
|
|
- name: file_format
|
|
type: enum
|
|
values: [mp3, wav]
|
|
constraints: [not_null]
|
|
- name: file_size
|
|
type: integer
|
|
description: File size in bytes
|
|
constraints: [not_null]
|
|
- name: waveform_data
|
|
type: jsonb
|
|
description: Waveform visualization data
|
|
constraints: [nullable]
|
|
- name: cover_art_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: release_date
|
|
type: date
|
|
constraints: [nullable]
|
|
- name: play_count
|
|
type: integer
|
|
default: 0
|
|
- name: is_public
|
|
type: boolean
|
|
default: true
|
|
- name: track_number
|
|
type: integer
|
|
description: Position in album
|
|
constraints: [nullable]
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_artist
|
|
foreign_key: artist_id
|
|
- type: belongs_to
|
|
to: model_album
|
|
foreign_key: album_id
|
|
optional: true
|
|
- type: has_many
|
|
to: model_genre
|
|
through: song_genres
|
|
foreign_key: song_id
|
|
- type: has_many
|
|
to: model_playlist_song
|
|
foreign_key: song_id
|
|
indexes:
|
|
- fields: [artist_id]
|
|
- fields: [album_id]
|
|
- fields: [release_date]
|
|
- fields: [play_count]
|
|
- fields: [is_public]
|
|
timestamps: true
|
|
|
|
- id: model_song_genre
|
|
name: SongGenre
|
|
table_name: song_genres
|
|
description: Junction table for song-genre many-to-many
|
|
fields:
|
|
- name: song_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: songs.id
|
|
- name: genre_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: genres.id
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_song
|
|
foreign_key: song_id
|
|
- type: belongs_to
|
|
to: model_genre
|
|
foreign_key: genre_id
|
|
indexes:
|
|
- fields: [song_id, genre_id]
|
|
unique: true
|
|
timestamps: false
|
|
|
|
- id: model_playlist
|
|
name: Playlist
|
|
table_name: playlists
|
|
description: User-created song collection
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: user_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: users.id
|
|
- name: name
|
|
type: string
|
|
constraints: [not_null]
|
|
- name: description
|
|
type: text
|
|
constraints: [nullable]
|
|
- name: cover_image_url
|
|
type: string
|
|
constraints: [nullable]
|
|
- name: is_public
|
|
type: boolean
|
|
default: false
|
|
- name: created_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
- name: updated_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_user
|
|
foreign_key: user_id
|
|
- type: has_many
|
|
to: model_playlist_song
|
|
foreign_key: playlist_id
|
|
indexes:
|
|
- fields: [user_id]
|
|
- fields: [is_public]
|
|
timestamps: true
|
|
|
|
- id: model_playlist_song
|
|
name: PlaylistSong
|
|
table_name: playlist_songs
|
|
description: Junction table with ordering for playlists
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
constraints: [primary_key]
|
|
- name: playlist_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: playlists.id
|
|
- name: song_id
|
|
type: uuid
|
|
constraints: [not_null, foreign_key]
|
|
references: songs.id
|
|
- name: position
|
|
type: integer
|
|
constraints: [not_null]
|
|
- name: added_at
|
|
type: timestamp
|
|
constraints: [not_null]
|
|
relations:
|
|
- type: belongs_to
|
|
to: model_playlist
|
|
foreign_key: playlist_id
|
|
- type: belongs_to
|
|
to: model_song
|
|
foreign_key: song_id
|
|
indexes:
|
|
- fields: [playlist_id, position]
|
|
unique: true
|
|
- fields: [playlist_id, song_id]
|
|
unique: true
|
|
timestamps: false
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LAYER 2: API ENDPOINTS
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
api_endpoints:
|
|
# ─────────────────────────────────────────────────────────────
|
|
# AUTH ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_register
|
|
method: POST
|
|
path: /api/auth/register
|
|
description: Register new user account
|
|
request_body:
|
|
email: string
|
|
password: string
|
|
name: string
|
|
role: enum[musician, listener, label]
|
|
responses:
|
|
- status: 201
|
|
description: User created successfully
|
|
schema:
|
|
user:
|
|
id: uuid
|
|
email: string
|
|
name: string
|
|
role: string
|
|
token: string
|
|
- status: 400
|
|
description: Validation error
|
|
schema:
|
|
error: string
|
|
- status: 409
|
|
description: Email already exists
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_user]
|
|
|
|
- id: api_login
|
|
method: POST
|
|
path: /api/auth/login
|
|
description: Login with email and password
|
|
request_body:
|
|
email: string
|
|
password: string
|
|
responses:
|
|
- status: 200
|
|
description: Login successful
|
|
schema:
|
|
user:
|
|
id: uuid
|
|
email: string
|
|
name: string
|
|
role: string
|
|
token: string
|
|
- status: 401
|
|
description: Invalid credentials
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_user]
|
|
|
|
- id: api_forgot_password
|
|
method: POST
|
|
path: /api/auth/forgot-password
|
|
description: Request password reset email
|
|
request_body:
|
|
email: string
|
|
responses:
|
|
- status: 200
|
|
description: Reset email sent
|
|
schema:
|
|
message: string
|
|
- status: 404
|
|
description: Email not found
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_user]
|
|
|
|
- id: api_reset_password
|
|
method: POST
|
|
path: /api/auth/reset-password
|
|
description: Reset password with token
|
|
request_body:
|
|
token: string
|
|
password: string
|
|
responses:
|
|
- status: 200
|
|
description: Password reset successful
|
|
schema:
|
|
message: string
|
|
- status: 400
|
|
description: Invalid or expired token
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_user]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# USER ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_get_current_user
|
|
method: GET
|
|
path: /api/users/me
|
|
description: Get current user profile
|
|
responses:
|
|
- status: 200
|
|
description: User profile
|
|
schema:
|
|
id: uuid
|
|
email: string
|
|
name: string
|
|
role: string
|
|
avatar_url: string
|
|
auth:
|
|
required: true
|
|
depends_on_models: [model_user]
|
|
|
|
- id: api_update_current_user
|
|
method: PUT
|
|
path: /api/users/me
|
|
description: Update current user profile
|
|
request_body:
|
|
name: string
|
|
avatar_url: string
|
|
responses:
|
|
- status: 200
|
|
description: User updated
|
|
schema:
|
|
id: uuid
|
|
email: string
|
|
name: string
|
|
avatar_url: string
|
|
auth:
|
|
required: true
|
|
depends_on_models: [model_user]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# ARTIST ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_create_artist_profile
|
|
method: POST
|
|
path: /api/artists
|
|
description: Create artist profile (musicians only)
|
|
request_body:
|
|
stage_name: string
|
|
bio: string
|
|
cover_image_url: string
|
|
social_links:
|
|
twitter: string
|
|
instagram: string
|
|
facebook: string
|
|
website: string
|
|
responses:
|
|
- status: 201
|
|
description: Artist profile created
|
|
schema:
|
|
id: uuid
|
|
stage_name: string
|
|
bio: string
|
|
cover_image_url: string
|
|
- status: 403
|
|
description: User is not a musician
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
roles: [musician]
|
|
depends_on_models: [model_artist, model_user]
|
|
|
|
- id: api_get_artist
|
|
method: GET
|
|
path: /api/artists/:id
|
|
description: Get artist profile by ID
|
|
responses:
|
|
- status: 200
|
|
description: Artist profile
|
|
schema:
|
|
id: uuid
|
|
stage_name: string
|
|
bio: string
|
|
cover_image_url: string
|
|
social_links: object
|
|
verified: boolean
|
|
- status: 404
|
|
description: Artist not found
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_artist]
|
|
|
|
- id: api_update_artist
|
|
method: PUT
|
|
path: /api/artists/:id
|
|
description: Update artist profile
|
|
request_body:
|
|
stage_name: string
|
|
bio: string
|
|
cover_image_url: string
|
|
social_links: object
|
|
responses:
|
|
- status: 200
|
|
description: Artist updated
|
|
schema:
|
|
id: uuid
|
|
stage_name: string
|
|
bio: string
|
|
- status: 403
|
|
description: Unauthorized
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_artist]
|
|
|
|
- id: api_get_artist_songs
|
|
method: GET
|
|
path: /api/artists/:id/songs
|
|
description: Get all songs by artist
|
|
responses:
|
|
- status: 200
|
|
description: List of songs
|
|
schema:
|
|
songs:
|
|
- id: uuid
|
|
title: string
|
|
duration: integer
|
|
cover_art_url: string
|
|
play_count: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_artist, model_song]
|
|
|
|
- id: api_get_artist_albums
|
|
method: GET
|
|
path: /api/artists/:id/albums
|
|
description: Get all albums by artist
|
|
responses:
|
|
- status: 200
|
|
description: List of albums
|
|
schema:
|
|
albums:
|
|
- id: uuid
|
|
title: string
|
|
cover_art_url: string
|
|
release_date: string
|
|
album_type: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_artist, model_album]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# SONG ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_upload_song
|
|
method: POST
|
|
path: /api/songs/upload
|
|
description: Upload new song (musicians only)
|
|
request_body:
|
|
file: binary
|
|
title: string
|
|
album_id: uuid
|
|
genre_ids: array[uuid]
|
|
release_date: string
|
|
track_number: integer
|
|
responses:
|
|
- status: 201
|
|
description: Song uploaded successfully
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
file_url: string
|
|
duration: integer
|
|
- status: 400
|
|
description: Invalid file format or size
|
|
schema:
|
|
error: string
|
|
- status: 403
|
|
description: User is not a musician
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
roles: [musician]
|
|
depends_on_models: [model_song, model_artist]
|
|
|
|
- id: api_get_song
|
|
method: GET
|
|
path: /api/songs/:id
|
|
description: Get song details
|
|
responses:
|
|
- status: 200
|
|
description: Song details
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
duration: integer
|
|
file_url: string
|
|
cover_art_url: string
|
|
artist:
|
|
id: uuid
|
|
stage_name: string
|
|
album:
|
|
id: uuid
|
|
title: string
|
|
genres: array
|
|
play_count: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_song, model_artist, model_album]
|
|
|
|
- id: api_update_song
|
|
method: PUT
|
|
path: /api/songs/:id
|
|
description: Update song metadata
|
|
request_body:
|
|
title: string
|
|
album_id: uuid
|
|
genre_ids: array[uuid]
|
|
release_date: string
|
|
is_public: boolean
|
|
responses:
|
|
- status: 200
|
|
description: Song updated
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
- status: 403
|
|
description: Unauthorized
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_song]
|
|
|
|
- id: api_delete_song
|
|
method: DELETE
|
|
path: /api/songs/:id
|
|
description: Delete song
|
|
responses:
|
|
- status: 204
|
|
description: Song deleted
|
|
- status: 403
|
|
description: Unauthorized
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_song]
|
|
|
|
- id: api_increment_play_count
|
|
method: POST
|
|
path: /api/songs/:id/play
|
|
description: Increment play count
|
|
request_body: null # Action endpoint - no body needed
|
|
responses:
|
|
- status: 200
|
|
description: Play count incremented
|
|
schema:
|
|
play_count: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_song]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# ALBUM ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_create_album
|
|
method: POST
|
|
path: /api/albums
|
|
description: Create new album
|
|
request_body:
|
|
title: string
|
|
description: string
|
|
cover_art_url: string
|
|
release_date: string
|
|
album_type: enum[album, ep, single]
|
|
responses:
|
|
- status: 201
|
|
description: Album created
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
cover_art_url: string
|
|
auth:
|
|
required: true
|
|
roles: [musician]
|
|
depends_on_models: [model_album, model_artist]
|
|
|
|
- id: api_get_album
|
|
method: GET
|
|
path: /api/albums/:id
|
|
description: Get album details with songs
|
|
responses:
|
|
- status: 200
|
|
description: Album details
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
description: string
|
|
cover_art_url: string
|
|
release_date: string
|
|
artist:
|
|
id: uuid
|
|
stage_name: string
|
|
songs:
|
|
- id: uuid
|
|
title: string
|
|
duration: integer
|
|
track_number: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_album, model_song, model_artist]
|
|
|
|
- id: api_update_album
|
|
method: PUT
|
|
path: /api/albums/:id
|
|
description: Update album metadata
|
|
request_body:
|
|
title: string
|
|
description: string
|
|
cover_art_url: string
|
|
release_date: string
|
|
responses:
|
|
- status: 200
|
|
description: Album updated
|
|
schema:
|
|
id: uuid
|
|
title: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_album]
|
|
|
|
- id: api_delete_album
|
|
method: DELETE
|
|
path: /api/albums/:id
|
|
description: Delete album
|
|
responses:
|
|
- status: 204
|
|
description: Album deleted
|
|
- status: 403
|
|
description: Unauthorized
|
|
schema:
|
|
error: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_album]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# PLAYLIST ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_create_playlist
|
|
method: POST
|
|
path: /api/playlists
|
|
description: Create new playlist
|
|
request_body:
|
|
name: string
|
|
description: string
|
|
is_public: boolean
|
|
responses:
|
|
- status: 201
|
|
description: Playlist created
|
|
schema:
|
|
id: uuid
|
|
name: string
|
|
description: string
|
|
auth:
|
|
required: true
|
|
depends_on_models: [model_playlist]
|
|
|
|
- id: api_get_user_playlists
|
|
method: GET
|
|
path: /api/playlists
|
|
description: Get current user's playlists
|
|
responses:
|
|
- status: 200
|
|
description: List of playlists
|
|
schema:
|
|
playlists:
|
|
- id: uuid
|
|
name: string
|
|
cover_image_url: string
|
|
song_count: integer
|
|
auth:
|
|
required: true
|
|
depends_on_models: [model_playlist]
|
|
|
|
- id: api_get_playlist
|
|
method: GET
|
|
path: /api/playlists/:id
|
|
description: Get playlist details with songs
|
|
responses:
|
|
- status: 200
|
|
description: Playlist details
|
|
schema:
|
|
id: uuid
|
|
name: string
|
|
description: string
|
|
songs:
|
|
- id: uuid
|
|
title: string
|
|
artist:
|
|
stage_name: string
|
|
position: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_playlist, model_playlist_song]
|
|
|
|
- id: api_update_playlist
|
|
method: PUT
|
|
path: /api/playlists/:id
|
|
description: Update playlist metadata
|
|
request_body:
|
|
name: string
|
|
description: string
|
|
is_public: boolean
|
|
responses:
|
|
- status: 200
|
|
description: Playlist updated
|
|
schema:
|
|
id: uuid
|
|
name: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_playlist]
|
|
|
|
- id: api_delete_playlist
|
|
method: DELETE
|
|
path: /api/playlists/:id
|
|
description: Delete playlist
|
|
responses:
|
|
- status: 204
|
|
description: Playlist deleted
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_playlist]
|
|
|
|
- id: api_add_song_to_playlist
|
|
method: POST
|
|
path: /api/playlists/:id/songs
|
|
description: Add song to playlist
|
|
request_body:
|
|
song_id: uuid
|
|
position: integer
|
|
responses:
|
|
- status: 201
|
|
description: Song added to playlist
|
|
schema:
|
|
playlist_id: uuid
|
|
song_id: uuid
|
|
position: integer
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_playlist, model_playlist_song]
|
|
|
|
- id: api_remove_song_from_playlist
|
|
method: DELETE
|
|
path: /api/playlists/:playlistId/songs/:songId
|
|
description: Remove song from playlist
|
|
responses:
|
|
- status: 204
|
|
description: Song removed from playlist
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_playlist, model_playlist_song]
|
|
|
|
- id: api_reorder_playlist_songs
|
|
method: PUT
|
|
path: /api/playlists/:id/reorder
|
|
description: Reorder songs in playlist
|
|
request_body:
|
|
song_ids: array[uuid]
|
|
responses:
|
|
- status: 200
|
|
description: Playlist reordered
|
|
schema:
|
|
message: string
|
|
auth:
|
|
required: true
|
|
owner_only: true
|
|
depends_on_models: [model_playlist, model_playlist_song]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# DISCOVERY ENDPOINTS
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_get_trending_songs
|
|
method: GET
|
|
path: /api/discover/trending
|
|
description: Get trending songs
|
|
query_params:
|
|
limit: integer
|
|
offset: integer
|
|
responses:
|
|
- status: 200
|
|
description: List of trending songs
|
|
schema:
|
|
songs:
|
|
- id: uuid
|
|
title: string
|
|
artist:
|
|
stage_name: string
|
|
play_count: integer
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_song, model_artist]
|
|
|
|
- id: api_get_new_releases
|
|
method: GET
|
|
path: /api/discover/new-releases
|
|
description: Get recently released songs
|
|
query_params:
|
|
limit: integer
|
|
offset: integer
|
|
responses:
|
|
- status: 200
|
|
description: List of new releases
|
|
schema:
|
|
songs:
|
|
- id: uuid
|
|
title: string
|
|
release_date: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_song]
|
|
|
|
- id: api_get_genres
|
|
method: GET
|
|
path: /api/discover/genres
|
|
description: Get all genres
|
|
responses:
|
|
- status: 200
|
|
description: List of genres
|
|
schema:
|
|
genres:
|
|
- id: uuid
|
|
name: string
|
|
slug: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_genre]
|
|
|
|
- id: api_get_songs_by_genre
|
|
method: GET
|
|
path: /api/discover/genres/:slug
|
|
description: Get songs by genre
|
|
query_params:
|
|
limit: integer
|
|
offset: integer
|
|
responses:
|
|
- status: 200
|
|
description: List of songs in genre
|
|
schema:
|
|
genre:
|
|
name: string
|
|
songs: array
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_genre, model_song]
|
|
|
|
- id: api_search
|
|
method: GET
|
|
path: /api/search
|
|
description: Search songs, artists, and albums
|
|
query_params:
|
|
q: string
|
|
type: enum[song, artist, album, all]
|
|
limit: integer
|
|
responses:
|
|
- status: 200
|
|
description: Search results
|
|
schema:
|
|
songs: array
|
|
artists: array
|
|
albums: array
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_song, model_artist, model_album]
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# LABEL ENDPOINTS (MVP)
|
|
# ─────────────────────────────────────────────────────────────
|
|
- id: api_create_label_profile
|
|
method: POST
|
|
path: /api/labels
|
|
description: Create label profile
|
|
request_body:
|
|
label_name: string
|
|
description: string
|
|
logo_url: string
|
|
website: string
|
|
responses:
|
|
- status: 201
|
|
description: Label created
|
|
schema:
|
|
id: uuid
|
|
label_name: string
|
|
auth:
|
|
required: true
|
|
roles: [label]
|
|
depends_on_models: [model_label]
|
|
|
|
- id: api_get_label_artists
|
|
method: GET
|
|
path: /api/labels/:id/artists
|
|
description: Get artists under label
|
|
responses:
|
|
- status: 200
|
|
description: List of artists
|
|
schema:
|
|
artists:
|
|
- id: uuid
|
|
stage_name: string
|
|
auth:
|
|
required: false
|
|
depends_on_models: [model_label, model_artist]
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LAYER 3: PAGES
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
pages:
|
|
- id: page_login
|
|
path: /login
|
|
title: Login
|
|
description: User login page
|
|
data_needs:
|
|
- api_id: api_login
|
|
purpose: Authenticate user
|
|
on_load: false
|
|
components:
|
|
- component_auth_form
|
|
auth:
|
|
required: false
|
|
redirect_if_authenticated: /
|
|
|
|
- id: page_register
|
|
path: /register
|
|
title: Register
|
|
description: User registration page
|
|
data_needs:
|
|
- api_id: api_register
|
|
purpose: Create new account
|
|
on_load: false
|
|
components:
|
|
- component_auth_form
|
|
auth:
|
|
required: false
|
|
redirect_if_authenticated: /
|
|
|
|
- id: page_forgot_password
|
|
path: /forgot-password
|
|
title: Forgot Password
|
|
description: Password reset request page
|
|
data_needs:
|
|
- api_id: api_forgot_password
|
|
purpose: Request password reset
|
|
on_load: false
|
|
components:
|
|
- component_auth_form
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_home
|
|
path: /
|
|
title: Discover Music
|
|
description: Main discovery feed
|
|
data_needs:
|
|
- api_id: api_get_trending_songs
|
|
purpose: Show trending songs
|
|
on_load: true
|
|
- api_id: api_get_new_releases
|
|
purpose: Show new releases
|
|
on_load: true
|
|
- api_id: api_get_genres
|
|
purpose: Genre navigation
|
|
on_load: true
|
|
components:
|
|
- component_song_card
|
|
- component_genre_badge
|
|
- component_section_header
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_artist_profile
|
|
path: /artist/:id
|
|
title: Artist Profile
|
|
description: Artist profile with songs and albums
|
|
data_needs:
|
|
- api_id: api_get_artist
|
|
purpose: Artist details
|
|
on_load: true
|
|
- api_id: api_get_artist_songs
|
|
purpose: Artist songs
|
|
on_load: true
|
|
- api_id: api_get_artist_albums
|
|
purpose: Artist albums
|
|
on_load: true
|
|
components:
|
|
- component_artist_header
|
|
- component_song_card
|
|
- component_album_card
|
|
- component_social_links
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_album_detail
|
|
path: /album/:id
|
|
title: Album
|
|
description: Album detail with track list
|
|
data_needs:
|
|
- api_id: api_get_album
|
|
purpose: Album details and songs
|
|
on_load: true
|
|
components:
|
|
- component_album_header
|
|
- component_track_list
|
|
- component_song_card
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_upload
|
|
path: /upload
|
|
title: Upload Music
|
|
description: Song upload page (musicians only)
|
|
data_needs:
|
|
- api_id: api_upload_song
|
|
purpose: Upload song file
|
|
on_load: false
|
|
- api_id: api_get_artist_albums
|
|
purpose: Select album
|
|
on_load: true
|
|
- api_id: api_get_genres
|
|
purpose: Select genres
|
|
on_load: true
|
|
components:
|
|
- component_upload_form
|
|
- component_waveform_display
|
|
auth:
|
|
required: true
|
|
roles: [musician]
|
|
redirect_if_unauthorized: /login
|
|
|
|
- id: page_playlists
|
|
path: /playlists
|
|
title: My Playlists
|
|
description: User's playlists
|
|
data_needs:
|
|
- api_id: api_get_user_playlists
|
|
purpose: Load playlists
|
|
on_load: true
|
|
components:
|
|
- component_playlist_card
|
|
- component_create_playlist_modal
|
|
auth:
|
|
required: true
|
|
redirect_if_unauthorized: /login
|
|
|
|
- id: page_playlist_detail
|
|
path: /playlist/:id
|
|
title: Playlist
|
|
description: Playlist detail with songs
|
|
data_needs:
|
|
- api_id: api_get_playlist
|
|
purpose: Playlist details and songs
|
|
on_load: true
|
|
components:
|
|
- component_playlist_header
|
|
- component_track_list
|
|
- component_song_card
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_profile
|
|
path: /profile
|
|
title: Profile Settings
|
|
description: User profile settings
|
|
data_needs:
|
|
- api_id: api_get_current_user
|
|
purpose: Load user data
|
|
on_load: true
|
|
- api_id: api_update_current_user
|
|
purpose: Update profile
|
|
on_load: false
|
|
components:
|
|
- component_profile_form
|
|
- component_avatar_upload
|
|
auth:
|
|
required: true
|
|
redirect_if_unauthorized: /login
|
|
|
|
- id: page_search
|
|
path: /search
|
|
title: Search
|
|
description: Search results page
|
|
data_needs:
|
|
- api_id: api_search
|
|
purpose: Search songs, artists, albums
|
|
on_load: false
|
|
components:
|
|
- component_search_bar
|
|
- component_search_results
|
|
- component_song_card
|
|
- component_artist_card
|
|
- component_album_card
|
|
auth:
|
|
required: false
|
|
|
|
- id: page_genre_browse
|
|
path: /genre/:slug
|
|
title: Browse Genre
|
|
description: Browse songs by genre
|
|
data_needs:
|
|
- api_id: api_get_songs_by_genre
|
|
purpose: Load genre songs
|
|
on_load: true
|
|
components:
|
|
- component_genre_header
|
|
- component_song_card
|
|
auth:
|
|
required: false
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LAYER 3: COMPONENTS
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
components:
|
|
- id: component_audio_player
|
|
name: AudioPlayer
|
|
description: Global audio player with full controls
|
|
props:
|
|
- name: currentSong
|
|
type: Song
|
|
required: false
|
|
- name: queue
|
|
type: array[Song]
|
|
required: false
|
|
- name: autoplay
|
|
type: boolean
|
|
default: false
|
|
state:
|
|
- name: isPlaying
|
|
type: boolean
|
|
- name: currentTime
|
|
type: number
|
|
- name: volume
|
|
type: number
|
|
- name: isShuffle
|
|
type: boolean
|
|
- name: repeatMode
|
|
type: enum[off, one, all]
|
|
events:
|
|
- name: onPlay
|
|
payload:
|
|
songId: string
|
|
- name: onPause
|
|
payload: null
|
|
- name: onSeek
|
|
payload:
|
|
time: number
|
|
- name: onVolumeChange
|
|
payload:
|
|
volume: number
|
|
- name: onNext
|
|
payload: null
|
|
- name: onPrevious
|
|
payload: null
|
|
- name: onShuffle
|
|
payload: null
|
|
- name: onRepeat
|
|
payload: null
|
|
uses_apis:
|
|
- api_increment_play_count
|
|
uses_components:
|
|
- component_waveform_display
|
|
- component_player_controls
|
|
|
|
- id: component_player_controls
|
|
name: PlayerControls
|
|
description: Play/pause/seek controls
|
|
props:
|
|
- name: isPlaying
|
|
type: boolean
|
|
required: true
|
|
- name: currentTime
|
|
type: number
|
|
required: true
|
|
- name: duration
|
|
type: number
|
|
required: true
|
|
events:
|
|
- name: onPlay
|
|
payload: null
|
|
- name: onPause
|
|
payload: null
|
|
- name: onSeek
|
|
payload:
|
|
time: number
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_song_card
|
|
name: SongCard
|
|
description: Song display card with play button
|
|
props:
|
|
- name: song
|
|
type: Song
|
|
required: true
|
|
- name: showArtist
|
|
type: boolean
|
|
default: true
|
|
- name: showAlbum
|
|
type: boolean
|
|
default: false
|
|
events:
|
|
- name: onPlay
|
|
payload:
|
|
songId: string
|
|
- name: onAddToPlaylist
|
|
payload:
|
|
songId: string
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_album_card
|
|
name: AlbumCard
|
|
description: Album display card
|
|
props:
|
|
- name: album
|
|
type: Album
|
|
required: true
|
|
- name: showArtist
|
|
type: boolean
|
|
default: true
|
|
events:
|
|
- name: onClick
|
|
payload:
|
|
albumId: string
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_artist_card
|
|
name: ArtistCard
|
|
description: Artist preview card
|
|
props:
|
|
- name: artist
|
|
type: Artist
|
|
required: true
|
|
events:
|
|
- name: onClick
|
|
payload:
|
|
artistId: string
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_playlist_card
|
|
name: PlaylistCard
|
|
description: Playlist preview card
|
|
props:
|
|
- name: playlist
|
|
type: Playlist
|
|
required: true
|
|
events:
|
|
- name: onClick
|
|
payload:
|
|
playlistId: string
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_upload_form
|
|
name: UploadForm
|
|
description: Song upload form with file input
|
|
props:
|
|
- name: albums
|
|
type: array[Album]
|
|
required: true
|
|
- name: genres
|
|
type: array[Genre]
|
|
required: true
|
|
state:
|
|
- name: file
|
|
type: File
|
|
- name: title
|
|
type: string
|
|
- name: selectedAlbum
|
|
type: string
|
|
- name: selectedGenres
|
|
type: array[string]
|
|
- name: uploadProgress
|
|
type: number
|
|
events:
|
|
- name: onUpload
|
|
payload:
|
|
file: File
|
|
metadata: object
|
|
- name: onCancel
|
|
payload: null
|
|
uses_apis:
|
|
- api_upload_song
|
|
uses_components:
|
|
- component_waveform_display
|
|
|
|
- id: component_waveform_display
|
|
name: WaveformDisplay
|
|
description: Audio waveform visualization
|
|
props:
|
|
- name: audioUrl
|
|
type: string
|
|
required: true
|
|
- name: waveformData
|
|
type: array[number]
|
|
required: false
|
|
- name: currentTime
|
|
type: number
|
|
required: false
|
|
events:
|
|
- name: onSeek
|
|
payload:
|
|
time: number
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_genre_badge
|
|
name: GenreBadge
|
|
description: Genre tag display
|
|
props:
|
|
- name: genre
|
|
type: Genre
|
|
required: true
|
|
- name: clickable
|
|
type: boolean
|
|
default: true
|
|
events:
|
|
- name: onClick
|
|
payload:
|
|
genreSlug: string
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_track_list
|
|
name: TrackList
|
|
description: List of songs with track numbers
|
|
props:
|
|
- name: songs
|
|
type: array[Song]
|
|
required: true
|
|
- name: showTrackNumber
|
|
type: boolean
|
|
default: true
|
|
- name: reorderable
|
|
type: boolean
|
|
default: false
|
|
events:
|
|
- name: onPlay
|
|
payload:
|
|
songId: string
|
|
- name: onReorder
|
|
payload:
|
|
songIds: array[string]
|
|
uses_apis: []
|
|
uses_components:
|
|
- component_song_card
|
|
|
|
- id: component_artist_header
|
|
name: ArtistHeader
|
|
description: Artist profile header with cover image
|
|
props:
|
|
- name: artist
|
|
type: Artist
|
|
required: true
|
|
events: []
|
|
uses_apis: []
|
|
uses_components:
|
|
- component_social_links
|
|
|
|
- id: component_album_header
|
|
name: AlbumHeader
|
|
description: Album detail header with cover art
|
|
props:
|
|
- name: album
|
|
type: Album
|
|
required: true
|
|
- name: artist
|
|
type: Artist
|
|
required: true
|
|
events:
|
|
- name: onPlayAll
|
|
payload: null
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_playlist_header
|
|
name: PlaylistHeader
|
|
description: Playlist header with cover and controls
|
|
props:
|
|
- name: playlist
|
|
type: Playlist
|
|
required: true
|
|
- name: isOwner
|
|
type: boolean
|
|
default: false
|
|
events:
|
|
- name: onPlayAll
|
|
payload: null
|
|
- name: onEdit
|
|
payload: null
|
|
- name: onDelete
|
|
payload: null
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_social_links
|
|
name: SocialLinks
|
|
description: Social media links display
|
|
props:
|
|
- name: links
|
|
type: object
|
|
required: true
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_auth_form
|
|
name: AuthForm
|
|
description: Reusable authentication form
|
|
props:
|
|
- name: mode
|
|
type: enum[login, register, forgot]
|
|
required: true
|
|
state:
|
|
- name: email
|
|
type: string
|
|
- name: password
|
|
type: string
|
|
- name: name
|
|
type: string
|
|
- name: role
|
|
type: string
|
|
events:
|
|
- name: onSubmit
|
|
payload: object
|
|
uses_apis:
|
|
- api_login
|
|
- api_register
|
|
- api_forgot_password
|
|
uses_components: []
|
|
|
|
- id: component_search_bar
|
|
name: SearchBar
|
|
description: Search input with autocomplete
|
|
props:
|
|
- name: placeholder
|
|
type: string
|
|
default: "Search songs, artists, albums..."
|
|
state:
|
|
- name: query
|
|
type: string
|
|
events:
|
|
- name: onSearch
|
|
payload:
|
|
query: string
|
|
uses_apis:
|
|
- api_search
|
|
uses_components: []
|
|
|
|
- id: component_search_results
|
|
name: SearchResults
|
|
description: Search results with filters
|
|
props:
|
|
- name: results
|
|
type: object
|
|
required: true
|
|
events: []
|
|
uses_apis: []
|
|
uses_components:
|
|
- component_song_card
|
|
- component_artist_card
|
|
- component_album_card
|
|
|
|
- id: component_create_playlist_modal
|
|
name: CreatePlaylistModal
|
|
description: Modal for creating new playlist
|
|
props:
|
|
- name: isOpen
|
|
type: boolean
|
|
required: true
|
|
state:
|
|
- name: name
|
|
type: string
|
|
- name: description
|
|
type: string
|
|
- name: isPublic
|
|
type: boolean
|
|
events:
|
|
- name: onCreate
|
|
payload:
|
|
name: string
|
|
description: string
|
|
isPublic: boolean
|
|
- name: onClose
|
|
payload: null
|
|
uses_apis:
|
|
- api_create_playlist
|
|
uses_components: []
|
|
|
|
- id: component_profile_form
|
|
name: ProfileForm
|
|
description: User profile edit form
|
|
props:
|
|
- name: user
|
|
type: User
|
|
required: true
|
|
state:
|
|
- name: name
|
|
type: string
|
|
- name: avatarUrl
|
|
type: string
|
|
events:
|
|
- name: onSave
|
|
payload:
|
|
name: string
|
|
avatarUrl: string
|
|
uses_apis:
|
|
- api_update_current_user
|
|
uses_components:
|
|
- component_avatar_upload
|
|
|
|
- id: component_avatar_upload
|
|
name: AvatarUpload
|
|
description: Avatar image upload component
|
|
props:
|
|
- name: currentAvatarUrl
|
|
type: string
|
|
required: false
|
|
state:
|
|
- name: file
|
|
type: File
|
|
- name: preview
|
|
type: string
|
|
events:
|
|
- name: onUpload
|
|
payload:
|
|
file: File
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_section_header
|
|
name: SectionHeader
|
|
description: Section title with optional action
|
|
props:
|
|
- name: title
|
|
type: string
|
|
required: true
|
|
- name: actionLabel
|
|
type: string
|
|
required: false
|
|
events:
|
|
- name: onActionClick
|
|
payload: null
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
- id: component_genre_header
|
|
name: GenreHeader
|
|
description: Genre browse page header
|
|
props:
|
|
- name: genre
|
|
type: Genre
|
|
required: true
|
|
events: []
|
|
uses_apis: []
|
|
uses_components: []
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SUMMARY
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
summary:
|
|
data_models: 10
|
|
api_endpoints: 46
|
|
pages: 11
|
|
components: 24
|
|
total_entities: 91
|