feat(social): add listening activity, shared rooms and modern chat
ci / api / api (push) Canceled after 0s
ci / module boundaries / boundaries (push) Canceled after 0s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Canceled after 11m29s
ci / db / db (push) Canceled after 11m22s
ci / mobile / mobile (Metro-бандл Android) (push) Canceled after 6m43s
ci / recommender / recommender (push) Canceled after 6m47s
ci / ts / ts (packages + web) (push) Canceled after 1s
ci / api / api (push) Canceled after 0s
ci / module boundaries / boundaries (push) Canceled after 0s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Canceled after 11m29s
ci / db / db (push) Canceled after 11m22s
ci / mobile / mobile (Metro-бандл Android) (push) Canceled after 6m43s
ci / recommender / recommender (push) Canceled after 6m47s
ci / ts / ts (packages + web) (push) Canceled after 1s
This commit is contained in:
1 parent
0a05bfe2cd
commit
11a16cd023
59 files changed
+5022
-542
No files matched your search
+296
-2
@@ -22,6 +22,118 @@ tags:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
paths:
|
||||
/api/v1/me/social/recommendations:
|
||||
put:
|
||||
operationId: setFriendsRecommendations
|
||||
tags: [me]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [enabled]
|
||||
properties:
|
||||
enabled: {type: boolean}
|
||||
responses:
|
||||
'204': {description: Saved}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/listening:
|
||||
get:
|
||||
operationId: getListeningState
|
||||
tags: [me]
|
||||
responses:
|
||||
'200':
|
||||
description: Listening preferences, current room and pending requests
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ListeningState'}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/listening/preferences:
|
||||
put:
|
||||
operationId: setListeningPreferences
|
||||
tags: [me]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ListeningPreferences'}
|
||||
responses:
|
||||
'204': {description: Saved}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/listening/actions:
|
||||
post:
|
||||
operationId: listeningAction
|
||||
tags: [me]
|
||||
description: Join requests target the host; invites target a guest and use the sender's own stream. Accept/decline/cancel target a request UUID. Leave/end target the room UUID. Automatic acceptance always respects the receiving user's admission policy. No control transfer.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ListeningAction'}
|
||||
responses:
|
||||
'204': {description: Applied}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/listening/state:
|
||||
put:
|
||||
operationId: publishListeningState
|
||||
tags: [me]
|
||||
description: Only the room owner's source device may publish. Monotonic sequence prevents stale updates; room expires after 30 seconds without host heartbeat.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ListeningRoomInput'}
|
||||
responses:
|
||||
'204': {description: Applied}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/activity:
|
||||
put:
|
||||
operationId: publishListeningActivity
|
||||
tags: [me]
|
||||
description: Ephemeral playback lease, expires after 90 seconds. Ignored when visibility is nobody. Sequence is monotonic per device; paused updates clear only that device. No offline replay.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ListeningActivityInput'}
|
||||
responses:
|
||||
'204': {description: Applied}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/activity/visibility:
|
||||
put:
|
||||
operationId: setActivityVisibility
|
||||
tags: [me]
|
||||
description: Applies to all devices. Nobody clears all current activity immediately.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [visibility]
|
||||
properties:
|
||||
visibility: {$ref: '#/components/schemas/ActivityVisibility'}
|
||||
responses:
|
||||
'204': {description: Saved}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/social/activity/{userId}/playlist:
|
||||
get:
|
||||
operationId: getActivityPlaylist
|
||||
tags: [me]
|
||||
description: Read-only current playlist subject to live visibility, friendship, blocking and expiry. Does not create or reveal a permanent share token.
|
||||
parameters:
|
||||
- in: path
|
||||
name: userId
|
||||
required: true
|
||||
schema: {type: string, format: uuid}
|
||||
responses:
|
||||
'200':
|
||||
description: Read-only playlist
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/Playlist'}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social:
|
||||
get:
|
||||
operationId: getSocial
|
||||
@@ -79,6 +191,9 @@ paths:
|
||||
- in: query
|
||||
name: before
|
||||
schema: {type: integer, format: int64, minimum: 1}
|
||||
- in: query
|
||||
name: q
|
||||
schema: {type: string, maxLength: 100}
|
||||
responses:
|
||||
'200':
|
||||
description: Message page
|
||||
@@ -104,6 +219,69 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/DirectMessage'}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/chats/{peerId}/meta:
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema: {type: string, format: uuid}
|
||||
get:
|
||||
operationId: getChatMeta
|
||||
tags: [me]
|
||||
responses:
|
||||
'200':
|
||||
description: Ephemeral typing indicator, friends only
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [typing]
|
||||
properties:
|
||||
typing: {type: boolean}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
put:
|
||||
operationId: setChatTyping
|
||||
tags: [me]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [typing]
|
||||
properties:
|
||||
typing: {type: boolean}
|
||||
responses:
|
||||
'204': {description: Applied, lease expires after 6 seconds}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/me/social/chats/{peerId}/messages/{messageId}:
|
||||
patch:
|
||||
operationId: updateChatMessage
|
||||
tags: [me]
|
||||
description: Friends only. Edit/delete are sender-only. Delete removes content for both participants; react toggles one allowed emoji. Reply targets must belong to the same conversation.
|
||||
parameters:
|
||||
- in: path
|
||||
name: peerId
|
||||
required: true
|
||||
schema: {type: string, format: uuid}
|
||||
- in: path
|
||||
name: messageId
|
||||
required: true
|
||||
schema: {type: string, format: uuid}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [action]
|
||||
properties:
|
||||
action: {type: string, enum: [edit, delete, react]}
|
||||
text: {type: string, maxLength: 4000}
|
||||
emoji: {type: string, enum: ['❤️', '👍', '🔥', '😂', '🎵', '👏']}
|
||||
responses:
|
||||
'204': {description: Applied}
|
||||
default: {$ref: '#/components/responses/Problem'}
|
||||
/api/v1/admin/server/updates:
|
||||
get:
|
||||
operationId: getServerUpdates
|
||||
@@ -2076,6 +2254,103 @@ components:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Problem"
|
||||
schemas:
|
||||
ListeningPreferences:
|
||||
type: object
|
||||
required: [requests, auto_accept, autoplay]
|
||||
properties:
|
||||
requests: {$ref: '#/components/schemas/ActivityVisibility'}
|
||||
auto_accept: {$ref: '#/components/schemas/ActivityVisibility'}
|
||||
autoplay: {type: boolean, default: true}
|
||||
ListeningAction:
|
||||
type: object
|
||||
required: [id, action, target_id]
|
||||
properties:
|
||||
id: {type: string, format: uuid}
|
||||
action: {type: string, enum: [join, invite, accept, decline, cancel, leave, end]}
|
||||
target_id: {type: string, format: uuid}
|
||||
device_id: {type: string, format: uuid}
|
||||
track_id: {type: string, format: uuid}
|
||||
position_ms: {type: integer, minimum: 0}
|
||||
playing: {type: boolean}
|
||||
ListeningRoomInput:
|
||||
type: object
|
||||
required: [room_id, device_id, sequence, playing, position_ms]
|
||||
properties:
|
||||
room_id: {type: string, format: uuid}
|
||||
device_id: {type: string, format: uuid}
|
||||
sequence: {type: integer, format: int64, minimum: 1}
|
||||
track_id: {type: string, format: uuid}
|
||||
playing: {type: boolean}
|
||||
position_ms: {type: integer, minimum: 0}
|
||||
ListeningRoom:
|
||||
type: object
|
||||
required: [id, owner_id, owner_name, device_id, track_id, position_ms, playing, updated_at, participants]
|
||||
properties:
|
||||
id: {type: string, format: uuid}
|
||||
owner_id: {type: string, format: uuid}
|
||||
owner_name: {type: string}
|
||||
device_id: {type: string, format: uuid}
|
||||
track_id: {type: string, format: uuid, nullable: true}
|
||||
position_ms: {type: integer}
|
||||
playing: {type: boolean}
|
||||
updated_at: {type: string, format: date-time}
|
||||
participants:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [id, name]
|
||||
properties:
|
||||
id: {type: string, format: uuid}
|
||||
name: {type: string}
|
||||
ListeningRequest:
|
||||
type: object
|
||||
required: [id, sender_id, sender_name, recipient_id, owner_id, kind, expires_at]
|
||||
properties:
|
||||
id: {type: string, format: uuid}
|
||||
sender_id: {type: string, format: uuid}
|
||||
sender_name: {type: string}
|
||||
recipient_id: {type: string, format: uuid}
|
||||
owner_id: {type: string, format: uuid}
|
||||
kind: {type: string, enum: [join, invite]}
|
||||
expires_at: {type: string, format: date-time}
|
||||
ListeningState:
|
||||
type: object
|
||||
required: [preferences, room, requests, server_time]
|
||||
properties:
|
||||
preferences: {$ref: '#/components/schemas/ListeningPreferences'}
|
||||
room:
|
||||
nullable: true
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/ListeningRoom'}
|
||||
requests:
|
||||
type: array
|
||||
items: {$ref: '#/components/schemas/ListeningRequest'}
|
||||
server_time: {type: string, format: date-time}
|
||||
ActivityVisibility:
|
||||
type: string
|
||||
enum: [nobody, friends, everyone]
|
||||
default: nobody
|
||||
ListeningActivityInput:
|
||||
type: object
|
||||
required: [device_id, sequence, playing, context]
|
||||
properties:
|
||||
device_id: {type: string, format: uuid}
|
||||
sequence: {type: integer, format: int64, minimum: 1}
|
||||
playing: {type: boolean}
|
||||
position_ms: {type: integer, minimum: 0, default: 0}
|
||||
track_id: {type: string, format: uuid}
|
||||
context: {type: string, enum: [other, wave, playlist]}
|
||||
playlist_id: {type: string, format: uuid}
|
||||
ListeningActivity:
|
||||
type: object
|
||||
required: [track_id, track_title, artist, context]
|
||||
properties:
|
||||
track_id: {type: string, format: uuid}
|
||||
track_title: {type: string}
|
||||
artist: {type: string}
|
||||
context: {type: string, enum: [other, wave, playlist]}
|
||||
playlist_id: {type: string, format: uuid}
|
||||
playlist_name: {type: string}
|
||||
SocialPerson:
|
||||
type: object
|
||||
required: [id, username, display_name, relationship, unread]
|
||||
@@ -2085,6 +2360,7 @@ components:
|
||||
display_name: {type: string}
|
||||
relationship: {type: string, enum: [none, incoming, outgoing, friend, blocked]}
|
||||
unread: {type: integer}
|
||||
activity: {$ref: '#/components/schemas/ListeningActivity'}
|
||||
SocialNotification:
|
||||
type: object
|
||||
required: [id, actor_id, actor_name, kind, created_at, read]
|
||||
@@ -2092,13 +2368,15 @@ components:
|
||||
id: {type: string, format: uuid}
|
||||
actor_id: {type: string, format: uuid}
|
||||
actor_name: {type: string}
|
||||
kind: {type: string, enum: [friend_request, friend_accepted, message, music]}
|
||||
kind: {type: string, enum: [friend_request, friend_accepted, message, music, listening_request, listening_invite, listening_accepted]}
|
||||
created_at: {type: string, format: date-time}
|
||||
read: {type: boolean}
|
||||
SocialInbox:
|
||||
type: object
|
||||
required: [people, notifications, unread]
|
||||
required: [people, notifications, unread, activity_visibility, friends_recommendations]
|
||||
properties:
|
||||
friends_recommendations: {type: boolean, default: true}
|
||||
activity_visibility: {$ref: '#/components/schemas/ActivityVisibility'}
|
||||
people:
|
||||
type: array
|
||||
items: {$ref: '#/components/schemas/SocialPerson'}
|
||||
@@ -2121,6 +2399,7 @@ components:
|
||||
text: {type: string, maxLength: 4000}
|
||||
attachment_kind: {type: string, enum: [none, track, playlist]}
|
||||
attachment_id: {type: string, format: uuid}
|
||||
reply_to: {type: string, format: uuid}
|
||||
DirectMessage:
|
||||
type: object
|
||||
required: [id, seq, sender_id, text, attachment_kind, created_at, read]
|
||||
@@ -2133,6 +2412,21 @@ components:
|
||||
attachment_id: {type: string, format: uuid}
|
||||
created_at: {type: string, format: date-time}
|
||||
read: {type: boolean}
|
||||
reply_to: {type: string, format: uuid}
|
||||
reply_preview: {type: string}
|
||||
edited_at: {type: string, format: date-time}
|
||||
deleted: {type: boolean}
|
||||
reactions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MessageReaction'
|
||||
MessageReaction:
|
||||
type: object
|
||||
required: [emoji, count, mine]
|
||||
properties:
|
||||
emoji: {type: string}
|
||||
count: {type: integer}
|
||||
mine: {type: boolean}
|
||||
ServerSource:
|
||||
type: object
|
||||
required: [id, name, url]
|
||||
|
||||
Reference in new issue
Block a user