diff --git a/apiserver/plane/urls.py b/apiserver/plane/urls.py index 75b4c2609..e437da078 100644 --- a/apiserver/plane/urls.py +++ b/apiserver/plane/urls.py @@ -19,8 +19,11 @@ urlpatterns = [ if settings.DEBUG: - import debug_toolbar + try: + import debug_toolbar - urlpatterns = [ - re_path(r"^__debug__/", include(debug_toolbar.urls)), - ] + urlpatterns + urlpatterns = [ + re_path(r"^__debug__/", include(debug_toolbar.urls)), + ] + urlpatterns + except ImportError: + pass diff --git a/web/components/analytics/project-modal/modal.tsx b/web/components/analytics/project-modal/modal.tsx index a6422c595..6dfbfdd6b 100644 --- a/web/components/analytics/project-modal/modal.tsx +++ b/web/components/analytics/project-modal/modal.tsx @@ -27,18 +27,17 @@ export const ProjectAnalyticsModal: React.FC = observer((props) => { return ( -
- - {/* TODO: fix full screen mode */} - + +
= observer((props) => { projectDetails={projectDetails} />
-
-
-
+ + +
); diff --git a/web/store/application/event-tracker.store.ts b/web/store/application/event-tracker.store.ts index c30a8ee40..80f3ca01b 100644 --- a/web/store/application/event-tracker.store.ts +++ b/web/store/application/event-tracker.store.ts @@ -9,7 +9,7 @@ export interface IEventTrackerStore { postHogEventTracker: ( eventName: string, payload: object | [] | null, - group?: { isGrouping: boolean | null; groupType: string | null; gorupId: string | null } | null + group?: { isGrouping: boolean | null; groupType: string | null; groupId: string | null } | null ) => void; } @@ -32,16 +32,19 @@ export class EventTrackerStore implements IEventTrackerStore { postHogEventTracker = ( eventName: string, payload: object | [] | null, - group?: { isGrouping: boolean | null; groupType: string | null; gorupId: string | null } | null + group?: { isGrouping: boolean | null; groupType: string | null; groupId: string | null } | null ) => { try { + const currentWorkspaceDetails = this.rootStore.workspace.currentWorkspace; + const currentProjectDetails = this.rootStore.project.projects.currentProjectDetails; + let extras: any = { - workspace_name: this.rootStore.workspace.currentWorkspace?.name ?? "", - workspace_id: this.rootStore.workspace.currentWorkspace?.id ?? "", - workspace_slug: this.rootStore.workspace.currentWorkspace?.slug ?? "", - project_name: this.rootStore.project.currentProjectDetails?.name ?? "", - project_id: this.rootStore.project.currentProjectDetails?.id ?? "", - project_identifier: this.rootStore.project.currentProjectDetails?.identifier ?? "", + workspace_name: currentWorkspaceDetails?.name ?? "", + workspace_id: currentWorkspaceDetails?.id ?? "", + workspace_slug: currentWorkspaceDetails?.slug ?? "", + project_name: currentProjectDetails?.name ?? "", + project_id: currentProjectDetails?.id ?? "", + project_identifier: currentProjectDetails?.identifier ?? "", }; if (["PROJECT_CREATED", "PROJECT_UPDATED"].includes(eventName)) { const project_details: any = payload as object; @@ -54,9 +57,9 @@ export class EventTrackerStore implements IEventTrackerStore { } if (group && group!.isGrouping === true) { - posthog?.group(group!.groupType!, group!.gorupId!, { + posthog?.group(group!.groupType!, group!.groupId!, { date: new Date(), - workspace_id: group!.gorupId, + workspace_id: group!.groupId, }); posthog?.capture(eventName, { ...payload, diff --git a/web/store/application/router.store.ts b/web/store/application/router.store.ts index 20224915b..0e06f1b98 100644 --- a/web/store/application/router.store.ts +++ b/web/store/application/router.store.ts @@ -1,21 +1,85 @@ -import { action, makeObservable, observable } from "mobx"; +import { action, computed, makeObservable, observable } from "mobx"; +import { ParsedUrlQuery } from "querystring"; export interface IRouterStore { - query: any; - setQuery: (query: any) => void; + query: ParsedUrlQuery; + setQuery: (query: ParsedUrlQuery) => void; + + workspaceSlug: string | undefined; + projectId: string | undefined; + cycleId: string | undefined; + moduleId: string | undefined; + viewId: string | undefined; + userId: string | undefined; + peekId: string | undefined; + issueId: string | undefined; + inboxId: string | undefined; + webhookId: string | undefined; } export class RouterStore implements IRouterStore { - query = {}; + query: ParsedUrlQuery = {}; constructor() { makeObservable(this, { query: observable, setQuery: action, + + //computed + workspaceSlug: computed, + projectId: computed, + cycleId: computed, + moduleId: computed, + viewId: computed, + userId: computed, + peekId: computed, + issueId: computed, + inboxId: computed, + webhookId: computed, }); } - setQuery(query: any) { + setQuery(query: ParsedUrlQuery) { this.query = query; } + + get workspaceSlug() { + return this.query?.workspaceSlug?.toString(); + } + + get projectId() { + return this.query?.projectId?.toString(); + } + + get moduleId() { + return this.query?.moduleId?.toString(); + } + + get cycleId() { + return this.query?.cycleId?.toString(); + } + + get viewId() { + return this.query?.viewId?.toString(); + } + + get userId() { + return this.query?.userId?.toString(); + } + + get peekId() { + return this.query?.peekId?.toString(); + } + + get issueId() { + return this.query?.issueId?.toString(); + } + + get inboxId() { + return this.query?.inboxId?.toString(); + } + + get webhookId() { + return this.query?.webhookId?.toString(); + } } diff --git a/web/store/project/index.ts b/web/store/project/index.ts index f99736ae8..ceaa026e0 100644 --- a/web/store/project/index.ts +++ b/web/store/project/index.ts @@ -1,10 +1,15 @@ -import { ProjectsStore } from "./projects.store"; -import { ProjectPublishStore } from "./project-publish.store"; +import { IProjectsStore, ProjectsStore } from "./projects.store"; +import { IProjectPublishStore, ProjectPublishStore } from "./project-publish.store"; import { RootStore } from "store/root.store"; +export interface IProjectRootStore { + projects: IProjectsStore; + publish: IProjectPublishStore; +} + export class ProjectRootStore { - projects: ProjectsStore; - publish: ProjectPublishStore; + projects: IProjectsStore; + publish: IProjectPublishStore; constructor(_root: RootStore) { this.projects = new ProjectsStore(_root); diff --git a/web/store/project/projects.store.ts b/web/store/project/projects.store.ts index 676011fab..f8fd206d5 100644 --- a/web/store/project/projects.store.ts +++ b/web/store/project/projects.store.ts @@ -104,9 +104,9 @@ export class ProjectsStore implements IProjectsStore { } get searchedProjects() { - if (!this.rootStore.workspace.workspaceSlug) return []; + if (!this.rootStore.app.router.query.workspaceSlug) return []; - const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug]; + const currentProjectsMap = this.projectsMap[this.rootStore.app.router.query.workspaceSlug.toString()]; const projectIds = Object.keys(currentProjectsMap); return this.searchQuery === "" ? projectIds @@ -117,8 +117,8 @@ export class ProjectsStore implements IProjectsStore { } get workspaceProjects() { - if (!this.rootStore.workspace.workspaceSlug) return null; - const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug]; + if (!this.rootStore.app.router.workspaceSlug) return null; + const currentProjectsMap = this.projectsMap[this.rootStore.app.router.query.workspaceSlug.toString()]; const projectIds = Object.keys(currentProjectsMap); if (!projectIds) return null; @@ -126,8 +126,8 @@ export class ProjectsStore implements IProjectsStore { } get currentProjectDetails() { - if (!this.projectId || !this.rootStore.workspace.workspaceSlug) return; - return this.projectsMap[this.rootStore.workspace.workspaceSlug][this.projectId]; + if (!this.rootStore.app.router.query.projectId || !this.rootStore.app.router.query.workspaceSlug) return; + return this.projectsMap[!this.rootStore.app.router.query.workspaceSlug][this.projectId]; } get joinedProjects() { diff --git a/web/store/root.store.ts b/web/store/root.store.ts index 792a5d2d0..a69d3427e 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -1,29 +1,30 @@ import { enableStaticRendering } from "mobx-react-lite"; // root stores import { AppRootStore, IAppRootStore } from "./application"; -import { ProjectRootStore } from "./project"; -import { CycleStore } from "./cycle.store"; -import { ProjectViewsStore } from "./project-view.store"; -import { ModulesStore } from "./module.store"; -import { UserStore, IUserStore } from "./user"; -import { LabelStore, ILabelStore } from "./label.store"; +import { IProjectRootStore, ProjectRootStore } from "./project"; +import { CycleStore, ICycleStore } from "./cycle.store"; +import { IProjectViewsStore, ProjectViewsStore } from "./project-view.store"; +import { IModuleStore, ModulesStore } from "./module.store"; +import { IUserStore, UserStore } from "./user"; +import { ILabelStore, LabelStore } from "./label.store"; +import { IWorkspaceRootStore, WorkspaceRootStore } from "./workspace"; enableStaticRendering(typeof window === "undefined"); export class RootStore { app: IAppRootStore; user: IUserStore; - // workspace; - project; - cycle; - module; - projectView; + workspace: IWorkspaceRootStore; + project: IProjectRootStore; + cycle: ICycleStore; + module: IModuleStore; + projectView: IProjectViewsStore; label: ILabelStore; constructor() { this.app = new AppRootStore(this); this.user = new UserStore(this); - // this.workspace = new WorkspaceRootStore(); + this.workspace = new WorkspaceRootStore(this); this.project = new ProjectRootStore(this); this.cycle = new CycleStore(this); this.module = new ModulesStore(this); diff --git a/web/store/user/index.ts b/web/store/user/index.ts index 79ccd69e8..b82b56d68 100644 --- a/web/store/user/index.ts +++ b/web/store/user/index.ts @@ -1,4 +1,4 @@ -import { action, observable, runInAction, makeObservable, computed } from "mobx"; +import { action, observable, runInAction, makeObservable } from "mobx"; // services import { UserService } from "services/user.service"; import { AuthService } from "services/auth.service"; @@ -6,7 +6,7 @@ import { AuthService } from "services/auth.service"; import { IUser, IUserSettings } from "types/users"; // store import { RootStore } from "../root.store"; -import { UserMembershipStore } from "./user-membership.store"; +import { IUserMembershipStore, UserMembershipStore } from "./user-membership.store"; export interface IUserStore { loader: boolean; @@ -33,7 +33,7 @@ export interface IUserStore { deactivateAccount: () => Promise; signOut: () => Promise; - membership: UserMembershipStore; + membership: IUserMembershipStore; } export class UserStore implements IUserStore { diff --git a/web/store/user/user-membership.store.ts b/web/store/user/user-membership.store.ts index 7e964940f..e7cd475ed 100644 --- a/web/store/user/user-membership.store.ts +++ b/web/store/user/user-membership.store.ts @@ -1,12 +1,10 @@ // mobx import { action, observable, runInAction, makeObservable, computed } from "mobx"; // services -import { ProjectMemberService, ProjectService } from "services/project"; +import { ProjectMemberService } from "services/project"; import { UserService } from "services/user.service"; import { WorkspaceService } from "services/workspace.service"; -import { AuthService } from "services/auth.service"; // interfaces -import { IUser, IUserSettings } from "types/users"; import { IWorkspaceMemberMe, IProjectMember, TUserProjectRole, TUserWorkspaceRole } from "types"; import { RootStore } from "../root.store"; @@ -53,14 +51,12 @@ export class UserMembershipStore implements IUserMembershipStore { hasPermissionToProject: { [projectId: string]: boolean; } = {}; - // root store - rootStore; + // stores + router; // services userService; workspaceService; - projectService; projectMemberService; - authService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -83,42 +79,41 @@ export class UserMembershipStore implements IUserMembershipStore { hasPermissionToCurrentWorkspace: computed, hasPermissionToCurrentProject: computed, }); - this.rootStore = _rootStore; + this.router = _rootStore.app.router; + // services this.userService = new UserService(); this.workspaceService = new WorkspaceService(); - this.projectService = new ProjectService(); this.projectMemberService = new ProjectMemberService(); - this.authService = new AuthService(); } get currentWorkspaceMemberInfo() { - if (!this.rootStore.workspace.workspaceSlug) return; - return this.workspaceMemberInfo[this.rootStore.workspace.workspaceSlug]; + if (!this.router.query?.workspaceSlug) return; + return this.workspaceMemberInfo[this.router.query?.workspaceSlug]; } get currentWorkspaceRole() { - if (!this.rootStore.workspace.workspaceSlug) return; - return this.workspaceMemberInfo[this.rootStore.workspace.workspaceSlug]?.role; + if (!this.router.query?.workspaceSlug) return; + return this.workspaceMemberInfo[this.router.query?.workspaceSlug]?.role; } get currentProjectMemberInfo() { - if (!this.rootStore.project.projectId) return; - return this.projectMemberInfo[this.rootStore.project.projectId]; + if (!this.router.query?.projectId) return; + return this.projectMemberInfo[this.router.query?.projectId]; } get currentProjectRole() { - if (!this.rootStore.project.projectId) return; - return this.projectMemberInfo[this.rootStore.project.projectId]?.role; + if (!this.router.query?.projectId) return; + return this.projectMemberInfo[this.router.query?.projectId]?.role; } get hasPermissionToCurrentWorkspace() { - if (!this.rootStore.workspace.workspaceSlug) return; - return this.hasPermissionToWorkspace[this.rootStore.workspace.workspaceSlug]; + if (!this.router.query?.workspaceSlug) return; + return this.hasPermissionToWorkspace[this.router.query?.workspaceSlug]; } get hasPermissionToCurrentProject() { - if (!this.rootStore.project.projectId) return; - return this.hasPermissionToProject[this.rootStore.project.projectId]; + if (!this.router.query?.projectId) return; + return this.hasPermissionToProject[this.router.query?.projectId]; } fetchUserWorkspaceInfo = async (workspaceSlug: string) => { diff --git a/web/store/workspace/api-token.store.ts b/web/store/workspace/api-token.store.ts new file mode 100644 index 000000000..69a614062 --- /dev/null +++ b/web/store/workspace/api-token.store.ts @@ -0,0 +1,184 @@ +// mobx +import { action, observable, makeObservable, runInAction } from "mobx"; +import { APITokenService } from "services/api_token.service"; +import { RootStore } from "../root.store"; +// types +import { IApiToken } from "types/api_token"; + +export interface IApiTokenStore { + // states + loader: boolean; + error: any | null; + + // observables + apiTokens: Record | null; + + // computed actions + getApiTokenById: (apiTokenId: string) => IApiToken | null; + + // actions + fetchApiTokens: (workspaceSlug: string) => Promise; + fetchApiTokenDetails: (workspaceSlug: string, tokenId: string) => Promise; + createApiToken: (workspaceSlug: string, data: Partial) => Promise; + deleteApiToken: (workspaceSlug: string, tokenId: string) => Promise; +} + +export class ApiTokenStore implements IApiTokenStore { + // states + loader: boolean = false; + error: any | null = null; + + // observables + apiTokens: Record | null = null; + + // services + apiTokenService; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // states + loader: observable.ref, + error: observable.ref, + + // observables + apiTokens: observable, + + // computed actions + getApiTokenById: action, + + // actions + fetchApiTokens: action, + fetchApiTokenDetails: action, + createApiToken: action, + deleteApiToken: action, + }); + + // services + this.apiTokenService = new APITokenService(); + // root store + this.rootStore = _rootStore; + } + + /** + * get API token by id + * @param apiTokenId + */ + getApiTokenById = (apiTokenId: string) => { + if (!this.apiTokens) return null; + + return this.apiTokens[apiTokenId] || null; + }; + + /** + * fetch all the API tokens for a workspace + * @param workspaceSlug + */ + fetchApiTokens = async (workspaceSlug: string) => { + try { + this.loader = true; + this.error = null; + + const response = await this.apiTokenService.getApiTokens(workspaceSlug); + + const apiTokensObject: { [apiTokenId: string]: IApiToken } = response.reduce((accumulator, currentWebhook) => { + if (currentWebhook && currentWebhook.id) { + return { ...accumulator, [currentWebhook.id]: currentWebhook }; + } + return accumulator; + }, {}); + + runInAction(() => { + this.apiTokens = apiTokensObject; + }); + + return response; + } catch (error) { + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; + + /** + * fetch API token details using token id + * @param workspaceSlug + * @param tokenId + */ + fetchApiTokenDetails = async (workspaceSlug: string, tokenId: string) => { + try { + this.loader = true; + this.error = null; + + const response = await this.apiTokenService.retrieveApiToken(workspaceSlug, tokenId); + + runInAction(() => { + this.apiTokens = { ...this.apiTokens, [response.id]: response }; + }); + + return response; + } catch (error) { + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; + + /** + * create API token using data + * @param workspaceSlug + * @param data + */ + createApiToken = async (workspaceSlug: string, data: Partial) => { + try { + this.loader = true; + this.error = null; + + const response = await this.apiTokenService.createApiToken(workspaceSlug, data); + + runInAction(() => { + this.apiTokens = { ...this.apiTokens, [response.id]: response }; + }); + + return response; + } catch (error) { + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; + + /** + * delete API token using token id + * @param workspaceSlug + * @param tokenId + */ + deleteApiToken = async (workspaceSlug: string, tokenId: string) => { + try { + this.loader = true; + this.error = null; + + await this.apiTokenService.deleteApiToken(workspaceSlug, tokenId); + + const updatedApiTokens = { ...this.apiTokens }; + delete updatedApiTokens[tokenId]; + + runInAction(() => { + this.apiTokens = updatedApiTokens; + }); + } catch (error) { + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; +} diff --git a/web/store/workspace/index.ts b/web/store/workspace/index.ts new file mode 100644 index 000000000..0738a60ba --- /dev/null +++ b/web/store/workspace/index.ts @@ -0,0 +1,245 @@ +import { action, computed, observable, makeObservable, runInAction } from "mobx"; +import { RootStore } from "../root.store"; +// types +import { IWorkspace } from "types"; +// services +import { WorkspaceService } from "services/workspace.service"; +// sub-stores +import { IWebhookStore, WebhookStore } from "./webhook.store"; +import { ApiTokenStore, IApiTokenStore } from "./api-token.store"; + +export interface IWorkspaceRootStore { + // states + loader: boolean; + error: any | null; + + // observables + workspaces: IWorkspace[] | undefined; + + // computed + currentWorkspace: IWorkspace | null; + workspacesCreatedByCurrentUser: IWorkspace[] | null; + + // computed actions + getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null; + getWorkspaceById: (workspaceId: string) => IWorkspace | null; + + // actions + fetchWorkspaces: () => Promise; + createWorkspace: (data: Partial) => Promise; + updateWorkspace: (workspaceSlug: string, data: Partial) => Promise; + deleteWorkspace: (workspaceSlug: string) => Promise; + + // sub-stores + webhook: IWebhookStore; + apiToken: IApiTokenStore; +} + +export class WorkspaceRootStore implements IWorkspaceRootStore { + // states + loader: boolean = false; + error: any | null = null; + + // observables + workspaces: IWorkspace[] | undefined = []; + + // services + workspaceService; + // root store + rootStore; + // sub-stores + webhook: WebhookStore; + apiToken: ApiTokenStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // states + loader: observable.ref, + error: observable.ref, + + // observables + workspaces: observable, + + // computed + currentWorkspace: computed, + workspacesCreatedByCurrentUser: computed, + + // computed actions + getWorkspaceBySlug: action, + getWorkspaceById: action, + + // actions + fetchWorkspaces: action, + createWorkspace: action, + updateWorkspace: action, + deleteWorkspace: action, + }); + + // services + this.workspaceService = new WorkspaceService(); + // root store + this.rootStore = _rootStore; + // sub-stores + this.webhook = new WebhookStore(_rootStore); + this.apiToken = new ApiTokenStore(_rootStore); + } + + /** + * computed value of current workspace based on workspace slug saved in the query store + */ + get currentWorkspace() { + const workspaceSlug = this.rootStore.app.router.workspaceSlug; + + if (!workspaceSlug) return null; + + return this.workspaces?.find((workspace) => workspace.slug === workspaceSlug) || null; + } + + /** + * computed value of all the workspaces created by the current logged in user + */ + get workspacesCreatedByCurrentUser() { + if (!this.workspaces) return null; + + const user = this.rootStore.user.currentUser; + + if (!user) return null; + + return this.workspaces.filter((w) => w.created_by === user?.id); + } + + /** + * get workspace info from the array of workspaces in the store using workspace slug + * @param workspaceSlug + */ + getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces?.find((w) => w.slug == workspaceSlug) || null; + + /** + * get workspace info from the array of workspaces in the store using workspace id + * @param workspaceId + */ + getWorkspaceById = (workspaceId: string) => this.workspaces?.find((w) => w.id == workspaceId) || null; + + /** + * fetch user workspaces from API + */ + fetchWorkspaces = async () => { + try { + this.loader = true; + this.error = null; + + const workspaceResponse = await this.workspaceService.userWorkspaces(); + + runInAction(() => { + this.workspaces = workspaceResponse; + this.loader = false; + this.error = null; + }); + + return workspaceResponse; + } catch (error) { + console.log("Failed to fetch user workspaces in workspace store", error); + + runInAction(() => { + this.loader = false; + this.error = error; + this.workspaces = []; + }); + + throw error; + } + }; + + /** + * create workspace using the workspace data + * @param data + */ + createWorkspace = async (data: Partial) => { + try { + runInAction(() => { + this.loader = true; + this.error = null; + }); + + const response = await this.workspaceService.createWorkspace(data); + + runInAction(() => { + this.loader = false; + this.error = null; + this.workspaces = [...(this.workspaces ?? []), response]; + }); + + return response; + } catch (error) { + runInAction(() => { + this.loader = false; + this.error = error; + }); + + throw error; + } + }; + + /** + * update workspace using the workspace slug and new workspace data + * @param workspaceSlug + * @param data + */ + updateWorkspace = async (workspaceSlug: string, data: Partial) => { + const newWorkspaces = this.workspaces?.map((w) => (w.slug === workspaceSlug ? { ...w, ...data } : w)); + + try { + runInAction(() => { + this.loader = true; + this.error = null; + }); + + const response = await this.workspaceService.updateWorkspace(workspaceSlug, data); + + runInAction(() => { + this.loader = false; + this.error = null; + this.workspaces = newWorkspaces; + }); + + return response; + } catch (error) { + runInAction(() => { + this.loader = false; + this.error = error; + }); + + throw error; + } + }; + + /** + * delete workspace using the workspace slug + * @param workspaceSlug + */ + deleteWorkspace = async (workspaceSlug: string) => { + const newWorkspaces = this.workspaces?.filter((w) => w.slug !== workspaceSlug); + + try { + runInAction(() => { + this.loader = true; + this.error = null; + }); + + await this.workspaceService.deleteWorkspace(workspaceSlug); + + runInAction(() => { + this.loader = false; + this.error = null; + this.workspaces = newWorkspaces; + }); + } catch (error) { + runInAction(() => { + this.loader = false; + this.error = error; + }); + + throw error; + } + }; +} diff --git a/web/store/workspace/webhook.store.ts b/web/store/workspace/webhook.store.ts new file mode 100644 index 000000000..08132bb48 --- /dev/null +++ b/web/store/workspace/webhook.store.ts @@ -0,0 +1,251 @@ +// mobx +import { action, observable, makeObservable, computed, runInAction } from "mobx"; +import { IWebhook } from "types"; +import { WebhookService } from "services/webhook.service"; +import { RootStore } from "../root.store"; + +export interface IWebhookStore { + // states + loader: boolean; + error: any | null; + // observables + webhooks: Record | null; + webhookSecretKey: string | null; + // computed + currentWebhook: IWebhook | null; + // computed actions + getWebhookById: (webhookId: string) => IWebhook | null; + // actions + fetchWebhooks: (workspaceSlug: string) => Promise; + fetchWebhookById: (workspaceSlug: string, webhookId: string) => Promise; + createWebhook: ( + workspaceSlug: string, + data: Partial + ) => Promise<{ webHook: IWebhook; secretKey: string | null }>; + updateWebhook: (workspaceSlug: string, webhookId: string, data: Partial) => Promise; + removeWebhook: (workspaceSlug: string, webhookId: string) => Promise; + regenerateSecretKey: ( + workspaceSlug: string, + webhookId: string + ) => Promise<{ webHook: IWebhook; secretKey: string | null }>; + clearSecretKey: () => void; +} + +export class WebhookStore implements IWebhookStore { + // states + loader: boolean = false; + error: any | null = null; + // observables + webhooks: Record | null = null; + webhookSecretKey: string | null = null; + // services + webhookService; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // states + loader: observable.ref, + error: observable.ref, + // observables + webhooks: observable, + webhookSecretKey: observable.ref, + // computed + currentWebhook: computed, + // computed actions + getWebhookById: action, + // actions + fetchWebhooks: action, + fetchWebhookById: action, + createWebhook: action, + updateWebhook: action, + removeWebhook: action, + regenerateSecretKey: action, + clearSecretKey: action, + }); + + // services + this.webhookService = new WebhookService(); + // root store + this.rootStore = _rootStore; + } + + /** + * computed value of current webhook based on webhook id saved in the query store + */ + get currentWebhook() { + const webhookId = this.rootStore.app.router.webhookId; + + if (!webhookId) return null; + + const currentWebhook = this.webhooks?.[webhookId] ?? null; + return currentWebhook; + } + + /** + * get webhook info from the object of webhooks in the store using webhook id + * @param webhookId + */ + getWebhookById = (webhookId: string) => this.webhooks?.[webhookId] || null; + + /** + * fetch all the webhooks for a workspace + * @param workspaceSlug + */ + fetchWebhooks = async (workspaceSlug: string) => { + try { + this.loader = true; + this.error = null; + + const webhookResponse = await this.webhookService.fetchWebhooksList(workspaceSlug); + + const webHookObject: { [webhookId: string]: IWebhook } = webhookResponse.reduce((accumulator, currentWebhook) => { + if (currentWebhook && currentWebhook.id) { + return { ...accumulator, [currentWebhook.id]: currentWebhook }; + } + return accumulator; + }, {}); + + runInAction(() => { + this.webhooks = webHookObject; + this.loader = false; + this.error = null; + }); + + return webhookResponse; + } catch (error) { + this.loader = false; + this.error = error; + + throw error; + } + }; + + /** + * fetch webhook info from API using webhook id + * @param workspaceSlug + * @param webhookId + */ + fetchWebhookById = async (workspaceSlug: string, webhookId: string) => { + try { + const webhookResponse = await this.webhookService.fetchWebhookDetails(workspaceSlug, webhookId); + + runInAction(() => { + this.webhooks = { + ...this.webhooks, + [webhookResponse.id]: webhookResponse, + }; + }); + + return webhookResponse; + } catch (error) { + throw error; + } + }; + + /** + * create a new webhook for a workspace using the data + * @param workspaceSlug + * @param data + */ + createWebhook = async (workspaceSlug: string, data: Partial) => { + try { + const webhookResponse = await this.webhookService.createWebhook(workspaceSlug, data); + + const _secretKey = webhookResponse?.secret_key ?? null; + delete webhookResponse?.secret_key; + const _webhooks = this.webhooks; + + if (webhookResponse && webhookResponse.id && _webhooks) _webhooks[webhookResponse.id] = webhookResponse; + + runInAction(() => { + this.webhookSecretKey = _secretKey || null; + this.webhooks = _webhooks; + }); + + return { webHook: webhookResponse, secretKey: _secretKey }; + } catch (error) { + throw error; + } + }; + + /** + * update a webhook using the data + * @param workspaceSlug + * @param webhookId + * @param data + */ + updateWebhook = async (workspaceSlug: string, webhookId: string, data: Partial) => { + try { + let _webhooks = this.webhooks; + + if (webhookId && _webhooks && this.webhooks) + _webhooks = { ..._webhooks, [webhookId]: { ...this.webhooks[webhookId], ...data } }; + + runInAction(() => { + this.webhooks = _webhooks; + }); + + const webhookResponse = await this.webhookService.updateWebhook(workspaceSlug, webhookId, data); + + return webhookResponse; + } catch (error) { + this.fetchWebhooks(workspaceSlug); + throw error; + } + }; + + /** + * delete a webhook using webhook id + * @param workspaceSlug + * @param webhookId + */ + removeWebhook = async (workspaceSlug: string, webhookId: string) => { + try { + await this.webhookService.deleteWebhook(workspaceSlug, webhookId); + + const _webhooks = this.webhooks ?? {}; + delete _webhooks[webhookId]; + runInAction(() => { + this.webhooks = _webhooks; + }); + } catch (error) { + throw error; + } + }; + + /** + * regenerate secret key for a webhook using webhook id + * @param workspaceSlug + * @param webhookId + */ + regenerateSecretKey = async (workspaceSlug: string, webhookId: string) => { + try { + const webhookResponse = await this.webhookService.regenerateSecretKey(workspaceSlug, webhookId); + + const _secretKey = webhookResponse?.secret_key ?? null; + delete webhookResponse?.secret_key; + const _webhooks = this.webhooks; + + if (_webhooks && webhookResponse && webhookResponse.id) { + _webhooks[webhookResponse.id] = webhookResponse; + } + + runInAction(() => { + this.webhookSecretKey = _secretKey || null; + this.webhooks = _webhooks; + }); + return { webHook: webhookResponse, secretKey: _secretKey }; + } catch (error) { + throw error; + } + }; + + /** + * clear secret key from the store + */ + clearSecretKey = () => { + this.webhookSecretKey = null; + }; +}