569 lines
16 KiB
YAML
569 lines
16 KiB
YAML
workflow_version: "v005"
|
|
feature: "examine what is missing in current app and implement it"
|
|
created_at: "2025-12-20T22:05:00Z"
|
|
status: "draft"
|
|
revision: 1
|
|
|
|
data_models:
|
|
# New RefreshToken model for token rotation
|
|
- id: model_refresh_token
|
|
name: RefreshToken
|
|
description: "JWT refresh tokens for secure authentication"
|
|
table_name: refresh_tokens
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique identifier"
|
|
- name: token
|
|
type: string
|
|
constraints: ["unique", "not_null"]
|
|
description: "Refresh token value"
|
|
- name: userId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "User who owns this token"
|
|
- name: expiresAt
|
|
type: datetime
|
|
constraints: ["not_null", "indexed"]
|
|
description: "Token expiration time"
|
|
- name: createdAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "When token was created"
|
|
- name: isRevoked
|
|
type: boolean
|
|
constraints: ["default(false)", "indexed"]
|
|
description: "Whether token has been revoked"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: userId
|
|
on_delete: cascade
|
|
timestamps: false
|
|
|
|
# New Session model for active session tracking
|
|
- id: model_session
|
|
name: Session
|
|
description: "Active user sessions with device tracking"
|
|
table_name: sessions
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique session identifier"
|
|
- name: userId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "User who owns this session"
|
|
- name: token
|
|
type: string
|
|
constraints: ["unique", "not_null"]
|
|
description: "Session token"
|
|
- name: deviceInfo
|
|
type: json
|
|
constraints: []
|
|
description: "Device information (browser, OS)"
|
|
- name: ipAddress
|
|
type: string
|
|
constraints: []
|
|
description: "Client IP address"
|
|
- name: userAgent
|
|
type: string
|
|
constraints: []
|
|
description: "Browser user agent"
|
|
- name: lastActivity
|
|
type: datetime
|
|
constraints: ["default(now())"]
|
|
description: "Last activity timestamp"
|
|
- name: createdAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "When session was created"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: userId
|
|
relation_name: UserSessions
|
|
on_delete: cascade
|
|
timestamps: false
|
|
|
|
# New PlayHistory model
|
|
- id: model_play_history
|
|
name: PlayHistory
|
|
description: "Track user play history for analytics"
|
|
table_name: play_history
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique identifier"
|
|
- name: userId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "User who played the song"
|
|
- name: songId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "Song that was played"
|
|
- name: playedAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())", "indexed"]
|
|
description: "When playback started"
|
|
- name: playedDuration
|
|
type: integer
|
|
constraints: []
|
|
description: "Seconds actually played"
|
|
- name: completed
|
|
type: boolean
|
|
constraints: ["default(false)"]
|
|
description: "Did user listen to completion"
|
|
- name: source
|
|
type: string
|
|
constraints: []
|
|
description: "Where playback was initiated"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: userId
|
|
relation_name: UserPlayHistory
|
|
on_delete: cascade
|
|
- type: belongs_to
|
|
target: model_song
|
|
foreign_key: songId
|
|
relation_name: SongPlayHistory
|
|
on_delete: cascade
|
|
timestamps: false
|
|
|
|
# New Queue model for persistent queues
|
|
- id: model_queue
|
|
name: Queue
|
|
description: "User playback queue with state"
|
|
table_name: queues
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique queue identifier"
|
|
- name: userId
|
|
type: string
|
|
constraints: ["not_null", "unique"]
|
|
description: "User who owns this queue"
|
|
- name: songIds
|
|
type: json
|
|
constraints: ["not_null"]
|
|
description: "Array of song IDs in order"
|
|
- name: currentIndex
|
|
type: integer
|
|
constraints: ["default(0)"]
|
|
description: "Current playing index"
|
|
- name: isShuffled
|
|
type: boolean
|
|
constraints: ["default(false)"]
|
|
description: "Whether queue is shuffled"
|
|
- name: repeatMode
|
|
type: string
|
|
constraints: ["default('none')"]
|
|
description: "Repeat mode: none, one, all"
|
|
- name: createdAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "When queue was created"
|
|
- name: updatedAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "Last update time"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: userId
|
|
relation_name: UserQueue
|
|
on_delete: cascade
|
|
timestamps: false
|
|
|
|
# New UploadSession model for chunked uploads
|
|
- id: model_upload_session
|
|
name: UploadSession
|
|
description: "Chunked upload session tracking"
|
|
table_name: upload_sessions
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique upload session ID"
|
|
- name: userId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "User uploading the file"
|
|
- name: fileName
|
|
type: string
|
|
constraints: ["not_null"]
|
|
description: "Original file name"
|
|
- name: fileSize
|
|
type: integer
|
|
constraints: ["not_null"]
|
|
description: "Total file size in bytes"
|
|
- name: mimeType
|
|
type: string
|
|
constraints: ["not_null"]
|
|
description: "File MIME type"
|
|
- name: chunkSize
|
|
type: integer
|
|
constraints: ["not_null"]
|
|
description: "Size of each chunk"
|
|
- name: totalChunks
|
|
type: integer
|
|
constraints: ["not_null"]
|
|
description: "Total number of chunks"
|
|
- name: uploadedChunks
|
|
type: json
|
|
constraints: []
|
|
description: "Array of uploaded chunk numbers"
|
|
- name: status
|
|
type: string
|
|
constraints: ["default('pending')"]
|
|
description: "Upload status"
|
|
- name: fileId
|
|
type: string
|
|
constraints: []
|
|
description: "Associated file ID when complete"
|
|
- name: metadata
|
|
type: json
|
|
constraints: []
|
|
description: "Additional file metadata"
|
|
- name: createdAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "When upload started"
|
|
- name: expiresAt
|
|
type: datetime
|
|
constraints: ["not_null"]
|
|
description: "When upload session expires"
|
|
relations:
|
|
- type: belongs_to
|
|
target: model_user
|
|
foreign_key: userId
|
|
on_delete: cascade
|
|
timestamps: false
|
|
|
|
# New SearchIndex model
|
|
- id: model_search_index
|
|
name: SearchIndex
|
|
description: "Full-text search index for entities"
|
|
table_name: search_index
|
|
fields:
|
|
- name: id
|
|
type: string
|
|
constraints: ["primary_key", "default(cuid())"]
|
|
description: "Unique index entry ID"
|
|
- name: entityType
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "Type of entity (song, album, artist)"
|
|
- name: entityId
|
|
type: string
|
|
constraints: ["not_null", "indexed"]
|
|
description: "ID of the indexed entity"
|
|
- name: title
|
|
type: string
|
|
constraints: ["not_null"]
|
|
description: "Entity title for search"
|
|
- name: content
|
|
type: text
|
|
constraints: []
|
|
description: "Full text content"
|
|
- name: metadata
|
|
type: json
|
|
constraints: []
|
|
description: "Additional searchable metadata"
|
|
- name: createdAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "When indexed"
|
|
- name: updatedAt
|
|
type: datetime
|
|
constraints: ["not_null", "default(now())"]
|
|
description: "Last update"
|
|
indexes:
|
|
- fields: ["entityType", "entityId"]
|
|
unique: true
|
|
name: idx_search_entity
|
|
timestamps: false
|
|
|
|
api_endpoints:
|
|
# Session Management
|
|
- id: api_auth_refresh
|
|
method: POST
|
|
path: /api/auth/refresh
|
|
description: "Refresh access token using refresh token"
|
|
auth_required: false
|
|
request_body:
|
|
refreshToken: string
|
|
responses:
|
|
- status: 200
|
|
description: "Token refreshed successfully"
|
|
- status: 401
|
|
description: "Invalid or expired refresh token"
|
|
|
|
- id: api_auth_logout
|
|
method: POST
|
|
path: /api/auth/logout
|
|
description: "Logout user and invalidate tokens"
|
|
auth_required: true
|
|
|
|
- id: api_auth_sessions
|
|
method: GET
|
|
path: /api/auth/sessions
|
|
description: "List all active sessions"
|
|
auth_required: true
|
|
|
|
- id: api_auth_revoke_session
|
|
method: DELETE
|
|
path: /api/auth/sessions/:id
|
|
description: "Revoke specific session"
|
|
auth_required: true
|
|
|
|
# Email Verification
|
|
- id: api_auth_verify_email
|
|
method: POST
|
|
path: /api/auth/verify-email
|
|
description: "Send email verification"
|
|
auth_required: true
|
|
|
|
- id: api_auth_confirm_email
|
|
method: POST
|
|
path: /api/auth/confirm-email
|
|
description: "Confirm email with token"
|
|
auth_required: false
|
|
|
|
# Audio Player API
|
|
- id: api_player_play
|
|
method: POST
|
|
path: /api/player/play
|
|
description: "Start or resume playback"
|
|
auth_required: true
|
|
|
|
- id: api_player_pause
|
|
method: POST
|
|
path: /api/player/pause
|
|
description: "Pause playback"
|
|
auth_required: true
|
|
|
|
- id: api_player_next
|
|
method: POST
|
|
path: /api/player/next
|
|
description: "Skip to next track"
|
|
auth_required: true
|
|
|
|
- id: api_player_previous
|
|
method: POST
|
|
path: /api/player/previous
|
|
description: "Go to previous track"
|
|
auth_required: true
|
|
|
|
- id: api_player_queue
|
|
method: GET
|
|
path: /api/player/queue
|
|
description: "Get current queue"
|
|
auth_required: true
|
|
|
|
- id: api_player_queue_add
|
|
method: POST
|
|
path: /api/player/queue
|
|
description: "Add songs to queue"
|
|
auth_required: true
|
|
|
|
- id: api_player_queue_clear
|
|
method: DELETE
|
|
path: /api/player/queue
|
|
description: "Clear queue"
|
|
auth_required: true
|
|
|
|
- id: api_player_history
|
|
method: GET
|
|
path: /api/player/history
|
|
description: "Get play history"
|
|
auth_required: true
|
|
|
|
# File Upload API
|
|
- id: api_upload_init
|
|
method: POST
|
|
path: /api/upload/init
|
|
description: "Initialize chunked upload"
|
|
auth_required: true
|
|
|
|
- id: api_upload_chunk
|
|
method: POST
|
|
path: /api/upload/chunk/:uploadId/:chunkIndex
|
|
description: "Upload chunk"
|
|
auth_required: true
|
|
|
|
- id: api_upload_complete
|
|
method: POST
|
|
path: /api/upload/complete/:uploadId
|
|
description: "Complete upload"
|
|
auth_required: true
|
|
|
|
- id: api_upload_presigned
|
|
method: GET
|
|
path: /api/upload/presigned-url
|
|
description: "Get presigned URL for direct upload"
|
|
auth_required: true
|
|
|
|
# Search API
|
|
- id: api_search
|
|
method: GET
|
|
path: /api/search
|
|
description: "Search across all content"
|
|
auth_required: false
|
|
|
|
- id: api_search_suggestions
|
|
method: GET
|
|
path: /api/search/suggestions
|
|
description: "Get search suggestions"
|
|
auth_required: false
|
|
|
|
- id: api_search_index
|
|
method: POST
|
|
path: /api/search/index
|
|
description: "Index entity for search"
|
|
auth_required: true
|
|
|
|
pages:
|
|
- id: page_auth_verify_email
|
|
name: EmailVerification
|
|
path: /auth/verify-email
|
|
description: "Email verification page"
|
|
data_needs: []
|
|
components: []
|
|
auth_required: false
|
|
|
|
- id: page_settings_security
|
|
name: SecuritySettings
|
|
path: /settings/security
|
|
description: "Security settings page"
|
|
data_needs:
|
|
- api_id: api_auth_sessions
|
|
purpose: "Load active sessions"
|
|
on_load: true
|
|
components:
|
|
- component_session_list
|
|
- component_change_password
|
|
auth_required: true
|
|
|
|
components:
|
|
- id: component_audio_player
|
|
name: AudioPlayer
|
|
description: "Full-featured audio player with controls"
|
|
props:
|
|
- name: song
|
|
type: object
|
|
required: false
|
|
description: "Current playing song"
|
|
- name: isPlaying
|
|
type: boolean
|
|
required: true
|
|
description: "Playback state"
|
|
- name: progress
|
|
type: number
|
|
required: true
|
|
description: "Current playback progress (0-1)"
|
|
- name: volume
|
|
type: number
|
|
required: true
|
|
description: "Volume level (0-1)"
|
|
- name: queue
|
|
type: object
|
|
required: false
|
|
description: "Current queue"
|
|
events:
|
|
- name: play
|
|
description: "Play or resume playback"
|
|
- name: pause
|
|
description: "Pause playback"
|
|
- name: seek
|
|
description: "Seek to position"
|
|
- name: volumeChange
|
|
description: "Change volume"
|
|
- name: next
|
|
description: "Play next song"
|
|
- name: previous
|
|
description: "Play previous song"
|
|
- name: shuffle
|
|
description: "Toggle shuffle"
|
|
- name: repeat
|
|
description: "Set repeat mode"
|
|
uses_apis:
|
|
- api_player_play
|
|
- api_player_pause
|
|
- api_player_next
|
|
- api_player_previous
|
|
- api_player_queue
|
|
|
|
- id: component_upload_manager
|
|
name: UploadManager
|
|
description: "Handles chunked file uploads"
|
|
props:
|
|
- name: onUploadComplete
|
|
type: function
|
|
required: true
|
|
description: "Callback when upload completes"
|
|
- name: acceptedTypes
|
|
type: array
|
|
required: false
|
|
description: "Accepted file types"
|
|
- name: maxSize
|
|
type: number
|
|
required: false
|
|
description: "Max file size in bytes"
|
|
events:
|
|
- name: progress
|
|
description: "Upload progress update"
|
|
- name: error
|
|
description: "Upload error"
|
|
uses_apis:
|
|
- api_upload_init
|
|
- api_upload_chunk
|
|
- api_upload_complete
|
|
|
|
- id: component_search_bar
|
|
name: SearchBar
|
|
description: "Search input with suggestions"
|
|
props:
|
|
- name: placeholder
|
|
type: string
|
|
required: false
|
|
description: "Input placeholder"
|
|
- name: autoFocus
|
|
type: boolean
|
|
required: false
|
|
description: "Auto focus input"
|
|
events:
|
|
- name: search
|
|
description: "Search query submitted"
|
|
- name: suggestionSelect
|
|
description: "Suggestion selected"
|
|
uses_apis:
|
|
- api_search_suggestions
|
|
|
|
- id: component_search_results
|
|
name: SearchResults
|
|
description: "Display search results"
|
|
props:
|
|
- name: query
|
|
type: string
|
|
required: true
|
|
description: "Search query"
|
|
- name: filters
|
|
type: object
|
|
required: false
|
|
description: "Search filters"
|
|
events:
|
|
- name: playSong
|
|
description: "Play selected song"
|
|
- name: viewAlbum
|
|
description: "View album details"
|
|
- name: viewArtist
|
|
description: "View artist profile"
|
|
uses_apis:
|
|
- api_search |