import { DatabaseId, FolderView, RowId, User, View, ViewId, ViewLayout, Workspace, Invitation, Types, AFWebUser, GetRequestAccessInfoResponse, Subscriptions, SubscriptionPlan, SubscriptionInterval, RequestAccessInfoStatus, ViewInfo, UpdatePagePayload, CreatePagePayload, CreateSpacePayload, UpdateSpacePayload, Role, WorkspaceMember, QuickNote, QuickNoteEditorData, CreateWorkspacePayload, UpdateWorkspacePayload, PublishViewPayload, UploadPublishNamespacePayload, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { initGrantService, refreshToken } from '@/application/services/js-services/http/gotrue'; import { blobToBytes } from '@/application/services/js-services/http/utils'; import { AFCloudConfig } from '@/application/services/services.type'; import { getTokenParsed, invalidToken } from '@/application/session/token'; import { Template, TemplateCategory, TemplateCategoryFormValues, TemplateCreator, TemplateCreatorFormValues, TemplateSummary, UploadTemplatePayload, } from '@/application/template.type'; import axios, { AxiosInstance } from 'axios'; import dayjs from 'dayjs'; import { omit } from 'lodash-es'; import { nanoid } from 'nanoid'; import { notify } from '@/components/_shared/notify'; export * from './gotrue'; let axiosInstance: AxiosInstance | null = null; export function initAPIService (config: AFCloudConfig) { if (axiosInstance) { return; } axiosInstance = axios.create({ baseURL: config.baseURL, headers: { 'Content-Type': 'application/json', }, }); initGrantService(config.gotrueURL); axiosInstance.interceptors.request.use( async (config) => { const token = getTokenParsed(); if (!token) { return config; } const isExpired = dayjs().isAfter(dayjs.unix(token.expires_at)); let access_token = token.access_token; const refresh_token = token.refresh_token; if (isExpired) { try { const newToken = await refreshToken(refresh_token); access_token = newToken?.access_token || ''; } catch (e) { invalidToken(); return config; } } if (access_token) { Object.assign(config.headers, { Authorization: `Bearer ${access_token}`, }); } return config; }, (error) => { return Promise.reject(error); }, ); axiosInstance.interceptors.response.use(async (response) => { const status = response.status; if (status === 401) { const token = getTokenParsed(); if (!token) { invalidToken(); return response; } const refresh_token = token.refresh_token; try { await refreshToken(refresh_token); } catch (e) { invalidToken(); } } return response; }); } export async function signInWithUrl (url: string) { const hash = new URL(url).hash; if (!hash) { return Promise.reject('No hash found'); } const params = new URLSearchParams(hash.slice(1)); const accessToken = params.get('access_token'); const refresh_token = params.get('refresh_token'); if (!accessToken || !refresh_token) { return Promise.reject({ code: -1, message: 'No access token or refresh token found', }); } try { await verifyToken(accessToken); } catch (e) { return Promise.reject({ code: -1, message: 'Verify token failed', }); } try { await refreshToken(refresh_token); } catch (e) { return Promise.reject({ code: -1, message: 'Refresh token failed', }); } } export async function verifyToken (accessToken: string) { const url = `/api/user/verify/${accessToken}`; const response = await axiosInstance?.get<{ code: number; data?: { is_new: boolean; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function getCurrentUser (): Promise { const url = '/api/user/profile'; const response = await axiosInstance?.get<{ code: number; data?: { uid: number; uuid: string; email: string; name: string; metadata: { icon_url: string; }; encryption_sign: null; latest_workspace_id: string; updated_at: number; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { const { uid, uuid, email, name, metadata } = data.data; return { uid: String(uid), uuid, email, name, avatar: metadata.icon_url, latestWorkspaceId: data.data.latest_workspace_id, }; } return Promise.reject(data); } interface AFWorkspace { workspace_id: string, owner_uid: number, owner_name: string, workspace_name: string, icon: string, created_at: string, member_count: number, database_storage_id: string, } function afWorkspace2Workspace (workspace: AFWorkspace): Workspace { return { id: workspace.workspace_id, owner: { uid: workspace.owner_uid, name: workspace.owner_name, }, name: workspace.workspace_name, icon: workspace.icon, memberCount: workspace.member_count, databaseStorageId: workspace.database_storage_id, createdAt: workspace.created_at, }; } export async function openWorkspace (workspaceId: string) { const url = `/api/workspace/${workspaceId}/open`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function updateWorkspace (workspaceId: string, payload: UpdateWorkspacePayload) { const url = `/api/workspace`; const response = await axiosInstance?.patch<{ code: number; data?: { workspace_id: string; }; message: string; }>(url, { workspace_id: workspaceId, ...payload, }); const data = response?.data; if (data?.code === 0) { return; } return Promise.reject(data); } export async function createWorkspace (payload: CreateWorkspacePayload) { const url = '/api/workspace'; const response = await axiosInstance?.post<{ code: number; data?: { workspace_id: string; }; message: string; }>(url, payload); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.workspace_id; } return Promise.reject(data); } export async function getUserWorkspaceInfo (): Promise<{ user_id: string; selected_workspace: Workspace; workspaces: Workspace[]; }> { const url = '/api/user/workspace'; const response = await axiosInstance?.get<{ code: number, message: string, data: { user_profile: { uuid: string; }, visiting_workspace: AFWorkspace, workspaces: AFWorkspace[] } }>(url); const data = response?.data; if (data?.code === 0) { const { visiting_workspace, workspaces, user_profile } = data.data; return { user_id: user_profile.uuid, selected_workspace: afWorkspace2Workspace(visiting_workspace), workspaces: workspaces.map(afWorkspace2Workspace), }; } return Promise.reject(data); } export async function publishView (workspaceId: string, viewId: string, payload?: PublishViewPayload) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/publish`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, payload); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function unpublishView (workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/unpublish`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function updatePublishNamespace (workspaceId: string, payload: UploadPublishNamespacePayload) { const url = `/api/workspace/${workspaceId}/publish-namespace`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url, payload); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function getPublishViewMeta (namespace: string, publishName: string) { const url = `/api/workspace/v1/published/${namespace}/${publishName}`; const response = await axiosInstance?.get<{ code: number; data: { view: ViewInfo; child_views: ViewInfo[]; ancestor_views: ViewInfo[]; }; message: string; }>(url); if (response?.data.code !== 0) { return Promise.reject(response?.data); } return response?.data.data; } export async function getPublishViewBlob (namespace: string, publishName: string) { const url = `/api/workspace/published/${namespace}/${publishName}/blob`; const response = await axiosInstance?.get(url, { responseType: 'blob', }); return blobToBytes(response?.data); } export async function updateCollab (workspaceId: string, objectId: string, collabType: Types, docState: Uint8Array, context: { version_vector: number; }) { const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}/web-update`; let deviceId = localStorage.getItem('x-device-id'); if (!deviceId) { deviceId = nanoid(8); localStorage.setItem('x-device-id', deviceId); } const response = await axiosInstance?.post<{ code: number; message: string; }>(url, { doc_state: Array.from(docState), collab_type: collabType, }, { headers: { 'client-version': 'web', 'device-id': deviceId, }, }); if (response?.data.code !== 0) { return Promise.reject(response?.data); } return context; } export async function getCollab (workspaceId: string, objectId: string, collabType: Types) { const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}`; const response = await axiosInstance?.get<{ code: number; data: { doc_state: number[]; object_id: string; }; message: string; }>(url, { params: { collab_type: collabType, }, }); if (response?.data.code !== 0) { return Promise.reject(response?.data); } const docState = response?.data.data.doc_state; return { data: new Uint8Array(docState), }; } export async function getPageCollab (workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; const response = await axiosInstance?.get<{ code: number; data: { view: View; data: { encoded_collab: number[]; row_data: Record; owner?: User; last_editor?: User; } }; message: string; }>(url); if (!response) { return Promise.reject('No response'); } if (response.data.code !== 0) { return Promise.reject(response?.data); } const { encoded_collab, row_data, owner, last_editor } = response.data.data.data; return { data: new Uint8Array(encoded_collab), rows: row_data, owner, lastEditor: last_editor, }; } export async function getPublishView (publishNamespace: string, publishName: string) { const meta = await getPublishViewMeta(publishNamespace, publishName); const blob = await getPublishViewBlob(publishNamespace, publishName); if (meta.view.layout === ViewLayout.Document) { return { data: blob, meta, }; } try { const decoder = new TextDecoder('utf-8'); const jsonStr = decoder.decode(blob); const res = JSON.parse(jsonStr) as { database_collab: Uint8Array; database_row_collabs: Record; database_row_document_collabs: Record; visible_database_view_ids: ViewId[]; database_relations: Record; }; return { data: new Uint8Array(res.database_collab), rows: res.database_row_collabs, visibleViewIds: res.visible_database_view_ids, relations: res.database_relations, subDocuments: res.database_row_document_collabs, meta, }; } catch (e) { return Promise.reject(e); } } export async function getPublishInfoWithViewId (viewId: string) { const url = `/api/workspace/v1/published-info/${viewId}`; const response = await axiosInstance?.get<{ code: number; data?: { namespace: string; publish_name: string; publisher_email: string; view_id: string; publish_timestamp: string; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function getAppFavorites (workspaceId: string) { const url = `/api/workspace/${workspaceId}/favorite`; const response = await axiosInstance?.get<{ code: number; data?: { views: View[] }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.views; } return Promise.reject(data); } export async function getAppTrash (workspaceId: string) { const url = `/api/workspace/${workspaceId}/trash`; const response = await axiosInstance?.get<{ code: number; data?: { views: View[] }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.views; } return Promise.reject(data); } export async function getAppRecent (workspaceId: string) { const url = `/api/workspace/${workspaceId}/recent`; const response = await axiosInstance?.get<{ code: number; data?: { views: View[] }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.views; } return Promise.reject(data); } export async function getAppOutline (workspaceId: string) { const url = `/api/workspace/${workspaceId}/folder?depth=10`; const response = await axiosInstance?.get<{ code: number; data?: View; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.children; } return Promise.reject(data); } export async function getView (workspaceId: string, viewId: string, depth: number = 1) { const url = `/api/workspace/${workspaceId}/folder?depth=${depth}&root_view_id=${viewId}`; const response = await axiosInstance?.get<{ code: number; data?: View; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function getPublishNamespace (workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-namespace`; const response = await axiosInstance?.get<{ code: number; data?: string; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function getPublishHomepage (workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.get<{ code: number; data?: { namespace: string; publish_name: string; publisher_email: string; view_id: string; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function updatePublishHomepage (workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url, { view_id: viewId, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function removePublishHomepage (workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function getPublishOutline (publishNamespace: string) { const url = `/api/workspace/published-outline/${publishNamespace}`; const response = await axiosInstance?.get<{ code: number; data?: View; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.children; } return Promise.reject(data); } export async function getPublishViewComments (viewId: string): Promise { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.get<{ code: number; data?: { comments: { comment_id: string; user: { uuid: string; name: string; avatar_url: string | null; }; content: string; created_at: string; last_updated_at: string; reply_comment_id: string | null; is_deleted: boolean; can_be_deleted: boolean; }[]; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { const { comments } = data.data; return comments.map((comment) => { return { commentId: comment.comment_id, user: { uuid: comment.user?.uuid || '', name: comment.user?.name || '', avatarUrl: comment.user?.avatar_url || null, }, content: comment.content, createdAt: comment.created_at, lastUpdatedAt: comment.last_updated_at, replyCommentId: comment.reply_comment_id, isDeleted: comment.is_deleted, canDeleted: comment.can_be_deleted, }; }); } return Promise.reject(data); } export async function getReactions (viewId: string, commentId?: string): Promise> { let url = `/api/workspace/published-info/${viewId}/reaction`; if (commentId) { url += `?comment_id=${commentId}`; } const response = await axiosInstance?.get<{ code: number; data?: { reactions: { reaction_type: string; react_users: { uuid: string; name: string; avatar_url: string | null; }[]; comment_id: string; }[]; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { const { reactions } = data.data; const reactionsMap: Record = {}; for (const reaction of reactions) { if (!reactionsMap[reaction.comment_id]) { reactionsMap[reaction.comment_id] = []; } reactionsMap[reaction.comment_id].push({ reactionType: reaction.reaction_type, commentId: reaction.comment_id, reactUsers: reaction.react_users.map((user) => ({ uuid: user.uuid, name: user.name, avatarUrl: user.avatar_url, })), }); } return reactionsMap; } return Promise.reject(data); } export async function createGlobalCommentOnPublishView (viewId: string, content: string, replyCommentId?: string) { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.post<{ code: number; message: string }>(url, { content, reply_comment_id: replyCommentId, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function deleteGlobalCommentOnPublishView (viewId: string, commentId: string) { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { data: { comment_id: commentId, }, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function addReaction (viewId: string, commentId: string, reactionType: string) { const url = `/api/workspace/published-info/${viewId}/reaction`; const response = await axiosInstance?.post<{ code: number; message: string }>(url, { comment_id: commentId, reaction_type: reactionType, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function removeReaction (viewId: string, commentId: string, reactionType: string) { const url = `/api/workspace/published-info/${viewId}/reaction`; const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { data: { comment_id: commentId, reaction_type: reactionType, }, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function getWorkspaces (): Promise { const query = new URLSearchParams({ include_member_count: 'true', }); const url = `/api/workspace?${query.toString()}`; const response = await axiosInstance?.get<{ code: number; data?: AFWorkspace[]; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.map(afWorkspace2Workspace); } return Promise.reject(data); } export interface WorkspaceFolder { view_id: string; icon: string | null; name: string; is_space: boolean; is_private: boolean; extra: { is_space: boolean; space_created_at: number; space_icon: string; space_icon_color: string; space_permission: number; }; children: WorkspaceFolder[]; } function iterateFolder (folder: WorkspaceFolder): FolderView { return { id: folder.view_id, name: folder.name, icon: folder.icon, isSpace: folder.is_space, extra: folder.extra ? JSON.stringify(folder.extra) : null, isPrivate: folder.is_private, children: folder.children.map((child: WorkspaceFolder) => { return iterateFolder(child); }), }; } export async function getWorkspaceFolder (workspaceId: string): Promise { const url = `/api/workspace/${workspaceId}/folder`; const response = await axiosInstance?.get<{ code: number; data?: WorkspaceFolder; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return iterateFolder(data.data); } return Promise.reject(data); } export interface DuplicatePublishViewPayload { published_collab_type: 0 | 1 | 2 | 3 | 4 | 5 | 6; published_view_id: string; dest_view_id: string; } export async function duplicatePublishView (workspaceId: string, payload: DuplicatePublishViewPayload) { const url = `/api/workspace/${workspaceId}/published-duplicate`; const res = await axiosInstance?.post<{ code: number; data: { view_id: string; }; message: string; }>(url, payload); if (res?.data.code === 0) { return res.data.data.view_id; } return Promise.reject(res?.data.message); } export async function createTemplate (template: UploadTemplatePayload) { const url = '/api/template-center/template'; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, template); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function updateTemplate (viewId: string, template: UploadTemplatePayload) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url, template); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function getTemplates ({ categoryId, nameContains, }: { categoryId?: string; nameContains?: string; }) { const url = `/api/template-center/template`; const response = await axiosInstance?.get<{ code: number; data?: { templates: TemplateSummary[]; }; message: string; }>(url, { params: { category_id: categoryId, name_contains: nameContains, }, }); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.templates; } return Promise.reject(data); } export async function getTemplateById (viewId: string) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.get<{ code: number; data?: Template; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function deleteTemplate (viewId: string) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function getTemplateCategories () { const url = '/api/template-center/category'; const response = await axiosInstance?.get<{ code: number; data?: { categories: TemplateCategory[] }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.categories; } return Promise.reject(data); } export async function addTemplateCategory (category: TemplateCategoryFormValues) { const url = '/api/template-center/category'; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, category); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function updateTemplateCategory (id: string, category: TemplateCategoryFormValues) { const url = `/api/template-center/category/${id}`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url, category); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function deleteTemplateCategory (categoryId: string) { const url = `/api/template-center/category/${categoryId}`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function getTemplateCreators () { const url = '/api/template-center/creator'; const response = await axiosInstance?.get<{ code: number; data?: { creators: TemplateCreator[]; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data.creators; } return Promise.reject(data); } export async function createTemplateCreator (creator: TemplateCreatorFormValues) { const url = '/api/template-center/creator'; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, creator); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function updateTemplateCreator (creatorId: string, creator: TemplateCreatorFormValues) { const url = `/api/template-center/creator/${creatorId}`; const response = await axiosInstance?.put<{ code: number; message: string; }>(url, creator); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function deleteTemplateCreator (creatorId: string) { const url = `/api/template-center/creator/${creatorId}`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function uploadTemplateAvatar (file: File) { const url = '/api/template-center/avatar'; const formData = new FormData(); formData.append('avatar', file); const response = await axiosInstance?.request<{ code: number; data?: { file_id: string; }; message: string; }>({ method: 'PUT', url, data: formData, headers: { 'Content-Type': 'multipart/form-data', }, }); const data = response?.data; if (data?.code === 0 && data.data) { return axiosInstance?.defaults.baseURL + '/api/template-center/avatar/' + data.data.file_id; } return Promise.reject(data); } export async function getInvitation (invitationId: string) { const url = `/api/workspace/invite/${invitationId}`; const response = await axiosInstance?.get<{ code: number; data?: Invitation; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function acceptInvitation (invitationId: string) { const url = `/api/workspace/accept-invite/${invitationId}`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } export async function getRequestAccessInfo (requestId: string): Promise { const url = `/api/access-request/${requestId}`; const response = await axiosInstance?.get<{ code: number; data?: { request_id: string; workspace: AFWorkspace; requester: AFWebUser & { email: string; }; view: View; status: RequestAccessInfoStatus; }; message: string; }>(url); const data = response?.data; if (data?.code === 0 && data.data) { const workspace = data.data.workspace; return { ...data.data, workspace: afWorkspace2Workspace(workspace), }; } return Promise.reject(data); } export async function approveRequestAccess (requestId: string) { const url = `/api/access-request/${requestId}/approve`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, { is_approved: true, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function sendRequestAccess (workspaceId: string, viewId: string) { const url = `/api/access-request`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, { workspace_id: workspaceId, view_id: viewId, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function getSubscriptionLink (workspaceId: string, plan: SubscriptionPlan, interval: SubscriptionInterval) { const url = `/billing/api/v1/subscription-link`; const response = await axiosInstance?.get<{ code: number; data?: string; message: string; }>(url, { params: { workspace_subscription_plan: plan, recurring_interval: interval, workspace_id: workspaceId, success_url: window.location.href, }, }); const data = response?.data; if (data?.code === 0 && data.data) { return data.data; } return Promise.reject(data); } export async function getSubscriptions () { const url = `/billing/api/v1/subscriptions`; const response = await axiosInstance?.get<{ code: number; data: Subscriptions; message: string; }>(url); if (response?.data.code === 0) { return response?.data.data; } return Promise.reject(response?.data); } export async function getWorkspaceSubscriptions (workspaceId: string) { try { const plans = await getActiveSubscription(workspaceId); const subscriptions = await getSubscriptions(); return subscriptions?.filter((subscription) => plans?.includes(subscription.plan)); } catch (e) { return Promise.reject(e); } } export async function getActiveSubscription (workspaceId: string) { const url = `/billing/api/v1/active-subscription/${workspaceId}`; const response = await axiosInstance?.get<{ code: number; data: SubscriptionPlan[]; message: string; }>(url); if (response?.data.code === 0) { return response?.data.data; } return Promise.reject(response?.data); } export async function createImportTask (file: File) { const url = `/api/import/create`; const fileName = file.name.split('.').slice(0, -1).join('.') || crypto.randomUUID(); const res = await axiosInstance?.post<{ code: number; data: { task_id: string; presigned_url: string; }; message: string; }>(url, { workspace_name: fileName, content_length: file.size, }); if (res?.data.code === 0) { return { taskId: res?.data.data.task_id, presignedUrl: res?.data.data.presigned_url, }; } return Promise.reject(res?.data); } export async function uploadImportFile (presignedUrl: string, file: File, onProgress: (progress: number) => void) { const response = await axios.put(presignedUrl, file, { onUploadProgress: (progressEvent) => { const { progress = 0 } = progressEvent; console.log(`Upload progress: ${progress * 100}%`); onProgress(progress); }, headers: { 'Content-Type': 'application/zip', }, }); if (response.status === 200) { return; } return Promise.reject({ code: -1, message: `Upload file failed. ${response.statusText}`, }); } export async function addAppPage (workspaceId: string, parentViewId: string, { layout, name, }: CreatePagePayload) { const url = `/api/workspace/${workspaceId}/page-view`; const response = await axiosInstance?.post<{ code: number; data: { view_id: string; }; message: string; }>(url, { parent_view_id: parentViewId, layout, name, }); if (response?.data.code === 0) { return response?.data.data.view_id; } return Promise.reject(response?.data); } export async function updatePage (workspaceId: string, viewId: string, data: UpdatePagePayload) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; const res = await axiosInstance?.patch<{ code: number; message: string; }>(url, data); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function deleteTrash (workspaceId: string, viewId?: string) { if (viewId) { const url = `/api/workspace/${workspaceId}/trash/${viewId}`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } else { const url = `/api/workspace/${workspaceId}/delete-all-pages-from-trash`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } } export async function moveToTrash (workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move-to-trash`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function movePageTo (workspaceId: string, viewId: string, parentViewId: string, prevViewId?: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, { new_parent_view_id: parentViewId, prev_view_id: prevViewId, }); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function restorePage (workspaceId: string, viewId?: string) { const url = viewId ? `/api/workspace/${workspaceId}/page-view/${viewId}/restore-from-trash` : `/api/workspace/${workspaceId}/restore-all-pages-from-trash`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function createSpace (workspaceId: string, payload: CreateSpacePayload) { const url = `/api/workspace/${workspaceId}/space`; const response = await axiosInstance?.post<{ code: number; data: { view_id: string; }; message: string; }>(url, payload); if (response?.data.code === 0) { return response?.data.data.view_id; } return Promise.reject(response?.data); } export async function updateSpace (workspaceId: string, payload: UpdateSpacePayload) { const url = `/api/workspace/${workspaceId}/space/${payload.view_id}`; const data = omit(payload, ['view_id']); const response = await axiosInstance?.patch<{ code: number; message: string; }>(url, data); if (response?.data.code === 0) { return; } return Promise.reject(response?.data); } export async function uploadFile (workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) { const url = `/api/file_storage/${workspaceId}/v1/blob/${viewId}`; // Check file size, if over 7MB, check subscription plan if (file.size > 7 * 1024 * 1024) { const plan = await getActiveSubscription(workspaceId); if (plan?.length === 0 || plan?.[0] === SubscriptionPlan.Free) { notify.error('Your file is over 7 MB limit of the Free plan. Upgrade for unlimited uploads.'); return Promise.reject({ code: 413, message: 'File size is too large. Please upgrade your plan for unlimited uploads.', }); } } try { const response = await axiosInstance?.put<{ code: number; message: string; data: { file_id: string; } }>(url, file, { onUploadProgress: (progressEvent) => { const { progress = 0 } = progressEvent; onProgress?.(progress); }, headers: { 'Content-Type': file.type || 'application/octet-stream', }, }); if (response?.data.code === 0) { const baseURL = axiosInstance?.defaults.baseURL; const url = `${baseURL}/api/file_storage/${workspaceId}/v1/blob/${viewId}/${response?.data.data.file_id}`; return url; } return Promise.reject(response?.data); // eslint-disable-next-line } catch (e: any) { if (e.response.status === 413) { return Promise.reject({ code: 413, message: 'File size is too large. Please upgrade your plan for unlimited uploads.', }); } } return Promise.reject({ code: -1, message: 'Upload file failed.', }); } export async function inviteMembers (workspaceId: string, emails: string[]) { const url = `/api/workspace/${workspaceId}/invite`; const payload = emails.map(e => ({ email: e, role: Role.Member, })); const res = await axiosInstance?.post<{ code: number; message: string; }>(url, payload); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function getMembers (workspaceId: string) { const url = `/api/workspace/${workspaceId}/member`; const res = await axiosInstance?.get<{ code: number; data: WorkspaceMember[]; message: string; }>(url); if (res?.data.code === 0) { return res?.data.data; } return Promise.reject(res?.data); } export async function leaveWorkspace (workspaceId: string) { const url = `/api/workspace/${workspaceId}/leave`; const res = await axiosInstance?.post<{ code: number; message: string; }>(url); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function deleteWorkspace (workspaceId: string) { const url = `/api/workspace/${workspaceId}`; const res = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function getQuickNoteList (workspaceId: string, params: { offset?: number; limit?: number; searchTerm?: string; }) { const url = `/api/workspace/${workspaceId}/quick-note`; const res = await axiosInstance?.get<{ code: number; data: { quick_notes: QuickNote[]; has_more: boolean; }; message: string; }>(url, { params: { offset: params.offset, limit: params.limit, search_term: params.searchTerm || undefined, }, }); if (res?.data.code === 0) { return { data: res?.data.data.quick_notes, has_more: res?.data.data.has_more, }; } return Promise.reject(res?.data); } export async function createQuickNote (workspaceId: string, payload: QuickNoteEditorData[]): Promise { const url = `/api/workspace/${workspaceId}/quick-note`; const res = await axiosInstance?.post<{ code: number; data: QuickNote; message: string; }>(url, { data: payload, }); if (res?.data.code === 0) { return res?.data.data; } return Promise.reject(res?.data); } export async function updateQuickNote (workspaceId: string, noteId: string, payload: QuickNoteEditorData[]) { const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; const res = await axiosInstance?.put<{ code: number; message: string; }>(url, { data: payload, }); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function deleteQuickNote (workspaceId: string, noteId: string) { const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; const res = await axiosInstance?.delete<{ code: number; message: string; }>(url); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function cancelSubscription (workspaceId: string, plan: SubscriptionPlan, reason?: string) { const url = `/billing/api/v1/cancel-subscription`; const res = await axiosInstance?.post<{ code: number; message: string; }>(url, { workspace_id: workspaceId, plan, sync: true, reason, }); if (res?.data.code === 0) { return; } return Promise.reject(res?.data); } export async function searchWorkspace (workspaceId: string, query: string) { const url = `/api/search/${workspaceId}`; const res = await axiosInstance?.get<{ code: number; data: { object_id: string }[]; message: string; }>(url, { params: { query, }, }); if (res?.data.code === 0) { return res?.data.data.map(item => item.object_id); } return Promise.reject(res?.data); }