feat: stabilize social backend contracts
هذا الالتزام موجود في:
@@ -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
oudelaa_dashboard/lib/api/mobile-social.ts
Normal file
173
oudelaa_dashboard/lib/api/mobile-social.ts
Normal 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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم