@shipfox/api-projects 11.0.0 → 12.2.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 (98) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +53 -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 +144 -6
  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/integration-event-dedup.d.ts +1 -1
  31. package/dist/db/schema/outbox.d.ts +1 -1
  32. package/dist/db/schema/projects.d.ts +69 -1
  33. package/dist/db/schema/projects.d.ts.map +1 -1
  34. package/dist/db/schema/projects.js +12 -1
  35. package/dist/db/schema/projects.js.map +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +8 -5
  39. package/dist/index.js.map +1 -1
  40. package/dist/presentation/dto/project.d.ts +2 -1
  41. package/dist/presentation/dto/project.d.ts.map +1 -1
  42. package/dist/presentation/dto/project.js +1 -0
  43. package/dist/presentation/dto/project.js.map +1 -1
  44. package/dist/presentation/e2eRoutes/create-project.d.ts.map +1 -1
  45. package/dist/presentation/e2eRoutes/create-project.js +34 -8
  46. package/dist/presentation/e2eRoutes/create-project.js.map +1 -1
  47. package/dist/presentation/inter-module.d.ts +4 -1
  48. package/dist/presentation/inter-module.d.ts.map +1 -1
  49. package/dist/presentation/inter-module.js +63 -3
  50. package/dist/presentation/inter-module.js.map +1 -1
  51. package/dist/presentation/routes/create-project.d.ts.map +1 -1
  52. package/dist/presentation/routes/create-project.js +8 -2
  53. package/dist/presentation/routes/create-project.js.map +1 -1
  54. package/dist/presentation/routes/index.d.ts.map +1 -1
  55. package/dist/presentation/routes/index.js +3 -1
  56. package/dist/presentation/routes/index.js.map +1 -1
  57. package/dist/presentation/routes/update-project.d.ts +2 -0
  58. package/dist/presentation/routes/update-project.d.ts.map +1 -0
  59. package/dist/presentation/routes/update-project.js +53 -0
  60. package/dist/presentation/routes/update-project.js.map +1 -0
  61. package/dist/presentation/subscribers/index.d.ts +1 -0
  62. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  63. package/dist/presentation/subscribers/index.js +1 -0
  64. package/dist/presentation/subscribers/index.js.map +1 -1
  65. package/dist/presentation/subscribers/on-source-repository-updated.d.ts +4 -0
  66. package/dist/presentation/subscribers/on-source-repository-updated.d.ts.map +1 -0
  67. package/dist/presentation/subscribers/on-source-repository-updated.js +39 -0
  68. package/dist/presentation/subscribers/on-source-repository-updated.js.map +1 -0
  69. package/dist/tsconfig.test.tsbuildinfo +1 -1
  70. package/drizzle/0000_initial.sql +6 -0
  71. package/drizzle/meta/0000_snapshot.json +78 -0
  72. package/package.json +13 -12
  73. package/src/core/entities/project.ts +4 -0
  74. package/src/core/errors.ts +7 -0
  75. package/src/core/index.ts +3 -2
  76. package/src/core/projects.test.ts +10 -0
  77. package/src/core/projects.ts +90 -35
  78. package/src/db/db.ts +3 -0
  79. package/src/db/index.ts +6 -0
  80. package/src/db/projects.test.ts +44 -1
  81. package/src/db/projects.ts +242 -33
  82. package/src/db/schema/projects.ts +16 -0
  83. package/src/index.ts +9 -3
  84. package/src/presentation/dto/project.ts +1 -0
  85. package/src/presentation/e2eRoutes/create-project.test.ts +23 -0
  86. package/src/presentation/e2eRoutes/create-project.ts +39 -10
  87. package/src/presentation/inter-module.test.ts +349 -0
  88. package/src/presentation/inter-module.ts +90 -4
  89. package/src/presentation/routes/create-project.ts +10 -2
  90. package/src/presentation/routes/index.ts +7 -1
  91. package/src/presentation/routes/projects.test.ts +300 -4
  92. package/src/presentation/routes/update-project.ts +46 -0
  93. package/src/presentation/subscribers/index.ts +1 -0
  94. package/src/presentation/subscribers/on-source-commit-pushed.test.ts +2 -0
  95. package/src/presentation/subscribers/on-source-repository-updated.test.ts +108 -0
  96. package/src/presentation/subscribers/on-source-repository-updated.ts +50 -0
  97. package/test/factories/project.ts +6 -0
  98. package/tsconfig.build.tsbuildinfo +1 -1
