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

This commit is contained in:
mrleo1nid committed 2026-10-23 10:45:40 +03:00
1 parent 0a05bfe2cd
commit 11a16cd023
59 files changed
+5022 -542

No files matched your search

+563 -1
View File
@@ -4,6 +4,139 @@
*/
export interface paths {
"/api/v1/me/social/recommendations": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put: operations["setFriendsRecommendations"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/listening": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getListeningState"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/listening/preferences": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put: operations["setListeningPreferences"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/listening/actions": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** @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. */
post: operations["listeningAction"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/listening/state": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/** @description Only the room owner's source device may publish. Monotonic sequence prevents stale updates; room expires after 30 seconds without host heartbeat. */
put: operations["publishListeningState"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/activity": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/** @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. */
put: operations["publishListeningActivity"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/activity/visibility": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/** @description Applies to all devices. Nobody clears all current activity immediately. */
put: operations["setActivityVisibility"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/social/activity/{userId}/playlist": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** @description Read-only current playlist subject to live visibility, friendship, blocking and expiry. Does not create or reveal a permanent share token. */
get: operations["getActivityPlaylist"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social": {
parameters: {
query?: never;
@@ -74,6 +207,41 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/me/social/chats/{peerId}/meta": {
parameters: {
query?: never;
header?: never;
path: {
peerId: string;
};
cookie?: never;
};
get: operations["getChatMeta"];
put: operations["setChatTyping"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/me/social/chats/{peerId}/messages/{messageId}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
/** @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. */
patch: operations["updateChatMessage"];
trace?: never;
};
"/api/v1/admin/server/updates": {
parameters: {
query?: never;
@@ -1692,6 +1860,111 @@ export interface paths {
export type webhooks = Record<string, never>;
export interface components {
schemas: {
ListeningPreferences: {
requests: components["schemas"]["ActivityVisibility"];
auto_accept: components["schemas"]["ActivityVisibility"];
/** @default true */
autoplay: boolean;
};
ListeningAction: {
/** Format: uuid */
id: string;
/** @enum {string} */
action: "join" | "invite" | "accept" | "decline" | "cancel" | "leave" | "end";
/** Format: uuid */
target_id: string;
/** Format: uuid */
device_id?: string;
/** Format: uuid */
track_id?: string;
position_ms?: number;
playing?: boolean;
};
ListeningRoomInput: {
/** Format: uuid */
room_id: string;
/** Format: uuid */
device_id: string;
/** Format: int64 */
sequence: number;
/** Format: uuid */
track_id?: string;
playing: boolean;
position_ms: number;
};
ListeningRoom: {
/** Format: uuid */
id: string;
/** Format: uuid */
owner_id: string;
owner_name: string;
/** Format: uuid */
device_id: string;
/** Format: uuid */
track_id: string | null;
position_ms: number;
playing: boolean;
/** Format: date-time */
updated_at: string;
participants: {
/** Format: uuid */
id: string;
name: string;
}[];
};
ListeningRequest: {
/** Format: uuid */
id: string;
/** Format: uuid */
sender_id: string;
sender_name: string;
/** Format: uuid */
recipient_id: string;
/** Format: uuid */
owner_id: string;
/** @enum {string} */
kind: "join" | "invite";
/** Format: date-time */
expires_at: string;
};
ListeningState: {
preferences: components["schemas"]["ListeningPreferences"];
room: components["schemas"]["ListeningRoom"] | null;
requests: components["schemas"]["ListeningRequest"][];
/** Format: date-time */
server_time: string;
};
/**
* @default nobody
* @enum {string}
*/
ActivityVisibility: "nobody" | "friends" | "everyone";
ListeningActivityInput: {
/** Format: uuid */
device_id: string;
/** Format: int64 */
sequence: number;
playing: boolean;
/** @default 0 */
position_ms: number;
/** Format: uuid */
track_id?: string;
/** @enum {string} */
context: "other" | "wave" | "playlist";
/** Format: uuid */
playlist_id?: string;
};
ListeningActivity: {
/** Format: uuid */
track_id: string;
track_title: string;
artist: string;
/** @enum {string} */
context: "other" | "wave" | "playlist";
/** Format: uuid */
playlist_id?: string;
playlist_name?: string;
};
SocialPerson: {
/** Format: uuid */
id: string;
@@ -1700,6 +1973,7 @@ export interface components {
/** @enum {string} */
relationship: "none" | "incoming" | "outgoing" | "friend" | "blocked";
unread: number;
activity?: components["schemas"]["ListeningActivity"];
};
SocialNotification: {
/** Format: uuid */
@@ -1708,12 +1982,15 @@ export interface components {
actor_id: string;
actor_name: string;
/** @enum {string} */
kind: "friend_request" | "friend_accepted" | "message" | "music";
kind: "friend_request" | "friend_accepted" | "message" | "music" | "listening_request" | "listening_invite" | "listening_accepted";
/** Format: date-time */
created_at: string;
read: boolean;
};
SocialInbox: {
/** @default true */
friends_recommendations: boolean;
activity_visibility: components["schemas"]["ActivityVisibility"];
people: components["schemas"]["SocialPerson"][];
notifications: components["schemas"]["SocialNotification"][];
unread: number;
@@ -1734,6 +2011,8 @@ export interface components {
attachment_kind: "none" | "track" | "playlist";
/** Format: uuid */
attachment_id?: string;
/** Format: uuid */
reply_to?: string;
};
DirectMessage: {
/** Format: uuid */
@@ -1750,6 +2029,18 @@ export interface components {
/** Format: date-time */
created_at: string;
read: boolean;
/** Format: uuid */
reply_to?: string;
reply_preview?: string;
/** Format: date-time */
edited_at?: string;
deleted?: boolean;
reactions?: components["schemas"]["MessageReaction"][];
};
MessageReaction: {
emoji: string;
count: number;
mine: boolean;
};
ServerSource: {
id: string;
@@ -2917,6 +3208,192 @@ export interface components {
}
export type $defs = Record<string, never>;
export interface operations {
setFriendsRecommendations: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
enabled: boolean;
};
};
};
responses: {
/** @description Saved */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
getListeningState: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Listening preferences, current room and pending requests */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ListeningState"];
};
};
default: components["responses"]["Problem"];
};
};
setListeningPreferences: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ListeningPreferences"];
};
};
responses: {
/** @description Saved */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
listeningAction: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ListeningAction"];
};
};
responses: {
/** @description Applied */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
publishListeningState: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ListeningRoomInput"];
};
};
responses: {
/** @description Applied */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
publishListeningActivity: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ListeningActivityInput"];
};
};
responses: {
/** @description Applied */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
setActivityVisibility: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
visibility: components["schemas"]["ActivityVisibility"];
};
};
};
responses: {
/** @description Saved */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
getActivityPlaylist: {
parameters: {
query?: never;
header?: never;
path: {
userId: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Read-only playlist */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Playlist"];
};
};
default: components["responses"]["Problem"];
};
};
getSocial: {
parameters: {
query?: never;
@@ -2988,6 +3465,7 @@ export interface operations {
parameters: {
query?: {
before?: number;
q?: string;
};
header?: never;
path: {
@@ -3036,6 +3514,90 @@ export interface operations {
default: components["responses"]["Problem"];
};
};
getChatMeta: {
parameters: {
query?: never;
header?: never;
path: {
peerId: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Ephemeral typing indicator, friends only */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
typing: boolean;
};
};
};
default: components["responses"]["Problem"];
};
};
setChatTyping: {
parameters: {
query?: never;
header?: never;
path: {
peerId: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": {
typing: boolean;
};
};
};
responses: {
/** @description Applied */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
updateChatMessage: {
parameters: {
query?: never;
header?: never;
path: {
peerId: string;
messageId: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": {
/** @enum {string} */
action: "edit" | "delete" | "react";
text?: string;
/** @enum {string} */
emoji?: "❤️" | "👍" | "🔥" | "😂" | "🎵" | "👏";
};
};
};
responses: {
/** @description Applied */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
default: components["responses"]["Problem"];
};
};
getServerUpdates: {
parameters: {
query?: {