diff --git a/packages/api-client/jest.config.ts b/packages/api-client/jest.config.ts index 586ee443..8d90756d 100644 --- a/packages/api-client/jest.config.ts +++ b/packages/api-client/jest.config.ts @@ -13,7 +13,7 @@ const config: Config = { preset: 'ts-jest', testEnvironment: 'node', moduleNameMapper: { - '^@package/(.*)$': '/src/$1' + '^@api-client/(.*)$': '/src/$1' } } diff --git a/packages/api-client/src/controllers/environment.ts b/packages/api-client/src/controllers/environment.ts index 56c70a7f..709084ae 100644 --- a/packages/api-client/src/controllers/environment.ts +++ b/packages/api-client/src/controllers/environment.ts @@ -1,6 +1,5 @@ -import { ClientResponse } from '../types/index.types' -import { APIClient } from '../core/client' -import { parseResponse } from '../core/response-parser' +import { APIClient } from '@api-client/core/client' +import { parseResponse } from '@api-client/core/response-parser' import { CreateEnvironmentRequest, CreateEnvironmentResponse, @@ -12,7 +11,8 @@ import { GetEnvironmentByIdResponse, UpdateEnvironmentRequest, UpdateEnvironmentResponse -} from '../types/environment.types' +} from '@api-client/types/environment.types' +import { ClientResponse } from '@api-client/types/index.types' export default class EnvironmentController { private apiClient: APIClient diff --git a/packages/api-client/src/controllers/event.ts b/packages/api-client/src/controllers/event.ts index f35e5a3d..f14fd5cd 100644 --- a/packages/api-client/src/controllers/event.ts +++ b/packages/api-client/src/controllers/event.ts @@ -1,7 +1,10 @@ -import { GetEventsRequest, GetEventsResponse } from '../types/event.types' +import { + GetEventsRequest, + GetEventsResponse +} from '@api-client/types/event.types' import { APIClient } from '../core/client' -import { ClientResponse } from '../types/index.types' -import { parseResponse } from '../core/response-parser' +import { ClientResponse } from '@api-client/types/index.types' +import { parseResponse } from '@api-client/core/response-parser' export default class EventController { private apiClient: APIClient diff --git a/packages/api-client/src/controllers/integration.ts b/packages/api-client/src/controllers/integration.ts index d0c128c9..bd08279e 100644 --- a/packages/api-client/src/controllers/integration.ts +++ b/packages/api-client/src/controllers/integration.ts @@ -9,10 +9,10 @@ import { GetIntegrationResponse, UpdateIntegrationRequest, UpdateIntegrationResponse -} from '../types/integration.types' -import { APIClient } from '../core/client' -import { ClientResponse } from '../types/index.types' -import { parseResponse } from '../core/response-parser' +} from '@api-client/types/integration.types' +import { APIClient } from '@api-client/core/client' +import { ClientResponse } from '@api-client/types/index.types' +import { parseResponse } from '@api-client/core/response-parser' export default class IntegrationController { private apiClient: APIClient diff --git a/packages/api-client/src/controllers/project.ts b/packages/api-client/src/controllers/project.ts new file mode 100644 index 00000000..1d4ed26a --- /dev/null +++ b/packages/api-client/src/controllers/project.ts @@ -0,0 +1,149 @@ +import { ClientResponse } from '@api-client/types/index.types' +import { APIClient } from '@api-client/core/client' +import { + CreateProjectRequest, + CreateProjectResponse, + DeleteProjectRequest, + DeleteProjectResponse, + ForkProjectRequest, + ForkProjectResponse, + GetAllProjectsRequest, + GetAllProjectsResponse, + GetForkRequest, + GetForkResponse, + GetProjectRequest, + GetProjectResponse, + SyncProjectRequest, + SyncProjectResponse, + UnlinkProjectRequest, + UnlinkProjectResponse, + UpdateProjectRequest, + UpdateProjectResponse +} from '@api-client/types/project.types' +import { parseResponse } from '@api-client/core/response-parser' + +export default class ProjectController { + private apiClient: APIClient + + constructor(private readonly backendUrl: string) { + this.apiClient = new APIClient(this.backendUrl) + } + + async createProject( + request: CreateProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.post( + `/api/project/${request.workspaceId}`, + request, + headers + ) + + return await parseResponse(response) + } + + async updateProject( + request: UpdateProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.put( + `/api/project/${request.projectId}`, + request, + headers + ) + + return await parseResponse(response) + } + + async deleteProject( + request: DeleteProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.delete( + `/api/project/${request.projectId}`, + headers + ) + + return await parseResponse(response) + } + + async getProject( + request: GetProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.get( + `/api/project/${request.projectId}`, + headers + ) + + return await parseResponse(response) + } + + async forkProject( + request: ForkProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.post( + `/api/project/${request.projectId}/fork`, + request, + headers + ) + + return await parseResponse(response) + } + + async syncFork( + request: SyncProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.put( + `/project/${request.projectId}/fork`, + request, + headers + ) + + return await parseResponse(response) + } + + async unlinkFork( + request: UnlinkProjectRequest, + headers: Record + ): Promise> { + const response = await this.apiClient.delete( + `/api/project/${request.projectId}/fork`, + headers + ) + + return await parseResponse(response) + } + + async getForks( + request: GetForkRequest, + headers: Record + ): Promise> { + let url = `/api/project/${request.projectId}/forks` + request.page && (url += `page=${request.page}&`) + request.limit && (url += `limit=${request.limit}&`) + request.sort && (url += `sort=${request.sort}&`) + request.order && (url += `order=${request.order}&`) + request.search && (url += `search=${request.search}&`) + const response = await this.apiClient.get(url, headers) + + return await parseResponse(response) + } + + async getAllProjects( + request: GetAllProjectsRequest, + headers: Record + ): Promise> { + let url = `/api/project/all/${request.workspaceId}` + request.page && (url += `page=${request.page}&`) + request.limit && (url += `limit=${request.limit}&`) + request.sort && (url += `sort=${request.sort}&`) + request.order && (url += `order=${request.order}&`) + request.search && (url += `search=${request.search}&`) + const response = await this.apiClient.get(url, headers) + + return await parseResponse(response) + } +} diff --git a/packages/api-client/src/controllers/secret.ts b/packages/api-client/src/controllers/secret.ts index 0cf22058..82b8802a 100644 --- a/packages/api-client/src/controllers/secret.ts +++ b/packages/api-client/src/controllers/secret.ts @@ -1,6 +1,6 @@ -import { APIClient } from '../core/client' -import { ClientResponse } from '../types/index.types' -import { parseResponse } from '../core/response-parser' +import { APIClient } from '@api-client/core/client' +import { ClientResponse } from '@api-client/types/index.types' +import { parseResponse } from '@api-client/core/response-parser' import { CreateSecretRequest, CreateSecretResponse, @@ -14,7 +14,7 @@ import { RollBackSecretResponse, UpdateSecretRequest, UpdateSecretResponse -} from '../types/secret.types' +} from '@api-client/types/secret.types' export default class SecretController { private apiClient: APIClient diff --git a/packages/api-client/src/controllers/variable.ts b/packages/api-client/src/controllers/variable.ts index 65a83404..f3972a0f 100644 --- a/packages/api-client/src/controllers/variable.ts +++ b/packages/api-client/src/controllers/variable.ts @@ -1,6 +1,6 @@ -import { APIClient } from '../core/client' -import { parseResponse } from '../core/response-parser' -import { ClientResponse } from '../types/index.types' +import { APIClient } from '@api-client/core/client' +import { parseResponse } from '@api-client/core/response-parser' +import { ClientResponse } from '@api-client/types/index.types' import { CreateVariableRequest, CreateVariableResponse, @@ -14,7 +14,7 @@ import { RollBackVariableResponse, UpdateVariableRequest, UpdateVariableResponse -} from '../types/variable.types' +} from '@api-client/types/variable.types' export default class VariableController { private apiClient: APIClient diff --git a/packages/api-client/src/core/response-parser.ts b/packages/api-client/src/core/response-parser.ts index ce17508d..e44f3d01 100644 --- a/packages/api-client/src/core/response-parser.ts +++ b/packages/api-client/src/core/response-parser.ts @@ -1,4 +1,4 @@ -import { ClientResponse, ResponseError } from '../types/index.types' +import { ClientResponse, ResponseError } from '@api-client/types/index.types' export async function parseResponse( response: Response diff --git a/packages/api-client/src/types/environment.types.d.ts b/packages/api-client/src/types/environment.types.d.ts index ed140704..05540791 100644 --- a/packages/api-client/src/types/environment.types.d.ts +++ b/packages/api-client/src/types/environment.types.d.ts @@ -1,4 +1,4 @@ -import { Page } from '../../../../apps/cli/src/types/index.types' +import { Page } from './index.types' export interface CreateEnvironmentRequest { name: string diff --git a/packages/api-client/src/types/event.types.d.ts b/packages/api-client/src/types/event.types.d.ts index 30eee490..d893dece 100644 --- a/packages/api-client/src/types/event.types.d.ts +++ b/packages/api-client/src/types/event.types.d.ts @@ -1,4 +1,4 @@ -import { Page } from '../../../../apps/cli/src/types/index.types' +import { Page } from './index.types' export enum EventSource { SECRET, diff --git a/packages/api-client/src/types/integration.types.d.ts b/packages/api-client/src/types/integration.types.d.ts index 3c225ccf..e60d1619 100644 --- a/packages/api-client/src/types/integration.types.d.ts +++ b/packages/api-client/src/types/integration.types.d.ts @@ -1,4 +1,5 @@ -import { Page } from '../../../../apps/cli/src/types/index.types' +import { Page } from './index.types' + export enum IntegrationType { DISCORD, SLACK, diff --git a/packages/api-client/src/types/project.types.d.ts b/packages/api-client/src/types/project.types.d.ts new file mode 100644 index 00000000..dd72be53 --- /dev/null +++ b/packages/api-client/src/types/project.types.d.ts @@ -0,0 +1,161 @@ +import { Page } from './index.types' + +export interface CreateProjectRequest { + name: string + workspaceId: string + description?: string + storePrivateKey?: boolean + environments?: CreateEnvironment[] + accessLevel: string +} + +export interface CreateProjectResponse { + id: string + name: string + description: string + createdAt: string + updatedAt: string + publicKey: string + privateKey: string + storePrivateKey: boolean + isDisabled: boolean + accessLevel: string + pendingCreation: boolean + isForked: boolean + lastUpdatedById: string + workspaceId: string + forkedFromId: string +} + +export interface UpdateProjectRequest { + projectId: string + name?: string +} + +export interface UpdateProjectResponse { + id: string + name: string + description: string + createdAt: string + updatedAt: string + publicKey: string + privateKey: string + storePrivateKey: boolean + isDisabled: boolean + accessLevel: string + pendingCreation: boolean + isForked: boolean + lastUpdatedById: string + workspaceId: string + forkedFromId: string +} + +export interface DeleteProjectRequest { + projectId: string +} + +export interface DeleteProjectResponse {} + +export interface GetProjectRequest { + projectId: string +} + +export interface GetProjectResponse { + id: string + name: string + description: string + createdAt: string + updatedAt: string + publicKey: string + privateKey: string + storePrivateKey: boolean + isDisabled: boolean + accessLevel: string + pendingCreation: boolean + isForked: boolean + lastUpdatedById: string + workspaceId: string + forkedFromId: string +} + +export interface ForkProjectRequest { + projectId: string + name?: string + workspaceId?: string + storePrivateKey?: boolean +} + +export interface ForkProjectResponse { + id: string + name: string + description: string + createdAt: string + updatedAt: string + publicKey: string + privateKey: string + storePrivateKey: boolean + isDisabled: boolean + accessLevel: string + pendingCreation: boolean + isForked: boolean + lastUpdatedById: string + workspaceId: string + forkedFromId: string +} + +export interface SyncProjectRequest { + projectId: string +} + +export interface SyncProjectResponse {} + +export interface UnlinkProjectRequest { + projectId: string + workspaceId: string +} + +export interface UnlinkProjectResponse {} + +export interface GetForkRequest { + projectId: string + workspaceId: string + page?: number + limit?: number + sort?: string + order?: string + search?: string +} + +export interface GetForkResponse + extends Page<{ + id: string + name: string + description: string + createdAt: string + updatedAt: string + publicKey: string + privateKey: string + storePrivateKey: boolean + isDisabled: boolean + accessLevel: string + pendingCreation: boolean + isForked: boolean + lastUpdatedById: string + workspaceId: string + forkedFromId: string + }> {} + +export interface GetAllProjectsRequest { + workspaceId: string + page?: number + limit?: number + sort?: string + order?: string + search?: string +} + +export interface GetAllProjectsResponse + extends Page<{ + id: string + name: string + }> {} diff --git a/packages/api-client/src/types/secret.types.d.ts b/packages/api-client/src/types/secret.types.d.ts index 20f00fad..12336787 100644 --- a/packages/api-client/src/types/secret.types.d.ts +++ b/packages/api-client/src/types/secret.types.d.ts @@ -1,4 +1,4 @@ -import { Page } from '../../../../apps/cli/src/types/index.types' +import { Page } from './index.types' export interface CreateSecretRequest { projectId: string @@ -119,9 +119,8 @@ export interface GetAllSecretsOfEnvironmentRequest { order?: string search?: string } -export interface GetAllSecretsOfEnvironmentResponse - extends Page<{ - name: string - value: string - isPlaintext: boolean - }> {} +export interface GetAllSecretsOfEnvironmentResponse { + name: string + value: string + isPlaintext: boolean +} diff --git a/packages/api-client/src/types/variable.types.d.ts b/packages/api-client/src/types/variable.types.d.ts index a6b97091..a7b51e5f 100644 --- a/packages/api-client/src/types/variable.types.d.ts +++ b/packages/api-client/src/types/variable.types.d.ts @@ -1,4 +1,4 @@ -import { Page } from '../../../../apps/cli/src/types/index.types' +import { Page } from './index.types' export interface CreateVariableRequest { projectId: string diff --git a/packages/api-client/tests/environment.spec.ts b/packages/api-client/tests/environment.spec.ts index b3b3875e..073aad92 100644 --- a/packages/api-client/tests/environment.spec.ts +++ b/packages/api-client/tests/environment.spec.ts @@ -1,5 +1,5 @@ -import { APIClient } from '../src/core/client' -import EnvironmentController from '../src/controllers/environment' +import { APIClient } from '@api-client/core/client' +import EnvironmentController from '@api-client/controllers/environment' describe('Environments Controller Tests', () => { const backendUrl = process.env.BACKEND_URL diff --git a/packages/api-client/tests/project.spec.ts b/packages/api-client/tests/project.spec.ts new file mode 100644 index 00000000..e09e873f --- /dev/null +++ b/packages/api-client/tests/project.spec.ts @@ -0,0 +1,263 @@ +import { APIClient } from '../src/core/client' +import ProjectController from '@api-client/controllers/project' + +describe('Get Project Tests', () => { + const backendUrl = process.env.BACKEND_URL + + const client = new APIClient(backendUrl) + const projectController = new ProjectController(backendUrl) + + const email = 'johndoe@example.com' + let projectId: string | null + let workspaceId: string | null + + beforeAll(async () => { + //Create the user's workspace + const workspaceResponse = (await ( + await client.post( + '/api/workspace', + { + name: 'My Workspace' + }, + { + 'x-e2e-user-email': email + } + ) + ).json()) as any + + workspaceId = workspaceResponse.id + }) + + afterAll(async () => { + // Delete the workspace + await client.delete(`/api/workspace/${workspaceId}`, { + 'x-e2e-user-email': email + }) + }) + + beforeEach(async () => { + // Create a project + const project = ( + await projectController.createProject( + { + name: 'Project', + description: 'Project Description', + storePrivateKey: true, + workspaceId, + accessLevel: 'GLOBAL' + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + projectId = project.id + }) + + afterEach(async () => { + // Delete all projects + await client.delete(`/api/project/${projectId}`, { + 'x-e2e-user-email': email + }) + }) + + it('should create a project', async () => { + const project = ( + await projectController.createProject( + { + name: 'Project 2', + description: 'Project Description', + storePrivateKey: true, + workspaceId, + accessLevel: 'GLOBAL' + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + expect(project.id).toBeDefined() + expect(project.name).toBe('Project 2') + expect(project.description).toBe('Project Description') + expect(project.storePrivateKey).toBe(true) + expect(project.workspaceId).toBe(workspaceId) + expect(project.publicKey).toBeDefined() + expect(project.privateKey).toBeDefined() + expect(project.createdAt).toBeDefined() + expect(project.updatedAt).toBeDefined() + + // Delete the project + await projectController.deleteProject( + { + projectId: project.id + }, + { + 'x-e2e-user-email': email + } + ) + }) + + it('should be able to get a project by ID', async () => { + const project = ( + await projectController.getProject( + { + projectId + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + expect(project.id).toBe(projectId) + expect(project.name).toBe('Project') + }) + + it('should fork the project', async () => { + const fork = ( + await projectController.forkProject( + { + projectId, + workspaceId, + name: 'Forked Stuff' + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + expect(fork.isForked).toBe(true) + expect(fork.forkedFromId).toBe(projectId) + + // Delete the fork + await projectController.deleteProject( + { + projectId: fork.id + }, + { + 'x-e2e-user-email': email + } + ) + }) + + it('should be able to get all forks of the project', async () => { + // Create a fork of the project + const fork = ( + await projectController.forkProject( + { + projectId, + workspaceId, + name: 'Forked Stuff' + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + const forks = await projectController.getForks( + { + projectId, + workspaceId + }, + { + 'x-e2e-user-email': email + } + ) + expect(forks.data.items).toHaveLength(1) + + // Delete the fork + await projectController.deleteProject( + { + projectId: fork.id + }, + { + 'x-e2e-user-email': email + } + ) + }) + + it('should unlink fork the project', async () => { + // Create a fork of the project + const fork = ( + await projectController.forkProject( + { + projectId, + workspaceId, + name: 'Forked Stuff' + }, + { + 'x-e2e-user-email': email + } + ) + ).data + + // Unlink the fork + await projectController.unlinkFork( + { + projectId: fork.id, + workspaceId + }, + { + 'x-e2e-user-email': email + } + ) + + const forks = await projectController.getForks( + { + projectId, + workspaceId + }, + { + 'x-e2e-user-email': email + } + ) + expect(forks.data.items).toHaveLength(0) + + // Delete the fork + await projectController.deleteProject( + { + projectId: fork.id + }, + { + 'x-e2e-user-email': email + } + ) + }) + + it('should get all projects in the workspace', async () => { + const projects = await projectController.getAllProjects( + { + workspaceId + }, + { + 'x-e2e-user-email': email + } + ) + expect(projects.data.items).toHaveLength(1) + }) + + it('should get delete a the project', async () => { + await projectController.deleteProject( + { + projectId + }, + { + 'x-e2e-user-email': email + } + ) + + const projects = await projectController.getAllProjects( + { + workspaceId + }, + { + 'x-e2e-user-email': email + } + ) + expect(projects.data.items).toHaveLength(0) + }) +}) diff --git a/packages/api-client/tsconfig.json b/packages/api-client/tsconfig.json index c2ce8a99..75d6ba4e 100644 --- a/packages/api-client/tsconfig.json +++ b/packages/api-client/tsconfig.json @@ -1,22 +1,11 @@ { + "extends": "../tsconfig/base.json", "compilerOptions": { - "module": "commonjs", - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "inlineSourceMap": true, "outDir": "./dist", "baseUrl": "./", - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true + "paths": { + "@api-client/*": ["src/*"] + } }, "include": ["src/**/*.ts", "tests/**/*.ts", "jest.config.ts"], "exclude": ["node_modules", "dist"] diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json index a59a7db6..78c3d884 100644 --- a/packages/tsconfig/base.json +++ b/packages/tsconfig/base.json @@ -1,21 +1,32 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "display": "Default", - "compilerOptions": { - "composite": false, - "declaration": true, - "declarationMap": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "inlineSources": false, - "isolatedModules": true, - "moduleResolution": "node", - "noUnusedLocals": false, - "noUnusedParameters": false, - "preserveWatchOutput": true, - "skipLibCheck": true, - "strict": true, - "strictNullChecks": true - }, - "exclude": ["node_modules"] - } \ No newline at end of file + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "node", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": false, + "module": "ES2022", + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2022", + "inlineSourceMap": true, + "incremental": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": true + }, + "exclude": ["node_modules"] +}