@shipfox/api-projects 11.0.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +39 -0
- package/README.md +83 -0
- package/dist/core/entities/project.d.ts +4 -0
- package/dist/core/entities/project.d.ts.map +1 -1
- package/dist/core/entities/project.js.map +1 -1
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/projects.d.ts +8 -0
- package/dist/core/projects.d.ts.map +1 -1
- package/dist/core/projects.js +62 -19
- package/dist/core/projects.js.map +1 -1
- package/dist/db/db.d.ts +138 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/projects.d.ts +53 -0
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/projects.js +116 -18
- package/dist/db/projects.js.map +1 -1
- package/dist/db/schema/projects.d.ts +68 -0
- package/dist/db/schema/projects.d.ts.map +1 -1
- package/dist/db/schema/projects.js +12 -1
- package/dist/db/schema/projects.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/presentation/dto/project.d.ts +1 -0
- package/dist/presentation/dto/project.d.ts.map +1 -1
- package/dist/presentation/dto/project.js +1 -0
- package/dist/presentation/dto/project.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-project.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-project.js +34 -8
- package/dist/presentation/e2eRoutes/create-project.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +44 -3
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/create-project.d.ts.map +1 -1
- package/dist/presentation/routes/create-project.js +8 -2
- package/dist/presentation/routes/create-project.js.map +1 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +3 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/update-project.d.ts +2 -0
- package/dist/presentation/routes/update-project.d.ts.map +1 -0
- package/dist/presentation/routes/update-project.js +53 -0
- package/dist/presentation/routes/update-project.js.map +1 -0
- package/dist/presentation/subscribers/index.d.ts +1 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +1 -0
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/presentation/subscribers/on-source-repository-updated.d.ts +4 -0
- package/dist/presentation/subscribers/on-source-repository-updated.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-source-repository-updated.js +39 -0
- package/dist/presentation/subscribers/on-source-repository-updated.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +6 -0
- package/drizzle/meta/0000_snapshot.json +78 -0
- package/package.json +13 -12
- package/src/core/entities/project.ts +4 -0
- package/src/core/errors.ts +7 -0
- package/src/core/index.ts +3 -2
- package/src/core/projects.test.ts +10 -0
- package/src/core/projects.ts +90 -35
- package/src/db/db.ts +3 -0
- package/src/db/index.ts +6 -0
- package/src/db/projects.test.ts +44 -1
- package/src/db/projects.ts +242 -33
- package/src/db/schema/projects.ts +16 -0
- package/src/index.ts +9 -3
- package/src/presentation/dto/project.ts +1 -0
- package/src/presentation/e2eRoutes/create-project.test.ts +23 -0
- package/src/presentation/e2eRoutes/create-project.ts +39 -10
- package/src/presentation/inter-module.test.ts +309 -0
- package/src/presentation/inter-module.ts +76 -4
- package/src/presentation/routes/create-project.ts +10 -2
- package/src/presentation/routes/index.ts +7 -1
- package/src/presentation/routes/projects.test.ts +300 -4
- package/src/presentation/routes/update-project.ts +46 -0
- package/src/presentation/subscribers/index.ts +1 -0
- package/src/presentation/subscribers/on-source-commit-pushed.test.ts +2 -0
- package/src/presentation/subscribers/on-source-repository-updated.test.ts +108 -0
- package/src/presentation/subscribers/on-source-repository-updated.ts +50 -0
- package/test/factories/project.ts +6 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {\n INTEGRATION_SOURCE_COMMIT_PUSHED,\n type IntegrationsEventMap,\n} from '@shipfox/api-integration-core-dto';\nimport type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';\nimport {projectsEventSchemas} from '@shipfox/api-projects-dto';\nimport {type ShipfoxModule, subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, projectsOutbox} from '#db/index.js';\nimport {registerProjectsServiceMetrics} from '#metrics/index.js';\nimport {projectsE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createProjectRoutes} from '#presentation/index.js';\nimport {createProjectsInterModulePresentation} from '#presentation/inter-module.js';\nimport {onSourceCommitPushed} from '#presentation/subscribers/index.js';\nimport {createProjectsMaintenanceActivities} from '#temporal/activities/index.js';\nimport {PROJECTS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');\n\nconst subscriber = subscriberFactory<IntegrationsEventMap>();\n\nexport type {Project} from '#core/index.js';\nexport {\n createProjectFromSource,\n ProjectAccessDeniedError,\n ProjectAlreadyExistsError,\n ProjectNotFoundError,\n} from '#core/index.js';\nexport type {GetProjectBySourceParams} from '#db/index.js';\nexport {\n createProject,\n db,\n getProjectById,\n getProjectBySource,\n getWorkspaceProjectCounts,\n listProjects,\n migrationsPath,\n projectsOutbox,\n requireProjectForWorkspace,\n} from '#db/index.js';\nexport {createProjectRoutes, requireProjectAccess} from '#presentation/index.js';\n\nexport interface CreateProjectsModuleOptions {\n integrations: IntegrationsModuleClient;\n auth: AuthInterModuleClient;\n}\n\nexport function createProjectsModule({\n integrations,\n auth,\n}: CreateProjectsModuleOptions): ShipfoxModule {\n return {\n name: 'projects',\n database: {db, migrationsPath, databaseNamespace: 'projects'},\n routes: createProjectRoutes(integrations, auth),\n e2eRoutes: [projectsE2eRoutes],\n metrics: registerProjectsServiceMetrics,\n publishers: [{name: 'projects', table: projectsOutbox, db, eventSchemas: projectsEventSchemas}],\n interModulePresentations: [createProjectsInterModulePresentation()],\n subscribers: [subscriber(INTEGRATION_SOURCE_COMMIT_PUSHED, onSourceCommitPushed)],\n workers: [\n {\n taskQueue: PROJECTS_MAINTENANCE_TASK_QUEUE,\n workflowsPath: maintenanceWorkflowsPath,\n activities: createProjectsMaintenanceActivities,\n workflows: [\n {\n name: 'pruneIntegrationEventDedupCron',\n id: 'projects-prune-integration-event-dedup',\n cronSchedule: '0 3 * * *',\n },\n ],\n },\n ],\n };\n}\n"],"names":["dirname","resolve","fileURLToPath","INTEGRATION_SOURCE_COMMIT_PUSHED","projectsEventSchemas","subscriberFactory","db","migrationsPath","projectsOutbox","registerProjectsServiceMetrics","projectsE2eRoutes","createProjectRoutes","createProjectsInterModulePresentation","onSourceCommitPushed","createProjectsMaintenanceActivities","PROJECTS_MAINTENANCE_TASK_QUEUE","packageRoot","url","maintenanceWorkflowsPath","subscriber","createProjectFromSource","ProjectAccessDeniedError","ProjectAlreadyExistsError","ProjectNotFoundError","createProject","getProjectById","getProjectBySource","getWorkspaceProjectCounts","listProjects","requireProjectForWorkspace","requireProjectAccess","createProjectsModule","integrations","auth","name","database","databaseNamespace","routes","e2eRoutes","metrics","publishers","table","eventSchemas","interModulePresentations","subscribers","workers","taskQueue","workflowsPath","activities","workflows","id","cronSchedule"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,SACEC,gCAAgC,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport {\n INTEGRATION_SOURCE_COMMIT_PUSHED,\n INTEGRATION_SOURCE_REPOSITORY_UPDATED,\n type IntegrationsEventMap,\n} from '@shipfox/api-integration-core-dto';\nimport type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';\nimport {projectsEventSchemas} from '@shipfox/api-projects-dto';\nimport {type ShipfoxModule, subscriberFactory} from '@shipfox/node-module';\nimport {db, migrationsPath, projectsOutbox} from '#db/index.js';\nimport {registerProjectsServiceMetrics} from '#metrics/index.js';\nimport {projectsE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createProjectRoutes} from '#presentation/index.js';\nimport {createProjectsInterModulePresentation} from '#presentation/inter-module.js';\nimport {onSourceCommitPushed, onSourceRepositoryUpdated} from '#presentation/subscribers/index.js';\nimport {createProjectsMaintenanceActivities} from '#temporal/activities/index.js';\nimport {PROJECTS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');\n\nconst subscriber = subscriberFactory<IntegrationsEventMap>();\n\nexport type {Project} from '#core/index.js';\nexport {\n createProjectFromSource,\n ProjectAccessDeniedError,\n ProjectAlreadyExistsError,\n ProjectNotFoundError,\n ProjectSlugConflictError,\n updateProjectDetails,\n} from '#core/index.js';\nexport type {GetProjectBySourceParams} from '#db/index.js';\nexport {\n createProject,\n db,\n getProjectById,\n getProjectBySource,\n getWorkspaceProjectCounts,\n listProjects,\n migrationsPath,\n projectsOutbox,\n requireProjectForWorkspace,\n} from '#db/index.js';\nexport {createProjectRoutes, requireProjectAccess} from '#presentation/index.js';\n\nexport interface CreateProjectsModuleOptions {\n integrations: IntegrationsModuleClient;\n auth: AuthInterModuleClient;\n}\n\nexport function createProjectsModule({\n integrations,\n auth,\n}: CreateProjectsModuleOptions): ShipfoxModule {\n return {\n name: 'projects',\n database: {db, migrationsPath, databaseNamespace: 'projects'},\n routes: createProjectRoutes(integrations, auth),\n e2eRoutes: [projectsE2eRoutes],\n metrics: registerProjectsServiceMetrics,\n publishers: [{name: 'projects', table: projectsOutbox, db, eventSchemas: projectsEventSchemas}],\n interModulePresentations: [createProjectsInterModulePresentation({integrations})],\n subscribers: [\n subscriber(INTEGRATION_SOURCE_COMMIT_PUSHED, onSourceCommitPushed),\n subscriber(INTEGRATION_SOURCE_REPOSITORY_UPDATED, onSourceRepositoryUpdated),\n ],\n workers: [\n {\n taskQueue: PROJECTS_MAINTENANCE_TASK_QUEUE,\n workflowsPath: maintenanceWorkflowsPath,\n activities: createProjectsMaintenanceActivities,\n workflows: [\n {\n name: 'pruneIntegrationEventDedupCron',\n id: 'projects-prune-integration-event-dedup',\n cronSchedule: '0 3 * * *',\n },\n ],\n },\n ],\n };\n}\n"],"names":["dirname","resolve","fileURLToPath","INTEGRATION_SOURCE_COMMIT_PUSHED","INTEGRATION_SOURCE_REPOSITORY_UPDATED","projectsEventSchemas","subscriberFactory","db","migrationsPath","projectsOutbox","registerProjectsServiceMetrics","projectsE2eRoutes","createProjectRoutes","createProjectsInterModulePresentation","onSourceCommitPushed","onSourceRepositoryUpdated","createProjectsMaintenanceActivities","PROJECTS_MAINTENANCE_TASK_QUEUE","packageRoot","url","maintenanceWorkflowsPath","subscriber","createProjectFromSource","ProjectAccessDeniedError","ProjectAlreadyExistsError","ProjectNotFoundError","ProjectSlugConflictError","updateProjectDetails","createProject","getProjectById","getProjectBySource","getWorkspaceProjectCounts","listProjects","requireProjectForWorkspace","requireProjectAccess","createProjectsModule","integrations","auth","name","database","databaseNamespace","routes","e2eRoutes","metrics","publishers","table","eventSchemas","interModulePresentations","subscribers","workers","taskQueue","workflowsPath","activities","workflows","id","cronSchedule"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,SACEC,gCAAgC,EAChCC,qCAAqC,QAEhC,oCAAoC;AAE3C,SAAQC,oBAAoB,QAAO,4BAA4B;AAC/D,SAA4BC,iBAAiB,QAAO,uBAAuB;AAC3E,SAAQC,EAAE,EAAEC,cAAc,EAAEC,cAAc,QAAO,eAAe;AAChE,SAAQC,8BAA8B,QAAO,oBAAoB;AACjE,SAAQC,iBAAiB,QAAO,mCAAmC;AACnE,SAAQC,mBAAmB,QAAO,yBAAyB;AAC3D,SAAQC,qCAAqC,QAAO,gCAAgC;AACpF,SAAQC,oBAAoB,EAAEC,yBAAyB,QAAO,qCAAqC;AACnG,SAAQC,mCAAmC,QAAO,gCAAgC;AAClF,SAAQC,+BAA+B,QAAO,yBAAyB;AAEvE,MAAMC,cAAcjB,QAAQD,QAAQE,cAAc,YAAYiB,GAAG,IAAI;AACrE,MAAMC,2BAA2BnB,QAAQiB,aAAa;AAEtD,MAAMG,aAAaf;AAGnB,SACEgB,uBAAuB,EACvBC,wBAAwB,EACxBC,yBAAyB,EACzBC,oBAAoB,EACpBC,wBAAwB,EACxBC,oBAAoB,QACf,iBAAiB;AAExB,SACEC,aAAa,EACbrB,EAAE,EACFsB,cAAc,EACdC,kBAAkB,EAClBC,yBAAyB,EACzBC,YAAY,EACZxB,cAAc,EACdC,cAAc,EACdwB,0BAA0B,QACrB,eAAe;AACtB,SAAQrB,mBAAmB,EAAEsB,oBAAoB,QAAO,yBAAyB;AAOjF,OAAO,SAASC,qBAAqB,EACnCC,YAAY,EACZC,IAAI,EACwB;IAC5B,OAAO;QACLC,MAAM;QACNC,UAAU;YAAChC;YAAIC;YAAgBgC,mBAAmB;QAAU;QAC5DC,QAAQ7B,oBAAoBwB,cAAcC;QAC1CK,WAAW;YAAC/B;SAAkB;QAC9BgC,SAASjC;QACTkC,YAAY;YAAC;gBAACN,MAAM;gBAAYO,OAAOpC;gBAAgBF;gBAAIuC,cAAczC;YAAoB;SAAE;QAC/F0C,0BAA0B;YAAClC,sCAAsC;gBAACuB;YAAY;SAAG;QACjFY,aAAa;YACX3B,WAAWlB,kCAAkCW;YAC7CO,WAAWjB,uCAAuCW;SACnD;QACDkC,SAAS;YACP;gBACEC,WAAWjC;gBACXkC,eAAe/B;gBACfgC,YAAYpC;gBACZqC,WAAW;oBACT;wBACEf,MAAM;wBACNgB,IAAI;wBACJC,cAAc;oBAChB;iBACD;YACH;SACD;IACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iBAAiB,CAAC;AAEzD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iBAAiB,CAAC;AAEzD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO;;;;;;;;;;;EAa5C;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,mBAAmB;;;;;;;EASpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/dto/project.ts"],"sourcesContent":["import type {Project} from '#core/entities/index.js';\nimport type {AdminProjectSummary} from '#db/projects.js';\n\nexport function toProjectDto(project: Project) {\n return {\n id: project.id,\n workspace_id: project.workspaceId,\n name: project.name,\n source: {\n connection_id: project.sourceConnectionId,\n external_repository_id: project.sourceExternalRepositoryId,\n },\n created_at: project.createdAt.toISOString(),\n updated_at: project.updatedAt.toISOString(),\n };\n}\n\nexport function toAdminProjectSummaryDto(project: AdminProjectSummary) {\n return {\n id: project.id,\n name: project.name,\n status: 'active' as const,\n workspace_id: project.workspaceId,\n created_at: project.createdAt.toISOString(),\n updated_at: project.updatedAt.toISOString(),\n };\n}\n"],"names":["toProjectDto","project","id","workspace_id","workspaceId","name","source","connection_id","sourceConnectionId","external_repository_id","sourceExternalRepositoryId","created_at","createdAt","toISOString","updated_at","updatedAt","toAdminProjectSummaryDto","status"],"mappings":"AAGA,OAAO,SAASA,aAAaC,OAAgB;IAC3C,OAAO;QACLC,IAAID,QAAQC,EAAE;QACdC,cAAcF,QAAQG,WAAW;QACjCC,MAAMJ,QAAQI,IAAI;QAClBC,QAAQ;YACNC,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/project.ts"],"sourcesContent":["import type {Project} from '#core/entities/index.js';\nimport type {AdminProjectSummary} from '#db/projects.js';\n\nexport function toProjectDto(project: Project) {\n return {\n id: project.id,\n workspace_id: project.workspaceId,\n name: project.name,\n slug: project.slug,\n source: {\n connection_id: project.sourceConnectionId,\n external_repository_id: project.sourceExternalRepositoryId,\n },\n created_at: project.createdAt.toISOString(),\n updated_at: project.updatedAt.toISOString(),\n };\n}\n\nexport function toAdminProjectSummaryDto(project: AdminProjectSummary) {\n return {\n id: project.id,\n name: project.name,\n status: 'active' as const,\n workspace_id: project.workspaceId,\n created_at: project.createdAt.toISOString(),\n updated_at: project.updatedAt.toISOString(),\n };\n}\n"],"names":["toProjectDto","project","id","workspace_id","workspaceId","name","slug","source","connection_id","sourceConnectionId","external_repository_id","sourceExternalRepositoryId","created_at","createdAt","toISOString","updated_at","updatedAt","toAdminProjectSummaryDto","status"],"mappings":"AAGA,OAAO,SAASA,aAAaC,OAAgB;IAC3C,OAAO;QACLC,IAAID,QAAQC,EAAE;QACdC,cAAcF,QAAQG,WAAW;QACjCC,MAAMJ,QAAQI,IAAI;QAClBC,MAAML,QAAQK,IAAI;QAClBC,QAAQ;YACNC,eAAeP,QAAQQ,kBAAkB;YACzCC,wBAAwBT,QAAQU,0BAA0B;QAC5D;QACAC,YAAYX,QAAQY,SAAS,CAACC,WAAW;QACzCC,YAAYd,QAAQe,SAAS,CAACF,WAAW;IAC3C;AACF;AAEA,OAAO,SAASG,yBAAyBhB,OAA4B;IACnE,OAAO;QACLC,IAAID,QAAQC,EAAE;QACdG,MAAMJ,QAAQI,IAAI;QAClBa,QAAQ;QACRf,cAAcF,QAAQG,WAAW;QACjCQ,YAAYX,QAAQY,SAAS,CAACC,WAAW;QACzCC,YAAYd,QAAQe,SAAS,CAACF,WAAW;IAC3C;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-project.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../../src/presentation/e2eRoutes/create-project.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,qBAAqB,iDAwDhC,CAAC"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { slugifyName, withSlugSuffix } from '@shipfox/api-common-dto';
|
|
2
3
|
import { e2eCreateProjectBodySchema, e2eCreateProjectResponseSchema } from '@shipfox/api-projects-dto';
|
|
3
4
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
|
-
import { ProjectAlreadyExistsError } from '#core/index.js';
|
|
5
|
+
import { ProjectAlreadyExistsError, ProjectSlugConflictError } from '#core/index.js';
|
|
5
6
|
import { createProject } from '#db/index.js';
|
|
6
7
|
import { toProjectDto } from '#presentation/dto/index.js';
|
|
7
8
|
function syntheticExternalRepositoryId() {
|
|
8
9
|
return `e2e:${randomUUID()}`;
|
|
9
10
|
}
|
|
11
|
+
const MAX_GENERATED_SLUG_ATTEMPTS = 5;
|
|
12
|
+
function generatedProjectSlug(slugBase) {
|
|
13
|
+
const suffix = Number.parseInt(randomUUID().replaceAll('-', '').slice(0, 8), 16);
|
|
14
|
+
return withSlugSuffix(slugBase, suffix);
|
|
15
|
+
}
|
|
10
16
|
export const createE2eProjectRoute = defineRoute({
|
|
11
17
|
method: 'POST',
|
|
12
18
|
path: '/',
|
|
@@ -28,17 +34,37 @@ export const createE2eProjectRoute = defineRoute({
|
|
|
28
34
|
status: 409
|
|
29
35
|
});
|
|
30
36
|
}
|
|
37
|
+
if (error instanceof ProjectSlugConflictError) {
|
|
38
|
+
throw new ClientError('Project slug already exists', 'slug-conflict', {
|
|
39
|
+
status: 409
|
|
40
|
+
});
|
|
41
|
+
}
|
|
31
42
|
throw error;
|
|
32
43
|
},
|
|
33
44
|
handler: async (request, reply)=>{
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sourceConnectionId: request.body.source_connection_id ?? randomUUID(),
|
|
38
|
-
sourceExternalRepositoryId: request.body.source_external_repository_id ?? syntheticExternalRepositoryId()
|
|
45
|
+
const requestedSlug = request.body.slug;
|
|
46
|
+
const slugBase = slugifyName(request.body.name, {
|
|
47
|
+
fallback: 'project'
|
|
39
48
|
});
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
let slug = requestedSlug ?? generatedProjectSlug(slugBase);
|
|
50
|
+
for(let attempt = 0; attempt < MAX_GENERATED_SLUG_ATTEMPTS; attempt += 1){
|
|
51
|
+
try {
|
|
52
|
+
const project = await createProject({
|
|
53
|
+
workspaceId: request.body.workspace_id,
|
|
54
|
+
name: request.body.name,
|
|
55
|
+
slug,
|
|
56
|
+
sourceConnectionId: request.body.source_connection_id ?? randomUUID(),
|
|
57
|
+
sourceExternalRepositoryId: request.body.source_external_repository_id ?? syntheticExternalRepositoryId()
|
|
58
|
+
});
|
|
59
|
+
reply.code(201);
|
|
60
|
+
return toProjectDto(project);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const shouldRetrySlug = requestedSlug === undefined && error instanceof ProjectSlugConflictError && attempt < MAX_GENERATED_SLUG_ATTEMPTS - 1;
|
|
63
|
+
if (!shouldRetrySlug) throw error;
|
|
64
|
+
slug = generatedProjectSlug(slugBase);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error('Unable to generate a unique project slug');
|
|
42
68
|
}
|
|
43
69
|
});
|
|
44
70
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/e2eRoutes/create-project.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {\n e2eCreateProjectBodySchema,\n e2eCreateProjectResponseSchema,\n} from '@shipfox/api-projects-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {ProjectAlreadyExistsError} from '#core/index.js';\nimport {createProject} from '#db/index.js';\nimport {toProjectDto} from '#presentation/dto/index.js';\n\nfunction syntheticExternalRepositoryId(): string {\n return `e2e:${randomUUID()}`;\n}\n\nexport const createE2eProjectRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Create a synthetic project for E2E tests.',\n schema: {\n body: e2eCreateProjectBodySchema,\n response: {\n 201: e2eCreateProjectResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof ProjectAlreadyExistsError) {\n throw new ClientError(error.message, 'project-already-exists', {\n details: {\n existing_project_id: error.existingProjectId,\n source_connection_id: error.sourceConnectionId,\n source_external_repository_id: error.sourceExternalRepositoryId,\n },\n status: 409,\n });\n }\n throw error;\n },\n handler: async (request, reply) => {\n const project = await createProject({\n
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/e2eRoutes/create-project.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {slugifyName, withSlugSuffix} from '@shipfox/api-common-dto';\nimport {\n e2eCreateProjectBodySchema,\n e2eCreateProjectResponseSchema,\n} from '@shipfox/api-projects-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {ProjectAlreadyExistsError, ProjectSlugConflictError} from '#core/index.js';\nimport {createProject} from '#db/index.js';\nimport {toProjectDto} from '#presentation/dto/index.js';\n\nfunction syntheticExternalRepositoryId(): string {\n return `e2e:${randomUUID()}`;\n}\n\nconst MAX_GENERATED_SLUG_ATTEMPTS = 5;\n\nfunction generatedProjectSlug(slugBase: string): string {\n const suffix = Number.parseInt(randomUUID().replaceAll('-', '').slice(0, 8), 16);\n return withSlugSuffix(slugBase, suffix);\n}\n\nexport const createE2eProjectRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Create a synthetic project for E2E tests.',\n schema: {\n body: e2eCreateProjectBodySchema,\n response: {\n 201: e2eCreateProjectResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof ProjectAlreadyExistsError) {\n throw new ClientError(error.message, 'project-already-exists', {\n details: {\n existing_project_id: error.existingProjectId,\n source_connection_id: error.sourceConnectionId,\n source_external_repository_id: error.sourceExternalRepositoryId,\n },\n status: 409,\n });\n }\n if (error instanceof ProjectSlugConflictError) {\n throw new ClientError('Project slug already exists', 'slug-conflict', {status: 409});\n }\n throw error;\n },\n handler: async (request, reply) => {\n const requestedSlug = request.body.slug;\n const slugBase = slugifyName(request.body.name, {fallback: 'project'});\n let slug = requestedSlug ?? generatedProjectSlug(slugBase);\n\n for (let attempt = 0; attempt < MAX_GENERATED_SLUG_ATTEMPTS; attempt += 1) {\n try {\n const project = await createProject({\n workspaceId: request.body.workspace_id,\n name: request.body.name,\n slug,\n sourceConnectionId: request.body.source_connection_id ?? randomUUID(),\n sourceExternalRepositoryId:\n request.body.source_external_repository_id ?? syntheticExternalRepositoryId(),\n });\n\n reply.code(201);\n return toProjectDto(project);\n } catch (error) {\n const shouldRetrySlug =\n requestedSlug === undefined &&\n error instanceof ProjectSlugConflictError &&\n attempt < MAX_GENERATED_SLUG_ATTEMPTS - 1;\n if (!shouldRetrySlug) throw error;\n slug = generatedProjectSlug(slugBase);\n }\n }\n\n throw new Error('Unable to generate a unique project slug');\n },\n});\n"],"names":["randomUUID","slugifyName","withSlugSuffix","e2eCreateProjectBodySchema","e2eCreateProjectResponseSchema","ClientError","defineRoute","ProjectAlreadyExistsError","ProjectSlugConflictError","createProject","toProjectDto","syntheticExternalRepositoryId","MAX_GENERATED_SLUG_ATTEMPTS","generatedProjectSlug","slugBase","suffix","Number","parseInt","replaceAll","slice","createE2eProjectRoute","method","path","description","schema","body","response","errorHandler","error","message","details","existing_project_id","existingProjectId","source_connection_id","sourceConnectionId","source_external_repository_id","sourceExternalRepositoryId","status","handler","request","reply","requestedSlug","slug","name","fallback","attempt","project","workspaceId","workspace_id","code","shouldRetrySlug","undefined","Error"],"mappings":"AAAA,SAAQA,UAAU,QAAO,cAAc;AACvC,SAAQC,WAAW,EAAEC,cAAc,QAAO,0BAA0B;AACpE,SACEC,0BAA0B,EAC1BC,8BAA8B,QACzB,4BAA4B;AACnC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,yBAAyB,EAAEC,wBAAwB,QAAO,iBAAiB;AACnF,SAAQC,aAAa,QAAO,eAAe;AAC3C,SAAQC,YAAY,QAAO,6BAA6B;AAExD,SAASC;IACP,OAAO,CAAC,IAAI,EAAEX,cAAc;AAC9B;AAEA,MAAMY,8BAA8B;AAEpC,SAASC,qBAAqBC,QAAgB;IAC5C,MAAMC,SAASC,OAAOC,QAAQ,CAACjB,aAAakB,UAAU,CAAC,KAAK,IAAIC,KAAK,CAAC,GAAG,IAAI;IAC7E,OAAOjB,eAAeY,UAAUC;AAClC;AAEA,OAAO,MAAMK,wBAAwBd,YAAY;IAC/Ce,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMtB;QACNuB,UAAU;YACR,KAAKtB;QACP;IACF;IACAuB,cAAc,CAACC;QACb,IAAIA,iBAAiBrB,2BAA2B;YAC9C,MAAM,IAAIF,YAAYuB,MAAMC,OAAO,EAAE,0BAA0B;gBAC7DC,SAAS;oBACPC,qBAAqBH,MAAMI,iBAAiB;oBAC5CC,sBAAsBL,MAAMM,kBAAkB;oBAC9CC,+BAA+BP,MAAMQ,0BAA0B;gBACjE;gBACAC,QAAQ;YACV;QACF;QACA,IAAIT,iBAAiBpB,0BAA0B;YAC7C,MAAM,IAAIH,YAAY,+BAA+B,iBAAiB;gBAACgC,QAAQ;YAAG;QACpF;QACA,MAAMT;IACR;IACAU,SAAS,OAAOC,SAASC;QACvB,MAAMC,gBAAgBF,QAAQd,IAAI,CAACiB,IAAI;QACvC,MAAM5B,WAAWb,YAAYsC,QAAQd,IAAI,CAACkB,IAAI,EAAE;YAACC,UAAU;QAAS;QACpE,IAAIF,OAAOD,iBAAiB5B,qBAAqBC;QAEjD,IAAK,IAAI+B,UAAU,GAAGA,UAAUjC,6BAA6BiC,WAAW,EAAG;YACzE,IAAI;gBACF,MAAMC,UAAU,MAAMrC,cAAc;oBAClCsC,aAAaR,QAAQd,IAAI,CAACuB,YAAY;oBACtCL,MAAMJ,QAAQd,IAAI,CAACkB,IAAI;oBACvBD;oBACAR,oBAAoBK,QAAQd,IAAI,CAACQ,oBAAoB,IAAIjC;oBACzDoC,4BACEG,QAAQd,IAAI,CAACU,6BAA6B,IAAIxB;gBAClD;gBAEA6B,MAAMS,IAAI,CAAC;gBACX,OAAOvC,aAAaoC;YACtB,EAAE,OAAOlB,OAAO;gBACd,MAAMsB,kBACJT,kBAAkBU,aAClBvB,iBAAiBpB,4BACjBqC,UAAUjC,8BAA8B;gBAC1C,IAAI,CAACsC,iBAAiB,MAAMtB;gBAC5Bc,OAAO7B,qBAAqBC;YAC9B;QACF;QAEA,MAAM,IAAIsC,MAAM;IAClB;AACF,GAAG"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { IntegrationsModuleClient } from '@shipfox/api-integration-core-dto/inter-module';
|
|
1
2
|
import { projectsInterModuleContract } from '@shipfox/api-projects-dto/inter-module';
|
|
2
3
|
import { type InterModulePresentation } from '@shipfox/inter-module';
|
|
3
|
-
export declare function createProjectsInterModulePresentation(
|
|
4
|
+
export declare function createProjectsInterModulePresentation(params: {
|
|
5
|
+
integrations: Pick<IntegrationsModuleClient, 'resolveSourceRepository'>;
|
|
6
|
+
}): InterModulePresentation<typeof projectsInterModuleContract>;
|
|
4
7
|
//# sourceMappingURL=inter-module.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inter-module.d.ts","sourceRoot":"","sources":["../../src/presentation/inter-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,2BAA2B,EAAC,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"inter-module.d.ts","sourceRoot":"","sources":["../../src/presentation/inter-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,gDAAgD,CAAC;AAC7F,OAAO,EAAC,2BAA2B,EAAC,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAS/B,wBAAgB,qCAAqC,CAAC,MAAM,EAAE;IAC5D,YAAY,EAAE,IAAI,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,CAAC;CACzE,GAAG,uBAAuB,CAAC,OAAO,2BAA2B,CAAC,CA0F9D"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { projectsInterModuleContract } from '@shipfox/api-projects-dto/inter-module';
|
|
2
2
|
import { createInterModuleKnownError, defineInterModulePresentation } from '@shipfox/inter-module';
|
|
3
|
-
import { getProjectById, getWorkspaceProjectCounts } from '#db/projects.js';
|
|
4
|
-
export function createProjectsInterModulePresentation() {
|
|
3
|
+
import { getProjectById, getProjectBySource, getWorkspaceProjectCounts, resolveCheckoutTarget, updateProjectSourceMetadata } from '#db/projects.js';
|
|
4
|
+
export function createProjectsInterModulePresentation(params) {
|
|
5
5
|
return defineInterModulePresentation(projectsInterModuleContract, {
|
|
6
6
|
getProjectById: async ({ projectId })=>({
|
|
7
7
|
project: await getProjectById(projectId) ?? null
|
|
8
8
|
}),
|
|
9
|
+
getProjectBySource: async (input)=>({
|
|
10
|
+
project: await getProjectBySource(input) ?? null
|
|
11
|
+
}),
|
|
9
12
|
requireProjectForWorkspace: async ({ projectId, workspaceId })=>{
|
|
10
13
|
const project = await getProjectById(projectId);
|
|
11
14
|
if (project === undefined) {
|
|
@@ -27,7 +30,45 @@ export function createProjectsInterModulePresentation() {
|
|
|
27
30
|
counts: await getWorkspaceProjectCounts({
|
|
28
31
|
workspaceIds
|
|
29
32
|
})
|
|
30
|
-
})
|
|
33
|
+
}),
|
|
34
|
+
resolveCheckoutTarget: async (input)=>{
|
|
35
|
+
const target = await resolveCheckoutTarget(input);
|
|
36
|
+
if (target === undefined) {
|
|
37
|
+
throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
|
|
38
|
+
}
|
|
39
|
+
if ('repository' in input.target) {
|
|
40
|
+
const source = await params.integrations.resolveSourceRepository({
|
|
41
|
+
workspaceId: input.workspaceId,
|
|
42
|
+
connectionId: target.connectionId,
|
|
43
|
+
externalRepositoryId: target.externalRepositoryId
|
|
44
|
+
}).catch(()=>undefined);
|
|
45
|
+
if (source === undefined) {
|
|
46
|
+
throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
|
|
47
|
+
}
|
|
48
|
+
const separator = input.target.repository.indexOf('/');
|
|
49
|
+
const requestedOwner = separator === -1 ? input.defaults.owner : input.target.repository.slice(0, separator);
|
|
50
|
+
const requestedName = separator === -1 ? input.target.repository : input.target.repository.slice(separator + 1);
|
|
51
|
+
const repositoryMatches = source.repository.owner.toLowerCase() === requestedOwner.toLowerCase() && source.repository.name.toLowerCase() === requestedName.toLowerCase();
|
|
52
|
+
await updateProjectSourceMetadata({
|
|
53
|
+
workspaceId: input.workspaceId,
|
|
54
|
+
projectId: target.projectId,
|
|
55
|
+
sourceConnectionId: source.connection.id,
|
|
56
|
+
sourceExternalRepositoryId: source.repository.externalRepositoryId,
|
|
57
|
+
sourceRepositoryOwner: source.repository.owner,
|
|
58
|
+
sourceRepositoryName: source.repository.name,
|
|
59
|
+
sourceDefaultBranch: source.repository.defaultBranch
|
|
60
|
+
});
|
|
61
|
+
if (!repositoryMatches) {
|
|
62
|
+
throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...target,
|
|
66
|
+
connectionId: source.connection.id,
|
|
67
|
+
externalRepositoryId: source.repository.externalRepositoryId
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return target;
|
|
71
|
+
}
|
|
31
72
|
});
|
|
32
73
|
}
|
|
33
74
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/presentation/inter-module.ts"],"sourcesContent":["import {projectsInterModuleContract} from '@shipfox/api-projects-dto/inter-module';\nimport {\n createInterModuleKnownError,\n defineInterModulePresentation,\n type InterModulePresentation,\n} from '@shipfox/inter-module';\nimport {getProjectById
|
|
1
|
+
{"version":3,"sources":["../../src/presentation/inter-module.ts"],"sourcesContent":["import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';\nimport {projectsInterModuleContract} from '@shipfox/api-projects-dto/inter-module';\nimport {\n createInterModuleKnownError,\n defineInterModulePresentation,\n type InterModulePresentation,\n} from '@shipfox/inter-module';\nimport {\n getProjectById,\n getProjectBySource,\n getWorkspaceProjectCounts,\n resolveCheckoutTarget,\n updateProjectSourceMetadata,\n} from '#db/projects.js';\n\nexport function createProjectsInterModulePresentation(params: {\n integrations: Pick<IntegrationsModuleClient, 'resolveSourceRepository'>;\n}): InterModulePresentation<typeof projectsInterModuleContract> {\n return defineInterModulePresentation(projectsInterModuleContract, {\n getProjectById: async ({projectId}) => ({project: (await getProjectById(projectId)) ?? null}),\n getProjectBySource: async (input) => ({\n project: (await getProjectBySource(input)) ?? null,\n }),\n requireProjectForWorkspace: async ({projectId, workspaceId}) => {\n const project = await getProjectById(projectId);\n if (project === undefined) {\n throw createInterModuleKnownError(\n projectsInterModuleContract.methods.requireProjectForWorkspace,\n 'project-not-found',\n {projectId},\n );\n }\n if (project.workspaceId !== workspaceId) {\n throw createInterModuleKnownError(\n projectsInterModuleContract.methods.requireProjectForWorkspace,\n 'project-workspace-mismatch',\n {projectId, workspaceId},\n );\n }\n return {project};\n },\n getWorkspaceProjectCounts: async ({workspaceIds}) => ({\n counts: await getWorkspaceProjectCounts({workspaceIds}),\n }),\n resolveCheckoutTarget: async (input) => {\n const target = await resolveCheckoutTarget(input);\n if (target === undefined) {\n throw createInterModuleKnownError(\n projectsInterModuleContract.methods.resolveCheckoutTarget,\n 'checkout-repository-not-authorized',\n {},\n );\n }\n\n if ('repository' in input.target) {\n const source = await params.integrations\n .resolveSourceRepository({\n workspaceId: input.workspaceId,\n connectionId: target.connectionId,\n externalRepositoryId: target.externalRepositoryId,\n })\n .catch(() => undefined);\n if (source === undefined) {\n throw createInterModuleKnownError(\n projectsInterModuleContract.methods.resolveCheckoutTarget,\n 'checkout-repository-not-authorized',\n {},\n );\n }\n\n const separator = input.target.repository.indexOf('/');\n const requestedOwner =\n separator === -1 ? input.defaults.owner : input.target.repository.slice(0, separator);\n const requestedName =\n separator === -1 ? input.target.repository : input.target.repository.slice(separator + 1);\n const repositoryMatches =\n source.repository.owner.toLowerCase() === requestedOwner.toLowerCase() &&\n source.repository.name.toLowerCase() === requestedName.toLowerCase();\n\n await updateProjectSourceMetadata({\n workspaceId: input.workspaceId,\n projectId: target.projectId,\n sourceConnectionId: source.connection.id,\n sourceExternalRepositoryId: source.repository.externalRepositoryId,\n sourceRepositoryOwner: source.repository.owner,\n sourceRepositoryName: source.repository.name,\n sourceDefaultBranch: source.repository.defaultBranch,\n });\n\n if (!repositoryMatches) {\n throw createInterModuleKnownError(\n projectsInterModuleContract.methods.resolveCheckoutTarget,\n 'checkout-repository-not-authorized',\n {},\n );\n }\n\n return {\n ...target,\n connectionId: source.connection.id,\n externalRepositoryId: source.repository.externalRepositoryId,\n };\n }\n\n return target;\n },\n });\n}\n"],"names":["projectsInterModuleContract","createInterModuleKnownError","defineInterModulePresentation","getProjectById","getProjectBySource","getWorkspaceProjectCounts","resolveCheckoutTarget","updateProjectSourceMetadata","createProjectsInterModulePresentation","params","projectId","project","input","requireProjectForWorkspace","workspaceId","undefined","methods","workspaceIds","counts","target","source","integrations","resolveSourceRepository","connectionId","externalRepositoryId","catch","separator","repository","indexOf","requestedOwner","defaults","owner","slice","requestedName","repositoryMatches","toLowerCase","name","sourceConnectionId","connection","id","sourceExternalRepositoryId","sourceRepositoryOwner","sourceRepositoryName","sourceDefaultBranch","defaultBranch"],"mappings":"AACA,SAAQA,2BAA2B,QAAO,yCAAyC;AACnF,SACEC,2BAA2B,EAC3BC,6BAA6B,QAExB,wBAAwB;AAC/B,SACEC,cAAc,EACdC,kBAAkB,EAClBC,yBAAyB,EACzBC,qBAAqB,EACrBC,2BAA2B,QACtB,kBAAkB;AAEzB,OAAO,SAASC,sCAAsCC,MAErD;IACC,OAAOP,8BAA8BF,6BAA6B;QAChEG,gBAAgB,OAAO,EAACO,SAAS,EAAC,GAAM,CAAA;gBAACC,SAAS,AAAC,MAAMR,eAAeO,cAAe;YAAI,CAAA;QAC3FN,oBAAoB,OAAOQ,QAAW,CAAA;gBACpCD,SAAS,AAAC,MAAMP,mBAAmBQ,UAAW;YAChD,CAAA;QACAC,4BAA4B,OAAO,EAACH,SAAS,EAAEI,WAAW,EAAC;YACzD,MAAMH,UAAU,MAAMR,eAAeO;YACrC,IAAIC,YAAYI,WAAW;gBACzB,MAAMd,4BACJD,4BAA4BgB,OAAO,CAACH,0BAA0B,EAC9D,qBACA;oBAACH;gBAAS;YAEd;YACA,IAAIC,QAAQG,WAAW,KAAKA,aAAa;gBACvC,MAAMb,4BACJD,4BAA4BgB,OAAO,CAACH,0BAA0B,EAC9D,8BACA;oBAACH;oBAAWI;gBAAW;YAE3B;YACA,OAAO;gBAACH;YAAO;QACjB;QACAN,2BAA2B,OAAO,EAACY,YAAY,EAAC,GAAM,CAAA;gBACpDC,QAAQ,MAAMb,0BAA0B;oBAACY;gBAAY;YACvD,CAAA;QACAX,uBAAuB,OAAOM;YAC5B,MAAMO,SAAS,MAAMb,sBAAsBM;YAC3C,IAAIO,WAAWJ,WAAW;gBACxB,MAAMd,4BACJD,4BAA4BgB,OAAO,CAACV,qBAAqB,EACzD,sCACA,CAAC;YAEL;YAEA,IAAI,gBAAgBM,MAAMO,MAAM,EAAE;gBAChC,MAAMC,SAAS,MAAMX,OAAOY,YAAY,CACrCC,uBAAuB,CAAC;oBACvBR,aAAaF,MAAME,WAAW;oBAC9BS,cAAcJ,OAAOI,YAAY;oBACjCC,sBAAsBL,OAAOK,oBAAoB;gBACnD,GACCC,KAAK,CAAC,IAAMV;gBACf,IAAIK,WAAWL,WAAW;oBACxB,MAAMd,4BACJD,4BAA4BgB,OAAO,CAACV,qBAAqB,EACzD,sCACA,CAAC;gBAEL;gBAEA,MAAMoB,YAAYd,MAAMO,MAAM,CAACQ,UAAU,CAACC,OAAO,CAAC;gBAClD,MAAMC,iBACJH,cAAc,CAAC,IAAId,MAAMkB,QAAQ,CAACC,KAAK,GAAGnB,MAAMO,MAAM,CAACQ,UAAU,CAACK,KAAK,CAAC,GAAGN;gBAC7E,MAAMO,gBACJP,cAAc,CAAC,IAAId,MAAMO,MAAM,CAACQ,UAAU,GAAGf,MAAMO,MAAM,CAACQ,UAAU,CAACK,KAAK,CAACN,YAAY;gBACzF,MAAMQ,oBACJd,OAAOO,UAAU,CAACI,KAAK,CAACI,WAAW,OAAON,eAAeM,WAAW,MACpEf,OAAOO,UAAU,CAACS,IAAI,CAACD,WAAW,OAAOF,cAAcE,WAAW;gBAEpE,MAAM5B,4BAA4B;oBAChCO,aAAaF,MAAME,WAAW;oBAC9BJ,WAAWS,OAAOT,SAAS;oBAC3B2B,oBAAoBjB,OAAOkB,UAAU,CAACC,EAAE;oBACxCC,4BAA4BpB,OAAOO,UAAU,CAACH,oBAAoB;oBAClEiB,uBAAuBrB,OAAOO,UAAU,CAACI,KAAK;oBAC9CW,sBAAsBtB,OAAOO,UAAU,CAACS,IAAI;oBAC5CO,qBAAqBvB,OAAOO,UAAU,CAACiB,aAAa;gBACtD;gBAEA,IAAI,CAACV,mBAAmB;oBACtB,MAAMjC,4BACJD,4BAA4BgB,OAAO,CAACV,qBAAqB,EACzD,sCACA,CAAC;gBAEL;gBAEA,OAAO;oBACL,GAAGa,MAAM;oBACTI,cAAcH,OAAOkB,UAAU,CAACC,EAAE;oBAClCf,sBAAsBJ,OAAOO,UAAU,CAACH,oBAAoB;gBAC9D;YACF;YAEA,OAAOL;QACT;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/create-project.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,gDAAgD,CAAC;
|
|
1
|
+
{"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/create-project.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,gDAAgD,CAAC;AAiBxD,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,wBAAwB,mDAwFxE"}
|
|
@@ -3,7 +3,7 @@ import { integrationsInterModuleContract } from '@shipfox/api-integration-core-d
|
|
|
3
3
|
import { createProjectBodySchema, projectResponseSchema } from '@shipfox/api-projects-dto';
|
|
4
4
|
import { isInterModuleKnownError } from '@shipfox/inter-module';
|
|
5
5
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
6
|
-
import { createProjectFromSource, ProjectAlreadyExistsError } from '#core/index.js';
|
|
6
|
+
import { createProjectFromSource, ProjectAlreadyExistsError, ProjectSlugConflictError } from '#core/index.js';
|
|
7
7
|
import { toProjectDto } from '#presentation/dto/index.js';
|
|
8
8
|
function providerStatus(reason) {
|
|
9
9
|
if (reason === 'rate-limited') return 429;
|
|
@@ -59,6 +59,11 @@ export function createProjectRoute(integrations) {
|
|
|
59
59
|
status: 409
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
if (error instanceof ProjectSlugConflictError) {
|
|
63
|
+
throw new ClientError('Project slug already exists', 'slug-conflict', {
|
|
64
|
+
status: 409
|
|
65
|
+
});
|
|
66
|
+
}
|
|
62
67
|
if (known?.code === 'provider-failure') {
|
|
63
68
|
throw new ClientError('Integration provider request failed', known.details.reason, {
|
|
64
69
|
details: {
|
|
@@ -70,7 +75,7 @@ export function createProjectRoute(integrations) {
|
|
|
70
75
|
throw error;
|
|
71
76
|
},
|
|
72
77
|
handler: async (request, reply)=>{
|
|
73
|
-
const { workspace_id: workspaceId, name, source } = request.body;
|
|
78
|
+
const { workspace_id: workspaceId, name, slug, source } = request.body;
|
|
74
79
|
const actor = requireUserContext(request);
|
|
75
80
|
requireWorkspaceAccess({
|
|
76
81
|
request,
|
|
@@ -80,6 +85,7 @@ export function createProjectRoute(integrations) {
|
|
|
80
85
|
actorId: actor.userId,
|
|
81
86
|
workspaceId,
|
|
82
87
|
name,
|
|
88
|
+
slug,
|
|
83
89
|
sourceConnectionId: source.connection_id,
|
|
84
90
|
sourceExternalRepositoryId: source.external_repository_id,
|
|
85
91
|
integrations
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/create-project.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {\n type IntegrationsModuleClient,\n integrationsInterModuleContract,\n} from '@shipfox/api-integration-core-dto/inter-module';\nimport {createProjectBodySchema, projectResponseSchema} from '@shipfox/api-projects-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {createProjectFromSource
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/create-project.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext, requireWorkspaceAccess} from '@shipfox/api-auth-context';\nimport {\n type IntegrationsModuleClient,\n integrationsInterModuleContract,\n} from '@shipfox/api-integration-core-dto/inter-module';\nimport {createProjectBodySchema, projectResponseSchema} from '@shipfox/api-projects-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {\n createProjectFromSource,\n ProjectAlreadyExistsError,\n ProjectSlugConflictError,\n} from '#core/index.js';\nimport {toProjectDto} from '#presentation/dto/index.js';\n\nfunction providerStatus(reason: string): number {\n if (reason === 'rate-limited') return 429;\n if (reason === 'timeout' || reason === 'provider-unavailable') return 503;\n return 422;\n}\n\nexport function createProjectRoute(integrations: IntegrationsModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/',\n auth: AUTH_USER,\n description: 'Create a project bound to a source repository.',\n schema: {\n body: createProjectBodySchema,\n response: {\n 201: projectResponseSchema,\n },\n },\n errorHandler: (error) => {\n const known = isInterModuleKnownError(\n integrationsInterModuleContract.methods.resolveSourceRepository,\n error,\n )\n ? error\n : undefined;\n if (known?.code === 'connection-not-found') {\n throw new ClientError('Source connection not found', 'source-connection-not-found', {\n status: 404,\n });\n }\n if (known?.code === 'connection-workspace-mismatch') {\n throw new ClientError('Source connection does not belong to this workspace', 'forbidden', {\n status: 403,\n });\n }\n if (known?.code === 'connection-inactive') {\n throw new ClientError('Source connection is not active', 'source-connection-inactive', {\n status: 422,\n });\n }\n if (known?.code === 'provider-unavailable') {\n throw new ClientError(\n 'Integration provider is unavailable',\n 'integration-provider-unavailable',\n {status: 422},\n );\n }\n if (known?.code === 'capability-unavailable') {\n throw new ClientError(\n 'Integration capability is unavailable',\n 'integration-capability-unavailable',\n {status: 422},\n );\n }\n if (error instanceof ProjectAlreadyExistsError) {\n throw new ClientError(error.message, 'project-already-exists', {\n details: {\n existing_project_id: error.existingProjectId,\n source_connection_id: error.sourceConnectionId,\n source_external_repository_id: error.sourceExternalRepositoryId,\n },\n status: 409,\n });\n }\n if (error instanceof ProjectSlugConflictError) {\n throw new ClientError('Project slug already exists', 'slug-conflict', {status: 409});\n }\n if (known?.code === 'provider-failure') {\n throw new ClientError('Integration provider request failed', known.details.reason, {\n details: {retry_after_seconds: known.details.retryAfterSeconds},\n status: providerStatus(known.details.reason),\n });\n }\n throw error;\n },\n handler: async (request, reply) => {\n const {workspace_id: workspaceId, name, slug, source} = request.body;\n const actor = requireUserContext(request);\n\n requireWorkspaceAccess({request, workspaceId});\n const project = await createProjectFromSource({\n actorId: actor.userId,\n workspaceId,\n name,\n slug,\n sourceConnectionId: source.connection_id,\n sourceExternalRepositoryId: source.external_repository_id,\n integrations,\n });\n\n reply.status(201);\n return toProjectDto(project);\n },\n });\n}\n"],"names":["AUTH_USER","requireUserContext","requireWorkspaceAccess","integrationsInterModuleContract","createProjectBodySchema","projectResponseSchema","isInterModuleKnownError","ClientError","defineRoute","createProjectFromSource","ProjectAlreadyExistsError","ProjectSlugConflictError","toProjectDto","providerStatus","reason","createProjectRoute","integrations","method","path","auth","description","schema","body","response","errorHandler","error","known","methods","resolveSourceRepository","undefined","code","status","message","details","existing_project_id","existingProjectId","source_connection_id","sourceConnectionId","source_external_repository_id","sourceExternalRepositoryId","retry_after_seconds","retryAfterSeconds","handler","request","reply","workspace_id","workspaceId","name","slug","source","actor","project","actorId","userId","connection_id","external_repository_id"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,kBAAkB,EAAEC,sBAAsB,QAAO,4BAA4B;AAChG,SAEEC,+BAA+B,QAC1B,iDAAiD;AACxD,SAAQC,uBAAuB,EAAEC,qBAAqB,QAAO,4BAA4B;AACzF,SAAQC,uBAAuB,QAAO,wBAAwB;AAC9D,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SACEC,uBAAuB,EACvBC,yBAAyB,EACzBC,wBAAwB,QACnB,iBAAiB;AACxB,SAAQC,YAAY,QAAO,6BAA6B;AAExD,SAASC,eAAeC,MAAc;IACpC,IAAIA,WAAW,gBAAgB,OAAO;IACtC,IAAIA,WAAW,aAAaA,WAAW,wBAAwB,OAAO;IACtE,OAAO;AACT;AAEA,OAAO,SAASC,mBAAmBC,YAAsC;IACvE,OAAOR,YAAY;QACjBS,QAAQ;QACRC,MAAM;QACNC,MAAMnB;QACNoB,aAAa;QACbC,QAAQ;YACNC,MAAMlB;YACNmB,UAAU;gBACR,KAAKlB;YACP;QACF;QACAmB,cAAc,CAACC;YACb,MAAMC,QAAQpB,wBACZH,gCAAgCwB,OAAO,CAACC,uBAAuB,EAC/DH,SAEEA,QACAI;YACJ,IAAIH,OAAOI,SAAS,wBAAwB;gBAC1C,MAAM,IAAIvB,YAAY,+BAA+B,+BAA+B;oBAClFwB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,iCAAiC;gBACnD,MAAM,IAAIvB,YAAY,uDAAuD,aAAa;oBACxFwB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,uBAAuB;gBACzC,MAAM,IAAIvB,YAAY,mCAAmC,8BAA8B;oBACrFwB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,wBAAwB;gBAC1C,MAAM,IAAIvB,YACR,uCACA,oCACA;oBAACwB,QAAQ;gBAAG;YAEhB;YACA,IAAIL,OAAOI,SAAS,0BAA0B;gBAC5C,MAAM,IAAIvB,YACR,yCACA,sCACA;oBAACwB,QAAQ;gBAAG;YAEhB;YACA,IAAIN,iBAAiBf,2BAA2B;gBAC9C,MAAM,IAAIH,YAAYkB,MAAMO,OAAO,EAAE,0BAA0B;oBAC7DC,SAAS;wBACPC,qBAAqBT,MAAMU,iBAAiB;wBAC5CC,sBAAsBX,MAAMY,kBAAkB;wBAC9CC,+BAA+Bb,MAAMc,0BAA0B;oBACjE;oBACAR,QAAQ;gBACV;YACF;YACA,IAAIN,iBAAiBd,0BAA0B;gBAC7C,MAAM,IAAIJ,YAAY,+BAA+B,iBAAiB;oBAACwB,QAAQ;gBAAG;YACpF;YACA,IAAIL,OAAOI,SAAS,oBAAoB;gBACtC,MAAM,IAAIvB,YAAY,uCAAuCmB,MAAMO,OAAO,CAACnB,MAAM,EAAE;oBACjFmB,SAAS;wBAACO,qBAAqBd,MAAMO,OAAO,CAACQ,iBAAiB;oBAAA;oBAC9DV,QAAQlB,eAAea,MAAMO,OAAO,CAACnB,MAAM;gBAC7C;YACF;YACA,MAAMW;QACR;QACAiB,SAAS,OAAOC,SAASC;YACvB,MAAM,EAACC,cAAcC,WAAW,EAAEC,IAAI,EAAEC,IAAI,EAAEC,MAAM,EAAC,GAAGN,QAAQrB,IAAI;YACpE,MAAM4B,QAAQjD,mBAAmB0C;YAEjCzC,uBAAuB;gBAACyC;gBAASG;YAAW;YAC5C,MAAMK,UAAU,MAAM1C,wBAAwB;gBAC5C2C,SAASF,MAAMG,MAAM;gBACrBP;gBACAC;gBACAC;gBACAX,oBAAoBY,OAAOK,aAAa;gBACxCf,4BAA4BU,OAAOM,sBAAsB;gBACzDvC;YACF;YAEA4B,MAAMb,MAAM,CAAC;YACb,OAAOnB,aAAauC;QACtB;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,gDAAgD,CAAC;AAC7F,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,gDAAgD,CAAC;AAC7F,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAOtD,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,wBAAwB,EACtC,IAAI,EAAE,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,GACpD,UAAU,EAAE,CAgBd"}
|
|
@@ -2,6 +2,7 @@ import { createAdminProjectsRoute } from './admin-projects.js';
|
|
|
2
2
|
import { createProjectRoute } from './create-project.js';
|
|
3
3
|
import { getProjectRoute } from './get-project.js';
|
|
4
4
|
import { listProjectsRoute } from './list-projects.js';
|
|
5
|
+
import { updateProjectRoute } from './update-project.js';
|
|
5
6
|
export function createProjectRoutes(integrations, auth) {
|
|
6
7
|
return [
|
|
7
8
|
{
|
|
@@ -9,7 +10,8 @@ export function createProjectRoutes(integrations, auth) {
|
|
|
9
10
|
routes: [
|
|
10
11
|
createProjectRoute(integrations),
|
|
11
12
|
listProjectsRoute,
|
|
12
|
-
getProjectRoute
|
|
13
|
+
getProjectRoute,
|
|
14
|
+
updateProjectRoute
|
|
13
15
|
]
|
|
14
16
|
},
|
|
15
17
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createAdminProjectsRoute} from './admin-projects.js';\nimport {createProjectRoute} from './create-project.js';\nimport {getProjectRoute} from './get-project.js';\nimport {listProjectsRoute} from './list-projects.js';\n\nexport function createProjectRoutes(\n integrations: IntegrationsModuleClient,\n auth: Pick<AuthInterModuleClient, 'requireAdminRole'>,\n): RouteGroup[] {\n return [\n {\n prefix: '/projects',\n routes: [createProjectRoute(integrations)
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';\nimport type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';\nimport type {RouteGroup} from '@shipfox/node-fastify';\nimport {createAdminProjectsRoute} from './admin-projects.js';\nimport {createProjectRoute} from './create-project.js';\nimport {getProjectRoute} from './get-project.js';\nimport {listProjectsRoute} from './list-projects.js';\nimport {updateProjectRoute} from './update-project.js';\n\nexport function createProjectRoutes(\n integrations: IntegrationsModuleClient,\n auth: Pick<AuthInterModuleClient, 'requireAdminRole'>,\n): RouteGroup[] {\n return [\n {\n prefix: '/projects',\n routes: [\n createProjectRoute(integrations),\n listProjectsRoute,\n getProjectRoute,\n updateProjectRoute,\n ],\n },\n {\n prefix: '/admin/projects',\n routes: [createAdminProjectsRoute(auth)],\n },\n ];\n}\n"],"names":["createAdminProjectsRoute","createProjectRoute","getProjectRoute","listProjectsRoute","updateProjectRoute","createProjectRoutes","integrations","auth","prefix","routes"],"mappings":"AAGA,SAAQA,wBAAwB,QAAO,sBAAsB;AAC7D,SAAQC,kBAAkB,QAAO,sBAAsB;AACvD,SAAQC,eAAe,QAAO,mBAAmB;AACjD,SAAQC,iBAAiB,QAAO,qBAAqB;AACrD,SAAQC,kBAAkB,QAAO,sBAAsB;AAEvD,OAAO,SAASC,oBACdC,YAAsC,EACtCC,IAAqD;IAErD,OAAO;QACL;YACEC,QAAQ;YACRC,QAAQ;gBACNR,mBAAmBK;gBACnBH;gBACAD;gBACAE;aACD;QACH;QACA;YACEI,QAAQ;YACRC,QAAQ;gBAACT,yBAAyBO;aAAM;QAC1C;KACD;AACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-project.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/update-project.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,kBAAkB,iDAiC7B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AUTH_USER, requireUserContext } from '@shipfox/api-auth-context';
|
|
2
|
+
import { projectResponseSchema, updateProjectBodySchema } from '@shipfox/api-projects-dto';
|
|
3
|
+
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { ProjectNotFoundError, ProjectSlugConflictError, updateProjectDetails } from '#core/index.js';
|
|
6
|
+
import { requireProjectAccess } from '#presentation/auth/require-project-access.js';
|
|
7
|
+
import { toProjectDto } from '#presentation/dto/index.js';
|
|
8
|
+
const projectParamsSchema = z.object({
|
|
9
|
+
projectId: z.string().uuid()
|
|
10
|
+
});
|
|
11
|
+
export const updateProjectRoute = defineRoute({
|
|
12
|
+
method: 'PATCH',
|
|
13
|
+
path: '/:projectId',
|
|
14
|
+
auth: AUTH_USER,
|
|
15
|
+
description: 'Update a project.',
|
|
16
|
+
schema: {
|
|
17
|
+
params: projectParamsSchema,
|
|
18
|
+
body: updateProjectBodySchema,
|
|
19
|
+
response: {
|
|
20
|
+
200: projectResponseSchema
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
errorHandler: (error)=>{
|
|
24
|
+
if (error instanceof ProjectSlugConflictError) {
|
|
25
|
+
throw new ClientError('Project slug already exists', 'slug-conflict', {
|
|
26
|
+
status: 409
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (error instanceof ProjectNotFoundError) {
|
|
30
|
+
throw new ClientError('Project not found', 'project-not-found', {
|
|
31
|
+
status: 404
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
},
|
|
36
|
+
handler: async (request)=>{
|
|
37
|
+
const { projectId } = request.params;
|
|
38
|
+
await requireProjectAccess({
|
|
39
|
+
request,
|
|
40
|
+
projectId
|
|
41
|
+
});
|
|
42
|
+
const actor = requireUserContext(request);
|
|
43
|
+
const project = await updateProjectDetails({
|
|
44
|
+
actorId: actor.userId,
|
|
45
|
+
projectId,
|
|
46
|
+
name: request.body.name,
|
|
47
|
+
slug: request.body.slug
|
|
48
|
+
});
|
|
49
|
+
return toProjectDto(project);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=update-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/update-project.ts"],"sourcesContent":["import {AUTH_USER, requireUserContext} from '@shipfox/api-auth-context';\nimport {projectResponseSchema, updateProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {ProjectNotFoundError, ProjectSlugConflictError, updateProjectDetails} from '#core/index.js';\nimport {requireProjectAccess} from '#presentation/auth/require-project-access.js';\nimport {toProjectDto} from '#presentation/dto/index.js';\n\nconst projectParamsSchema = z.object({\n projectId: z.string().uuid(),\n});\n\nexport const updateProjectRoute = defineRoute({\n method: 'PATCH',\n path: '/:projectId',\n auth: AUTH_USER,\n description: 'Update a project.',\n schema: {\n params: projectParamsSchema,\n body: updateProjectBodySchema,\n response: {\n 200: projectResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof ProjectSlugConflictError) {\n throw new ClientError('Project slug already exists', 'slug-conflict', {status: 409});\n }\n if (error instanceof ProjectNotFoundError) {\n throw new ClientError('Project not found', 'project-not-found', {status: 404});\n }\n throw error;\n },\n handler: async (request) => {\n const {projectId} = request.params;\n await requireProjectAccess({request, projectId});\n const actor = requireUserContext(request);\n const project = await updateProjectDetails({\n actorId: actor.userId,\n projectId,\n name: request.body.name,\n slug: request.body.slug,\n });\n return toProjectDto(project);\n },\n});\n"],"names":["AUTH_USER","requireUserContext","projectResponseSchema","updateProjectBodySchema","ClientError","defineRoute","z","ProjectNotFoundError","ProjectSlugConflictError","updateProjectDetails","requireProjectAccess","toProjectDto","projectParamsSchema","object","projectId","string","uuid","updateProjectRoute","method","path","auth","description","schema","params","body","response","errorHandler","error","status","handler","request","actor","project","actorId","userId","name","slug"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,kBAAkB,QAAO,4BAA4B;AACxE,SAAQC,qBAAqB,EAAEC,uBAAuB,QAAO,4BAA4B;AACzF,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,oBAAoB,EAAEC,wBAAwB,EAAEC,oBAAoB,QAAO,iBAAiB;AACpG,SAAQC,oBAAoB,QAAO,+CAA+C;AAClF,SAAQC,YAAY,QAAO,6BAA6B;AAExD,MAAMC,sBAAsBN,EAAEO,MAAM,CAAC;IACnCC,WAAWR,EAAES,MAAM,GAAGC,IAAI;AAC5B;AAEA,OAAO,MAAMC,qBAAqBZ,YAAY;IAC5Ca,QAAQ;IACRC,MAAM;IACNC,MAAMpB;IACNqB,aAAa;IACbC,QAAQ;QACNC,QAAQX;QACRY,MAAMrB;QACNsB,UAAU;YACR,KAAKvB;QACP;IACF;IACAwB,cAAc,CAACC;QACb,IAAIA,iBAAiBnB,0BAA0B;YAC7C,MAAM,IAAIJ,YAAY,+BAA+B,iBAAiB;gBAACwB,QAAQ;YAAG;QACpF;QACA,IAAID,iBAAiBpB,sBAAsB;YACzC,MAAM,IAAIH,YAAY,qBAAqB,qBAAqB;gBAACwB,QAAQ;YAAG;QAC9E;QACA,MAAMD;IACR;IACAE,SAAS,OAAOC;QACd,MAAM,EAAChB,SAAS,EAAC,GAAGgB,QAAQP,MAAM;QAClC,MAAMb,qBAAqB;YAACoB;YAAShB;QAAS;QAC9C,MAAMiB,QAAQ9B,mBAAmB6B;QACjC,MAAME,UAAU,MAAMvB,qBAAqB;YACzCwB,SAASF,MAAMG,MAAM;YACrBpB;YACAqB,MAAML,QAAQN,IAAI,CAACW,IAAI;YACvBC,MAAMN,QAAQN,IAAI,CAACY,IAAI;QACzB;QACA,OAAOzB,aAAaqB;IACtB;AACF,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAC,yBAAyB,EAAC,MAAM,mCAAmC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/subscribers/index.ts"],"sourcesContent":["export {onSourceCommitPushed} from './on-source-commit-pushed.js';\n"],"names":["onSourceCommitPushed"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,+BAA+B"}
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/subscribers/index.ts"],"sourcesContent":["export {onSourceCommitPushed} from './on-source-commit-pushed.js';\nexport {onSourceRepositoryUpdated} from './on-source-repository-updated.js';\n"],"names":["onSourceCommitPushed","onSourceRepositoryUpdated"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,+BAA+B;AAClE,SAAQC,yBAAyB,QAAO,oCAAoC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IntegrationSourceRepositoryUpdatedEvent } from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import type { DomainEvent } from '@shipfox/node-outbox';
|
|
3
|
+
export declare function onSourceRepositoryUpdated(payload: IntegrationSourceRepositoryUpdatedEvent, event: DomainEvent<IntegrationSourceRepositoryUpdatedEvent>): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=on-source-repository-updated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-source-repository-updated.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-source-repository-updated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,uCAAuC,EAAC,MAAM,mCAAmC,CAAC;AAE/F,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAC;AAKtD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,uCAAuC,EAChD,KAAK,EAAE,WAAW,CAAC,uCAAuC,CAAC,GAC1D,OAAO,CAAC,IAAI,CAAC,CAuCf"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
|
+
import { db } from '#db/db.js';
|
|
3
|
+
import { recordIntegrationEventForProject } from '#db/integration-event-dedup.js';
|
|
4
|
+
import { getProjectBySource, updateProjectSourceRepository } from '#db/projects.js';
|
|
5
|
+
export async function onSourceRepositoryUpdated(payload, event) {
|
|
6
|
+
const project = await getProjectBySource({
|
|
7
|
+
workspaceId: payload.workspaceId,
|
|
8
|
+
sourceConnectionId: payload.connectionId,
|
|
9
|
+
sourceExternalRepositoryId: payload.repository.externalRepositoryId
|
|
10
|
+
});
|
|
11
|
+
if (project) {
|
|
12
|
+
const outcome = await db().transaction(async (tx)=>{
|
|
13
|
+
const { firstSeen } = await recordIntegrationEventForProject({
|
|
14
|
+
tx,
|
|
15
|
+
integrationEventId: event.id,
|
|
16
|
+
projectId: project.id
|
|
17
|
+
});
|
|
18
|
+
if (!firstSeen) return 'duplicate';
|
|
19
|
+
const updated = await updateProjectSourceRepository({
|
|
20
|
+
tx,
|
|
21
|
+
workspaceId: payload.workspaceId,
|
|
22
|
+
sourceConnectionId: payload.connectionId,
|
|
23
|
+
sourceExternalRepositoryId: payload.repository.externalRepositoryId,
|
|
24
|
+
sourceRepositoryOwner: payload.repository.owner,
|
|
25
|
+
sourceRepositoryName: payload.repository.name,
|
|
26
|
+
sourceDefaultBranch: payload.repository.defaultBranch
|
|
27
|
+
});
|
|
28
|
+
return updated ? 'updated' : 'missing';
|
|
29
|
+
});
|
|
30
|
+
if (outcome !== 'missing') return;
|
|
31
|
+
}
|
|
32
|
+
logger().info({
|
|
33
|
+
eventId: event.id,
|
|
34
|
+
connectionId: payload.connectionId,
|
|
35
|
+
externalRepositoryId: payload.repository.externalRepositoryId
|
|
36
|
+
}, 'source repository updated: no project bound to source, dropping');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=on-source-repository-updated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/subscribers/on-source-repository-updated.ts"],"sourcesContent":["import type {IntegrationSourceRepositoryUpdatedEvent} from '@shipfox/api-integration-core-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {DomainEvent} from '@shipfox/node-outbox';\nimport {db} from '#db/db.js';\nimport {recordIntegrationEventForProject} from '#db/integration-event-dedup.js';\nimport {getProjectBySource, updateProjectSourceRepository} from '#db/projects.js';\n\nexport async function onSourceRepositoryUpdated(\n payload: IntegrationSourceRepositoryUpdatedEvent,\n event: DomainEvent<IntegrationSourceRepositoryUpdatedEvent>,\n): Promise<void> {\n const project = await getProjectBySource({\n workspaceId: payload.workspaceId,\n sourceConnectionId: payload.connectionId,\n sourceExternalRepositoryId: payload.repository.externalRepositoryId,\n });\n\n if (project) {\n const outcome = await db().transaction(async (tx) => {\n const {firstSeen} = await recordIntegrationEventForProject({\n tx,\n integrationEventId: event.id,\n projectId: project.id,\n });\n if (!firstSeen) return 'duplicate';\n\n const updated = await updateProjectSourceRepository({\n tx,\n workspaceId: payload.workspaceId,\n sourceConnectionId: payload.connectionId,\n sourceExternalRepositoryId: payload.repository.externalRepositoryId,\n sourceRepositoryOwner: payload.repository.owner,\n sourceRepositoryName: payload.repository.name,\n sourceDefaultBranch: payload.repository.defaultBranch,\n });\n return updated ? 'updated' : 'missing';\n });\n\n if (outcome !== 'missing') return;\n }\n\n logger().info(\n {\n eventId: event.id,\n connectionId: payload.connectionId,\n externalRepositoryId: payload.repository.externalRepositoryId,\n },\n 'source repository updated: no project bound to source, dropping',\n );\n}\n"],"names":["logger","db","recordIntegrationEventForProject","getProjectBySource","updateProjectSourceRepository","onSourceRepositoryUpdated","payload","event","project","workspaceId","sourceConnectionId","connectionId","sourceExternalRepositoryId","repository","externalRepositoryId","outcome","transaction","tx","firstSeen","integrationEventId","id","projectId","updated","sourceRepositoryOwner","owner","sourceRepositoryName","name","sourceDefaultBranch","defaultBranch","info","eventId"],"mappings":"AACA,SAAQA,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,gCAAgC,QAAO,iCAAiC;AAChF,SAAQC,kBAAkB,EAAEC,6BAA6B,QAAO,kBAAkB;AAElF,OAAO,eAAeC,0BACpBC,OAAgD,EAChDC,KAA2D;IAE3D,MAAMC,UAAU,MAAML,mBAAmB;QACvCM,aAAaH,QAAQG,WAAW;QAChCC,oBAAoBJ,QAAQK,YAAY;QACxCC,4BAA4BN,QAAQO,UAAU,CAACC,oBAAoB;IACrE;IAEA,IAAIN,SAAS;QACX,MAAMO,UAAU,MAAMd,KAAKe,WAAW,CAAC,OAAOC;YAC5C,MAAM,EAACC,SAAS,EAAC,GAAG,MAAMhB,iCAAiC;gBACzDe;gBACAE,oBAAoBZ,MAAMa,EAAE;gBAC5BC,WAAWb,QAAQY,EAAE;YACvB;YACA,IAAI,CAACF,WAAW,OAAO;YAEvB,MAAMI,UAAU,MAAMlB,8BAA8B;gBAClDa;gBACAR,aAAaH,QAAQG,WAAW;gBAChCC,oBAAoBJ,QAAQK,YAAY;gBACxCC,4BAA4BN,QAAQO,UAAU,CAACC,oBAAoB;gBACnES,uBAAuBjB,QAAQO,UAAU,CAACW,KAAK;gBAC/CC,sBAAsBnB,QAAQO,UAAU,CAACa,IAAI;gBAC7CC,qBAAqBrB,QAAQO,UAAU,CAACe,aAAa;YACvD;YACA,OAAON,UAAU,YAAY;QAC/B;QAEA,IAAIP,YAAY,WAAW;IAC7B;IAEAf,SAAS6B,IAAI,CACX;QACEC,SAASvB,MAAMa,EAAE;QACjBT,cAAcL,QAAQK,YAAY;QAClCG,sBAAsBR,QAAQO,UAAU,CAACC,oBAAoB;IAC/D,GACA;AAEJ"}
|