@@ -54,6 +54,6 @@ export declare const projectsIntegrationEventDedup: import("drizzle-orm/pg-core"
54
54
  generated: undefined;
55
55
  }, {}, {}>;
56
56
  };
57
- dialect: "pg";
57
+ dialect: 'pg';
58
58
  }>;
59
59
  //# sourceMappingURL=integration-event-dedup.d.ts.map
@@ -190,6 +190,6 @@ export declare const projectsOutbox: import("drizzle-orm/pg-core").PgTableWithCo
190
190
  generated: undefined;
191
191
  }, {}, {}>;
192
192
  };
193
- dialect: "pg";
193
+ dialect: 'pg';
194
194
  }>;
195
195
  //# sourceMappingURL=outbox.d.ts.map
@@ -71,6 +71,57 @@ export declare const projects: import("drizzle-orm/pg-core").PgTableWithColumns<
71
71
  identity: undefined;
72
72
  generated: undefined;
73
73
  }, {}, {}>;
74
+ sourceRepositoryOwner: import("drizzle-orm/pg-core").PgColumn<{
75
+ name: "source_repository_owner";
76
+ tableName: "projects";
77
+ dataType: "string";
78
+ columnType: "PgText";
79
+ data: string;
80
+ driverParam: string;
81
+ notNull: false;
82
+ hasDefault: false;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: [string, ...string[]];
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {}>;
91
+ sourceRepositoryName: import("drizzle-orm/pg-core").PgColumn<{
92
+ name: "source_repository_name";
93
+ tableName: "projects";
94
+ dataType: "string";
95
+ columnType: "PgText";
96
+ data: string;
97
+ driverParam: string;
98
+ notNull: false;
99
+ hasDefault: false;
100
+ isPrimaryKey: false;
101
+ isAutoincrement: false;
102
+ hasRuntimeDefault: false;
103
+ enumValues: [string, ...string[]];
104
+ baseColumn: never;
105
+ identity: undefined;
106
+ generated: undefined;
107
+ }, {}, {}>;
108
+ sourceDefaultBranch: import("drizzle-orm/pg-core").PgColumn<{
109
+ name: "source_default_branch";
110
+ tableName: "projects";
111
+ dataType: "string";
112
+ columnType: "PgText";
113
+ data: string;
114
+ driverParam: string;
115
+ notNull: false;
116
+ hasDefault: false;
117
+ isPrimaryKey: false;
118
+ isAutoincrement: false;
119
+ hasRuntimeDefault: false;
120
+ enumValues: [string, ...string[]];
121
+ baseColumn: never;
122
+ identity: undefined;
123
+ generated: undefined;
124
+ }, {}, {}>;
74
125
  name: import("drizzle-orm/pg-core").PgColumn<{
75
126
  name: "name";
76
127
  tableName: "projects";
@@ -88,6 +139,23 @@ export declare const projects: import("drizzle-orm/pg-core").PgTableWithColumns<
88
139
  identity: undefined;
89
140
  generated: undefined;
90
141
  }, {}, {}>;
142
+ slug: import("drizzle-orm/pg-core").PgColumn<{
143
+ name: "slug";
144
+ tableName: "projects";
145
+ dataType: "string";
146
+ columnType: "PgText";
147
+ data: string;
148
+ driverParam: string;
149
+ notNull: true;
150
+ hasDefault: false;
151
+ isPrimaryKey: false;
152
+ isAutoincrement: false;
153
+ hasRuntimeDefault: false;
154
+ enumValues: [string, ...string[]];
155
+ baseColumn: never;
156
+ identity: undefined;
157
+ generated: undefined;
158
+ }, {}, {}>;
91
159
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
92
160
  name: "created_at";
93
161
  tableName: "projects";
@@ -123,7 +191,7 @@ export declare const projects: import("drizzle-orm/pg-core").PgTableWithColumns<
123
191
  generated: undefined;
124
192
  }, {}, {}>;
125
193
  };
126
- dialect: "pg";
194
+ dialect: 'pg';
127
195
  }>;
128
196
  export type ProjectDb = typeof projects.$inferSelect;
