feat: stabilize social backend contracts

This commit is contained in:
boutmoun123
2026-05-31 16:13:23 +03:00
parent ad6da6754d
commit 49e132909e
40 changed files with 12037 additions and 9562 deletions
+70
View File
@@ -13,6 +13,28 @@ export const apiEndpoints = {
revokeSuperAdminSession: (sessionId: string) => `/auth/superadmin/sessions/${sessionId}/revoke`,
},
users: {
meProfileSetup: "/users/me/profile-setup",
meMusicSetup: "/users/me/music-setup",
me: "/users/me",
publicList: "/users",
publicById: (userId: string) => `/users/${userId}`,
publicDiscover: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/discover?${toQueryString(params)}`,
publicProfileOverview: (userId: string) => `/users/${userId}/profile-overview`,
publicPresence: (userId: string) => `/users/${userId}/presence`,
publicPosts: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/${userId}/posts?${toQueryString(params)}`,
follow: (userId: string) => `/users/${userId}/follow`,
unfollow: (userId: string) => `/users/${userId}/follow`,
followStatus: (userId: string) => `/users/${userId}/follow-status`,
followers: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/${userId}/followers?${toQueryString(params)}`,
following: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/${userId}/following?${toQueryString(params)}`,
myFollowers: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/me/followers?${toQueryString(params)}`,
myFollowing: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/me/following?${toQueryString(params)}`,
all: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/users/admin?${toQueryString(params)}`,
byId: (userId: string) => `/users/admin/${userId}`,
@@ -31,6 +53,24 @@ export const apiEndpoints = {
profileOverview: (userId: string) => `/users/admin/${userId}/profile-overview`,
},
posts: {
create: "/posts",
reelsCreate: "/posts/reels",
reels: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/posts/reels?${toQueryString(params)}`,
byId: (postId: string) => `/posts/${postId}`,
byUser: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/posts/user/${userId}?${toQueryString(params)}`,
update: (postId: string) => `/posts/${postId}`,
repost: (postId: string) => `/posts/${postId}/repost`,
commentSettings: (postId: string) => `/posts/${postId}/comment-settings`,
pinProfile: (postId: string) => `/posts/${postId}/pin-profile`,
unpinProfile: (postId: string) => `/posts/${postId}/unpin-profile`,
archive: (postId: string) => `/posts/${postId}/archive`,
restoreArchive: (postId: string) => `/posts/${postId}/restore-archive`,
remove: (postId: string) => `/posts/${postId}`,
view: (postId: string) => `/posts/${postId}/view`,
play: (postId: string) => `/posts/${postId}/play`,
share: (postId: string) => `/posts/${postId}/share`,
moderation: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/posts/admin/moderation?${toQueryString(params)}`,
adminDelete: (postId: string) => `/posts/admin/${postId}`,
@@ -41,9 +81,39 @@ export const apiEndpoints = {
adminDelete: (commentId: string) => `/comments/admin/${commentId}`,
},
notifications: {
mine: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/notifications?${toQueryString(params)}`,
unreadCount: "/notifications/unread-count",
markRead: (notificationId: string) => `/notifications/${notificationId}/read`,
markAllRead: "/notifications/read-all",
superAdmin: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/notifications/superadmin?${toQueryString(params)}`,
},
follows: {
toggle: "/follows/toggle",
followers: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/follows/followers/${userId}?${toQueryString(params)}`,
following: (userId: string, params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/follows/following/${userId}?${toQueryString(params)}`,
status: (targetUserId: string) => `/follows/status/${targetUserId}`,
suggestions: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/follows/suggestions?${toQueryString(params)}`,
requests: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/follows/requests?${toQueryString(params)}`,
approveRequest: (requestId: string) => `/follows/requests/${requestId}/approve`,
rejectRequest: (requestId: string) => `/follows/requests/${requestId}/reject`,
},
devices: {
register: "/devices/register",
unregister: "/devices/unregister",
},
collaborationRequests: {
create: (postId: string) => `/posts/${postId}/collaboration-requests`,
list: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/collaboration-requests?${toQueryString(params)}`,
approve: (requestId: string) => `/collaboration-requests/${requestId}/approve`,
reject: (requestId: string) => `/collaboration-requests/${requestId}/reject`,
},
reports: {
superAdmin: (params: Record<string, string | number | boolean | null | undefined> = {}) =>
`/reports/superadmin?${toQueryString(params)}`,
+173
View File
@@ -0,0 +1,173 @@
import { apiEndpoints } from "@/lib/api/endpoints";
import { fetchWithAuth } from "@/lib/auth/client";
import type {
CollaborationRequestItem,
CollaborationRequestsResponse,
DeviceItem,
FollowListResponse,
FollowStatusResponse,
NotificationsResponse,
PostsResponse,
PresenceResponse,
RegisterDevicePayload,
SuccessMessage,
UnregisterDevicePayload,
} from "@/types/api";
export async function getUserPresence(userId: string) {
return fetchWithAuth<PresenceResponse>(apiEndpoints.users.publicPresence(userId));
}
export async function listUserPosts(
userId: string,
params: Record<string, string | number | boolean | null | undefined> = {},
) {
return fetchWithAuth<PostsResponse>(apiEndpoints.users.publicPosts(userId, params));
}
export async function followUser(userId: string) {
return fetchWithAuth<FollowStatusResponse>(apiEndpoints.users.follow(userId), {
method: "POST",
body: JSON.stringify({}),
});
}
export async function unfollowUser(userId: string) {
return fetchWithAuth<FollowStatusResponse>(apiEndpoints.users.unfollow(userId), {
method: "DELETE",
});
}
export async function getFollowStatus(userId: string) {
return fetchWithAuth<FollowStatusResponse>(apiEndpoints.users.followStatus(userId));
}
export async function listUserFollowers(
userId: string,
params: Record<string, string | number | boolean | null | undefined> = {},
) {
return fetchWithAuth<FollowListResponse>(apiEndpoints.users.followers(userId, params));
}
export async function listUserFollowing(
userId: string,
params: Record<string, string | number | boolean | null | undefined> = {},
) {
return fetchWithAuth<FollowListResponse>(apiEndpoints.users.following(userId, params));
}
export async function registerDevice(payload: RegisterDevicePayload) {
return fetchWithAuth<DeviceItem>(apiEndpoints.devices.register, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function unregisterDevice(payload: UnregisterDevicePayload) {
return fetchWithAuth<SuccessMessage>(apiEndpoints.devices.unregister, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function createCollaborationRequest(postId: string, targetUserId: string) {
return fetchWithAuth<CollaborationRequestItem>(apiEndpoints.collaborationRequests.create(postId), {
method: "POST",
body: JSON.stringify({ targetUserId }),
});
}
export async function listCollaborationRequests(
params: Record<string, string | number | boolean | null | undefined> = {},
) {
return fetchWithAuth<CollaborationRequestsResponse>(apiEndpoints.collaborationRequests.list(params));
}
export async function approveCollaborationRequest(requestId: string) {
return fetchWithAuth<CollaborationRequestItem>(apiEndpoints.collaborationRequests.approve(requestId), {
method: "PATCH",
body: JSON.stringify({}),
});
}
export async function rejectCollaborationRequest(requestId: string) {
return fetchWithAuth<CollaborationRequestItem>(apiEndpoints.collaborationRequests.reject(requestId), {
method: "PATCH",
body: JSON.stringify({}),
});
}
export async function listMyNotifications(
params: Record<string, string | number | boolean | null | undefined> = {},
) {
return fetchWithAuth<NotificationsResponse>(apiEndpoints.notifications.mine(params));
}
export async function getMyUnreadNotificationCount() {
return fetchWithAuth<{ unreadCount: number }>(apiEndpoints.notifications.unreadCount);
}
export async function markMyNotificationRead(notificationId: string) {
return fetchWithAuth<SuccessMessage>(apiEndpoints.notifications.markRead(notificationId), {
method: "PATCH",
body: JSON.stringify({}),
});
}
export async function markAllMyNotificationsRead() {
return fetchWithAuth<SuccessMessage>(apiEndpoints.notifications.markAllRead, {
method: "PATCH",
body: JSON.stringify({}),
});
}
export type MobileSocialRouteGroup = {
title: string;
routes: string[];
};
export const mobileSocialRouteGroups: MobileSocialRouteGroup[] = [
{
title: "users",
routes: [
"GET /users/:userId",
"GET /users/:userId/presence",
"GET /users/:userId/posts",
"GET /users/:userId/profile-overview",
],
},
{
title: "follows",
routes: [
"POST /users/:userId/follow",
"DELETE /users/:userId/follow",
"GET /users/:userId/follow-status",
"GET /users/:userId/followers",
"GET /users/:userId/following",
"GET /users/me/followers",
"GET /users/me/following",
],
},
{
title: "devices",
routes: ["POST /devices/register", "POST /devices/unregister"],
},
{
title: "collaborationRequests",
routes: [
"POST /posts/:postId/collaboration-requests",
"GET /collaboration-requests",
"PATCH /collaboration-requests/:requestId/approve",
"PATCH /collaboration-requests/:requestId/reject",
],
},
{
title: "notifications",
routes: [
"GET /notifications",
"GET /notifications/unread-count",
"PATCH /notifications/:notificationId/read",
"PATCH /notifications/read-all",
],
},
];
@@ -4,7 +4,8 @@
"notes": [
"This dashboard must authenticate with /auth/superadmin/* routes only.",
"User-only routes are not a valid contract for this dashboard unless they have a dedicated /admin or /superadmin variant.",
"The dashboard proxy should target the Nest backend API base URL defined in .env.local."
"The dashboard proxy should target the Nest backend API base URL defined in .env.local.",
"Mobile-facing routes are mirrored here for frontend contract visibility, but normal user routes require a normal user access token when called from Flutter."
],
"modules": {
"auth": [
@@ -15,6 +16,22 @@
"POST /auth/superadmin/sessions/:sessionId/revoke"
],
"users": [
"PATCH /users/me/profile-setup",
"PATCH /users/me/music-setup",
"PATCH /users/me",
"GET /users",
"GET /users/discover",
"GET /users/:userId",
"GET /users/:userId/presence",
"GET /users/:userId/profile-overview",
"GET /users/:userId/posts",
"POST /users/:userId/follow",
"DELETE /users/:userId/follow",
"GET /users/:userId/follow-status",
"GET /users/:userId/followers",
"GET /users/:userId/following",
"GET /users/me/followers",
"GET /users/me/following",
"GET /users/admin",
"GET /users/admin/admins",
"GET /users/admin/:userId",
@@ -31,6 +48,23 @@
"PATCH /superadmin/users/:userId/status"
],
"posts": [
"POST /posts",
"POST /posts/reels",
"GET /posts/reels",
"GET /posts/:postId",
"GET /posts/user/:userId",
"PATCH /posts/:postId",
"POST /posts/:postId/repost",
"PATCH /posts/:postId/comment-settings",
"PATCH /posts/:postId/pin-profile",
"PATCH /posts/:postId/unpin-profile",
"PATCH /posts/:postId/archive",
"PATCH /posts/:postId/restore-archive",
"DELETE /posts/:postId",
"POST /posts/:postId/view",
"POST /posts/:postId/play",
"POST /posts/:postId/share",
"POST /posts/:postId/collaboration-requests",
"GET /posts/admin/moderation",
"DELETE /posts/admin/:postId",
"PATCH /superadmin/posts/:postId/status"
@@ -41,8 +75,32 @@
"PATCH /superadmin/comments/:commentId/status"
],
"notifications": [
"GET /notifications",
"GET /notifications/unread-count",
"PATCH /notifications/:notificationId/read",
"PATCH /notifications/read-all",
"GET /notifications/superadmin"
],
"follows": [
"POST /follows/toggle",
"GET /follows/followers/:userId",
"GET /follows/following/:userId",
"GET /follows/status/:targetUserId",
"GET /follows/suggestions",
"GET /follows/requests",
"PATCH /follows/requests/:requestId/approve",
"PATCH /follows/requests/:requestId/reject"
],
"devices": [
"POST /devices/register",
"POST /devices/unregister"
],
"collaborationRequests": [
"POST /posts/:postId/collaboration-requests",
"GET /collaboration-requests",
"PATCH /collaboration-requests/:requestId/approve",
"PATCH /collaboration-requests/:requestId/reject"
],
"marketplace": [
"GET /marketplace/home",
"GET /marketplace/listings",
+65 -1
View File
@@ -210,8 +210,69 @@ export type SaveStatusResponse = {
export type FollowStatusResponse = {
following?: boolean;
isFollowing?: boolean;
isFollowedBy?: boolean;
isMutual?: boolean;
};
export type FollowListItem = {
_id?: string;
user?: ApiUser;
followerId?: ApiUser | string;
followingId?: ApiUser | string;
status?: "pending" | "accepted" | "rejected";
createdAt?: string;
updatedAt?: string;
};
export type FollowListResponse = PaginatedResponse<FollowListItem>;
export type PresenceResponse = {
userId: string;
isOnline: boolean;
lastSeenAt?: string | null;
};
export type RegisterDevicePayload = {
fcmToken: string;
platform: "android" | "ios" | "web";
deviceId?: string;
appVersion?: string;
locale?: string;
};
export type UnregisterDevicePayload = {
fcmToken?: string;
deviceId?: string;
};
export type DeviceItem = {
_id: string;
userId: string;
fcmToken: string;
platform: "android" | "ios" | "web";
deviceId?: string;
appVersion?: string;
locale?: string;
isActive?: boolean;
lastSeenAt?: string;
createdAt?: string;
updatedAt?: string;
};
export type CollaborationRequestStatus = "pending" | "approved" | "rejected";
export type CollaborationRequestItem = {
_id: string;
postId: ApiPost | string;
requesterId: ApiUser | string;
targetUserId: ApiUser | string;
status: CollaborationRequestStatus;
createdAt?: string;
updatedAt?: string;
};
export type CollaborationRequestsResponse = PaginatedResponse<CollaborationRequestItem>;
export type NotificationType =
| "like"
| "comment"
@@ -219,7 +280,10 @@ export type NotificationType =
| "message"
| "save"
| "share"
| "mention";
| "mention"
| "reply"
| "system"
| "collaboration_request";
export type NotificationItem = {
_id: string;