@shipfox/api-projects 2.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 (202) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +117 -0
  5. package/LICENSE +21 -0
  6. package/dist/core/entities/index.d.ts +2 -0
  7. package/dist/core/entities/index.d.ts.map +1 -0
  8. package/dist/core/entities/index.js +3 -0
  9. package/dist/core/entities/index.js.map +1 -0
  10. package/dist/core/entities/project.d.ts +10 -0
  11. package/dist/core/entities/project.d.ts.map +1 -0
  12. package/dist/core/entities/project.js +3 -0
  13. package/dist/core/entities/project.js.map +1 -0
  14. package/dist/core/errors.d.ts +13 -0
  15. package/dist/core/errors.d.ts.map +1 -0
  16. package/dist/core/errors.js +20 -0
  17. package/dist/core/errors.js.map +1 -0
  18. package/dist/core/index.d.ts +5 -0
  19. package/dist/core/index.d.ts.map +1 -0
  20. package/dist/core/index.js +4 -0
  21. package/dist/core/index.js.map +1 -0
  22. package/dist/core/projects.d.ts +12 -0
  23. package/dist/core/projects.d.ts.map +1 -0
  24. package/dist/core/projects.js +62 -0
  25. package/dist/core/projects.js.map +1 -0
  26. package/dist/db/db.d.ts +763 -0
  27. package/dist/db/db.d.ts.map +1 -0
  28. package/dist/db/db.js +22 -0
  29. package/dist/db/db.js.map +1 -0
  30. package/dist/db/index.d.ts +8 -0
  31. package/dist/db/index.d.ts.map +1 -0
  32. package/dist/db/index.js +10 -0
  33. package/dist/db/index.js.map +1 -0
  34. package/dist/db/integration-event-dedup.d.ts +17 -0
  35. package/dist/db/integration-event-dedup.d.ts.map +1 -0
  36. package/dist/db/integration-event-dedup.js +27 -0
  37. package/dist/db/integration-event-dedup.js.map +1 -0
  38. package/dist/db/projects.d.ts +36 -0
  39. package/dist/db/projects.d.ts.map +1 -0
  40. package/dist/db/projects.js +84 -0
  41. package/dist/db/projects.js.map +1 -0
  42. package/dist/db/schema/common.d.ts +2 -0
  43. package/dist/db/schema/common.d.ts.map +1 -0
  44. package/dist/db/schema/common.js +4 -0
  45. package/dist/db/schema/common.js.map +1 -0
  46. package/dist/db/schema/integration-event-dedup.d.ts +59 -0
  47. package/dist/db/schema/integration-event-dedup.d.ts.map +1 -0
  48. package/dist/db/schema/integration-event-dedup.js +19 -0
  49. package/dist/db/schema/integration-event-dedup.js.map +1 -0
  50. package/dist/db/schema/outbox.d.ts +195 -0
  51. package/dist/db/schema/outbox.d.ts.map +1 -0
  52. package/dist/db/schema/outbox.js +5 -0
  53. package/dist/db/schema/outbox.js.map +1 -0
  54. package/dist/db/schema/projects.d.ts +131 -0
  55. package/dist/db/schema/projects.d.ts.map +1 -0
  56. package/dist/db/schema/projects.js +32 -0
  57. package/dist/db/schema/projects.js.map +1 -0
  58. package/dist/index.d.ts +12 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +59 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/metrics/index.d.ts +3 -0
  63. package/dist/metrics/index.d.ts.map +1 -0
  64. package/dist/metrics/index.js +4 -0
  65. package/dist/metrics/index.js.map +1 -0
  66. package/dist/metrics/instance.d.ts +4 -0
  67. package/dist/metrics/instance.d.ts.map +1 -0
  68. package/dist/metrics/instance.js +25 -0
  69. package/dist/metrics/instance.js.map +1 -0
  70. package/dist/metrics/service.d.ts +2 -0
  71. package/dist/metrics/service.d.ts.map +1 -0
  72. package/dist/metrics/service.js +15 -0
  73. package/dist/metrics/service.js.map +1 -0
  74. package/dist/presentation/auth/require-project-access.d.ts +12 -0
  75. package/dist/presentation/auth/require-project-access.d.ts.map +1 -0
  76. package/dist/presentation/auth/require-project-access.js +25 -0
  77. package/dist/presentation/auth/require-project-access.js.map +1 -0
  78. package/dist/presentation/dto/index.d.ts +2 -0
  79. package/dist/presentation/dto/index.d.ts.map +1 -0
  80. package/dist/presentation/dto/index.js +3 -0
  81. package/dist/presentation/dto/index.js.map +1 -0
  82. package/dist/presentation/dto/project.d.ts +13 -0
  83. package/dist/presentation/dto/project.d.ts.map +1 -0
  84. package/dist/presentation/dto/project.js +15 -0
  85. package/dist/presentation/dto/project.js.map +1 -0
  86. package/dist/presentation/e2eRoutes/create-project.d.ts +2 -0
  87. package/dist/presentation/e2eRoutes/create-project.d.ts.map +1 -0
  88. package/dist/presentation/e2eRoutes/create-project.js +45 -0
  89. package/dist/presentation/e2eRoutes/create-project.js.map +1 -0
  90. package/dist/presentation/e2eRoutes/index.d.ts +3 -0
  91. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  92. package/dist/presentation/e2eRoutes/index.js +9 -0
  93. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  94. package/dist/presentation/index.d.ts +3 -0
  95. package/dist/presentation/index.d.ts.map +1 -0
  96. package/dist/presentation/index.js +4 -0
  97. package/dist/presentation/index.js.map +1 -0
  98. package/dist/presentation/routes/create-project.d.ts +3 -0
  99. package/dist/presentation/routes/create-project.d.ts.map +1 -0
  100. package/dist/presentation/routes/create-project.js +94 -0
  101. package/dist/presentation/routes/create-project.js.map +1 -0
  102. package/dist/presentation/routes/cursor.d.ts +2 -0
  103. package/dist/presentation/routes/cursor.d.ts.map +1 -0
  104. package/dist/presentation/routes/cursor.js +3 -0
  105. package/dist/presentation/routes/cursor.js.map +1 -0
  106. package/dist/presentation/routes/get-project.d.ts +2 -0
  107. package/dist/presentation/routes/get-project.d.ts.map +1 -0
  108. package/dist/presentation/routes/get-project.js +30 -0
  109. package/dist/presentation/routes/get-project.js.map +1 -0
  110. package/dist/presentation/routes/index.d.ts +4 -0
  111. package/dist/presentation/routes/index.d.ts.map +1 -0
  112. package/dist/presentation/routes/index.js +17 -0
  113. package/dist/presentation/routes/index.js.map +1 -0
  114. package/dist/presentation/routes/list-projects.d.ts +2 -0
  115. package/dist/presentation/routes/list-projects.d.ts.map +1 -0
  116. package/dist/presentation/routes/list-projects.js +43 -0
  117. package/dist/presentation/routes/list-projects.js.map +1 -0
  118. package/dist/presentation/subscribers/index.d.ts +2 -0
  119. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  120. package/dist/presentation/subscribers/index.js +3 -0
  121. package/dist/presentation/subscribers/index.js.map +1 -0
  122. package/dist/presentation/subscribers/on-source-commit-pushed.d.ts +4 -0
  123. package/dist/presentation/subscribers/on-source-commit-pushed.d.ts.map +1 -0
  124. package/dist/presentation/subscribers/on-source-commit-pushed.js +54 -0
  125. package/dist/presentation/subscribers/on-source-commit-pushed.js.map +1 -0
  126. package/dist/temporal/activities/index.d.ts +5 -0
  127. package/dist/temporal/activities/index.d.ts.map +1 -0
  128. package/dist/temporal/activities/index.js +8 -0
  129. package/dist/temporal/activities/index.js.map +1 -0
  130. package/dist/temporal/activities/prune-integration-event-dedup.d.ts +4 -0
  131. package/dist/temporal/activities/prune-integration-event-dedup.d.ts.map +1 -0
  132. package/dist/temporal/activities/prune-integration-event-dedup.js +11 -0
  133. package/dist/temporal/activities/prune-integration-event-dedup.js.map +1 -0
  134. package/dist/temporal/constants.d.ts +3 -0
  135. package/dist/temporal/constants.d.ts.map +1 -0
  136. package/dist/temporal/constants.js +4 -0
  137. package/dist/temporal/constants.js.map +1 -0
  138. package/dist/temporal/workflows/index.d.ts +2 -0
  139. package/dist/temporal/workflows/index.d.ts.map +1 -0
  140. package/dist/temporal/workflows/index.js +3 -0
  141. package/dist/temporal/workflows/index.js.map +1 -0
  142. package/dist/temporal/workflows/prune-integration-event-dedup-cron.d.ts +2 -0
  143. package/dist/temporal/workflows/prune-integration-event-dedup-cron.d.ts.map +1 -0
  144. package/dist/temporal/workflows/prune-integration-event-dedup-cron.js +14 -0
  145. package/dist/temporal/workflows/prune-integration-event-dedup-cron.js.map +1 -0
  146. package/dist/tsconfig.test.tsbuildinfo +1 -0
  147. package/drizzle/0000_initial.sql +37 -0
  148. package/drizzle/meta/0000_snapshot.json +307 -0
  149. package/drizzle/meta/_journal.json +13 -0
  150. package/drizzle.config.ts +7 -0
  151. package/package.json +76 -0
  152. package/src/core/entities/index.ts +1 -0
  153. package/src/core/entities/project.ts +9 -0
  154. package/src/core/errors.ts +26 -0
  155. package/src/core/index.ts +8 -0
  156. package/src/core/projects.test.ts +142 -0
  157. package/src/core/projects.ts +97 -0
  158. package/src/db/db.ts +22 -0
  159. package/src/db/index.ts +26 -0
  160. package/src/db/integration-event-dedup.ts +45 -0
  161. package/src/db/projects.test.ts +22 -0
  162. package/src/db/projects.ts +158 -0
  163. package/src/db/schema/common.ts +3 -0
  164. package/src/db/schema/integration-event-dedup.ts +15 -0
  165. package/src/db/schema/outbox.ts +4 -0
  166. package/src/db/schema/projects.ts +39 -0
  167. package/src/index.ts +71 -0
  168. package/src/metrics/index.ts +2 -0
  169. package/src/metrics/instance.ts +34 -0
  170. package/src/metrics/service.ts +17 -0
  171. package/src/presentation/auth/require-project-access.ts +29 -0
  172. package/src/presentation/dto/index.ts +1 -0
  173. package/src/presentation/dto/project.ts +15 -0
  174. package/src/presentation/e2eRoutes/create-project.test.ts +119 -0
  175. package/src/presentation/e2eRoutes/create-project.ts +50 -0
  176. package/src/presentation/e2eRoutes/index.ts +7 -0
  177. package/src/presentation/index.ts +2 -0
  178. package/src/presentation/routes/create-project.ts +102 -0
  179. package/src/presentation/routes/cursor.ts +5 -0
  180. package/src/presentation/routes/get-project.ts +24 -0
  181. package/src/presentation/routes/index.ts +14 -0
  182. package/src/presentation/routes/list-projects.ts +34 -0
  183. package/src/presentation/routes/projects.test.ts +266 -0
  184. package/src/presentation/subscribers/index.ts +1 -0
  185. package/src/presentation/subscribers/on-source-commit-pushed.test.ts +149 -0
  186. package/src/presentation/subscribers/on-source-commit-pushed.ts +64 -0
  187. package/src/temporal/activities/index.ts +7 -0
  188. package/src/temporal/activities/prune-integration-event-dedup.test.ts +50 -0
  189. package/src/temporal/activities/prune-integration-event-dedup.ts +9 -0
  190. package/src/temporal/constants.ts +3 -0
  191. package/src/temporal/workflows/index.ts +1 -0
  192. package/src/temporal/workflows/prune-integration-event-dedup-cron.ts +15 -0
  193. package/test/env.ts +14 -0
  194. package/test/factories/project.ts +24 -0
  195. package/test/globalSetup.ts +17 -0
  196. package/test/index.ts +1 -0
  197. package/test/setup.ts +17 -0
  198. package/tsconfig.build.json +9 -0
  199. package/tsconfig.build.tsbuildinfo +1 -0
  200. package/tsconfig.json +3 -0
  201. package/tsconfig.test.json +8 -0
  202. package/vitest.config.ts +12 -0
