UNPKG

@replyke/core

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

328 lines 15.9 kB
import { baseApi } from "./baseApi"; // ===== API Endpoints ===== export const spacesApi = baseApi.injectEndpoints({ endpoints: (builder) => ({ // ===== CRUD Operations ===== // Create a new space createSpace: builder.mutation({ query: ({ projectId, ...body }) => ({ url: `/${projectId}/spaces`, method: "POST", body, }), invalidatesTags: (result, error, { parentSpaceId }) => [ { type: "Space", id: "LIST" }, // Invalidate parent's children list if creating under a parent ...(parentSpaceId ? [{ type: "Space", id: `${parentSpaceId}-CHILDREN` }] : []), ], }), // Fetch many spaces (list with filters) fetchSpaces: builder.query({ query: ({ projectId, ...params }) => { const queryParams = new URLSearchParams(); if (params.page !== undefined) queryParams.append("page", params.page.toString()); if (params.limit !== undefined) queryParams.append("limit", params.limit.toString()); if (params.sortBy) queryParams.append("sortBy", params.sortBy); if (params.searchSlug) queryParams.append("searchSlug", params.searchSlug); if (params.searchName) queryParams.append("searchName", params.searchName); if (params.searchDescription) queryParams.append("searchDescription", params.searchDescription); if (params.searchAny) queryParams.append("searchAny", params.searchAny); if (params.readingPermission) queryParams.append("readingPermission", params.readingPermission); if (params.memberOf !== undefined) queryParams.append("memberOf", params.memberOf.toString()); if (params.parentSpaceId !== undefined) { // Convert null to "null" string for API queryParams.append("parentSpaceId", params.parentSpaceId === null ? "null" : params.parentSpaceId); } return { url: `/${projectId}/spaces?${queryParams.toString()}`, method: "GET", }; }, providesTags: (result) => [ { type: "Space", id: "LIST" }, ...(result?.data?.map(({ id }) => ({ type: "Space", id })) ?? []), ], }), // Fetch single space by ID (returns detailed space with memberPermissions, parentSpace, childSpaces) fetchSpace: builder.query({ query: ({ projectId, spaceId }) => ({ url: `/${projectId}/spaces/${spaceId}`, method: "GET", }), providesTags: (result, error, { spaceId }) => [ { type: "Space", id: spaceId }, ], }), // Fetch space by shortId (returns detailed space) fetchSpaceByShortId: builder.query({ query: ({ projectId, shortId }) => ({ url: `/${projectId}/spaces/by-short-id?shortId=${shortId}`, method: "GET", }), providesTags: (result) => [ ...(result ? [{ type: "Space", id: result.id }] : []), ], }), // Fetch space by slug (returns detailed space) fetchSpaceBySlug: builder.query({ query: ({ projectId, slug }) => ({ url: `/${projectId}/spaces/by-slug?slug=${slug}`, method: "GET", }), providesTags: (result) => [ ...(result ? [{ type: "Space", id: result.id }] : []), ], }), // Update space (returns detailed space) updateSpace: builder.mutation({ query: ({ projectId, spaceId, update }) => ({ url: `/${projectId}/spaces/${spaceId}`, method: "PATCH", body: update, }), // Optimistically update the cache async onQueryStarted({ projectId, spaceId, update }, { dispatch, queryFulfilled }) { const patches = []; // Update in fetchSpace query patches.push(dispatch(spacesApi.util.updateQueryData("fetchSpace", { projectId, spaceId }, (draft) => { Object.assign(draft, update); }))); try { await queryFulfilled; } catch { // Revert optimistic update on failure patches.forEach((patch) => patch.undo()); } }, invalidatesTags: (result, error, { spaceId }) => [ { type: "Space", id: spaceId }, { type: "Space", id: "LIST" }, ], }), // Delete space deleteSpace: builder.mutation({ query: ({ projectId, spaceId }) => ({ url: `/${projectId}/spaces/${spaceId}`, method: "DELETE", }), invalidatesTags: (result, error, { spaceId }) => [ { type: "Space", id: spaceId }, { type: "Space", id: "LIST" }, // Invalidate children queries as they're cascade deleted { type: "Space", id: `${spaceId}-CHILDREN` }, ], }), // ===== Hierarchy Operations ===== // Fetch child spaces fetchSpaceChildren: builder.query({ query: ({ projectId, spaceId, page = 1, limit = 20 }) => ({ url: `/${projectId}/spaces/${spaceId}/children?page=${page}&limit=${limit}`, method: "GET", }), providesTags: (result, error, { spaceId }) => [ { type: "Space", id: `${spaceId}-CHILDREN` }, ...(result?.map(({ id }) => ({ type: "Space", id })) ?? []), ], }), // Fetch space breadcrumb fetchSpaceBreadcrumb: builder.query({ query: ({ projectId, spaceId }) => ({ url: `/${projectId}/spaces/${spaceId}/breadcrumb`, method: "GET", }), providesTags: (result, error, { spaceId }) => [ { type: "Space", id: `${spaceId}-BREADCRUMB` }, ], }), // ===== Membership Operations ===== // Join a space joinSpace: builder.mutation({ query: ({ projectId, spaceId }) => ({ url: `/${projectId}/spaces/${spaceId}/join`, method: "POST", }), // Optimistically update member count and member permissions async onQueryStarted({ projectId, spaceId }, { dispatch, queryFulfilled }) { const patches = []; // Update space query to increment member count and add memberPermissions patches.push(dispatch(spacesApi.util.updateQueryData("fetchSpace", { projectId, spaceId }, (draft) => { draft.membersCount += 1; // Note: memberPermissions will be updated with actual data from response }))); try { const { data: member } = await queryFulfilled; // Update with actual member data dispatch(spacesApi.util.updateQueryData("fetchSpace", { projectId, spaceId }, (draft) => { // Filter out "rejected" status as it's not valid for memberPermissions const status = member.status === "rejected" ? null : member.status; draft.memberPermissions = { isAdmin: member.role === "admin", isModerator: member.role === "moderator" || member.role === "admin", isMember: member.status === "active", status, canPost: member.status === "active", canModerate: member.role === "moderator" || member.role === "admin", canRead: true, }; })); } catch { // Revert optimistic update on failure patches.forEach((patch) => patch.undo()); } }, invalidatesTags: (result, error, { spaceId }) => [ { type: "Space", id: spaceId }, { type: "SpaceMember", id: spaceId }, ], }), // Leave a space leaveSpace: builder.mutation({ query: ({ projectId, spaceId }) => ({ url: `/${projectId}/spaces/${spaceId}/leave`, method: "DELETE", }), // Optimistically update member count and member permissions async onQueryStarted({ projectId, spaceId }, { dispatch, queryFulfilled }) { const patches = []; // Update space query to decrement member count and remove memberPermissions patches.push(dispatch(spacesApi.util.updateQueryData("fetchSpace", { projectId, spaceId }, (draft) => { draft.membersCount = Math.max(0, draft.membersCount - 1); draft.memberPermissions = null; }))); try { await queryFulfilled; } catch { // Revert optimistic update on failure patches.forEach((patch) => patch.undo()); } }, invalidatesTags: (result, error, { spaceId }) => [ { type: "Space", id: spaceId }, { type: "SpaceMember", id: spaceId }, ], }), // Fetch space members fetchSpaceMembers: builder.query({ query: ({ projectId, spaceId, ...params }) => { const queryParams = new URLSearchParams(); if (params.page !== undefined) queryParams.append("page", params.page.toString()); if (params.limit !== undefined) queryParams.append("limit", params.limit.toString()); if (params.role) queryParams.append("role", params.role); if (params.status) queryParams.append("status", params.status); return { url: `/${projectId}/spaces/${spaceId}/members?${queryParams.toString()}`, method: "GET", }; }, providesTags: (result, error, { spaceId }) => [ { type: "SpaceMember", id: spaceId }, ...(result?.map(({ id }) => ({ type: "SpaceMember", id })) ?? []), ], }), // Fetch user's spaces fetchUserSpaces: builder.query({ query: ({ projectId, ...params }) => { const queryParams = new URLSearchParams(); if (params.page !== undefined) queryParams.append("page", params.page.toString()); if (params.limit !== undefined) queryParams.append("limit", params.limit.toString()); if (params.status) queryParams.append("status", params.status); if (params.role) queryParams.append("role", params.role); if (params.all) queryParams.append("all", "true"); const queryString = queryParams.toString(); return { url: `/${projectId}/spaces/user-spaces${queryString ? `?${queryString}` : ""}`, method: "GET", }; }, providesTags: (result) => [ { type: "Space", id: "USER-SPACES" }, ...(result?.map(({ id }) => ({ type: "Space", id })) ?? []), ], }), // Update member role (admin only) updateMemberRole: builder.mutation({ query: ({ projectId, spaceId, memberId, role }) => ({ url: `/${projectId}/spaces/${spaceId}/members/${memberId}/role`, method: "PATCH", body: { role }, }), invalidatesTags: (result, error, { spaceId, memberId }) => [ { type: "SpaceMember", id: spaceId }, { type: "SpaceMember", id: memberId }, ], }), // Approve pending member (moderator+) approveMember: builder.mutation({ query: ({ projectId, spaceId, memberId }) => ({ url: `/${projectId}/spaces/${spaceId}/members/${memberId}/approve`, method: "PATCH", }), invalidatesTags: (result, error, { spaceId, memberId }) => [ { type: "SpaceMember", id: spaceId }, { type: "SpaceMember", id: memberId }, ], }), // Decline pending member (moderator+) declineMember: builder.mutation({ query: ({ projectId, spaceId, memberId }) => ({ url: `/${projectId}/spaces/${spaceId}/members/${memberId}/decline`, method: "PATCH", }), invalidatesTags: (result, error, { spaceId, memberId }) => [ { type: "SpaceMember", id: spaceId }, { type: "SpaceMember", id: memberId }, ], }), // Remove/ban member (moderator+) removeMember: builder.mutation({ query: ({ projectId, spaceId, memberId }) => ({ url: `/${projectId}/spaces/${spaceId}/members/${memberId}`, method: "DELETE", }), invalidatesTags: (result, error, { spaceId, memberId }) => [ { type: "SpaceMember", id: spaceId }, { type: "SpaceMember", id: memberId }, { type: "Space", id: spaceId }, // Update member count ], }), // Unban a banned member (moderator+) unbanMember: builder.mutation({ query: ({ projectId, spaceId, memberId }) => ({ url: `/${projectId}/spaces/${spaceId}/members/${memberId}/unban`, method: "PATCH", }), invalidatesTags: (result, error, { spaceId, memberId }) => [ { type: "SpaceMember", id: spaceId }, { type: "SpaceMember", id: memberId }, ], }), }), }); // Export hooks for use in components export const { useCreateSpaceMutation, useFetchSpacesQuery, useLazyFetchSpacesQuery, useFetchSpaceQuery, useLazyFetchSpaceQuery, useFetchSpaceByShortIdQuery, useLazyFetchSpaceByShortIdQuery, useFetchSpaceBySlugQuery, useLazyFetchSpaceBySlugQuery, useUpdateSpaceMutation, useDeleteSpaceMutation, useFetchSpaceChildrenQuery, useLazyFetchSpaceChildrenQuery, useFetchSpaceBreadcrumbQuery, useLazyFetchSpaceBreadcrumbQuery, useJoinSpaceMutation, useLeaveSpaceMutation, useFetchSpaceMembersQuery, useLazyFetchSpaceMembersQuery, useFetchUserSpacesQuery, useLazyFetchUserSpacesQuery, useUpdateMemberRoleMutation, useApproveMemberMutation, useDeclineMemberMutation, useRemoveMemberMutation, useUnbanMemberMutation, } = spacesApi; // Export for manual cache management export const { createSpace, fetchSpaces, fetchSpace, fetchSpaceByShortId, fetchSpaceBySlug, updateSpace, deleteSpace, fetchSpaceChildren, fetchSpaceBreadcrumb, joinSpace, leaveSpace, fetchSpaceMembers, fetchUserSpaces, updateMemberRole, approveMember, declineMember, removeMember, unbanMember, } = spacesApi.endpoints; //# sourceMappingURL=spacesApi.js.map