From 043f4eaa5e93e81f6e386f7c88c08ed76b4c98b4 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 23 Dec 2024 01:51:30 +0530 Subject: [PATCH] chore: common services package (#6255) * fix: initial services package setup * fix: services packages updates * fix: services changes * fix: merge conflicts * chore: file structuring * fix: import fixes --- packages/constants/src/ai.ts | 3 + packages/constants/src/endpoints.ts | 19 +- packages/constants/src/index.ts | 1 + packages/eslint-config/library.js | 24 ++ packages/services/.eslintignore | 3 + packages/services/.eslintrc.js | 9 + packages/services/.prettierrc | 5 + packages/services/package.json | 14 ++ packages/services/src/ai/ai.service.ts | 67 ++++++ packages/services/src/ai/index.ts | 1 + .../src/analytics/analytics.service.ts | 93 ++++++++ packages/services/src/analytics/index.ts | 1 + packages/services/src/api.service.ts | 110 +++++++++ packages/services/src/auth/auth.service.ts | 124 ++++++++++ packages/services/src/auth/index.ts | 1 + .../src/cycle/cycle-analytics.service.ts | 78 +++++++ .../src/cycle/cycle-archive.service.ts | 83 +++++++ .../src/cycle/cycle-operations.service.ts | 70 ++++++ packages/services/src/cycle/cycle.service.ts | 184 +++++++++++++++ packages/services/src/cycle/index.ts | 4 + .../src/dashboard/dashboard.service.ts | 79 +++++++ packages/services/src/dashboard/index.ts | 1 + .../src/developer/api-token.service.ts | 68 ++++++ packages/services/src/developer/index.ts | 2 + .../services/src/developer/webhook.service.ts | 104 +++++++++ packages/services/src/index.ts | 12 + packages/services/src/instance/index.ts | 1 + .../services/src/instance/instance.service.ts | 44 ++++ packages/services/src/intake/index.ts | 2 + .../services/src/intake/intake.service.ts | 8 + packages/services/src/intake/issue.service.ts | 18 ++ packages/services/src/live.service.ts | 8 + packages/services/src/module/index.ts | 3 + packages/services/src/module/link.service.ts | 86 +++++++ .../services/src/module/module.service.ts | 212 ++++++++++++++++++ .../services/src/module/operations.service.ts | 146 ++++++++++++ packages/services/src/project/index.ts | 1 + packages/services/src/project/view.service.ts | 0 .../services/src/user/favorite.service.ts | 94 ++++++++ packages/services/src/user/index.ts | 1 + packages/services/src/workspace/index.ts | 5 + .../src/workspace/invitation.service.ts | 117 ++++++++++ .../services/src/workspace/member.service.ts | 92 ++++++++ .../src/workspace/notification.service.ts | 137 +++++++++++ .../services/src/workspace/view.service.ts | 67 ++++++ .../src/workspace/workspace.service.ts | 141 ++++++++++++ packages/services/tsconfig.json | 12 + 47 files changed, 2345 insertions(+), 10 deletions(-) create mode 100644 packages/constants/src/ai.ts create mode 100644 packages/services/.eslintignore create mode 100644 packages/services/.eslintrc.js create mode 100644 packages/services/.prettierrc create mode 100644 packages/services/package.json create mode 100644 packages/services/src/ai/ai.service.ts create mode 100644 packages/services/src/ai/index.ts create mode 100644 packages/services/src/analytics/analytics.service.ts create mode 100644 packages/services/src/analytics/index.ts create mode 100644 packages/services/src/api.service.ts create mode 100644 packages/services/src/auth/auth.service.ts create mode 100644 packages/services/src/auth/index.ts create mode 100644 packages/services/src/cycle/cycle-analytics.service.ts create mode 100644 packages/services/src/cycle/cycle-archive.service.ts create mode 100644 packages/services/src/cycle/cycle-operations.service.ts create mode 100644 packages/services/src/cycle/cycle.service.ts create mode 100644 packages/services/src/cycle/index.ts create mode 100644 packages/services/src/dashboard/dashboard.service.ts create mode 100644 packages/services/src/dashboard/index.ts create mode 100644 packages/services/src/developer/api-token.service.ts create mode 100644 packages/services/src/developer/index.ts create mode 100644 packages/services/src/developer/webhook.service.ts create mode 100644 packages/services/src/index.ts create mode 100644 packages/services/src/instance/index.ts create mode 100644 packages/services/src/instance/instance.service.ts create mode 100644 packages/services/src/intake/index.ts create mode 100644 packages/services/src/intake/intake.service.ts create mode 100644 packages/services/src/intake/issue.service.ts create mode 100644 packages/services/src/live.service.ts create mode 100644 packages/services/src/module/index.ts create mode 100644 packages/services/src/module/link.service.ts create mode 100644 packages/services/src/module/module.service.ts create mode 100644 packages/services/src/module/operations.service.ts create mode 100644 packages/services/src/project/index.ts create mode 100644 packages/services/src/project/view.service.ts create mode 100644 packages/services/src/user/favorite.service.ts create mode 100644 packages/services/src/user/index.ts create mode 100644 packages/services/src/workspace/index.ts create mode 100644 packages/services/src/workspace/invitation.service.ts create mode 100644 packages/services/src/workspace/member.service.ts create mode 100644 packages/services/src/workspace/notification.service.ts create mode 100644 packages/services/src/workspace/view.service.ts create mode 100644 packages/services/src/workspace/workspace.service.ts create mode 100644 packages/services/tsconfig.json diff --git a/packages/constants/src/ai.ts b/packages/constants/src/ai.ts new file mode 100644 index 0000000000..8125302440 --- /dev/null +++ b/packages/constants/src/ai.ts @@ -0,0 +1,3 @@ +export enum AI_EDITOR_TASKS { + ASK_ANYTHING = "ASK_ANYTHING", +} diff --git a/packages/constants/src/endpoints.ts b/packages/constants/src/endpoints.ts index 8f7b504644..b17f7c1a06 100644 --- a/packages/constants/src/endpoints.ts +++ b/packages/constants/src/endpoints.ts @@ -1,26 +1,25 @@ export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || ""; -// PI Base Url -export const PI_BASE_URL = process.env.NEXT_PUBLIC_PI_BASE_URL || ""; +export const API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH || "/"; +export const API_URL = encodeURI(`${API_BASE_URL}${API_BASE_PATH}`); // God Mode Admin App Base Url export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || ""; -export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || ""; -export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}/`); +export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "/"; +export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}`); // Publish App Base Url export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || ""; -export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || ""; -export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}/`); +export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "/"; +export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}`); // Live App Base Url export const LIVE_BASE_URL = process.env.NEXT_PUBLIC_LIVE_BASE_URL || ""; -export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || ""; -export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}/`); +export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || "/"; +export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}`); // Web App Base Url export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || ""; -export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || ""; +export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || "/"; export const WEB_URL = encodeURI(`${WEB_BASE_URL}${WEB_BASE_PATH}`); // plane website url export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://plane.so"; - // support email export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "support@plane.so"; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 0913197869..95a4f97843 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -1,3 +1,4 @@ +export * from "./ai"; export * from "./auth"; export * from "./endpoints"; export * from "./file"; diff --git a/packages/eslint-config/library.js b/packages/eslint-config/library.js index 283590693e..790364230f 100644 --- a/packages/eslint-config/library.js +++ b/packages/eslint-config/library.js @@ -44,6 +44,30 @@ module.exports = { "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-useless-empty-export": "error", "@typescript-eslint/prefer-ts-expect-error": "warn", + "import/order": [ + "warn", + { + groups: ["builtin", "external", "internal", "parent", "sibling"], + pathGroups: [ + { + pattern: "@plane/**", + group: "external", + position: "after", + }, + { + pattern: "@/**", + group: "internal", + position: "before", + }, + ], + pathGroupsExcludedImportTypes: ["builtin", "internal", "react"], + alphabetize: { + order: "asc", + caseInsensitive: true, + }, + }, + ], }, + ignorePatterns: [".*.js", "node_modules/", "dist/"], }; diff --git a/packages/services/.eslintignore b/packages/services/.eslintignore new file mode 100644 index 0000000000..6019047c3e --- /dev/null +++ b/packages/services/.eslintignore @@ -0,0 +1,3 @@ +build/* +dist/* +out/* \ No newline at end of file diff --git a/packages/services/.eslintrc.js b/packages/services/.eslintrc.js new file mode 100644 index 0000000000..558b8f76ed --- /dev/null +++ b/packages/services/.eslintrc.js @@ -0,0 +1,9 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["@plane/eslint-config/library.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/packages/services/.prettierrc b/packages/services/.prettierrc new file mode 100644 index 0000000000..87d988f1b2 --- /dev/null +++ b/packages/services/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/packages/services/package.json b/packages/services/package.json new file mode 100644 index 0000000000..e3473942f7 --- /dev/null +++ b/packages/services/package.json @@ -0,0 +1,14 @@ +{ + "name": "@plane/services", + "version": "0.24.1", + "private": true, + "main": "./src/index.ts", + "scripts": { + "lint": "eslint src --ext .ts,.tsx", + "lint:errors": "eslint src --ext .ts,.tsx --quiet" + }, + "dependencies": { + "@plane/constants": "*", + "axios": "^1.4.0" + } +} diff --git a/packages/services/src/ai/ai.service.ts b/packages/services/src/ai/ai.service.ts new file mode 100644 index 0000000000..6a3b3c637c --- /dev/null +++ b/packages/services/src/ai/ai.service.ts @@ -0,0 +1,67 @@ +// plane web constants +import { AI_EDITOR_TASKS, API_BASE_URL } from "@plane/constants"; +// services +import { APIService } from "@/api.service"; + +/** + * Payload type for AI editor tasks + * @typedef {Object} TTaskPayload + * @property {number} [casual_score] - Optional score for casual tone analysis + * @property {number} [formal_score] - Optional score for formal tone analysis + * @property {AI_EDITOR_TASKS} task - Type of AI editor task to perform + * @property {string} text_input - The input text to be processed + */ +export type TTaskPayload = { + casual_score?: number; + formal_score?: number; + task: AI_EDITOR_TASKS; + text_input: string; +}; + +/** + * Service class for handling AI-related API operations + * Extends the base APIService class to interact with AI endpoints + * @extends {APIService} + */ +export class AIService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Creates a GPT-based task for a specific workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {Object} data - The data payload for the GPT task + * @param {string} data.prompt - The prompt text for the GPT model + * @param {string} data.task - The type of task to be performed + * @returns {Promise} The response data from the GPT task + * @throws {Error} Throws the response error if the request fails + */ + async prompt(workspaceSlug: string, data: { prompt: string; task: string }): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/ai-assistant/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Performs an editor-specific AI task for text processing + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {TTaskPayload} data - The task payload containing text and processing parameters + * @returns {Promise<{response: string}>} The processed text response + * @throws {Error} Throws the response data if the request fails + */ + async rephraseGrammar( + workspaceSlug: string, + data: TTaskPayload + ): Promise<{ + response: string; + }> { + return this.post(`/api/workspaces/${workspaceSlug}/rephrase-grammar/`, data) + .then((res) => res?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/ai/index.ts b/packages/services/src/ai/index.ts new file mode 100644 index 0000000000..bce346aa66 --- /dev/null +++ b/packages/services/src/ai/index.ts @@ -0,0 +1 @@ +export * from "./ai.service"; diff --git a/packages/services/src/analytics/analytics.service.ts b/packages/services/src/analytics/analytics.service.ts new file mode 100644 index 0000000000..c012fd26f4 --- /dev/null +++ b/packages/services/src/analytics/analytics.service.ts @@ -0,0 +1,93 @@ +// constants +import { API_BASE_URL } from "@plane/constants"; +// types +import { + IAnalyticsParams, + IAnalyticsResponse, + IDefaultAnalyticsResponse, + IExportAnalyticsFormData, + ISaveAnalyticsFormData, +} from "@plane/types"; +// services +import { APIService } from "../api.service"; + +export class AnalyticsService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves analytics data for a specific workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {IAnalyticsParams} params - Parameters for filtering analytics data + * @param {string|number} [params.project] - Optional project identifier that will be converted to string + * @returns {Promise} The analytics data for the workspace + * @throws {Error} Throws response data if the request fails + */ + async getAnalytics(workspaceSlug: string, params: IAnalyticsParams): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/analytics/`, { + params: { + ...params, + project: params?.project ? params.project.toString() : null, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves default analytics data for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {Partial} [params] - Optional parameters for filtering default analytics + * @param {string|number} [params.project] - Optional project identifier that will be converted to string + * @returns {Promise} The default analytics data + * @throws {Error} Throws response data if the request fails + */ + async getDefaultAnalytics( + workspaceSlug: string, + params?: Partial + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/default-analytics/`, { + params: { + ...params, + project: params?.project ? params.project.toString() : null, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Saves analytics view configuration for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {ISaveAnalyticsFormData} data - The analytics configuration data to save + * @returns {Promise} The response from saving the analytics view + * @throws {Error} Throws response data if the request fails + */ + async save(workspaceSlug: string, data: ISaveAnalyticsFormData): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/analytic-view/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Exports analytics data for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {IExportAnalyticsFormData} data - Configuration for the analytics export + * @returns {Promise} The exported analytics data + * @throws {Error} Throws response data if the request fails + */ + async export(workspaceSlug: string, data: IExportAnalyticsFormData): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/export-analytics/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/analytics/index.ts b/packages/services/src/analytics/index.ts new file mode 100644 index 0000000000..7655bd4424 --- /dev/null +++ b/packages/services/src/analytics/index.ts @@ -0,0 +1 @@ +export * from "./analytics.service"; diff --git a/packages/services/src/api.service.ts b/packages/services/src/api.service.ts new file mode 100644 index 0000000000..3574424daf --- /dev/null +++ b/packages/services/src/api.service.ts @@ -0,0 +1,110 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; + +/** + * Abstract base class for making HTTP requests using axios + * @abstract + */ +export abstract class APIService { + protected baseURL: string; + private axiosInstance: AxiosInstance; + + /** + * Creates an instance of APIService + * @param {string} baseURL - The base URL for all HTTP requests + */ + constructor(baseURL: string) { + this.baseURL = baseURL; + this.axiosInstance = axios.create({ + baseURL, + withCredentials: true, + }); + + this.setupInterceptors(); + } + + /** + * Sets up axios interceptors for handling responses + * Currently handles 401 unauthorized responses by redirecting to login + * @private + */ + private setupInterceptors() { + this.axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + const currentPath = window.location.pathname; + window.location.replace(`/${currentPath ? `?next_path=${currentPath}` : ``}`); + } + return Promise.reject(error); + } + ); + } + + /** + * Makes a GET request to the specified URL + * @param {string} url - The endpoint URL + * @param {object} [params={}] - URL parameters + * @param {AxiosRequestConfig} [config={}] - Additional axios configuration + * @returns {Promise} Axios response promise + */ + get(url: string, params = {}, config: AxiosRequestConfig = {}) { + return this.axiosInstance.get(url, { + ...params, + ...config, + }); + } + + /** + * Makes a POST request to the specified URL + * @param {string} url - The endpoint URL + * @param {object} [data={}] - Request body data + * @param {AxiosRequestConfig} [config={}] - Additional axios configuration + * @returns {Promise} Axios response promise + */ + post(url: string, data = {}, config: AxiosRequestConfig = {}) { + return this.axiosInstance.post(url, data, config); + } + + /** + * Makes a PUT request to the specified URL + * @param {string} url - The endpoint URL + * @param {object} [data={}] - Request body data + * @param {AxiosRequestConfig} [config={}] - Additional axios configuration + * @returns {Promise} Axios response promise + */ + put(url: string, data = {}, config: AxiosRequestConfig = {}) { + return this.axiosInstance.put(url, data, config); + } + + /** + * Makes a PATCH request to the specified URL + * @param {string} url - The endpoint URL + * @param {object} [data={}] - Request body data + * @param {AxiosRequestConfig} [config={}] - Additional axios configuration + * @returns {Promise} Axios response promise + */ + patch(url: string, data = {}, config: AxiosRequestConfig = {}) { + return this.axiosInstance.patch(url, data, config); + } + + /** + * Makes a DELETE request to the specified URL + * @param {string} url - The endpoint URL + * @param {any} [data] - Request body data + * @param {AxiosRequestConfig} [config={}] - Additional axios configuration + * @returns {Promise} Axios response promise + */ + delete(url: string, data?: any, config: AxiosRequestConfig = {}) { + return this.axiosInstance.delete(url, { data, ...config }); + } + + /** + * Makes a custom request with the provided configuration + * @param {object} [config={}] - Axios request configuration + * @returns {Promise} Axios response promise + */ + request(config = {}) { + return this.axiosInstance(config); + } +} diff --git a/packages/services/src/auth/auth.service.ts b/packages/services/src/auth/auth.service.ts new file mode 100644 index 0000000000..87e75e536f --- /dev/null +++ b/packages/services/src/auth/auth.service.ts @@ -0,0 +1,124 @@ +import { API_BASE_URL } from "@plane/constants"; +// types +import { ICsrfTokenData, IEmailCheckData, IEmailCheckResponse } from "@plane/types"; +// services +import { APIService } from "../api.service"; + +/** + * Service class for handling authentication-related operations + * Provides methods for user authentication, password management, and session handling + * @extends {APIService} + */ +export default class AuthService extends APIService { + /** + * Creates an instance of AuthService + * Initializes with the base API URL + */ + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Requests a CSRF token for form submission security + * @returns {Promise} Object containing the CSRF token + * @throws {Error} Throws the complete error object if the request fails + */ + async requestCSRFToken(): Promise { + return this.get("/auth/get-csrf-token/") + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } + + /** + * Checks if an email exists in the system + * @param {IEmailCheckData} data - Email data to verify + * @returns {Promise} Response indicating email status + * @throws {Error} Throws response data if the request fails + */ + async emailCheck(data: IEmailCheckData): Promise { + return this.post("/auth/email-check/", data, { headers: {} }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Sends a password reset link to the specified email address + * @param {{ email: string }} data - Object containing the email address + * @returns {Promise} Response from the password reset request + * @throws {Error} Throws response object if the request fails + */ + async sendResetPasswordLink(data: { email: string }): Promise { + return this.post(`/auth/forgot-password/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Sets a new password using a reset token + * @param {string} token - CSRF token for form submission security + * @param {{ password: string }} data - Object containing the new password + * @returns {Promise} Response from the password update request + * @throws {Error} Throws response data if the request fails + */ + async setPassword(token: string, data: { password: string }): Promise { + return this.post(`/auth/set-password/`, data, { + headers: { + "X-CSRFTOKEN": token, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Generates a unique code for magic link authentication + * @param {{ email: string }} data - Object containing the email address + * @returns {Promise} Response containing the generated unique code + * @throws {Error} Throws response data if the request fails + */ + async generateUniqueCode(data: { email: string }): Promise { + return this.post("/auth/magic-generate/", data, { headers: {} }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Performs user sign out by submitting a form with CSRF token + * Creates and submits a form dynamically to handle the sign-out process + * @param {string} baseUrl - Base URL for the sign-out endpoint + * @returns {Promise} Resolves when sign-out is complete + * @throws {Error} Throws error if CSRF token is not found + */ + async signOut(baseUrl: string): Promise { + await this.requestCSRFToken().then((data) => { + const csrfToken = data?.csrf_token; + + if (!csrfToken) throw Error("CSRF token not found"); + + const form = document.createElement("form"); + const element1 = document.createElement("input"); + + form.method = "POST"; + form.action = `${baseUrl}/auth/sign-out/`; + + element1.value = csrfToken; + element1.name = "csrfmiddlewaretoken"; + element1.type = "hidden"; + form.appendChild(element1); + + document.body.appendChild(form); + + form.submit(); + }); + } +} diff --git a/packages/services/src/auth/index.ts b/packages/services/src/auth/index.ts new file mode 100644 index 0000000000..2ab33e86ab --- /dev/null +++ b/packages/services/src/auth/index.ts @@ -0,0 +1 @@ +export * from "./auth.service"; diff --git a/packages/services/src/cycle/cycle-analytics.service.ts b/packages/services/src/cycle/cycle-analytics.service.ts new file mode 100644 index 0000000000..4897926a92 --- /dev/null +++ b/packages/services/src/cycle/cycle-analytics.service.ts @@ -0,0 +1,78 @@ +import { API_BASE_URL } from "@plane/constants"; +import type { TCycleDistribution, TProgressSnapshot, TCycleEstimateDistribution } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing cycles within a workspace and project context. + * Extends APIService to handle HTTP requests to the cycle-related endpoints. + * @extends {APIService} + */ +export class CycleAnalyticsService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves analytics for active cycles in a workspace. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @param {string} [analytic_type="points"] - The type of analytics to retrieve + * @returns {Promise} The cycle analytics data + * @throws {Error} If the request fails + */ + async workspaceActiveCyclesAnalytics( + workspaceSlug: string, + projectId: string, + cycleId: string, + analytic_type: string = "points" + ): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/analytics?type=${analytic_type}` + ) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + + /** + * Retrieves progress data for active cycles. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @returns {Promise} The cycle progress data + * @throws {Error} If the request fails + */ + async workspaceActiveCyclesProgress( + workspaceSlug: string, + projectId: string, + cycleId: string + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/progress/`) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + + /** + * Retrieves advanced progress data for active cycles (Pro feature). + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @returns {Promise} The detailed cycle progress data + * @throws {Error} If the request fails + */ + async workspaceActiveCyclesProgressPro( + workspaceSlug: string, + projectId: string, + cycleId: string + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-progress/`) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } +} diff --git a/packages/services/src/cycle/cycle-archive.service.ts b/packages/services/src/cycle/cycle-archive.service.ts new file mode 100644 index 0000000000..8c40f0a298 --- /dev/null +++ b/packages/services/src/cycle/cycle-archive.service.ts @@ -0,0 +1,83 @@ +import { API_BASE_URL } from "@plane/constants"; +import { ICycle } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing archived cycles in a project + * Provides methods for retrieving, archiving, and restoring project cycles + * @extends {APIService} + */ +export class CycleArchiveService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves all archived cycles for a specific project + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @returns {Promise} Array of archived cycles + * @throws {Error} Throws response data if the request fails + */ + async list(workspaceSlug: string, projectId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-cycles/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves details of a specific archived cycle + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} cycleId - The unique identifier for the cycle + * @returns {Promise} Details of the archived cycle + * @throws {Error} Throws response data if the request fails + */ + async retrieve(workspaceSlug: string, projectId: string, cycleId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/archived-cycles/${cycleId}/`) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + + /** + * Archives a specific cycle in a project + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} cycleId - The unique identifier for the cycle to archive + * @returns {Promise<{archived_at: string}>} Object containing the archive timestamp + * @throws {Error} Throws response data if the request fails + */ + async archive( + workspaceSlug: string, + projectId: string, + cycleId: string + ): Promise<{ + archived_at: string; + }> { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/archive/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Restores a previously archived cycle + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} cycleId - The unique identifier for the cycle to restore + * @returns {Promise} Resolves when the cycle is successfully restored + * @throws {Error} Throws response data if the request fails + */ + async restore(workspaceSlug: string, projectId: string, cycleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/archive/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/cycle/cycle-operations.service.ts b/packages/services/src/cycle/cycle-operations.service.ts new file mode 100644 index 0000000000..3e6f32cd9a --- /dev/null +++ b/packages/services/src/cycle/cycle-operations.service.ts @@ -0,0 +1,70 @@ +import { API_BASE_URL } from "@plane/constants"; +import { APIService } from "@/api.service"; + +export class CycleOperationsService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Adds a cycle to user favorites. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {{cycle: string}} data - The favorite cycle data + * @returns {Promise} The response data + * @throws {Error} If the request fails + */ + async addToFavorites( + workspaceSlug: string, + projectId: string, + data: { + cycle: string; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-cycles/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Removes a cycle from user favorites. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @returns {Promise} The removal response + * @throws {Error} If the request fails + */ + async removeFromFavorites(workspaceSlug: string, projectId: string, cycleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-cycles/${cycleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Transfers issues between cycles. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The source cycle identifier + * @param {{new_cycle_id: string}} data - The target cycle data + * @returns {Promise} The transfer response + * @throws {Error} If the request fails + */ + async transferIssues( + workspaceSlug: string, + projectId: string, + cycleId: string, + data: { + new_cycle_id: string; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/transfer-issues/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/cycle/cycle.service.ts b/packages/services/src/cycle/cycle.service.ts new file mode 100644 index 0000000000..c697c2da4a --- /dev/null +++ b/packages/services/src/cycle/cycle.service.ts @@ -0,0 +1,184 @@ +import { API_BASE_URL } from "@plane/constants"; +import type { CycleDateCheckData, ICycle, TIssuesResponse, IWorkspaceActiveCyclesResponse } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing cycles within a workspace and project context. + * Extends APIService to handle HTTP requests to the cycle-related endpoints. + * @extends {APIService} + */ +export class CycleService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves paginated list of active cycles in a workspace. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} cursor - The pagination cursor + * @param {number} per_page - Number of items per page + * @returns {Promise} Paginated active cycles data + * @throws {Error} If the request fails + */ + async workspaceActiveCycles( + workspaceSlug: string, + cursor: string, + per_page: number + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/active-cycles/`, { + params: { + per_page, + cursor, + }, + }) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + + /** + * Gets all cycles in a workspace. + * @param {string} workspaceSlug - The workspace identifier + * @returns {Promise} Array of cycle objects + * @throws {Error} If the request fails + */ + async getWorkspaceCycles(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/cycles/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Creates a new cycle in a project. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {any} data - The cycle creation data + * @returns {Promise} The created cycle object + * @throws {Error} If the request fails + */ + async create(workspaceSlug: string, projectId: string, data: any): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves cycles with optional filtering parameters. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {"current"} [cycleType] - Optional filter for cycle type + * @returns {Promise} Array of filtered cycle objects + * @throws {Error} If the request fails + */ + async getWithParams(workspaceSlug: string, projectId: string, cycleType?: "current"): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, { + params: { + cycle_view: cycleType, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves detailed information for a specific cycle. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @returns {Promise} The cycle details + * @throws {Error} If the request fails + */ + async retrieve(workspaceSlug: string, projectId: string, cycleId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + + /** + * Retrieves issues associated with a specific cycle. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @param {any} [queries] - Optional query parameters + * @param {object} [config={}] - Optional request configuration + * @returns {Promise} The cycle issues data + * @throws {Error} If the request fails + */ + async getCycleIssues( + workspaceSlug: string, + projectId: string, + cycleId: string, + queries?: any, + config = {} + ): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/cycle-issues/`, + { + params: queries, + }, + config + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates a cycle with partial data. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @param {Partial} data - The partial cycle data to update + * @returns {Promise} The update response + * @throws {Error} If the request fails + */ + async update(workspaceSlug: string, projectId: string, cycleId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Deletes a specific cycle. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {string} cycleId - The cycle identifier + * @returns {Promise} The deletion response + * @throws {Error} If the request fails + */ + async destroy(workspaceSlug: string, projectId: string, cycleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Validates cycle dates. + * @param {string} workspaceSlug - The workspace identifier + * @param {string} projectId - The project identifier + * @param {CycleDateCheckData} data - The date check data + * @returns {Promise} The validation response + * @throws {Error} If the request fails + */ + async validateDates(workspaceSlug: string, projectId: string, data: CycleDateCheckData): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/date-check/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/cycle/index.ts b/packages/services/src/cycle/index.ts new file mode 100644 index 0000000000..5956023cbd --- /dev/null +++ b/packages/services/src/cycle/index.ts @@ -0,0 +1,4 @@ +export * from "./cycle-analytics.service"; +export * from "./cycle-archive.service"; +export * from "./cycle-operations.service"; +export * from "./cycle.service"; diff --git a/packages/services/src/dashboard/dashboard.service.ts b/packages/services/src/dashboard/dashboard.service.ts new file mode 100644 index 0000000000..6a4a6bcca7 --- /dev/null +++ b/packages/services/src/dashboard/dashboard.service.ts @@ -0,0 +1,79 @@ +import { API_BASE_URL } from "@plane/constants"; +import { THomeDashboardResponse, TWidget, TWidgetStatsResponse, TWidgetStatsRequestParams } from "@plane/types"; +import { APIService } from "../api.service"; + +export default class DashboardService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves home dashboard widgets for a specific workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @returns {Promise} Promise resolving to dashboard widget data + * @throws {Error} If the API request fails + */ + async getHomeWidgets(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/dashboard/`, { + params: { + dashboard_type: "home", + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Fetches statistics for a specific dashboard widget + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} dashboardId - The unique identifier for the dashboard + * @param {TWidgetStatsRequestParams} params - Parameters for filtering widget statistics + * @returns {Promise} Promise resolving to widget statistics data + * @throws {Error} If the API request fails + */ + async getWidgetStats( + workspaceSlug: string, + dashboardId: string, + params: TWidgetStatsRequestParams + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/dashboard/${dashboardId}/`, { + params, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves detailed information about a specific dashboard + * @param {string} dashboardId - The unique identifier for the dashboard + * @returns {Promise} Promise resolving to dashboard details + * @throws {Error} If the API request fails + */ + async retrieve(dashboardId: string): Promise { + return this.get(`/api/dashboard/${dashboardId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates a specific widget within a dashboard + * @param {string} dashboardId - The unique identifier for the dashboard + * @param {string} widgetId - The unique identifier for the widget + * @param {Partial} data - Partial widget data to update + * @returns {Promise} Promise resolving to the updated widget data + * @throws {Error} If the API request fails + */ + async updateWidget(dashboardId: string, widgetId: string, data: Partial): Promise { + return this.patch(`/api/dashboard/${dashboardId}/widgets/${widgetId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/dashboard/index.ts b/packages/services/src/dashboard/index.ts new file mode 100644 index 0000000000..79e3f04009 --- /dev/null +++ b/packages/services/src/dashboard/index.ts @@ -0,0 +1 @@ +export * from "./dashboard.service"; diff --git a/packages/services/src/developer/api-token.service.ts b/packages/services/src/developer/api-token.service.ts new file mode 100644 index 0000000000..92ee523ea6 --- /dev/null +++ b/packages/services/src/developer/api-token.service.ts @@ -0,0 +1,68 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IApiToken } from "@plane/types"; +import { APIService } from "@/api.service"; + +export class APITokenService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves all API tokens for a specific workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @returns {Promise} Array of API tokens associated with the workspace + * @throws {Error} Throws response data if the request fails + */ + async list(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/api-tokens/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves a specific API token by its ID + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} tokenId - The unique identifier of the API token + * @returns {Promise} The requested API token's details + * @throws {Error} Throws response data if the request fails + */ + async retrieve(workspaceSlug: string, tokenId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/api-tokens/${tokenId}`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Creates a new API token for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {Partial} data - The data for creating the new API token + * @returns {Promise} The newly created API token + * @throws {Error} Throws response data if the request fails + */ + async create(workspaceSlug: string, data: Partial): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/api-tokens/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Deletes a specific API token from the workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} tokenId - The unique identifier of the API token to delete + * @returns {Promise} The deleted API token's details + * @throws {Error} Throws response data if the request fails + */ + async destroy(workspaceSlug: string, tokenId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/api-tokens/${tokenId}`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/developer/index.ts b/packages/services/src/developer/index.ts new file mode 100644 index 0000000000..a78a7b0929 --- /dev/null +++ b/packages/services/src/developer/index.ts @@ -0,0 +1,2 @@ +export * from "./api-token.service"; +export * from "./webhook.service"; diff --git a/packages/services/src/developer/webhook.service.ts b/packages/services/src/developer/webhook.service.ts new file mode 100644 index 0000000000..e48da3430b --- /dev/null +++ b/packages/services/src/developer/webhook.service.ts @@ -0,0 +1,104 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IWebhook } from "@plane/types"; +import { APIService } from "../api.service"; + +/** + * Service class for managing webhooks + * Handles CRUD operations for webhooks and secret key management + * @extends {APIService} + */ +export default class WebhookService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves all webhooks for a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to array of webhooks + * @throws {Error} If the API request fails + */ + async list(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/webhooks/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves details of a specific webhook + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} webhookId - The unique identifier for the webhook + * @returns {Promise} Promise resolving to webhook details + * @throws {Error} If the API request fails + */ + async retrieve(workspaceSlug: string, webhookId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Creates a new webhook in the workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {Object} [data={}] - Webhook configuration data + * @returns {Promise} Promise resolving to the created webhook + * @throws {Error} If the API request fails + */ + async create(workspaceSlug: string, data = {}): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/webhooks/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates an existing webhook + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} webhookId - The unique identifier for the webhook + * @param {Object} [data={}] - Updated webhook configuration data + * @returns {Promise} Promise resolving to the updated webhook + * @throws {Error} If the API request fails + */ + async update(workspaceSlug: string, webhookId: string, data = {}): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Deletes a webhook from the workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} webhookId - The unique identifier for the webhook + * @returns {Promise} Promise resolving when webhook is deleted + * @throws {Error} If the API request fails + */ + async destroy(workspaceSlug: string, webhookId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Regenerates the secret key for a webhook + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} webhookId - The unique identifier for the webhook + * @returns {Promise} Promise resolving to the webhook with new secret key + * @throws {Error} If the API request fails + */ + async regenerateSecretKey(workspaceSlug: string, webhookId: string): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/webhooks/${webhookId}/regenerate/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/index.ts b/packages/services/src/index.ts new file mode 100644 index 0000000000..677aa28e7a --- /dev/null +++ b/packages/services/src/index.ts @@ -0,0 +1,12 @@ +export * from "./ai"; +export * from "./analytics"; +export * from "./developer"; +export * from "./auth"; +export * from "./cycle"; +export * from "./dashboard"; +export * from "./instance"; +export * from "./intake"; +export * from "./module"; +export * from "./user"; +export * from "./project"; +export * from "./workspace"; diff --git a/packages/services/src/instance/index.ts b/packages/services/src/instance/index.ts new file mode 100644 index 0000000000..dd3d4a663c --- /dev/null +++ b/packages/services/src/instance/index.ts @@ -0,0 +1 @@ +export * from "./instance.service"; diff --git a/packages/services/src/instance/instance.service.ts b/packages/services/src/instance/instance.service.ts new file mode 100644 index 0000000000..0ffe451fbc --- /dev/null +++ b/packages/services/src/instance/instance.service.ts @@ -0,0 +1,44 @@ +import { API_BASE_URL } from "@plane/constants"; +import type { IInstanceInfo, TPage } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing instance-related operations + * Handles retrieval of instance information and changelog + * @extends {APIService} + */ +export default class InstanceService extends APIService { + /** + * Creates an instance of InstanceService + * Initializes the service with the base API URL + */ + constructor() { + super(API_BASE_URL); + } + + /** + * Retrieves information about the current instance + * @returns {Promise} Promise resolving to instance information + * @throws {Error} If the API request fails + */ + async info(): Promise { + return this.get("/api/instances/") + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } + + /** + * Fetches the changelog for the current instance + * @returns {Promise} Promise resolving to the changelog page data + * @throws {Error} If the API request fails + */ + async changelog(): Promise { + return this.get("/api/instances/changelog/") + .then((response) => response.data) + .catch((error) => { + throw error; + }); + } +} diff --git a/packages/services/src/intake/index.ts b/packages/services/src/intake/index.ts new file mode 100644 index 0000000000..cde9ddbd34 --- /dev/null +++ b/packages/services/src/intake/index.ts @@ -0,0 +1,2 @@ +export * from "./intake.service"; +export * from "./issue.service"; diff --git a/packages/services/src/intake/intake.service.ts b/packages/services/src/intake/intake.service.ts new file mode 100644 index 0000000000..1f7f722956 --- /dev/null +++ b/packages/services/src/intake/intake.service.ts @@ -0,0 +1,8 @@ +import { API_BASE_URL } from "@plane/constants"; +import { APIService } from "@/api.service"; + +export default class IntakeService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } +} diff --git a/packages/services/src/intake/issue.service.ts b/packages/services/src/intake/issue.service.ts new file mode 100644 index 0000000000..37e1f81dca --- /dev/null +++ b/packages/services/src/intake/issue.service.ts @@ -0,0 +1,18 @@ +import { API_BASE_URL } from "@plane/constants"; +import { APIService } from "@/api.service"; + +export default class IntakeIssueService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + async list(workspaceSlug: string, projectId: string, params = {}) { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/inbox-issues/`, { + params, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/live.service.ts b/packages/services/src/live.service.ts new file mode 100644 index 0000000000..ae4b80864c --- /dev/null +++ b/packages/services/src/live.service.ts @@ -0,0 +1,8 @@ +import { API_BASE_URL } from "@plane/constants"; +import { APIService } from "./api.service"; + +export abstract class LiveService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } +} diff --git a/packages/services/src/module/index.ts b/packages/services/src/module/index.ts new file mode 100644 index 0000000000..8f08f92b83 --- /dev/null +++ b/packages/services/src/module/index.ts @@ -0,0 +1,3 @@ +export * from "./link.service"; +export * from "./module.service"; +export * from "./operations.service"; diff --git a/packages/services/src/module/link.service.ts b/packages/services/src/module/link.service.ts new file mode 100644 index 0000000000..0caee9e198 --- /dev/null +++ b/packages/services/src/module/link.service.ts @@ -0,0 +1,86 @@ +// types +import type { ILinkDetails, ModuleLink } from "@plane/types"; +// services +import { APIService } from "@/api.service"; + +/** + * Service class for handling module link related operations. + * Extends the base APIService class to interact with module link endpoints. + */ +export class ModuleLinkService extends APIService { + /** + * Creates an instance of ModuleLinkService. + * @param {string} baseURL - The base URL for the API endpoints + */ + constructor(baseURL: string) { + super(baseURL); + } + + /** + * Creates a new module link. + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} moduleId - The unique identifier for the module + * @param {Partial} data - The module link data to be created + * @returns {Promise} The created module link details + * @throws {Error} When the API request fails + */ + async create( + workspaceSlug: string, + projectId: string, + moduleId: string, + data: Partial + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Updates an existing module link. + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} moduleId - The unique identifier for the module + * @param {string} linkId - The unique identifier for the link to update + * @param {Partial} data - The module link data to be updated + * @returns {Promise} The updated module link details + * @throws {Error} When the API request fails + */ + async update( + workspaceSlug: string, + projectId: string, + moduleId: string, + linkId: string, + data: Partial + ): Promise { + return this.patch( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`, + data + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Deletes a module link. + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} projectId - The unique identifier for the project + * @param {string} moduleId - The unique identifier for the module + * @param {string} linkId - The unique identifier for the link to delete + * @returns {Promise} Response data from the server + * @throws {Error} When the API request fails + */ + async destroy(workspaceSlug: string, projectId: string, moduleId: string, linkId: string): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/module/module.service.ts b/packages/services/src/module/module.service.ts new file mode 100644 index 0000000000..1d1732aa91 --- /dev/null +++ b/packages/services/src/module/module.service.ts @@ -0,0 +1,212 @@ +// types +import type { IModule, ILinkDetails, ModuleLink, TIssuesResponse } from "@plane/types"; +// services +import { APIService } from "@/api.service"; + +export class ModuleService extends APIService { + constructor(baseURL: string) { + super(baseURL); + } + + async workspaceModulesList(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/modules/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async projectModulesList(workspaceSlug: string, projectId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async create(workspaceSlug: string, projectId: string, data: any): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async retrieve(workspaceSlug: string, projectId: string, moduleId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + // async update(workspaceSlug: string, projectId: string, moduleId: string, data: any): Promise { + // return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data) + // .then((response) => response?.data) + // .catch((error) => { + // throw error?.response?.data; + // }); + // } + + async update(workspaceSlug: string, projectId: string, moduleId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async destroy(workspaceSlug: string, projectId: string, moduleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async getModuleIssues( + workspaceSlug: string, + projectId: string, + moduleId: string, + queries?: any, + config = {} + ): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, + { + params: queries, + }, + config + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async addIssuesToModule( + workspaceSlug: string, + projectId: string, + moduleId: string, + data: { issues: string[] } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async addModulesToIssue( + workspaceSlug: string, + projectId: string, + issueId: string, + data: { modules: string[]; removed_modules?: string[] } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/modules/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async removeIssuesFromModuleBulk( + workspaceSlug: string, + projectId: string, + moduleId: string, + issueIds: string[] + ): Promise { + const promiseDataUrls: any = []; + issueIds.forEach((issueId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + + async removeModulesFromIssueBulk( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ): Promise { + const promiseDataUrls: any = []; + moduleIds.forEach((moduleId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + + async createModuleLink( + workspaceSlug: string, + projectId: string, + moduleId: string, + data: Partial + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async updateModuleLink( + workspaceSlug: string, + projectId: string, + moduleId: string, + linkId: string, + data: Partial + ): Promise { + return this.patch( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/`, + data + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async deleteModuleLink(workspaceSlug: string, projectId: string, moduleId: string, linkId: string): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/module-links/${linkId}/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async addModuleToFavorites( + workspaceSlug: string, + projectId: string, + data: { + module: string; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async removeModuleFromFavorites(workspaceSlug: string, projectId: string, moduleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/${moduleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/module/operations.service.ts b/packages/services/src/module/operations.service.ts new file mode 100644 index 0000000000..b8fddb37d5 --- /dev/null +++ b/packages/services/src/module/operations.service.ts @@ -0,0 +1,146 @@ +// types +// import type { IModule, ILinkDetails, ModuleLink, TIssuesResponse } from "@plane/types"; +// services +import { APIService } from "@/api.service"; + +export class ModuleOperationService extends APIService { + constructor(baseURL: string) { + super(baseURL); + } + + /** + * Add issues to a module + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {string} moduleId - The ID of the module + * @param {object} data - The data to be sent in the request body + * @param {string[]} data.issues - The IDs of the issues to be added + * @returns {Promise} + */ + async addIssuesToModule( + workspaceSlug: string, + projectId: string, + moduleId: string, + data: { issues: string[] } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Add modules to an issue + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {string} issueId - The ID of the issue + * @param {object} data - The data to be sent in the request body + * @param {string[]} data.modules - The IDs of the modules to be added + * @param {string[]} [data.removed_modules] - The IDs of the modules to be removed + * @returns {Promise} + */ + async addModulesToIssue( + workspaceSlug: string, + projectId: string, + issueId: string, + data: { modules: string[]; removed_modules?: string[] } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/modules/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Remove issues from a module + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {string} moduleId - The ID of the module + * @param {string[]} issueIds - The IDs of the issues to be removed + * @returns {Promise} + */ + async removeIssuesFromModuleBulk( + workspaceSlug: string, + projectId: string, + moduleId: string, + issueIds: string[] + ): Promise { + const promiseDataUrls: any = []; + issueIds.forEach((issueId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Remove modules from an issue + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {string} issueId - The ID of the issue + * @param {string[]} moduleIds - The IDs of the modules to be removed + * @returns {Promise} + */ + async removeModulesFromIssueBulk( + workspaceSlug: string, + projectId: string, + issueId: string, + moduleIds: string[] + ): Promise { + const promiseDataUrls: any = []; + moduleIds.forEach((moduleId) => { + promiseDataUrls.push( + this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/issues/${issueId}/`) + ); + }); + await Promise.all(promiseDataUrls) + .then((response) => response) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Add a module to favorites + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {object} data - The data to be sent in the request body + * @param {string} data.module - The ID of the module to be added + * @returns {Promise} + */ + async addModuleToFavorites( + workspaceSlug: string, + projectId: string, + data: { + module: string; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Remove a module from favorites + * @param {string} workspaceSlug - The slug of the workspace + * @param {string} projectId - The ID of the project + * @param {string} moduleId - The ID of the module to be removed + * @returns {Promise} + */ + async removeModuleFromFavorites(workspaceSlug: string, projectId: string, moduleId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-modules/${moduleId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/project/index.ts b/packages/services/src/project/index.ts new file mode 100644 index 0000000000..6ec55d7f7a --- /dev/null +++ b/packages/services/src/project/index.ts @@ -0,0 +1 @@ +export * from "./view.service"; diff --git a/packages/services/src/project/view.service.ts b/packages/services/src/project/view.service.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/services/src/user/favorite.service.ts b/packages/services/src/user/favorite.service.ts new file mode 100644 index 0000000000..7e838a3c6c --- /dev/null +++ b/packages/services/src/user/favorite.service.ts @@ -0,0 +1,94 @@ +import { API_BASE_URL } from "@plane/constants"; +import type { IFavorite } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing user favorites + * Handles operations for adding, updating, removing, and retrieving user favorites within a workspace + * @extends {APIService} + */ +export class UserFavoriteService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Adds a new item to user favorites + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {Partial} data - Favorite item data to be added + * @returns {Promise} Promise resolving to the created favorite item + * @throws {Error} If the API request fails + */ + async add(workspaceSlug: string, data: Partial): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/user-favorites/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Updates an existing favorite item + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} favoriteId - The unique identifier for the favorite item + * @param {Partial} data - Updated favorite item data + * @returns {Promise} Promise resolving to the updated favorite item + * @throws {Error} If the API request fails + */ + async update(workspaceSlug: string, favoriteId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/user-favorites/${favoriteId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Removes an item from user favorites + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} favoriteId - The unique identifier for the favorite item to remove + * @returns {Promise} Promise resolving when the favorite item is removed + * @throws {Error} If the API request fails + */ + async remove(workspaceSlug: string, favoriteId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/user-favorites/${favoriteId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Retrieves all favorite items for a user in a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to array of favorite items + * @throws {Error} If the API request fails + * @remarks This method includes the 'all' parameter to retrieve all favorites + */ + async list(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/user-favorites/`, { + params: { + all: true, + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves grouped favorite items for a specific favorite in a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} favoriteId - The unique identifier for the favorite item to get grouped items for + * @returns {Promise} Promise resolving to array of grouped favorite items + * @throws {Error} If the API request fails + */ + async groupedList(workspaceSlug: string, favoriteId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/user-favorites/${favoriteId}/group/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/user/index.ts b/packages/services/src/user/index.ts new file mode 100644 index 0000000000..41df23a178 --- /dev/null +++ b/packages/services/src/user/index.ts @@ -0,0 +1 @@ +export * from "./favorite.service"; diff --git a/packages/services/src/workspace/index.ts b/packages/services/src/workspace/index.ts new file mode 100644 index 0000000000..4076a51f52 --- /dev/null +++ b/packages/services/src/workspace/index.ts @@ -0,0 +1,5 @@ +export * from "./invitation.service"; +export * from "./member.service"; +export * from "./notification.service"; +export * from "./view.service"; +export * from "./workspace.service"; diff --git a/packages/services/src/workspace/invitation.service.ts b/packages/services/src/workspace/invitation.service.ts new file mode 100644 index 0000000000..fa34460202 --- /dev/null +++ b/packages/services/src/workspace/invitation.service.ts @@ -0,0 +1,117 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData, IWorkspaceMember } from "@plane/types"; +import { APIService } from "@/api.service"; + +/** + * Service class for managing workspace invitations + * Handles operations related to inviting users to workspaces and managing invitations + * @extends {APIService} + */ +export class WorkspaceInvitationService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves all workspace invitations for the current user + * @returns {Promise} Promise resolving to array of workspace invitations + * @throws {Error} If the API request fails + */ + async userInvitations(): Promise { + return this.get("/api/users/me/workspaces/invitations/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves all invitations for a specific workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to array of workspace invitations + * @throws {Error} If the API request fails + */ + async workspaceInvitations(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/invitations/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Sends bulk invitations to users for a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {IWorkspaceBulkInviteFormData} data - Bulk invitation data containing user information + * @returns {Promise} Promise resolving to the invitation response + * @throws {Error} If the API request fails + */ + async invite(workspaceSlug: string, data: IWorkspaceBulkInviteFormData): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/invitations/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Update Invitation + * @param workspaceSlug + * @param invitationId + * @param data + * @returns + */ + async update(workspaceSlug: string, invitationId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Delete Workspace invitation + * @param workspaceSlug + * @param invitationId + * @returns + */ + async destroy(workspaceSlug: string, invitationId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Accepts an invitation to join a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} invitationId - The unique identifier for the invitation + * @param {any} data - Additional data required for joining the workspace + * @returns {Promise} Promise resolving to the join response + * @throws {Error} If the API request fails + */ + async join(workspaceSlug: string, invitationId: string, data: any): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/invitations/${invitationId}/join/`, data, { + headers: {}, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Accepts multiple workspace invitations at once + * @param {any} data - Data containing information about invitations to accept + * @returns {Promise} Promise resolving to the bulk join response + * @throws {Error} If the API request fails + */ + async joinMany(data: any): Promise { + return this.post("/api/users/me/workspaces/invitations/", data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/workspace/member.service.ts b/packages/services/src/workspace/member.service.ts new file mode 100644 index 0000000000..e92225af38 --- /dev/null +++ b/packages/services/src/workspace/member.service.ts @@ -0,0 +1,92 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IWorkspaceMemberMe, IWorkspaceMember, IUserProjectsRole } from "@plane/types"; +import { APIService } from "../api.service"; + +/** + * Service class for managing workspace members + * Handles operations related to workspace membership, including member information, + * updates, deletions, and role management + * @extends {APIService} + */ +export class WorkspaceMemberService extends APIService { + /** + * Creates an instance of WorkspaceMemberService + * @param {string} baseUrl - The base URL for API requests + */ + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves current user's information for a specific workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to current user's workspace member information + * @throws {Error} If the API request fails + */ + async myInfo(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Retrieves all members of a specific workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to array of workspace members + * @throws {Error} If the API request fails + */ + async list(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/members/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates a workspace member's information + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} memberId - The unique identifier for the member + * @param {Partial} data - Updated member data + * @returns {Promise} Promise resolving to the updated member information + * @throws {Error} If the API request fails + */ + async update(workspaceSlug: string, memberId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/members/${memberId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Removes a member from a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {string} memberId - The unique identifier for the member to remove + * @returns {Promise} Promise resolving to the deletion response + * @throws {Error} If the API request fails + */ + async destroy(workspaceSlug: string, memberId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/members/${memberId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves the current user's project roles within a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to user's project roles + * @throws {Error} If the API request fails + */ + async getWorkspaceUserProjectsRole(workspaceSlug: string): Promise { + return this.get(`/api/users/me/workspaces/${workspaceSlug}/project-roles/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/workspace/notification.service.ts b/packages/services/src/workspace/notification.service.ts new file mode 100644 index 0000000000..a21f632222 --- /dev/null +++ b/packages/services/src/workspace/notification.service.ts @@ -0,0 +1,137 @@ +import { API_BASE_URL } from "@plane/constants"; +import { + TUnreadNotificationsCount, + TNotificationPaginatedInfo, + TNotification, + TNotificationPaginatedInfoQueryParams, +} from "@plane/types"; +// services +import { APIService } from "../api.service"; + +export class WorkspaceNotificationService extends APIService { + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + /** + * Retrieves the count of unread notifications for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @returns {Promise} The count of unread notifications + */ + async getUnreadCount(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/users/notifications/unread/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves paginated notifications for a workspace + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {TNotificationPaginatedInfoQueryParams} params - Query parameters for pagination and filtering + * @returns {Promise} Paginated list of notifications + */ + async list( + workspaceSlug: string, + params: TNotificationPaginatedInfoQueryParams + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/users/notifications`, { params }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates a specific notification by ID + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} notificationId - The unique identifier for the notification + * @param {Partial} data - The notification data to update + * @returns {Promise} The updated notification + */ + async update( + workspaceSlug: string, + notificationId: string, + data: Partial + ): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Marks a notification as read + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} notificationId - The unique identifier for the notification + * @returns {Promise} The updated notification + */ + async markAsRead(workspaceSlug: string, notificationId: string): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/read/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Marks a notification as unread + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} notificationId - The unique identifier for the notification + * @returns {Promise} The updated notification + */ + async markAsUnread(workspaceSlug: string, notificationId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/read/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Archives a notification + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} notificationId - The unique identifier for the notification + * @returns {Promise} The updated notification + */ + async archive(workspaceSlug: string, notificationId: string): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/archive/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Unarchives a notification + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {string} notificationId - The unique identifier for the notification + * @returns {Promise} The updated notification + */ + async unarchive(workspaceSlug: string, notificationId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/users/notifications/${notificationId}/archive/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Marks all notifications as read based on filter criteria + * @param {string} workspaceSlug - The unique identifier for the workspace + * @param {TNotificationPaginatedInfoQueryParams} data - Filter criteria for notifications to mark as read + * @returns {Promise} The result of the operation + */ + async markAllAsRead( + workspaceSlug: string, + data: TNotificationPaginatedInfoQueryParams + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/users/notifications/mark-all-read/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/workspace/view.service.ts b/packages/services/src/workspace/view.service.ts new file mode 100644 index 0000000000..ca782d4335 --- /dev/null +++ b/packages/services/src/workspace/view.service.ts @@ -0,0 +1,67 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IWorkspaceView, TIssuesResponse } from "@plane/types"; +import { APIService } from "@/api.service"; + +export class WorkspaceViewService extends APIService { + /** + * Creates an instance of WorkspaceViewService + * @param {string} baseUrl - The base URL for API requests + */ + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + + async create(workspaceSlug: string, data: Partial): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/views/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async update(workspaceSlug: string, viewId: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/views/${viewId}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async destroy(workspaceSlug: string, viewId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async list(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/views/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async retrieve(workspaceSlug: string, viewId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/views/${viewId}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async getViewIssues(workspaceSlug: string, params: any, config = {}): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/issues/`, + { + params, + }, + config + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/src/workspace/workspace.service.ts b/packages/services/src/workspace/workspace.service.ts new file mode 100644 index 0000000000..72ef9fcefd --- /dev/null +++ b/packages/services/src/workspace/workspace.service.ts @@ -0,0 +1,141 @@ +import { API_BASE_URL } from "@plane/constants"; +import { IWorkspace, ILastActiveWorkspaceDetails, IWorkspaceSearchResults } from "@plane/types"; +import { APIService } from "../api.service"; + +/** + * Service class for managing workspace operations + * Handles CRUD operations and various workspace-related functionalities + * @extends {APIService} + */ +export class WorkspaceService extends APIService { + /** + * Creates an instance of WorkspaceService + * @param {string} baseUrl - The base URL for API requests + */ + constructor(BASE_URL?: string) { + super(BASE_URL || API_BASE_URL); + } + /** + * Retrieves all workspaces for the current user + * @returns {Promise} Promise resolving to an array of workspaces + * @throws {Error} If the API request fails + */ + async list(): Promise { + return this.get("/api/users/me/workspaces/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves details of a specific workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to workspace details + * @throws {Error} If the API request fails + */ + async retrieve(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + /** + * Creates a new workspace + * @param {Partial} data - Workspace data for creation + * @returns {Promise} Promise resolving to the created workspace + * @throws {Error} If the API request fails + */ + async create(data: Partial): Promise { + return this.post("/api/workspaces/", data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Updates an existing workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {Partial} data - Updated workspace data + * @returns {Promise} Promise resolving to the updated workspace + * @throws {Error} If the API request fails + */ + async update(workspaceSlug: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Deletes a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @returns {Promise} Promise resolving to the deletion response + * @throws {Error} If the API request fails + */ + async destroy(workspaceSlug: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Retrieves information about the user's last visited workspace + * @returns {Promise} Promise resolving to last active workspace details + * @throws {Error} If the API request fails + */ + async lastVisited(): Promise { + return this.get("/api/users/last-visited-workspace/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Checks if a workspace slug is available + * @param {string} slug - The workspace slug to check + * @returns {Promise} Promise resolving to slug availability status + * @throws {Error} If the API request fails + */ + async slugCheck(slug: string): Promise { + return this.get(`/api/workspace-slug-check/?slug=${slug}`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + /** + * Searches within a workspace + * @param {string} workspaceSlug - The unique slug identifier for the workspace + * @param {Object} params - Search parameters + * @param {string} [params.project_id] - Optional project ID to scope the search + * @param {string} params.search - Search query string + * @param {boolean} params.workspace_search - Whether to search across the entire workspace + * @returns {Promise} Promise resolving to search results + * @throws {Error} If the API request fails + */ + async search( + workspaceSlug: string, + params: { + project_id?: string; + search: string; + workspace_search: boolean; + } + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/search/`, { + params, + }) + .then((res) => res?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/packages/services/tsconfig.json b/packages/services/tsconfig.json new file mode 100644 index 0000000000..0c2f64d1a8 --- /dev/null +++ b/packages/services/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@plane/typescript-config/react-library.json", + "compilerOptions": { + "jsx": "react", + "lib": ["esnext", "dom"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["./src"], + "exclude": ["dist", "build", "node_modules"] +}