616 lines
19 KiB
YAML
616 lines
19 KiB
YAML
workflow_version: "v004"
|
|
feature: "add share music system"
|
|
created_at: "2025-12-18T18:07:00"
|
|
status: draft
|
|
revision: 1
|
|
|
|
data_models:
|
|
- id: model_share
|
|
name: Share
|
|
table_name: shares
|
|
description: Tracks shared content links with analytics
|
|
primary_key: id
|
|
fields:
|
|
- name: id
|
|
type: String
|
|
constraints: [primary_key, cuid]
|
|
description: Unique identifier for the share
|
|
- name: type
|
|
type: ShareType
|
|
constraints: [required]
|
|
description: Type of content being shared (SONG, PLAYLIST, ALBUM)
|
|
- name: targetId
|
|
type: String
|
|
constraints: [required]
|
|
description: ID of the shared content (references songs/playlists/albums)
|
|
- name: token
|
|
type: String
|
|
constraints: [required, unique]
|
|
description: Unique URL-safe token for share links
|
|
- name: userId
|
|
type: String
|
|
constraints: [optional]
|
|
description: User who created the share (null for anonymous shares)
|
|
- name: platform
|
|
type: String
|
|
constraints: [optional]
|
|
description: Platform where content was shared to (twitter, facebook, etc)
|
|
- name: clickCount
|
|
type: Int
|
|
default: 0
|
|
description: Number of times the share link was clicked
|
|
- name: createdAt
|
|
type: DateTime
|
|
default: now()
|
|
description: Timestamp when the share was created
|
|
relations: []
|
|
indexes:
|
|
- fields: [token]
|
|
unique: true
|
|
description: Fast lookup by share token
|
|
- fields: [targetId, type]
|
|
description: Fast lookup of shares for specific content
|
|
timestamps: false
|
|
validations:
|
|
- field: token
|
|
rule: minLength(8)
|
|
message: Token must be at least 8 characters
|
|
- field: type
|
|
rule: enum(SONG, PLAYLIST, ALBUM)
|
|
message: Type must be SONG, PLAYLIST, or ALBUM
|
|
|
|
api_endpoints:
|
|
- id: api_create_song_share
|
|
method: POST
|
|
path: /api/share/song/[id]
|
|
description: Generate share link for a song
|
|
auth:
|
|
required: false
|
|
description: No authentication required to share content
|
|
request_params:
|
|
- name: id
|
|
type: string
|
|
location: path
|
|
required: true
|
|
description: Song ID to generate share link for
|
|
request_body:
|
|
- name: platform
|
|
type: string
|
|
required: false
|
|
description: Platform being shared to (twitter, facebook, etc)
|
|
responses:
|
|
- status: 200
|
|
description: Share link created successfully
|
|
schema:
|
|
shareUrl: string
|
|
token: string
|
|
type: string
|
|
- status: 404
|
|
description: Song not found
|
|
schema:
|
|
error: string
|
|
business_logic:
|
|
- Verify song exists and is public
|
|
- Generate unique 10-character alphanumeric token
|
|
- Create Share record with type=SONG
|
|
- Return full share URL (https://domain.com/s/[token])
|
|
depends_on_models: [model_share]
|
|
|
|
- id: api_create_playlist_share
|
|
method: POST
|
|
path: /api/share/playlist/[id]
|
|
description: Generate share link for a playlist
|
|
auth:
|
|
required: false
|
|
request_params:
|
|
- name: id
|
|
type: string
|
|
location: path
|
|
required: true
|
|
description: Playlist ID to generate share link for
|
|
request_body:
|
|
- name: platform
|
|
type: string
|
|
required: false
|
|
description: Platform being shared to
|
|
responses:
|
|
- status: 200
|
|
description: Share link created successfully
|
|
schema:
|
|
shareUrl: string
|
|
token: string
|
|
type: string
|
|
- status: 404
|
|
description: Playlist not found
|
|
schema:
|
|
error: string
|
|
business_logic:
|
|
- Verify playlist exists and is public
|
|
- Generate unique token
|
|
- Create Share record with type=PLAYLIST
|
|
- Return full share URL
|
|
depends_on_models: [model_share]
|
|
|
|
- id: api_create_album_share
|
|
method: POST
|
|
path: /api/share/album/[id]
|
|
description: Generate share link for an album
|
|
auth:
|
|
required: false
|
|
request_params:
|
|
- name: id
|
|
type: string
|
|
location: path
|
|
required: true
|
|
description: Album ID to generate share link for
|
|
request_body:
|
|
- name: platform
|
|
type: string
|
|
required: false
|
|
description: Platform being shared to
|
|
responses:
|
|
- status: 200
|
|
description: Share link created successfully
|
|
schema:
|
|
shareUrl: string
|
|
token: string
|
|
type: string
|
|
- status: 404
|
|
description: Album not found
|
|
schema:
|
|
error: string
|
|
business_logic:
|
|
- Verify album exists
|
|
- Generate unique token
|
|
- Create Share record with type=ALBUM
|
|
- Return full share URL
|
|
depends_on_models: [model_share]
|
|
|
|
- id: api_resolve_share
|
|
method: GET
|
|
path: /api/share/[token]
|
|
description: Resolve share token and get content details
|
|
auth:
|
|
required: false
|
|
request_params:
|
|
- name: token
|
|
type: string
|
|
location: path
|
|
required: true
|
|
description: Share token to resolve
|
|
responses:
|
|
- status: 200
|
|
description: Share resolved successfully
|
|
schema:
|
|
type: string
|
|
targetId: string
|
|
content: object
|
|
shareUrl: string
|
|
- status: 404
|
|
description: Share not found or content no longer available
|
|
schema:
|
|
error: string
|
|
business_logic:
|
|
- Lookup Share record by token
|
|
- Fetch associated content based on type (Song/Playlist/Album)
|
|
- Include artist information for songs/albums
|
|
- Include song list for playlists
|
|
- Verify content is still public and available
|
|
- Return content details for display
|
|
depends_on_models: [model_share]
|
|
|
|
- id: api_track_share_click
|
|
method: POST
|
|
path: /api/share/[token]/click
|
|
description: Increment share click count for analytics
|
|
auth:
|
|
required: false
|
|
request_params:
|
|
- name: token
|
|
type: string
|
|
location: path
|
|
required: true
|
|
description: Share token to track click for
|
|
request_body: []
|
|
responses:
|
|
- status: 200
|
|
description: Click tracked successfully
|
|
schema:
|
|
success: boolean
|
|
clickCount: integer
|
|
- status: 404
|
|
description: Share not found
|
|
schema:
|
|
error: string
|
|
business_logic:
|
|
- Find Share record by token
|
|
- Increment clickCount by 1
|
|
- Return new click count
|
|
depends_on_models: [model_share]
|
|
|
|
pages:
|
|
- id: page_share
|
|
path: /s/[token]
|
|
name: SharePage
|
|
description: Public landing page for shared music content
|
|
layout: minimal
|
|
auth:
|
|
required: false
|
|
description: Publicly accessible share page
|
|
data_needs:
|
|
- api_id: api_resolve_share
|
|
purpose: Load shared content details
|
|
on_load: true
|
|
description: Fetch content details when page loads
|
|
- api_id: api_track_share_click
|
|
purpose: Track analytics
|
|
on_load: true
|
|
description: Track that user clicked on share link
|
|
components:
|
|
- component_share_content_display
|
|
seo:
|
|
dynamic_title: true
|
|
description: Dynamic based on shared content
|
|
og_image: true
|
|
twitter_card: true
|
|
ui_states:
|
|
- state: loading
|
|
description: Fetching shared content
|
|
- state: content_loaded
|
|
description: Display shared content with CTA
|
|
- state: not_found
|
|
description: Share token invalid or content no longer available
|
|
- state: private_content
|
|
description: Content is now private
|
|
|
|
components:
|
|
- id: component_share_button
|
|
name: ShareButton
|
|
description: Button component to trigger share modal
|
|
file_path: components/ShareButton.tsx
|
|
props:
|
|
- name: type
|
|
type: "'song' | 'playlist' | 'album'"
|
|
required: true
|
|
description: Type of content to share
|
|
- name: targetId
|
|
type: string
|
|
required: true
|
|
description: ID of the content to share
|
|
- name: title
|
|
type: string
|
|
required: true
|
|
description: Title to display in share modal
|
|
- name: className
|
|
type: string
|
|
required: false
|
|
description: Additional CSS classes
|
|
state:
|
|
- name: isModalOpen
|
|
type: boolean
|
|
description: Controls share modal visibility
|
|
- name: shareUrl
|
|
type: string | null
|
|
description: Generated share URL after creation
|
|
- name: isLoading
|
|
type: boolean
|
|
description: Share link generation in progress
|
|
events:
|
|
- name: onShare
|
|
payload: "{ shareUrl: string, token: string }"
|
|
description: Fired when share link is successfully generated
|
|
uses_components: [component_share_modal]
|
|
uses_apis: [api_create_song_share, api_create_playlist_share, api_create_album_share]
|
|
styling:
|
|
- Accessible button with share icon
|
|
- Loading state during share generation
|
|
- Error state if share creation fails
|
|
|
|
- id: component_share_modal
|
|
name: ShareModal
|
|
description: Modal displaying share options and social buttons
|
|
file_path: components/ShareModal.tsx
|
|
props:
|
|
- name: isOpen
|
|
type: boolean
|
|
required: true
|
|
description: Controls modal visibility
|
|
- name: onClose
|
|
type: "() => void"
|
|
required: true
|
|
description: Callback to close modal
|
|
- name: shareUrl
|
|
type: string
|
|
required: true
|
|
description: The share URL to display and copy
|
|
- name: title
|
|
type: string
|
|
required: true
|
|
description: Title of content being shared
|
|
- name: type
|
|
type: "'song' | 'playlist' | 'album'"
|
|
required: true
|
|
description: Type of content for display context
|
|
state:
|
|
- name: copied
|
|
type: boolean
|
|
description: Shows copied confirmation after clipboard copy
|
|
events:
|
|
- name: onCopy
|
|
payload: null
|
|
description: Fired when user copies link to clipboard
|
|
uses_components: [component_social_share_buttons]
|
|
uses_apis: []
|
|
styling:
|
|
- Centered modal with backdrop
|
|
- Copy to clipboard button with icon
|
|
- Success message after copy
|
|
- Close button
|
|
- Responsive design for mobile
|
|
|
|
- id: component_social_share_buttons
|
|
name: SocialShareButtons
|
|
description: Social media share buttons for Twitter and Facebook
|
|
file_path: components/SocialShareButtons.tsx
|
|
props:
|
|
- name: url
|
|
type: string
|
|
required: true
|
|
description: URL to share
|
|
- name: title
|
|
type: string
|
|
required: true
|
|
description: Title/text to include in share
|
|
- name: type
|
|
type: "'song' | 'playlist' | 'album'"
|
|
required: false
|
|
description: Type of content for customized share text
|
|
events:
|
|
- name: onPlatformShare
|
|
payload: "{ platform: string }"
|
|
description: Fired when user clicks a social share button
|
|
uses_components: []
|
|
uses_apis: []
|
|
business_logic:
|
|
- Twitter button opens Twitter intent URL with pre-filled text
|
|
- Facebook button opens Facebook sharer dialog
|
|
- Generate share text based on content type
|
|
- URL encode parameters properly
|
|
styling:
|
|
- Buttons with platform brand colors
|
|
- Platform icons (Twitter bird, Facebook f)
|
|
- Hover and focus states
|
|
- Responsive layout (stack on mobile)
|
|
|
|
- id: component_share_content_display
|
|
name: SharedContentDisplay
|
|
description: Displays shared song/playlist/album with call-to-action
|
|
file_path: components/SharedContentDisplay.tsx
|
|
props:
|
|
- name: type
|
|
type: "'song' | 'playlist' | 'album'"
|
|
required: true
|
|
description: Type of shared content
|
|
- name: content
|
|
type: object
|
|
required: true
|
|
description: Content data (song/playlist/album with artist info)
|
|
state:
|
|
- name: isPlaying
|
|
type: boolean
|
|
description: Audio playback state (for songs)
|
|
uses_components: []
|
|
uses_apis: [api_track_share_click]
|
|
business_logic:
|
|
- Display cover art, title, artist name
|
|
- For songs: show waveform, duration, play button
|
|
- For playlists: show track count, curator
|
|
- For albums: show release date, track count
|
|
- 'Call-to-action button: "Listen on Sonic Cloud" or "Sign up to create playlists"'
|
|
styling:
|
|
- Large cover art
|
|
- Prominent title and artist
|
|
- Clean, minimal design focused on content
|
|
- Branded CTA button
|
|
- Preview player for songs (if implemented)
|
|
|
|
utils:
|
|
- id: util_generate_share_token
|
|
name: generateShareToken
|
|
description: Generate unique URL-safe token for share links
|
|
file_path: lib/share.ts
|
|
function_signature: "() => string"
|
|
implementation:
|
|
- Generate random 10-character alphanumeric string
|
|
- Use crypto-safe random generation
|
|
- Verify uniqueness against existing tokens
|
|
- Return URL-safe token (no special characters)
|
|
|
|
- id: util_build_share_url
|
|
name: buildShareUrl
|
|
description: Build full share URL from token
|
|
file_path: lib/share.ts
|
|
function_signature: "(token: string) => string"
|
|
implementation:
|
|
- Get base URL from environment or request
|
|
- Construct URL: https://domain.com/s/[token]
|
|
- Return full shareable URL
|
|
|
|
- id: util_build_social_share_urls
|
|
name: buildSocialShareUrls
|
|
description: Generate platform-specific share URLs
|
|
file_path: lib/share.ts
|
|
function_signature: "(url: string, title: string, type: string) => { twitter: string, facebook: string }"
|
|
implementation:
|
|
- Twitter: https://twitter.com/intent/tweet?text=[title]&url=[url]
|
|
- Facebook: https://www.facebook.com/sharer/sharer.php?u=[url]
|
|
- URL encode all parameters
|
|
- Customize share text based on content type
|
|
|
|
types:
|
|
- id: type_share_type
|
|
name: ShareType
|
|
description: Enum for share content types
|
|
file_path: types/share.ts
|
|
definition: "enum ShareType { SONG = 'SONG', PLAYLIST = 'PLAYLIST', ALBUM = 'ALBUM' }"
|
|
|
|
- id: type_share
|
|
name: Share
|
|
description: TypeScript type for Share model
|
|
file_path: types/share.ts
|
|
definition: |
|
|
interface Share {
|
|
id: string;
|
|
type: ShareType;
|
|
targetId: string;
|
|
token: string;
|
|
userId: string | null;
|
|
platform: string | null;
|
|
clickCount: number;
|
|
createdAt: Date;
|
|
}
|
|
|
|
- id: type_share_response
|
|
name: ShareResponse
|
|
description: API response for share creation
|
|
file_path: types/share.ts
|
|
definition: |
|
|
interface ShareResponse {
|
|
shareUrl: string;
|
|
token: string;
|
|
type: ShareType;
|
|
}
|
|
|
|
- id: type_resolved_share
|
|
name: ResolvedShare
|
|
description: API response for share resolution
|
|
file_path: types/share.ts
|
|
definition: |
|
|
interface ResolvedShare {
|
|
type: ShareType;
|
|
targetId: string;
|
|
content: Song | Playlist | Album;
|
|
shareUrl: string;
|
|
}
|
|
|
|
architecture:
|
|
layer_1_data:
|
|
- Add Share model to Prisma schema
|
|
- Create ShareType enum in Prisma
|
|
- Add optional userId relation to User model
|
|
- Migration: create shares table with indexes
|
|
|
|
layer_2_api:
|
|
- POST /api/share/song/[id] - Create song share
|
|
- POST /api/share/playlist/[id] - Create playlist share
|
|
- POST /api/share/album/[id] - Create album share
|
|
- GET /api/share/[token] - Resolve share and fetch content
|
|
- POST /api/share/[token]/click - Track analytics
|
|
|
|
layer_3_ui:
|
|
- ShareButton component (trigger)
|
|
- ShareModal component (display options)
|
|
- SocialShareButtons component (Twitter/Facebook)
|
|
- SharedContentDisplay component (public page)
|
|
- /s/[token] page (public landing page)
|
|
|
|
layer_4_util:
|
|
- lib/share.ts - Token generation and URL building utilities
|
|
|
|
dependencies:
|
|
data_to_api:
|
|
model_share:
|
|
- api_create_song_share
|
|
- api_create_playlist_share
|
|
- api_create_album_share
|
|
- api_resolve_share
|
|
- api_track_share_click
|
|
|
|
api_to_component:
|
|
api_create_song_share: [component_share_button]
|
|
api_create_playlist_share: [component_share_button]
|
|
api_create_album_share: [component_share_button]
|
|
api_resolve_share: [component_share_content_display]
|
|
api_track_share_click: [component_share_content_display]
|
|
|
|
component_to_page:
|
|
component_share_button: [page_home, page_artist_profile, page_album_detail, page_playlist_detail, page_search]
|
|
component_share_content_display: [page_share]
|
|
|
|
component_to_component:
|
|
component_share_button:
|
|
- component_share_modal
|
|
component_share_modal:
|
|
- component_social_share_buttons
|
|
|
|
implementation_order:
|
|
phase_1_data:
|
|
- Add ShareType enum to Prisma schema
|
|
- Add Share model to Prisma schema
|
|
- Create and run migration
|
|
- Generate Prisma client
|
|
|
|
phase_2_utilities:
|
|
- Create lib/share.ts with token generation
|
|
- Create types/share.ts with TypeScript types
|
|
- Add share URL building utilities
|
|
|
|
phase_3_api:
|
|
- Implement POST /api/share/song/[id]
|
|
- Implement POST /api/share/playlist/[id]
|
|
- Implement POST /api/share/album/[id]
|
|
- Implement GET /api/share/[token]
|
|
- Implement POST /api/share/[token]/click
|
|
|
|
phase_4_components:
|
|
- Create SocialShareButtons component
|
|
- Create ShareModal component
|
|
- Create ShareButton component
|
|
- Create SharedContentDisplay component
|
|
|
|
phase_5_pages:
|
|
- Create /s/[token] page
|
|
- Integrate ShareButton into existing pages
|
|
|
|
testing_requirements:
|
|
unit_tests:
|
|
- Token generation uniqueness
|
|
- URL building correctness
|
|
- Share creation for each content type
|
|
- Share resolution with valid/invalid tokens
|
|
- Click count increment
|
|
|
|
integration_tests:
|
|
- Full share flow: create → resolve → display
|
|
- Social share URL generation
|
|
- Public page rendering for all content types
|
|
- Analytics tracking
|
|
|
|
edge_cases:
|
|
- Share private content (should fail or make public)
|
|
- Share deleted content (404 on resolution)
|
|
- Token collision (should regenerate)
|
|
- Anonymous vs authenticated shares
|
|
|
|
security_considerations:
|
|
- Share tokens must be cryptographically random (10+ characters)
|
|
- Rate limit share creation to prevent abuse
|
|
- Validate content is public before sharing
|
|
- Sanitize all user input in share URLs
|
|
- Handle deleted/private content gracefully
|
|
- No PII in share URLs
|
|
|
|
seo_requirements:
|
|
- Dynamic Open Graph tags on /s/[token] pages
|
|
- og:title from shared content
|
|
- og:image from cover art
|
|
- og:description from content description
|
|
- Twitter Card meta tags
|
|
- Canonical URL handling
|
|
|
|
analytics_requirements:
|
|
- Track share creation by content type
|
|
- Track share clicks (clickCount field)
|
|
- Track platform (Twitter, Facebook, copy link)
|
|
- Track conversion from share page to signup/login
|