129
197
  export type ProjectCreateDb = typeof projects.$inferInsert;
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/db/schema/projects.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAGvD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AAE3D,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAUjD"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/db/schema/projects.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAGvD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AAE3D,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAcjD"}
@@ -1,4 +1,5 @@
1
1
  import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
+ import { sql } from 'drizzle-orm';
2
3
  import { index, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
4
  import { pgTable } from './common.js';
4
5
  export const projects = pgTable('projects', {
@@ -6,7 +7,11 @@ export const projects = pgTable('projects', {
6
7
  workspaceId: uuid('workspace_id').notNull(),
7
8
  sourceConnectionId: uuid('source_connection_id').notNull(),
8
9
  sourceExternalRepositoryId: text('source_external_repository_id').notNull(),
10
+ sourceRepositoryOwner: text('source_repository_owner'),
11
+ sourceRepositoryName: text('source_repository_name'),
12
+ sourceDefaultBranch: text('source_default_branch'),
9
13
  name: text('name').notNull(),
14
+ slug: text('slug').notNull(),
10
15
  createdAt: timestamp('created_at', {
11
16
  withTimezone: true
12
17
  }).notNull().defaultNow(),
@@ -15,7 +20,9 @@ export const projects = pgTable('projects', {
15
20
  }).notNull().defaultNow()
16
21
  }, (table)=>[
17
22
  uniqueIndex('projects_source_unique').on(table.sourceConnectionId, table.sourceExternalRepositoryId),
18
- index('projects_workspace_created_id_idx').on(table.workspaceId, table.createdAt, table.id)
23
+ uniqueIndex('projects_workspace_slug_unique').on(table.workspaceId, table.slug),
24
+ index('projects_workspace_created_id_idx').on(table.workspaceId, table.createdAt, table.id),
25
+ index('projects_source_repository_lookup_idx').on(table.workspaceId, table.sourceConnectionId, sql`lower(${table.sourceRepositoryOwner})`, sql`lower(${table.sourceRepositoryName})`)
19
26
  ]);
20
27
  export function toProject(row) {
21
28
  return {
@@ -23,7 +30,11 @@ export function toProject(row) {
23
30
  workspaceId: row.workspaceId,
24
31
  sourceConnectionId: row.sourceConnectionId,
25
32
  sourceExternalRepositoryId: row.sourceExternalRepositoryId,
33
+ sourceRepositoryOwner: row.sourceRepositoryOwner,
34
+ sourceRepositoryName: row.sourceRepositoryName,
35
+ sourceDefaultBranch: row.sourceDefaultBranch,
26
36
  name: row.name,
37
+ slug: row.slug,
27
38
  createdAt: row.createdAt,
28
39
  updatedAt: row.updatedAt
29
40
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/projects.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {Project} from '#core/entities/project.js';\nimport {pgTable} from './common.js';\n\nexport const projects = pgTable(\n 'projects',\n {\n id: uuidv7PrimaryKey(),\n workspaceId: uuid('workspace_id').notNull(),\n sourceConnectionId: uuid('source_connection_id').notNull(),\n sourceExternalRepositoryId: text('source_external_repository_id').notNull(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('projects_source_unique').on(\n table.sourceConnectionId,\n table.sourceExternalRepositoryId,\n ),\n index('projects_workspace_created_id_idx').on(table.workspaceId, table.createdAt, table.id),\n ],\n);\n\nexport type ProjectDb = typeof projects.$inferSelect;\nexport type ProjectCreateDb = typeof projects.$inferInsert;\n\nexport function toProject(row: ProjectDb): Project {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n sourceConnectionId: row.sourceConnectionId,\n sourceExternalRepositoryId: row.sourceExternalRepositoryId,\n name: row.name,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","text","timestamp","uniqueIndex","uuid","pgTable","projects","id","workspaceId","notNull","sourceConnectionId","sourceExternalRepositoryId","name","createdAt","withTimezone","defaultNow","updatedAt","table","on","toProject","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,WAAWD,QACtB,YACA;IACEE,IAAIR;IACJS,aAAaJ,KAAK,gBAAgBK,OAAO;IACzCC,oBAAoBN,KAAK,wBAAwBK,OAAO;IACxDE,4BAA4BV,KAAK,iCAAiCQ,OAAO;IACzEG,MAAMX,KAAK,QAAQQ,OAAO;IAC1BI,WAAWX,UAAU,cAAc;QAACY,cAAc;IAAI,GAAGL,OAAO,GAAGM,UAAU;IAC7EC,WAAWd,UAAU,cAAc;QAACY,cAAc;IAAI,GAAGL,OAAO,GAAGM,UAAU;AAC/E,GACA,CAACE,QAAU;QACTd,YAAY,0BAA0Be,EAAE,CACtCD,MAAMP,kBAAkB,EACxBO,MAAMN,0BAA0B;QAElCX,MAAM,qCAAqCkB,EAAE,CAACD,MAAMT,WAAW,EAAES,MAAMJ,SAAS,EAAEI,MAAMV,EAAE;KAC3F,EACD;AAKF,OAAO,SAASY,UAAUC,GAAc;IACtC,OAAO;QACLb,IAAIa,IAAIb,EAAE;QACVC,aAAaY,IAAIZ,WAAW;QAC5BE,oBAAoBU,IAAIV,kBAAkB;QAC1CC,4BAA4BS,IAAIT,0BAA0B;QAC1DC,MAAMQ,IAAIR,IAAI;QACdC,WAAWO,IAAIP,SAAS;QACxBG,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
1
+ {"version":3,"sources":["../../../src/db/schema/projects.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {sql} from 'drizzle-orm';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {Project} from '#core/entities/project.js';\nimport {pgTable} from './common.js';\n\nexport const projects = pgTable(\n 'projects',\n {\n id: uuidv7PrimaryKey(),\n workspaceId: uuid('workspace_id').notNull(),\n sourceConnectionId: uuid('source_connection_id').notNull(),\n sourceExternalRepositoryId: text('source_external_repository_id').notNull(),\n sourceRepositoryOwner: text('source_repository_owner'),\n sourceRepositoryName: text('source_repository_name'),\n sourceDefaultBranch: text('source_default_branch'),\n name: text('name').notNull(),\n slug: text('slug').notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('projects_source_unique').on(\n table.sourceConnectionId,\n table.sourceExternalRepositoryId,\n ),\n uniqueIndex('projects_workspace_slug_unique').on(table.workspaceId, table.slug),\n index('projects_workspace_created_id_idx').on(table.workspaceId, table.createdAt, table.id),\n index('projects_source_repository_lookup_idx').on(\n table.workspaceId,\n table.sourceConnectionId,\n sql`lower(${table.sourceRepositoryOwner})`,\n sql`lower(${table.sourceRepositoryName})`,\n ),\n ],\n);\n\nexport type ProjectDb = typeof projects.$inferSelect;\nexport type ProjectCreateDb = typeof projects.$inferInsert;\n\nexport function toProject(row: ProjectDb): Project {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n sourceConnectionId: row.sourceConnectionId,\n sourceExternalRepositoryId: row.sourceExternalRepositoryId,\n sourceRepositoryOwner: row.sourceRepositoryOwner,\n sourceRepositoryName: row.sourceRepositoryName,\n sourceDefaultBranch: row.sourceDefaultBranch,\n name: row.name,\n slug: row.slug,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","sql","index","text","timestamp","uniqueIndex","uuid","pgTable","projects","id","workspaceId","notNull","sourceConnectionId","sourceExternalRepositoryId","sourceRepositoryOwner","sourceRepositoryName","sourceDefaultBranch","name","slug","createdAt","withTimezone","defaultNow","updatedAt","table","on","toProject","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,GAAG,QAAO,cAAc;AAChC,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,WAAWD,QACtB,YACA;IACEE,IAAIT;IACJU,aAAaJ,KAAK,gBAAgBK,OAAO;IACzCC,oBAAoBN,KAAK,wBAAwBK,OAAO;IACxDE,4BAA4BV,KAAK,iCAAiCQ,OAAO;IACzEG,uBAAuBX,KAAK;IAC5BY,sBAAsBZ,KAAK;IAC3Ba,qBAAqBb,KAAK;IAC1Bc,MAAMd,KAAK,QAAQQ,OAAO;IAC1BO,MAAMf,KAAK,QAAQQ,OAAO;IAC1BQ,WAAWf,UAAU,cAAc;QAACgB,cAAc;IAAI,GAAGT,OAAO,GAAGU,UAAU;IAC7EC,WAAWlB,UAAU,cAAc;QAACgB,cAAc;IAAI,GAAGT,OAAO,GAAGU,UAAU;AAC/E,GACA,CAACE,QAAU;QACTlB,YAAY,0BAA0BmB,EAAE,CACtCD,MAAMX,kBAAkB,EACxBW,MAAMV,0BAA0B;QAElCR,YAAY,kCAAkCmB,EAAE,CAACD,MAAMb,WAAW,EAAEa,MAAML,IAAI;QAC9EhB,MAAM,qCAAqCsB,EAAE,CAACD,MAAMb,WAAW,EAAEa,MAAMJ,SAAS,EAAEI,MAAMd,EAAE;QAC1FP,MAAM,yCAAyCsB,EAAE,CAC/CD,MAAMb,WAAW,EACjBa,MAAMX,kBAAkB,EACxBX,GAAG,CAAC,MAAM,EAAEsB,MAAMT,qBAAqB,CAAC,CAAC,CAAC,EAC1Cb,GAAG,CAAC,MAAM,EAAEsB,MAAMR,oBAAoB,CAAC,CAAC,CAAC;KAE5C,EACD;AAKF,OAAO,SAASU,UAAUC,GAAc;IACtC,OAAO;QACLjB,IAAIiB,IAAIjB,EAAE;QACVC,aAAagB,IAAIhB,WAAW;QAC5BE,oBAAoBc,IAAId,kBAAkB;QAC1CC,4BAA4Ba,IAAIb,0BAA0B;QAC1DC,uBAAuBY,IAAIZ,qBAAqB;QAChDC,sBAAsBW,IAAIX,oBAAoB;QAC9CC,qBAAqBU,IAAIV,mBAAmB;QAC5CC,MAAMS,IAAIT,IAAI;QACdC,MAAMQ,IAAIR,IAAI;QACdC,WAAWO,IAAIP,SAAS;QACxBG,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { AuthInterModuleClient } from '@shipfox/api-auth-dto/inter-module';
2
2
  import type { IntegrationsModuleClient } from '@shipfox/api-integration-core-dto/inter-module';
3
3
  import { type ShipfoxModule } from '@shipfox/node-module';
4
4
  export type { Project } from '#core/index.js';
5
- export { createProjectFromSource, ProjectAccessDeniedError, ProjectAlreadyExistsError, ProjectNotFoundError, } from '#core/index.js';
5
+ export { createProjectFromSource, ProjectAccessDeniedError, ProjectAlreadyExistsError, ProjectNotFoundError, ProjectSlugConflictError, updateProjectDetails, } from '#core/index.js';
6
6
  export type { GetProjectBySourceParams } from '#db/index.js';
7
7
  export { createProject, db, getProjectById, getProjectBySource, getWorkspaceProjectCounts, listProjects, migrationsPath, projectsOutbox, requireProjectForWorkspace, } from '#db/index.js';
8
8
  export { createProjectRoutes, requireProjectAccess } from '#presentation/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAK9E,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,gDAAgD,CAAC;AAE7F,OAAO,EAAC,KAAK,aAAa,EAAoB,MAAM,sBAAsB,CAAC;AAe3E,YAAY,EAAC,OAAO,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAC,wBAAwB,EAAC,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,EAAE,EACF,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAEjF,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,wBAAwB,CAAC;IACvC,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,IAAI,GACL,EAAE,2BAA2B,GAAG,aAAa,CAyB7C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oCAAoC,CAAC;AAM9E,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,gDAAgD,CAAC;AAE7F,OAAO,EAAC,KAAK,aAAa,EAAoB,MAAM,sBAAsB,CAAC;AAe3E,YAAY,EAAC,OAAO,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAC,wBAAwB,EAAC,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,EAAE,EACF,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,mBAAmB,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAEjF,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,wBAAwB,CAAC;IACvC,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,IAAI,GACL,EAAE,2BAA2B,GAAG,aAAa,CA4B7C"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { INTEGRATION_SOURCE_COMMIT_PUSHED } from '@shipfox/api-integration-core-dto';
3
+ import { INTEGRATION_SOURCE_COMMIT_PUSHED, INTEGRATION_SOURCE_REPOSITORY_UPDATED } from '@shipfox/api-integration-core-dto';
4
4
  import { projectsEventSchemas } from '@shipfox/api-projects-dto';
5
5
  import { subscriberFactory } from '@shipfox/node-module';
6
6
  import { db, migrationsPath, projectsOutbox } from '#db/index.js';
@@ -8,13 +8,13 @@ import { registerProjectsServiceMetrics } from '#metrics/index.js';
8
8
  import { projectsE2eRoutes } from '#presentation/e2eRoutes/index.js';
9
9
  import { createProjectRoutes } from '#presentation/index.js';
10
10
  import { createProjectsInterModulePresentation } from '#presentation/inter-module.js';
11
- import { onSourceCommitPushed } from '#presentation/subscribers/index.js';
11
+ import { onSourceCommitPushed, onSourceRepositoryUpdated } from '#presentation/subscribers/index.js';
12
12
  import { createProjectsMaintenanceActivities } from '#temporal/activities/index.js';
13
13
  import { PROJECTS_MAINTENANCE_TASK_QUEUE } from '#temporal/constants.js';
14
14
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
15
15
  const maintenanceWorkflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');
16
16
  const subscriber = subscriberFactory();
17
- export { createProjectFromSource, ProjectAccessDeniedError, ProjectAlreadyExistsError, ProjectNotFoundError } from '#core/index.js';
17
+ export { createProjectFromSource, ProjectAccessDeniedError, ProjectAlreadyExistsError, ProjectNotFoundError, ProjectSlugConflictError, updateProjectDetails } from '#core/index.js';
18
18
  export { createProject, db, getProjectById, getProjectBySource, getWorkspaceProjectCounts, listProjects, migrationsPath, projectsOutbox, requireProjectForWorkspace } from '#db/index.js';
19
19
  export { createProjectRoutes, requireProjectAccess } from '#presentation/index.js';
20
20
  export function createProjectsModule({ integrations, auth }) {
@@ -39,10 +39,13 @@ export function createProjectsModule({ integrations, auth }) {
39
39
  }
40
40
  ],
41
41
  interModulePresentations: [
42
- createProjectsInterModulePresentation()
42
+ createProjectsInterModulePresentation({
43
+ integrations
44
+ })
43
45
  ],
44
46
  subscribers: [
45
- subscriber(INTEGRATION_SOURCE_COMMIT_PUSHED, onSourceCommitPushed)
47
+ subscriber(INTEGRATION_SOURCE_COMMIT_PUSHED, onSourceCommitPushed),
48
+ subscriber(INTEGRATION_SOURCE_REPOSITORY_UPDATED, onSourceRepositoryUpdated)
46
49
  ],
47
50
  workers: [
48
51
  {
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;
@@ -14,7 +15,7 @@ export declare function toProjectDto(project: Project): {
14
15
  export declare function toAdminProjectSummaryDto(project: AdminProjectSummary): {
15
16
  id: string;
16
17
  name: string;
17
- status: "active";
18
+ status: 'active';
18
19
  workspace_id: string;
19
20
  created_at: string;
20
21
  updated_at: 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;IAEzC,EAAE;IACF,YAAY;IACZ,IAAI;IACJ,IAAI;IACJ,MAAM;QACJ,aAAa;QACb,sBAAsB;;IAExB,UAAU;IACV,UAAU;EAEb;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,mBAAmB;IAEjE,EAAE;IACF,IAAI;IACJ,MAAM,EAAE,QAAQ;IAChB,YAAY;IACZ,UAAU;IACV,UAAU;EAEb"}
@@ -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;AAU/B,wBAAgB,qCAAqC,CAAC,MAAM,EAAE;IAC5D,YAAY,EAAE,IAAI,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,CAAC;CACzE,GAAG,uBAAuB,CAAC,OAAO,2BAA2B,CAAC,CAuG9D"}
@@ -1,11 +1,33 @@
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, listProjects, 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
+ }),
12
+ listProjectsByWorkspace: async ({ workspaceId, limit, cursor })=>{
13
+ const result = await listProjects({
14
+ workspaceId,
15
+ limit,
16
+ ...cursor ? {
17
+ cursor: {
18
+ createdAt: new Date(cursor.createdAt),
19
+ id: cursor.id
20
+ }
21
+ } : {}
22
+ });
23
+ return {
24
+ projects: result.projects,
25
+ nextCursor: result.nextCursor ? {
26
+ createdAt: result.nextCursor.createdAt.toISOString(),
27
+ id: result.nextCursor.id
28
+ } : null
29
+ };
30
+ },
9
31
  requireProjectForWorkspace: async ({ projectId, workspaceId })=>{
10
32
  const project = await getProjectById(projectId);
11
33
  if (project === undefined) {
@@ -27,7 +49,45 @@ export function createProjectsInterModulePresentation() {
27
49
  counts: await getWorkspaceProjectCounts({
28
50
  workspaceIds
29
51
  })
30
- })
52
+ }),
53
+ resolveCheckoutTarget: async (input)=>{
54
+ const target = await resolveCheckoutTarget(input);
55
+ if (target === undefined) {
56
+ throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
57
+ }
58
+ if ('repository' in input.target) {
59
+ const source = await params.integrations.resolveSourceRepository({
60
+ workspaceId: input.workspaceId,
61
+ connectionId: target.connectionId,
62
+ externalRepositoryId: target.externalRepositoryId
63
+ }).catch(()=>undefined);
64
+ if (source === undefined) {
65
+ throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
66
+ }
67
+ const separator = input.target.repository.indexOf('/');
68
+ const requestedOwner = separator === -1 ? input.defaults.owner : input.target.repository.slice(0, separator);
69
+ const requestedName = separator === -1 ? input.target.repository : input.target.repository.slice(separator + 1);
70
+ const repositoryMatches = source.repository.owner.toLowerCase() === requestedOwner.toLowerCase() && source.repository.name.toLowerCase() === requestedName.toLowerCase();
71
+ await updateProjectSourceMetadata({
72
+ workspaceId: input.workspaceId,
73
+ projectId: target.projectId,
74
+ sourceConnectionId: source.connection.id,
75
+ sourceExternalRepositoryId: source.repository.externalRepositoryId,
76
+ sourceRepositoryOwner: source.repository.owner,
77
+ sourceRepositoryName: source.repository.name,
78
+ sourceDefaultBranch: source.repository.defaultBranch
79
+ });
80
+ if (!repositoryMatches) {
81
+ throw createInterModuleKnownError(projectsInterModuleContract.methods.resolveCheckoutTarget, 'checkout-repository-not-authorized', {});
82
+ }
83
+ return {
84
+ ...target,
85
+ connectionId: source.connection.id,
86
+ externalRepositoryId: source.repository.externalRepositoryId
87
+ };
88
+ }
89
+ return target;
90
+ }
31
91
  });
32
92
  }
33
93
 
@@ -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 listProjects,\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 listProjectsByWorkspace: async ({workspaceId, limit, cursor}) => {\n const result = await listProjects({\n workspaceId,\n limit,\n ...(cursor ? {cursor: {createdAt: new Date(cursor.createdAt), id: cursor.id}} : {}),\n });\n return {\n projects: result.projects,\n nextCursor: result.nextCursor\n ? {createdAt: result.nextCursor.createdAt.toISOString(), id: result.nextCursor.id}\n : null,\n };\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","listProjects","resolveCheckoutTarget","updateProjectSourceMetadata","createProjectsInterModulePresentation","params","projectId","project","input","listProjectsByWorkspace","workspaceId","limit","cursor","result","createdAt","Date","id","projects","nextCursor","toISOString","requireProjectForWorkspace","undefined","methods","workspaceIds","counts","target","source","integrations","resolveSourceRepository","connectionId","externalRepositoryId","catch","separator","repository","indexOf","requestedOwner","defaults","owner","slice","requestedName","repositoryMatches","toLowerCase","name","sourceConnectionId","connection","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,YAAY,EACZC,qBAAqB,EACrBC,2BAA2B,QACtB,kBAAkB;AAEzB,OAAO,SAASC,sCAAsCC,MAErD;IACC,OAAOR,8BAA8BF,6BAA6B;QAChEG,gBAAgB,OAAO,EAACQ,SAAS,EAAC,GAAM,CAAA;gBAACC,SAAS,AAAC,MAAMT,eAAeQ,cAAe;YAAI,CAAA;QAC3FP,oBAAoB,OAAOS,QAAW,CAAA;gBACpCD,SAAS,AAAC,MAAMR,mBAAmBS,UAAW;YAChD,CAAA;QACAC,yBAAyB,OAAO,EAACC,WAAW,EAAEC,KAAK,EAAEC,MAAM,EAAC;YAC1D,MAAMC,SAAS,MAAMZ,aAAa;gBAChCS;gBACAC;gBACA,GAAIC,SAAS;oBAACA,QAAQ;wBAACE,WAAW,IAAIC,KAAKH,OAAOE,SAAS;wBAAGE,IAAIJ,OAAOI,EAAE;oBAAA;gBAAC,IAAI,CAAC,CAAC;YACpF;YACA,OAAO;gBACLC,UAAUJ,OAAOI,QAAQ;gBACzBC,YAAYL,OAAOK,UAAU,GACzB;oBAACJ,WAAWD,OAAOK,UAAU,CAACJ,SAAS,CAACK,WAAW;oBAAIH,IAAIH,OAAOK,UAAU,CAACF,EAAE;gBAAA,IAC/E;YACN;QACF;QACAI,4BAA4B,OAAO,EAACd,SAAS,EAAEI,WAAW,EAAC;YACzD,MAAMH,UAAU,MAAMT,eAAeQ;YACrC,IAAIC,YAAYc,WAAW;gBACzB,MAAMzB,4BACJD,4BAA4B2B,OAAO,CAACF,0BAA0B,EAC9D,qBACA;oBAACd;gBAAS;YAEd;YACA,IAAIC,QAAQG,WAAW,KAAKA,aAAa;gBACvC,MAAMd,4BACJD,4BAA4B2B,OAAO,CAACF,0BAA0B,EAC9D,8BACA;oBAACd;oBAAWI;gBAAW;YAE3B;YACA,OAAO;gBAACH;YAAO;QACjB;QACAP,2BAA2B,OAAO,EAACuB,YAAY,EAAC,GAAM,CAAA;gBACpDC,QAAQ,MAAMxB,0BAA0B;oBAACuB;gBAAY;YACvD,CAAA;QACArB,uBAAuB,OAAOM;YAC5B,MAAMiB,SAAS,MAAMvB,sBAAsBM;YAC3C,IAAIiB,WAAWJ,WAAW;gBACxB,MAAMzB,4BACJD,4BAA4B2B,OAAO,CAACpB,qBAAqB,EACzD,sCACA,CAAC;YAEL;YAEA,IAAI,gBAAgBM,MAAMiB,MAAM,EAAE;gBAChC,MAAMC,SAAS,MAAMrB,OAAOsB,YAAY,CACrCC,uBAAuB,CAAC;oBACvBlB,aAAaF,MAAME,WAAW;oBAC9BmB,cAAcJ,OAAOI,YAAY;oBACjCC,sBAAsBL,OAAOK,oBAAoB;gBACnD,GACCC,KAAK,CAAC,IAAMV;gBACf,IAAIK,WAAWL,WAAW;oBACxB,MAAMzB,4BACJD,4BAA4B2B,OAAO,CAACpB,qBAAqB,EACzD,sCACA,CAAC;gBAEL;gBAEA,MAAM8B,YAAYxB,MAAMiB,MAAM,CAACQ,UAAU,CAACC,OAAO,CAAC;gBAClD,MAAMC,iBACJH,cAAc,CAAC,IAAIxB,MAAM4B,QAAQ,CAACC,KAAK,GAAG7B,MAAMiB,MAAM,CAACQ,UAAU,CAACK,KAAK,CAAC,GAAGN;gBAC7E,MAAMO,gBACJP,cAAc,CAAC,IAAIxB,MAAMiB,MAAM,CAACQ,UAAU,GAAGzB,MAAMiB,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,MAAMtC,4BAA4B;oBAChCO,aAAaF,MAAME,WAAW;oBAC9BJ,WAAWmB,OAAOnB,SAAS;oBAC3BqC,oBAAoBjB,OAAOkB,UAAU,CAAC5B,EAAE;oBACxC6B,4BAA4BnB,OAAOO,UAAU,CAACH,oBAAoB;oBAClEgB,uBAAuBpB,OAAOO,UAAU,CAACI,KAAK;oBAC9CU,sBAAsBrB,OAAOO,UAAU,CAACS,IAAI;oBAC5CM,qBAAqBtB,OAAOO,UAAU,CAACgB,aAAa;gBACtD;gBAEA,IAAI,CAACT,mBAAmB;oBACtB,MAAM5C,4BACJD,4BAA4B2B,OAAO,CAACpB,qBAAqB,EACzD,sCACA,CAAC;gBAEL;gBAEA,OAAO;oBACL,GAAGuB,MAAM;oBACTI,cAAcH,OAAOkB,UAAU,CAAC5B,EAAE;oBAClCc,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