@shipfox/api-projects 10.2.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.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +83 -0
  4. package/dist/core/entities/project.d.ts +4 -0
  5. package/dist/core/entities/project.d.ts.map +1 -1
  6. package/dist/core/entities/project.js.map +1 -1
  7. package/dist/core/errors.d.ts +4 -0
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js +6 -0
  10. package/dist/core/errors.js.map +1 -1
  11. package/dist/core/index.d.ts +3 -3
  12. package/dist/core/index.d.ts.map +1 -1
  13. package/dist/core/index.js +2 -2
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/projects.d.ts +8 -0
  16. package/dist/core/projects.d.ts.map +1 -1
  17. package/dist/core/projects.js +62 -19
  18. package/dist/core/projects.js.map +1 -1
  19. package/dist/db/db.d.ts +138 -0
  20. package/dist/db/db.d.ts.map +1 -1
  21. package/dist/db/db.js.map +1 -1
  22. package/dist/db/index.d.ts +2 -2
  23. package/dist/db/index.d.ts.map +1 -1
  24. package/dist/db/index.js +1 -1
  25. package/dist/db/index.js.map +1 -1
  26. package/dist/db/projects.d.ts +53 -0
  27. package/dist/db/projects.d.ts.map +1 -1
  28. package/dist/db/projects.js +116 -18
  29. package/dist/db/projects.js.map +1 -1
  30. package/dist/db/schema/projects.d.ts +68 -0
  31. package/dist/db/schema/projects.d.ts.map +1 -1
  32. package/dist/db/schema/projects.js +12 -1
  33. package/dist/db/schema/projects.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -5
  37. package/dist/index.js.map +1 -1
  38. package/dist/presentation/dto/project.d.ts +1 -0
  39. package/dist/presentation/dto/project.d.ts.map +1 -1
  40. package/dist/presentation/dto/project.js +1 -0
  41. package/dist/presentation/dto/project.js.map +1 -1
  42. package/dist/presentation/e2eRoutes/create-project.d.ts.map +1 -1
  43. package/dist/presentation/e2eRoutes/create-project.js +34 -8
  44. package/dist/presentation/e2eRoutes/create-project.js.map +1 -1
  45. package/dist/presentation/inter-module.d.ts +4 -1
  46. package/dist/presentation/inter-module.d.ts.map +1 -1
  47. package/dist/presentation/inter-module.js +44 -3
  48. package/dist/presentation/inter-module.js.map +1 -1
  49. package/dist/presentation/routes/create-project.d.ts.map +1 -1
  50. package/dist/presentation/routes/create-project.js +8 -2
  51. package/dist/presentation/routes/create-project.js.map +1 -1
  52. package/dist/presentation/routes/index.d.ts.map +1 -1
  53. package/dist/presentation/routes/index.js +3 -1
  54. package/dist/presentation/routes/index.js.map +1 -1
  55. package/dist/presentation/routes/update-project.d.ts +2 -0
  56. package/dist/presentation/routes/update-project.d.ts.map +1 -0
  57. package/dist/presentation/routes/update-project.js +53 -0
  58. package/dist/presentation/routes/update-project.js.map +1 -0
  59. package/dist/presentation/subscribers/index.d.ts +1 -0
  60. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  61. package/dist/presentation/subscribers/index.js +1 -0
  62. package/dist/presentation/subscribers/index.js.map +1 -1
  63. package/dist/presentation/subscribers/on-source-repository-updated.d.ts +4 -0
  64. package/dist/presentation/subscribers/on-source-repository-updated.d.ts.map +1 -0
  65. package/dist/presentation/subscribers/on-source-repository-updated.js +39 -0
  66. package/dist/presentation/subscribers/on-source-repository-updated.js.map +1 -0
  67. package/dist/tsconfig.test.tsbuildinfo +1 -1
  68. package/drizzle/0000_initial.sql +6 -0
  69. package/drizzle/meta/0000_snapshot.json +78 -0
  70. package/package.json +13 -12
  71. package/src/core/entities/project.ts +4 -0
  72. package/src/core/errors.ts +7 -0
  73. package/src/core/index.ts +3 -2
  74. package/src/core/projects.test.ts +10 -0
  75. package/src/core/projects.ts +90 -35
  76. package/src/db/db.ts +3 -0
  77. package/src/db/index.ts +6 -0
  78. package/src/db/projects.test.ts +44 -1
  79. package/src/db/projects.ts +242 -33
  80. package/src/db/schema/projects.ts +16 -0
  81. package/src/index.ts +9 -3
  82. package/src/presentation/dto/project.ts +1 -0
  83. package/src/presentation/e2eRoutes/create-project.test.ts +23 -0
  84. package/src/presentation/e2eRoutes/create-project.ts +39 -10
  85. package/src/presentation/inter-module.test.ts +309 -0
  86. package/src/presentation/inter-module.ts +76 -4
  87. package/src/presentation/routes/create-project.ts +10 -2
  88. package/src/presentation/routes/index.ts +7 -1
  89. package/src/presentation/routes/projects.test.ts +301 -5
  90. package/src/presentation/routes/update-project.ts +46 -0
  91. package/src/presentation/subscribers/index.ts +1 -0
  92. package/src/presentation/subscribers/on-source-commit-pushed.test.ts +2 -0
  93. package/src/presentation/subscribers/on-source-repository-updated.test.ts +108 -0
  94. package/src/presentation/subscribers/on-source-repository-updated.ts +50 -0
  95. package/test/factories/project.ts +6 -0
  96. 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,QAE3B,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,QAAO,qCAAqC;AACxE,SAAQC,mCAAmC,QAAO,gCAAgC;AAClF,SAAQC,+BAA+B,QAAO,yBAAyB;AAEvE,MAAMC,cAAcf,QAAQD,QAAQE,cAAc,YAAYe,GAAG,IAAI;AACrE,MAAMC,2BAA2BjB,QAAQe,aAAa;AAEtD,MAAMG,aAAad;AAGnB,SACEe,uBAAuB,EACvBC,wBAAwB,EACxBC,yBAAyB,EACzBC,oBAAoB,QACf,iBAAiB;AAExB,SACEC,aAAa,EACblB,EAAE,EACFmB,cAAc,EACdC,kBAAkB,EAClBC,yBAAyB,EACzBC,YAAY,EACZrB,cAAc,EACdC,cAAc,EACdqB,0BAA0B,QACrB,eAAe;AACtB,SAAQlB,mBAAmB,EAAEmB,oBAAoB,QAAO,yBAAyB;AAOjF,OAAO,SAASC,qBAAqB,EACnCC,YAAY,EACZC,IAAI,EACwB;IAC5B,OAAO;QACLC,MAAM;QACNC,UAAU;YAAC7B;YAAIC;YAAgB6B,mBAAmB;QAAU;QAC5DC,QAAQ1B,oBAAoBqB,cAAcC;QAC1CK,WAAW;YAAC5B;SAAkB;QAC9B6B,SAAS9B;QACT+B,YAAY;YAAC;gBAACN,MAAM;gBAAYO,OAAOjC;gBAAgBF;gBAAIoC,cAActC;YAAoB;SAAE;QAC/FuC,0BAA0B;YAAC/B;SAAwC;QACnEgC,aAAa;YAACzB,WAAWhB,kCAAkCU;SAAsB;QACjFgC,SAAS;YACP;gBACEC,WAAW/B;gBACXgC,eAAe7B;gBACf8B,YAAYlC;gBACZmC,WAAW;oBACT;wBACEf,MAAM;wBACNgB,IAAI;wBACJC,cAAc;oBAChB;iBACD;YACH;SACD;IACH;AACF"}
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"}
@@ -4,6 +4,7 @@ export declare function toProjectDto(project: Project): {
4
4
  id: string;
5
5
  workspace_id: string;
6
6
  name: string;
7
+ slug: string;
7
8
  source: {
8
9
  connection_id: string;
9
10
  external_repository_id: string;
@@ -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;;;;;;;;;;EAY5C;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,mBAAmB;;;;;;;EASpE"}
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"}
@@ -3,6 +3,7 @@ export function toProjectDto(project) {
3
3
  id: project.id,
4
4
  workspace_id: project.workspaceId,
5
5
  name: project.name,
6
+ slug: project.slug,
6
7
  source: {
7
8
  connection_id: project.sourceConnectionId,
8
9
  external_repository_id: project.sourceExternalRepositoryId
@@ -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,eAAeN,QAAQO,kBAAkB;YACzCC,wBAAwBR,QAAQS,0BAA0B;QAC5D;QACAC,YAAYV,QAAQW,SAAS,CAACC,WAAW;QACzCC,YAAYb,QAAQc,SAAS,CAACF,WAAW;IAC3C;AACF;AAEA,OAAO,SAASG,yBAAyBf,OAA4B;IACnE,OAAO;QACLC,IAAID,QAAQC,EAAE;QACdG,MAAMJ,QAAQI,IAAI;QAClBY,QAAQ;QACRd,cAAcF,QAAQG,WAAW;QACjCO,YAAYV,QAAQW,SAAS,CAACC,WAAW;QACzCC,YAAYb,QAAQc,SAAS,CAACF,WAAW;IAC3C;AACF"}
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":"AAcA,eAAO,MAAM,qBAAqB,iDAmChC,CAAC"}
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 project = await createProject({
35
- workspaceId: request.body.workspace_id,
36
- name: request.body.name,
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
- reply.code(201);
41
- return toProjectDto(project);
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 workspaceId: request.body.workspace_id,\n name: request.body.name,\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 },\n});\n"],"names":["randomUUID","e2eCreateProjectBodySchema","e2eCreateProjectResponseSchema","ClientError","defineRoute","ProjectAlreadyExistsError","createProject","toProjectDto","syntheticExternalRepositoryId","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","project","workspaceId","workspace_id","name","code"],"mappings":"AAAA,SAAQA,UAAU,QAAO,cAAc;AACvC,SACEC,0BAA0B,EAC1BC,8BAA8B,QACzB,4BAA4B;AACnC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,yBAAyB,QAAO,iBAAiB;AACzD,SAAQC,aAAa,QAAO,eAAe;AAC3C,SAAQC,YAAY,QAAO,6BAA6B;AAExD,SAASC;IACP,OAAO,CAAC,IAAI,EAAER,cAAc;AAC9B;AAEA,OAAO,MAAMS,wBAAwBL,YAAY;IAC/CM,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAMb;QACNc,UAAU;YACR,KAAKb;QACP;IACF;IACAc,cAAc,CAACC;QACb,IAAIA,iBAAiBZ,2BAA2B;YAC9C,MAAM,IAAIF,YAAYc,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,MAAMT;IACR;IACAU,SAAS,OAAOC,SAASC;QACvB,MAAMC,UAAU,MAAMxB,cAAc;YAClCyB,aAAaH,QAAQd,IAAI,CAACkB,YAAY;YACtCC,MAAML,QAAQd,IAAI,CAACmB,IAAI;YACvBV,oBAAoBK,QAAQd,IAAI,CAACQ,oBAAoB,IAAItB;YACzDyB,4BACEG,QAAQd,IAAI,CAACU,6BAA6B,IAAIhB;QAClD;QAEAqB,MAAMK,IAAI,CAAC;QACX,OAAO3B,aAAauB;IACtB;AACF,GAAG"}
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(): InterModulePresentation<typeof projectsInterModuleContract>;
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;AAG/B,wBAAgB,qCAAqC,IAAI,uBAAuB,CAC9E,OAAO,2BAA2B,CACnC,CAyBA"}
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, getWorkspaceProjectCounts} from '#db/projects.js';\n\nexport function createProjectsInterModulePresentation(): InterModulePresentation<\n typeof projectsInterModuleContract\n> {\n return defineInterModulePresentation(projectsInterModuleContract, {\n getProjectById: async ({projectId}) => ({project: (await getProjectById(projectId)) ?? null}),\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 });\n}\n"],"names":["projectsInterModuleContract","createInterModuleKnownError","defineInterModulePresentation","getProjectById","getWorkspaceProjectCounts","createProjectsInterModulePresentation","projectId","project","requireProjectForWorkspace","workspaceId","undefined","methods","workspaceIds","counts"],"mappings":"AAAA,SAAQA,2BAA2B,QAAO,yCAAyC;AACnF,SACEC,2BAA2B,EAC3BC,6BAA6B,QAExB,wBAAwB;AAC/B,SAAQC,cAAc,EAAEC,yBAAyB,QAAO,kBAAkB;AAE1E,OAAO,SAASC;IAGd,OAAOH,8BAA8BF,6BAA6B;QAChEG,gBAAgB,OAAO,EAACG,SAAS,EAAC,GAAM,CAAA;gBAACC,SAAS,AAAC,MAAMJ,eAAeG,cAAe;YAAI,CAAA;QAC3FE,4BAA4B,OAAO,EAACF,SAAS,EAAEG,WAAW,EAAC;YACzD,MAAMF,UAAU,MAAMJ,eAAeG;YACrC,IAAIC,YAAYG,WAAW;gBACzB,MAAMT,4BACJD,4BAA4BW,OAAO,CAACH,0BAA0B,EAC9D,qBACA;oBAACF;gBAAS;YAEd;YACA,IAAIC,QAAQE,WAAW,KAAKA,aAAa;gBACvC,MAAMR,4BACJD,4BAA4BW,OAAO,CAACH,0BAA0B,EAC9D,8BACA;oBAACF;oBAAWG;gBAAW;YAE3B;YACA,OAAO;gBAACF;YAAO;QACjB;QACAH,2BAA2B,OAAO,EAACQ,YAAY,EAAC,GAAM,CAAA;gBACpDC,QAAQ,MAAMT,0BAA0B;oBAACQ;gBAAY;YACvD,CAAA;IACF;AACF"}
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;AAaxD,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,wBAAwB,mDAoFxE"}
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, ProjectAlreadyExistsError} 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 (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, 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 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","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","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,SAAQC,uBAAuB,EAAEC,yBAAyB,QAAO,iBAAiB;AAClF,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,OAAOP,YAAY;QACjBQ,QAAQ;QACRC,MAAM;QACNC,MAAMlB;QACNmB,aAAa;QACbC,QAAQ;YACNC,MAAMjB;YACNkB,UAAU;gBACR,KAAKjB;YACP;QACF;QACAkB,cAAc,CAACC;YACb,MAAMC,QAAQnB,wBACZH,gCAAgCuB,OAAO,CAACC,uBAAuB,EAC/DH,SAEEA,QACAI;YACJ,IAAIH,OAAOI,SAAS,wBAAwB;gBAC1C,MAAM,IAAItB,YAAY,+BAA+B,+BAA+B;oBAClFuB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,iCAAiC;gBACnD,MAAM,IAAItB,YAAY,uDAAuD,aAAa;oBACxFuB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,uBAAuB;gBACzC,MAAM,IAAItB,YAAY,mCAAmC,8BAA8B;oBACrFuB,QAAQ;gBACV;YACF;YACA,IAAIL,OAAOI,SAAS,wBAAwB;gBAC1C,MAAM,IAAItB,YACR,uCACA,oCACA;oBAACuB,QAAQ;gBAAG;YAEhB;YACA,IAAIL,OAAOI,SAAS,0BAA0B;gBAC5C,MAAM,IAAItB,YACR,yCACA,sCACA;oBAACuB,QAAQ;gBAAG;YAEhB;YACA,IAAIN,iBAAiBd,2BAA2B;gBAC9C,MAAM,IAAIH,YAAYiB,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,IAAIL,OAAOI,SAAS,oBAAoB;gBACtC,MAAM,IAAItB,YAAY,uCAAuCkB,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,MAAM,EAAC,GAAGL,QAAQrB,IAAI;YAC9D,MAAM2B,QAAQ/C,mBAAmByC;YAEjCxC,uBAAuB;gBAACwC;gBAASG;YAAW;YAC5C,MAAMI,UAAU,MAAMxC,wBAAwB;gBAC5CyC,SAASF,MAAMG,MAAM;gBACrBN;gBACAC;gBACAV,oBAAoBW,OAAOK,aAAa;gBACxCd,4BAA4BS,OAAOM,sBAAsB;gBACzDtC;YACF;YAEA4B,MAAMb,MAAM,CAAC;YACb,OAAOnB,aAAasC;QACtB;IACF;AACF"}
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;AAMtD,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,wBAAwB,EACtC,IAAI,EAAE,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,GACpD,UAAU,EAAE,CAWd"}
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), listProjectsRoute, getProjectRoute],\n },\n {\n prefix: '/admin/projects',\n routes: [createAdminProjectsRoute(auth)],\n },\n ];\n}\n"],"names":["createAdminProjectsRoute","createProjectRoute","getProjectRoute","listProjectsRoute","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;AAErD,OAAO,SAASC,oBACdC,YAAsC,EACtCC,IAAqD;IAErD,OAAO;QACL;YACEC,QAAQ;YACRC,QAAQ;gBAACP,mBAAmBI;gBAAeF;gBAAmBD;aAAgB;QAChF;QACA;YACEK,QAAQ;YACRC,QAAQ;gBAACR,yBAAyBM;aAAM;QAC1C;KACD;AACH"}
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,2 @@
1
+ export declare const updateProjectRoute: import("@shipfox/node-fastify").RouteDefinition;
2
+ //# sourceMappingURL=update-project.d.ts.map
@@ -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,2 +1,3 @@
1
1
  export { onSourceCommitPushed } from './on-source-commit-pushed.js';
2
+ export { onSourceRepositoryUpdated } from './on-source-repository-updated.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -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,3 +1,4 @@
1
1
  export { onSourceCommitPushed } from './on-source-commit-pushed.js';
2
+ export { onSourceRepositoryUpdated } from './on-source-repository-updated.js';
2
3
 
3
4
  //# sourceMappingURL=index.js.map
@@ -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"}