feat: stabilize social backend contracts

هذا الالتزام موجود في:
boutmoun123
2026-05-31 16:13:23 +03:00
الأصل ad6da6754d
التزام 49e132909e
40 ملفات معدلة مع 12037 إضافات و9562 حذوفات

عرض الملف

@@ -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)}`,

عرض الملف

@@ -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",

عرض الملف

@@ -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;