@@ -0,0 +1,37 @@
1
+ CREATE TABLE "projects_projects" (
2
+ "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
3
+ "workspace_id" uuid NOT NULL,
4
+ "source_connection_id" uuid NOT NULL,
5
+ "source_external_repository_id" text NOT NULL,
6
+ "name" text NOT NULL,
7
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
8
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
9
+ );
10
+ --> statement-breakpoint
11
+ CREATE TABLE "projects_outbox" (
12
+ "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
13
+ "event_type" text NOT NULL,
14
+ "ordering_key" text,
15
+ "payload" jsonb NOT NULL,
16
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
17
+ "dispatched_at" timestamp with time zone,
18
+ "dispatch_attempts" integer DEFAULT 0 NOT NULL,
19
+ "next_dispatch_at" timestamp with time zone DEFAULT now() NOT NULL,
20
+ "last_dispatch_error" jsonb,
21
+ "last_dispatch_failed_at" timestamp with time zone,
22
+ "dead_lettered_at" timestamp with time zone
23
+ );
24
+ --> statement-breakpoint
25
+ CREATE TABLE "projects_integration_event_dedup" (
26
+ "integration_event_id" uuid NOT NULL,
27
+ "project_id" uuid NOT NULL,
28
+ "received_at" timestamp with time zone DEFAULT now() NOT NULL,
29
+ CONSTRAINT "projects_integration_event_dedup_integration_event_id_project_id_pk" PRIMARY KEY ("integration_event_id","project_id")
30
+ );
31
+ --> statement-breakpoint
32
+ CREATE UNIQUE INDEX "projects_source_unique" ON "projects_projects" USING btree ("source_connection_id","source_external_repository_id");--> statement-breakpoint
33
+ CREATE INDEX "projects_workspace_created_id_idx" ON "projects_projects" USING btree ("workspace_id","created_at","id");--> statement-breakpoint
34
+ CREATE INDEX "projects_outbox_pending_idx" ON "projects_outbox" USING btree ("next_dispatch_at","created_at") WHERE "dispatched_at" IS NULL AND "dead_lettered_at" IS NULL;
35
+ --> statement-breakpoint
36
+ CREATE INDEX "projects_outbox_dispatched_retention_idx" ON "projects_outbox" USING btree ("dispatched_at","id") WHERE "dispatched_at" IS NOT NULL;--> statement-breakpoint
37
+ CREATE INDEX "projects_integration_event_dedup_received_at_idx" ON "projects_integration_event_dedup" USING btree ("received_at");
@@ -0,0 +1,307 @@
1
+ {
2
+ "id": "2686798f-1741-47d6-8d93-6554f138baa6",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.projects_integration_event_dedup": {
8
+ "name": "projects_integration_event_dedup",
9
+ "schema": "",
10
+ "columns": {
11
+ "integration_event_id": {
12
+ "name": "integration_event_id",
13
+ "type": "uuid",
14
+ "primaryKey": false,
15
+ "notNull": true
16
+ },
17
+ "project_id": {
18
+ "name": "project_id",
19
+ "type": "uuid",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "received_at": {
24
+ "name": "received_at",
25
+ "type": "timestamp with time zone",
26
+ "primaryKey": false,
27
+ "notNull": true,
28
+ "default": "now()"
29
+ }
30
+ },
31
+ "indexes": {
32
+ "projects_integration_event_dedup_received_at_idx": {
33
+ "name": "projects_integration_event_dedup_received_at_idx",
34
+ "columns": [
35
+ {
36
+ "expression": "received_at",
37
+ "isExpression": false,
38
+ "asc": true,
39
+ "nulls": "last"
40
+ }
41
+ ],
42
+ "isUnique": false,
43
+ "concurrently": false,
44
+ "method": "btree",
45
+ "with": {}
46
+ }
47
+ },
48
+ "foreignKeys": {},
49
+ "compositePrimaryKeys": {
50
+ "projects_integration_event_dedup_integration_event_id_project_id_pk": {
51
+ "name": "projects_integration_event_dedup_integration_event_id_project_id_pk",
52
+ "columns": ["integration_event_id", "project_id"]
53
+ }
54
+ },
55
+ "uniqueConstraints": {},
56
+ "policies": {},
57
+ "checkConstraints": {},
58
+ "isRLSEnabled": false
59
+ },
60
+ "public.projects_outbox": {
61
+ "name": "projects_outbox",
62
+ "schema": "",
63
+ "columns": {
64
+ "id": {
65
+ "name": "id",
66
+ "type": "uuid",
67
+ "primaryKey": true,
68
+ "notNull": true,
69
+ "default": "uuidv7()"
70
+ },
71
+ "event_type": {
72
+ "name": "event_type",
73
+ "type": "text",
74
+ "primaryKey": false,
75
+ "notNull": true
76
+ },
77
+ "ordering_key": {
78
+ "name": "ordering_key",
79
+ "type": "text",
80
+ "primaryKey": false,
81
+ "notNull": false
82
+ },
83
+ "payload": {
84
+ "name": "payload",
85
+ "type": "jsonb",
86
+ "primaryKey": false,
87
+ "notNull": true
88
+ },
89
+ "created_at": {
90
+ "name": "created_at",
91
+ "type": "timestamp with time zone",
92
+ "primaryKey": false,
93
+ "notNull": true,
94
+ "default": "now()"
95
+ },
96
+ "dispatched_at": {
97
+ "name": "dispatched_at",
98
+ "type": "timestamp with time zone",
99
+ "primaryKey": false,
100
+ "notNull": false
101
+ },
102
+ "dispatch_attempts": {
103
+ "name": "dispatch_attempts",
104
+ "type": "integer",
105
+ "primaryKey": false,
106
+ "notNull": true,
107
+ "default": 0
108
+ },
109
+ "next_dispatch_at": {
110
+ "name": "next_dispatch_at",
111
+ "type": "timestamp with time zone",
112
+ "primaryKey": false,
113
+ "notNull": true,
114
+ "default": "now()"
115
+ },
116
+ "last_dispatch_error": {
117
+ "name": "last_dispatch_error",
118
+ "type": "jsonb",
119
+ "primaryKey": false,
120
+ "notNull": false
121
+ },
122
+ "last_dispatch_failed_at": {
123
+ "name": "last_dispatch_failed_at",
124
+ "type": "timestamp with time zone",
125
+ "primaryKey": false,
126
+ "notNull": false
127
+ },
128
+ "dead_lettered_at": {
129
+ "name": "dead_lettered_at",
130
+ "type": "timestamp with time zone",
131
+ "primaryKey": false,
132
+ "notNull": false
133
+ }
134
+ },
135
+ "indexes": {
136
+ "projects_outbox_pending_idx": {
137
+ "name": "projects_outbox_pending_idx",
138
+ "columns": [
139
+ {
140
+ "expression": "next_dispatch_at",
141
+ "isExpression": false,
142
+ "asc": true,
143
+ "nulls": "last"
144
+ },
145
+ {
146
+ "expression": "created_at",
147
+ "isExpression": false,
148
+ "asc": true,
149
+ "nulls": "last"
150
+ }
151
+ ],
152
+ "isUnique": false,
153
+ "where": "\"dispatched_at\" IS NULL AND \"dead_lettered_at\" IS NULL",
154
+ "concurrently": false,
155
+ "method": "btree",
156
+ "with": {}
157
+ },
158
+ "projects_outbox_dispatched_retention_idx": {
159
+ "name": "projects_outbox_dispatched_retention_idx",
160
+ "columns": [
161
+ {
162
+ "expression": "dispatched_at",
163
+ "isExpression": false,
164
+ "asc": true,
165
+ "nulls": "last"
166
+ },
167
+ {
168
+ "expression": "id",
169
+ "isExpression": false,
170
+ "asc": true,
171
+ "nulls": "last"
172
+ }
173
+ ],
174
+ "isUnique": false,
175
+ "where": "\"dispatched_at\" IS NOT NULL",
176
+ "concurrently": false,
177
+ "method": "btree",
178
+ "with": {}
179
+ }
180
+ },
181
+ "foreignKeys": {},
182
+ "compositePrimaryKeys": {},
183
+ "uniqueConstraints": {},
184
+ "policies": {},
185
+ "checkConstraints": {},
186
+ "isRLSEnabled": false
187
+ },
188
+ "public.projects_projects": {
189
+ "name": "projects_projects",
190
+ "schema": "",
191
+ "columns": {
192
+ "id": {
193
+ "name": "id",
194
+ "type": "uuid",
195
+ "primaryKey": true,
196
+ "notNull": true,
197
+ "default": "uuidv7()"
198
+ },
199
+ "workspace_id": {
200
+ "name": "workspace_id",
201
+ "type": "uuid",
202
+ "primaryKey": false,
203
+ "notNull": true
204
+ },
205
+ "source_connection_id": {
206
+ "name": "source_connection_id",
207
+ "type": "uuid",
208
+ "primaryKey": false,
209
+ "notNull": true
210
+ },
211
+ "source_external_repository_id": {
212
+ "name": "source_external_repository_id",
213
+ "type": "text",
214
+ "primaryKey": false,
215
+ "notNull": true
216
+ },
217
+ "name": {
218
+ "name": "name",
219
+ "type": "text",
220
+ "primaryKey": false,
221
+ "notNull": true
222
+ },
223
+ "created_at": {
224
+ "name": "created_at",
225
+ "type": "timestamp with time zone",
226
+ "primaryKey": false,
227
+ "notNull": true,
228
+ "default": "now()"
229
+ },
230
+ "updated_at": {
231
+ "name": "updated_at",
232
+ "type": "timestamp with time zone",
233
+ "primaryKey": false,
234
+ "notNull": true,
235
+ "default": "now()"
236
+ }
237
+ },
238
+ "indexes": {
239
+ "projects_source_unique": {
240
+ "name": "projects_source_unique",
241
+ "columns": [
242
+ {
243
+ "expression": "source_connection_id",
244
+ "isExpression": false,
245
+ "asc": true,
246
+ "nulls": "last"
247
+ },
248
+ {
249
+ "expression": "source_external_repository_id",
250
+ "isExpression": false,
251
+ "asc": true,
252
+ "nulls": "last"
253
+ }
254
+ ],
255
+ "isUnique": true,
256
+ "concurrently": false,
257
+ "method": "btree",
258
+ "with": {}
259
+ },
260
+ "projects_workspace_created_id_idx": {
261
+ "name": "projects_workspace_created_id_idx",
262
+ "columns": [
263
+ {
264
+ "expression": "workspace_id",
265
+ "isExpression": false,
266
+ "asc": true,
267
+ "nulls": "last"
268
+ },
269
+ {
270
+ "expression": "created_at",
271
+ "isExpression": false,
272
+ "asc": true,
273
+ "nulls": "last"
274
+ },
275
+ {
276
+ "expression": "id",
277
+ "isExpression": false,
278
+ "asc": true,
279
+ "nulls": "last"
280
+ }
281
+ ],
282
+ "isUnique": false,
283
+ "concurrently": false,
284
+ "method": "btree",
285
+ "with": {}
286
+ }
287
+ },
288
+ "foreignKeys": {},
289
+ "compositePrimaryKeys": {},
290
+ "uniqueConstraints": {},
291
+ "policies": {},
292
+ "checkConstraints": {},
293
+ "isRLSEnabled": false
294
+ }
295
+ },
296
+ "enums": {},
297
+ "schemas": {},
298
+ "sequences": {},
299
+ "roles": {},
300
+ "policies": {},
301
+ "views": {},
302
+ "_meta": {
303
+ "columns": {},
304
+ "schemas": {},
305
+ "tables": {}
306
+ }
307
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1777381200000,
9
+ "tag": "0000_initial",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,7 @@
1
+ import {defineConfig} from 'drizzle-kit';
2
+
3
+ export default defineConfig({
4
+ schema: './src/db/schema/*.ts',
5
+ out: './drizzle',
6
+ dialect: 'postgresql',
7
+ });
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@shipfox/api-projects",
3
+ "license": "MIT",
4
+ "version": "2.0.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
8
+ "directory": "libs/api/projects"
9
+ },
10
+ "private": false,
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "imports": {
15
+ "#test/*": "./test/*",
16
+ "#*": {
17
+ "workspace-source": "./src/*",
18
+ "development": "./src/*",
19
+ "default": "./dist/*"
20
+ }
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "development": {
25
+ "types": "./src/index.ts",
26
+ "default": "./src/index.ts"
27
+ },
28
+ "default": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "@temporalio/workflow": "^1.16.1",
36
+ "drizzle-orm": "^0.45.2",
37
+ "zod": "^4.4.3",
38
+ "@shipfox/api-auth-context": "2.0.0",
39
+ "@shipfox/api-integration-core-dto": "2.0.0",
40
+ "@shipfox/api-integration-core": "2.0.0",
41
+ "@shipfox/node-fastify": "0.2.1",
42
+ "@shipfox/api-projects-dto": "2.0.0",
43
+ "@shipfox/node-drizzle": "0.2.1",
44
+ "@shipfox/node-opentelemetry": "0.5.0",
45
+ "@shipfox/node-outbox": "0.2.1",
46
+ "@shipfox/node-temporal": "0.2.0",
47
+ "@shipfox/node-module": "0.2.0",
48
+ "@shipfox/node-postgres": "0.4.1"
49
+ },
50
+ "devDependencies": {
51
+ "@temporalio/activity": "^1.16.1",
52
+ "@temporalio/client": "^1.16.1",
53
+ "@temporalio/common": "^1.16.1",
54
+ "@temporalio/testing": "^1.16.1",
55
+ "@temporalio/worker": "^1.16.1",
56
+ "@types/pg": "^8.15.5",
57
+ "drizzle-kit": "^0.31.10",
58
+ "fastify": "^5.3.3",
59
+ "fastify-type-provider-zod": "^6.0.0",
60
+ "fishery": "^2.2.2",
61
+ "@shipfox/swc": "1.2.5",
62
+ "@shipfox/typescript": "1.1.6",
63
+ "@shipfox/ts-config": "1.3.8",
64
+ "@shipfox/biome": "1.8.1",
65
+ "@shipfox/vitest": "1.2.2"
66
+ },
67
+ "scripts": {
68
+ "build": "shipfox-swc",
69
+ "check": "shipfox-biome-check",
70
+ "check:fix": "shipfox-biome-check --write",
71
+ "test": "shipfox-vitest-run",
72
+ "test:watch": "shipfox-vitest-watch",
73
+ "type": "shipfox-tsc-check",
74
+ "type:emit": "shipfox-tsc-emit"
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ export type * from './project.js';
@@ -0,0 +1,9 @@
1
+ export interface Project {
2
+ id: string;
3
+ workspaceId: string;
4
+ sourceConnectionId: string;
5
+ sourceExternalRepositoryId: string;
6
+ name: string;
7
+ createdAt: Date;
8
+ updatedAt: Date;
9
+ }
@@ -0,0 +1,26 @@
1
+ export class ProjectNotFoundError extends Error {
2
+ constructor(projectId: string) {
3
+ super(`Project not found: ${projectId}`);
4
+ this.name = 'ProjectNotFoundError';
5
+ }
6
+ }
7
+
8
+ export class ProjectAccessDeniedError extends Error {
9
+ constructor(projectId: string) {
10
+ super(`Access denied for project: ${projectId}`);
11
+ this.name = 'ProjectAccessDeniedError';
12
+ }
13
+ }
14
+
15
+ export class ProjectAlreadyExistsError extends Error {
16
+ constructor(
17
+ public readonly existingProjectId: string,
18
+ public readonly sourceConnectionId: string,
19
+ public readonly sourceExternalRepositoryId: string,
20
+ ) {
21
+ super(
22
+ `Project already exists for source repository: ${sourceConnectionId}/${sourceExternalRepositoryId}`,
23
+ );
24
+ this.name = 'ProjectAlreadyExistsError';
25
+ }
26
+ }
@@ -0,0 +1,8 @@
1
+ export type * from './entities/index.js';
2
+ export {
3
+ ProjectAccessDeniedError,
4
+ ProjectAlreadyExistsError,
5
+ ProjectNotFoundError,
6
+ } from './errors.js';
7
+ export type {CreateProjectFromSourceParams} from './projects.js';
8
+ export {createProjectFromSource} from './projects.js';
@@ -0,0 +1,142 @@
1
+ import type {IntegrationSourceControlService} from '@shipfox/api-integration-core';
2
+ import {IntegrationProviderError} from '@shipfox/api-integration-core';
3
+ import {sql} from 'drizzle-orm';
4
+ import {db} from '#db/index.js';
5
+ import {projectsOutbox} from '#db/schema/outbox.js';
6
+ import {createProjectFromSource} from './projects.js';
7
+
8
+ describe('createProjectFromSource', () => {
9
+ let actorId: string;
10
+ let workspaceId: string;
11
+ let sourceConnectionId: string;
12
+ let sourceControl: IntegrationSourceControlService;
13
+
14
+ beforeEach(() => {
15
+ actorId = crypto.randomUUID();
16
+ workspaceId = crypto.randomUUID();
17
+ sourceConnectionId = crypto.randomUUID();
18
+ sourceControl = {
19
+ getConnection: vi.fn(),
20
+ listRepositories: vi.fn(),
21
+ resolveRepository: vi.fn(async () => {
22
+ await Promise.resolve();
23
+ return {
24
+ connection: {
25
+ id: sourceConnectionId,
26
+ workspaceId,
27
+ provider: 'gitea' as const,
28
+ externalAccountId: 'gitea-owner',
29
+ slug: 'gitea_owner',
30
+ displayName: 'Gitea',
31
+ lifecycleStatus: 'active' as const,
32
+ capabilities: ['source_control' as const],
33
+ createdAt: new Date(),
34
+ updatedAt: new Date(),
35
+ },
36
+ repository: {
37
+ externalRepositoryId: 'gitea:gitea-owner/platform',
38
+ owner: 'gitea-owner',
39
+ name: 'platform',
40
+ fullName: 'gitea-owner/platform',
41
+ defaultBranch: 'main',
42
+ visibility: 'private' as const,
43
+ cloneUrl: 'https://gitea.local/gitea-owner/platform.git',
44
+ htmlUrl: 'https://gitea.local/gitea-owner/platform',
45
+ },
46
+ };
47
+ }),
48
+ listFiles: vi.fn(),
49
+ fetchFile: vi.fn(),
50
+ createCheckoutSpec: vi.fn(),
51
+ };
52
+ });
53
+
54
+ test('creates a project bound to a source repository', async () => {
55
+ const project = await createProjectFromSource({
56
+ actorId,
57
+ workspaceId,
58
+ name: 'Platform',
59
+ sourceConnectionId,
60
+ sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
61
+ sourceControl,
62
+ });
63
+
64
+ expect(project.workspaceId).toBe(workspaceId);
65
+ expect(project.name).toBe('Platform');
66
+ expect(project.sourceConnectionId).toBe(sourceConnectionId);
67
+ expect(project.sourceExternalRepositoryId).toBe('gitea:gitea-owner/platform');
68
+ });
69
+
70
+ test('emits project lifecycle events in the same transaction', async () => {
71
+ const project = await createProjectFromSource({
72
+ actorId,
73
+ workspaceId,
74
+ name: 'Platform',
75
+ sourceConnectionId,
76
+ sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
77
+ sourceControl,
78
+ });
79
+
80
+ const events = await db()
81
+ .select()
82
+ .from(projectsOutbox)
83
+ .where(sql`${projectsOutbox.payload}->>'projectId' = ${project.id}`);
84
+
85
+ expect(events.map((event) => event.eventType).sort()).toEqual([
86
+ 'projects.project.created',
87
+ 'projects.project.source_bound',
88
+ ]);
89
+ expect(events.map((event) => event.payload)).toEqual(
90
+ expect.arrayContaining([
91
+ expect.objectContaining({
92
+ sourceConnectionId,
93
+ sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
94
+ }),
95
+ expect.objectContaining({
96
+ sourceConnectionId,
97
+ externalRepositoryId: 'gitea:gitea-owner/platform',
98
+ provider: 'gitea',
99
+ }),
100
+ ]),
101
+ );
102
+ });
103
+
104
+ test('rejects a second project for the same source repository', async () => {
105
+ await createProjectFromSource({
106
+ actorId,
107
+ workspaceId,
108
+ name: 'Platform',
109
+ sourceConnectionId,
110
+ sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
111
+ sourceControl,
112
+ });
113
+
114
+ const result = createProjectFromSource({
115
+ actorId,
116
+ workspaceId,
117
+ name: 'Platform Again',
118
+ sourceConnectionId,
119
+ sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
120
+ sourceControl,
121
+ });
122
+
123
+ await expect(result).rejects.toThrow('Project already exists');
124
+ });
125
+
126
+ test('surfaces provider repository access failures', async () => {
127
+ vi.mocked(sourceControl.resolveRepository).mockRejectedValueOnce(
128
+ new IntegrationProviderError('repository-not-found', 'Repository not found'),
129
+ );
130
+
131
+ const result = createProjectFromSource({
132
+ actorId,
133
+ workspaceId,
134
+ name: 'Missing',
135
+ sourceConnectionId,
136
+ sourceExternalRepositoryId: 'not-found',
137
+ sourceControl,
138
+ });
139
+
140
+ await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
141
+ });
142
+ });