@replyke/core
Version:
Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.
328 lines • 15.9 kB
JavaScript
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