project-standalo-sonic-cloud/.workflow/versions/v005/design/design_document.yml

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