@shipfox/api-projects 10.2.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +83 -0
  4. package/dist/core/entities/project.d.ts +4 -0
  5. package/dist/core/entities/project.d.ts.map +1 -1
  6. package/dist/core/entities/project.js.map +1 -1
  7. package/dist/core/errors.d.ts +4 -0
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js +6 -0
  10. package/dist/core/errors.js.map +1 -1
  11. package/dist/core/index.d.ts +3 -3
  12. package/dist/core/index.d.ts.map +1 -1
  13. package/dist/core/index.js +2 -2
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/projects.d.ts +8 -0
  16. package/dist/core/projects.d.ts.map +1 -1
  17. package/dist/core/projects.js +62 -19
  18. package/dist/core/projects.js.map +1 -1
  19. package/dist/db/db.d.ts +138 -0
  20. package/dist/db/db.d.ts.map +1 -1
  21. package/dist/db/db.js.map +1 -1
  22. package/dist/db/index.d.ts +2 -2
  23. package/dist/db/index.d.ts.map +1 -1
  24. package/dist/db/index.js +1 -1
  25. package/dist/db/index.js.map +1 -1
  26. package/dist/db/projects.d.ts +53 -0
  27. package/dist/db/projects.d.ts.map +1 -1
  28. package/dist/db/projects.js +116 -18
  29. package/dist/db/projects.js.map +1 -1
  30. package/dist/db/schema/projects.d.ts +68 -0
  31. package/dist/db/schema/projects.d.ts.map +1 -1
  32. package/dist/db/schema/projects.js +12 -1
  33. package/dist/db/schema/projects.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -5
  37. package/dist/index.js.map +1 -1
  38. package/dist/presentation/dto/project.d.ts +1 -0
  39. package/dist/presentation/dto/project.d.ts.map +1 -1
  40. package/dist/presentation/dto/project.js +1 -0
  41. package/dist/presentation/dto/project.js.map +1 -1
  42. package/dist/presentation/e2eRoutes/create-project.d.ts.map +1 -1
  43. package/dist/presentation/e2eRoutes/create-project.js +34 -8
  44. package/dist/presentation/e2eRoutes/create-project.js.map +1 -1
  45. package/dist/presentation/inter-module.d.ts +4 -1
  46. package/dist/presentation/inter-module.d.ts.map +1 -1
  47. package/dist/presentation/inter-module.js +44 -3
  48. package/dist/presentation/inter-module.js.map +1 -1
  49. package/dist/presentation/routes/create-project.d.ts.map +1 -1
  50. package/dist/presentation/routes/create-project.js +8 -2
  51. package/dist/presentation/routes/create-project.js.map +1 -1
  52. package/dist/presentation/routes/index.d.ts.map +1 -1
  53. package/dist/presentation/routes/index.js +3 -1
  54. package/dist/presentation/routes/index.js.map +1 -1
  55. package/dist/presentation/routes/update-project.d.ts +2 -0
  56. package/dist/presentation/routes/update-project.d.ts.map +1 -0
  57. package/dist/presentation/routes/update-project.js +53 -0
  58. package/dist/presentation/routes/update-project.js.map +1 -0
  59. package/dist/presentation/subscribers/index.d.ts +1 -0
  60. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  61. package/dist/presentation/subscribers/index.js +1 -0
  62. package/dist/presentation/subscribers/index.js.map +1 -1
  63. package/dist/presentation/subscribers/on-source-repository-updated.d.ts +4 -0
  64. package/dist/presentation/subscribers/on-source-repository-updated.d.ts.map +1 -0
  65. package/dist/presentation/subscribers/on-source-repository-updated.js +39 -0
  66. package/dist/presentation/subscribers/on-source-repository-updated.js.map +1 -0
  67. package/dist/tsconfig.test.tsbuildinfo +1 -1
  68. package/drizzle/0000_initial.sql +6 -0
  69. package/drizzle/meta/0000_snapshot.json +78 -0
  70. package/package.json +13 -12
  71. package/src/core/entities/project.ts +4 -0
  72. package/src/core/errors.ts +7 -0
  73. package/src/core/index.ts +3 -2
  74. package/src/core/projects.test.ts +10 -0
  75. package/src/core/projects.ts +90 -35
  76. package/src/db/db.ts +3 -0
  77. package/src/db/index.ts +6 -0
  78. package/src/db/projects.test.ts +44 -1
  79. package/src/db/projects.ts +242 -33
  80. package/src/db/schema/projects.ts +16 -0
  81. package/src/index.ts +9 -3
  82. package/src/presentation/dto/project.ts +1 -0
  83. package/src/presentation/e2eRoutes/create-project.test.ts +23 -0
  84. package/src/presentation/e2eRoutes/create-project.ts +39 -10
  85. package/src/presentation/inter-module.test.ts +309 -0
  86. package/src/presentation/inter-module.ts +76 -4
  87. package/src/presentation/routes/create-project.ts +10 -2
  88. package/src/presentation/routes/index.ts +7 -1
  89. package/src/presentation/routes/projects.test.ts +301 -5
  90. package/src/presentation/routes/update-project.ts +46 -0
  91. package/src/presentation/subscribers/index.ts +1 -0
  92. package/src/presentation/subscribers/on-source-commit-pushed.test.ts +2 -0
  93. package/src/presentation/subscribers/on-source-repository-updated.test.ts +108 -0
  94. package/src/presentation/subscribers/on-source-repository-updated.ts +50 -0
  95. package/test/factories/project.ts +6 -0
  96. package/tsconfig.build.tsbuildinfo +1 -1
package/dist/db/index.js CHANGED
@@ -2,7 +2,7 @@ import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  export { closeDb, db, schema } from './db.js';
4
4
  export { pruneIntegrationEventDedup, recordIntegrationEventForProject } from './integration-event-dedup.js';
5
- export { createProject, getProjectById, getProjectBySource, getProjectCount, getWorkspaceProjectCounts, listAdminProjects, listProjects, requireProjectForWorkspace } from './projects.js';
5
+ export { createProject, getProjectById, getProjectBySource, getProjectCount, getWorkspaceProjectCounts, listAdminProjects, listProjects, requireProjectForWorkspace, resolveCheckoutTarget, updateProject } from './projects.js';
6
6
  export { projectsIntegrationEventDedup } from './schema/integration-event-dedup.js';
7
7
  export { projectsOutbox } from './schema/outbox.js';
8
8
  export const migrationsPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../drizzle');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nexport {closeDb, db, schema} from './db.js';\nexport {\n pruneIntegrationEventDedup,\n recordIntegrationEventForProject,\n} from './integration-event-dedup.js';\nexport type {\n AdminProjectSummary,\n CreateProjectParams,\n GetProjectBySourceParams,\n ListAdminProjectsParams,\n ListAdminProjectsResult,\n ListProjectsParams,\n ListProjectsResult,\n} from './projects.js';\nexport {\n createProject,\n getProjectById,\n getProjectBySource,\n getProjectCount,\n getWorkspaceProjectCounts,\n listAdminProjects,\n listProjects,\n requireProjectForWorkspace,\n} from './projects.js';\nexport {projectsIntegrationEventDedup} from './schema/integration-event-dedup.js';\nexport {projectsOutbox} from './schema/outbox.js';\n\nexport const migrationsPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../drizzle');\n"],"names":["dirname","resolve","fileURLToPath","closeDb","db","schema","pruneIntegrationEventDedup","recordIntegrationEventForProject","createProject","getProjectById","getProjectBySource","getProjectCount","getWorkspaceProjectCounts","listAdminProjects","listProjects","requireProjectForWorkspace","projectsIntegrationEventDedup","projectsOutbox","migrationsPath","url"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,SAAQC,OAAO,EAAEC,EAAE,EAAEC,MAAM,QAAO,UAAU;AAC5C,SACEC,0BAA0B,EAC1BC,gCAAgC,QAC3B,+BAA+B;AAUtC,SACEC,aAAa,EACbC,cAAc,EACdC,kBAAkB,EAClBC,eAAe,EACfC,yBAAyB,EACzBC,iBAAiB,EACjBC,YAAY,EACZC,0BAA0B,QACrB,gBAAgB;AACvB,SAAQC,6BAA6B,QAAO,sCAAsC;AAClF,SAAQC,cAAc,QAAO,qBAAqB;AAElD,OAAO,MAAMC,iBAAiBjB,QAAQD,QAAQE,cAAc,YAAYiB,GAAG,IAAI,iBAAiB"}
1
+ {"version":3,"sources":["../../src/db/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nexport {closeDb, db, schema} from './db.js';\nexport {\n pruneIntegrationEventDedup,\n recordIntegrationEventForProject,\n} from './integration-event-dedup.js';\nexport type {\n AdminProjectSummary,\n CreateProjectParams,\n GetProjectBySourceParams,\n ListAdminProjectsParams,\n ListAdminProjectsResult,\n ListProjectsParams,\n ListProjectsResult,\n ResolveCheckoutTargetParams,\n ResolvedCheckoutTarget,\n UpdateProjectParams,\n UpdateProjectResult,\n} from './projects.js';\nexport {\n createProject,\n getProjectById,\n getProjectBySource,\n getProjectCount,\n getWorkspaceProjectCounts,\n listAdminProjects,\n listProjects,\n requireProjectForWorkspace,\n resolveCheckoutTarget,\n updateProject,\n} from './projects.js';\nexport {projectsIntegrationEventDedup} from './schema/integration-event-dedup.js';\nexport {projectsOutbox} from './schema/outbox.js';\n\nexport const migrationsPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../drizzle');\n"],"names":["dirname","resolve","fileURLToPath","closeDb","db","schema","pruneIntegrationEventDedup","recordIntegrationEventForProject","createProject","getProjectById","getProjectBySource","getProjectCount","getWorkspaceProjectCounts","listAdminProjects","listProjects","requireProjectForWorkspace","resolveCheckoutTarget","updateProject","projectsIntegrationEventDedup","projectsOutbox","migrationsPath","url"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,SAAQC,OAAO,EAAEC,EAAE,EAAEC,MAAM,QAAO,UAAU;AAC5C,SACEC,0BAA0B,EAC1BC,gCAAgC,QAC3B,+BAA+B;AActC,SACEC,aAAa,EACbC,cAAc,EACdC,kBAAkB,EAClBC,eAAe,EACfC,yBAAyB,EACzBC,iBAAiB,EACjBC,YAAY,EACZC,0BAA0B,EAC1BC,qBAAqB,EACrBC,aAAa,QACR,gBAAgB;AACvB,SAAQC,6BAA6B,QAAO,sCAAsC;AAClF,SAAQC,cAAc,QAAO,qBAAqB;AAElD,OAAO,MAAMC,iBAAiBnB,QAAQD,QAAQE,cAAc,YAAYmB,GAAG,IAAI,iBAAiB"}
@@ -1,9 +1,22 @@
1
1
  import type { Project } from '#core/entities/project.js';
2
+ import { db, type Executor } from './db.js';
3
+ type ProjectDatabase = ReturnType<typeof db>;
4
+ type ProjectTransaction = Parameters<Parameters<ProjectDatabase['transaction']>[0]>[0];
2
5
  export interface CreateProjectParams {
3
6
  workspaceId: string;
4
7
  sourceConnectionId: string;
5
8
  sourceExternalRepositoryId: string;
6
9
  name: string;
10
+ slug: string;
11
+ }
12
+ export interface UpdateProjectParams {
13
+ projectId: string;
14
+ name?: string | undefined;
15
+ slug?: string | undefined;
16
+ }
17
+ export interface UpdateProjectResult {
18
+ project: Project;
19
+ changed: boolean;
7
20
  }
8
21
  export interface ProjectCursor {
9
22
  createdAt: Date;
@@ -31,6 +44,9 @@ export interface ListAdminProjectsResult {
31
44
  nextCursor: ProjectCursor | null;
32
45
  }
33
46
  export declare function createProject(params: CreateProjectParams): Promise<Project>;
47
+ export declare function updateProject(params: UpdateProjectParams, options?: {
48
+ tx?: ProjectDatabase | ProjectTransaction | undefined;
49
+ }): Promise<UpdateProjectResult | undefined>;
34
50
  export declare function getProjectById(id: string): Promise<Project | undefined>;
35
51
  export interface GetProjectBySourceParams {
36
52
  workspaceId: string;
@@ -38,10 +54,46 @@ export interface GetProjectBySourceParams {
38
54
  sourceExternalRepositoryId: string;
39
55
  }
40
56
  export declare function getProjectBySource(params: GetProjectBySourceParams): Promise<Project | undefined>;
57
+ export interface UpdateProjectSourceRepositoryParams extends GetProjectBySourceParams {
58
+ tx?: Executor;
59
+ sourceRepositoryOwner: string;
60
+ sourceRepositoryName: string;
61
+ sourceDefaultBranch: string;
62
+ }
63
+ export declare function updateProjectSourceRepository(params: UpdateProjectSourceRepositoryParams): Promise<Project | undefined>;
41
64
  export declare function requireProjectForWorkspace(params: {
42
65
  projectId: string;
43
66
  workspaceId: string;
44
67
  }): Promise<Project>;
68
+ export interface ResolveCheckoutTargetParams {
69
+ workspaceId: string;
70
+ defaults: {
71
+ connectionId: string;
72
+ owner: string;
73
+ };
74
+ target: {
75
+ project: string;
76
+ } | {
77
+ connection?: string | undefined;
78
+ repository: string;
79
+ };
80
+ }
81
+ export interface ResolvedCheckoutTarget {
82
+ projectId: string;
83
+ connectionId: string;
84
+ externalRepositoryId: string;
85
+ }
86
+ export interface UpdateProjectSourceMetadataParams {
87
+ workspaceId: string;
88
+ projectId: string;
89
+ sourceConnectionId: string;
90
+ sourceExternalRepositoryId: string;
91
+ sourceRepositoryOwner: string;
92
+ sourceRepositoryName: string;
93
+ sourceDefaultBranch: string;
94
+ }
95
+ export declare function updateProjectSourceMetadata(params: UpdateProjectSourceMetadataParams): Promise<void>;
96
+ export declare function resolveCheckoutTarget(params: ResolveCheckoutTargetParams): Promise<ResolvedCheckoutTarget | undefined>;
45
97
  export declare function listProjects(params: ListProjectsParams): Promise<ListProjectsResult>;
46
98
  export declare function listAdminProjects(params: ListAdminProjectsParams): Promise<ListAdminProjectsResult>;
47
99
  export declare function getProjectCount(): Promise<number>;
@@ -51,4 +103,5 @@ export declare function getWorkspaceProjectCounts(params: {
51
103
  workspaceId: string;
52
104
  count: number;
53
105
  }>>;
106
+ export {};
54
107
  //# sourceMappingURL=projects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAMvD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B,EAAE,MAAM,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAMD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,OAAO,EACP,IAAI,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAC1D,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;CAClC;AAUD,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwCjF;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAM7E;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B,EAAE,MAAM,CAAC;CACpC;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAgB9B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAKnB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuB1F;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC,CA8BlC;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAGvD;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,GAAG,OAAO,CAAC,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC,CAYvD"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"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"}
@@ -1,33 +1,48 @@
1
- import { and, count, desc, eq, ilike, inArray, lt, or } from 'drizzle-orm';
2
- import { ProjectAlreadyExistsError, ProjectNotFoundError } from '#core/errors.js';
1
+ import { isUniqueViolation } from '@shipfox/node-drizzle';
2
+ import { and, count, desc, eq, ilike, inArray, lt, or, sql } from 'drizzle-orm';
3
+ import { ProjectAlreadyExistsError, ProjectNotFoundError, ProjectSlugConflictError } from '#core/errors.js';
3
4
  import { recordProjectCreated } from '#metrics/instance.js';
4
5
  import { db } from './db.js';
5
6
  import { projects, toProject } from './schema/projects.js';
6
7
  function escapeIlikePattern(value) {
7
8
  return value.replace(/[\\%_]/g, (char)=>`\\${char}`);
8
9
  }
10
+ const PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT = 'projects_workspace_slug_unique';
9
11
  function cursorWhere(cursor) {
10
12
  if (!cursor) return undefined;
11
13
  return or(lt(projects.createdAt, cursor.createdAt), and(eq(projects.createdAt, cursor.createdAt), lt(projects.id, cursor.id)));
12
14
  }
13
15
  export async function createProject(params) {
14
16
  const project = await db().transaction(async (tx)=>{
15
- const [projectRow] = await tx.insert(projects).values({
16
- workspaceId: params.workspaceId,
17
- sourceConnectionId: params.sourceConnectionId,
18
- sourceExternalRepositoryId: params.sourceExternalRepositoryId,
19
- name: params.name
20
- }).onConflictDoNothing({
21
- target: [
22
- projects.sourceConnectionId,
23
- projects.sourceExternalRepositoryId
24
- ]
25
- }).returning();
26
- if (!projectRow) {
27
- const [conflict] = await tx.select().from(projects).where(and(eq(projects.sourceConnectionId, params.sourceConnectionId), eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId))).limit(1);
28
- if (conflict) {
29
- throw new ProjectAlreadyExistsError(conflict.id, params.sourceConnectionId, params.sourceExternalRepositoryId);
17
+ let projectRow;
18
+ for(let attempt = 0; attempt < 2 && !projectRow; attempt += 1){
19
+ try {
20
+ [projectRow] = await tx.insert(projects).values({
21
+ workspaceId: params.workspaceId,
22
+ sourceConnectionId: params.sourceConnectionId,
23
+ sourceExternalRepositoryId: params.sourceExternalRepositoryId,
24
+ name: params.name,
25
+ slug: params.slug
26
+ }).onConflictDoNothing({
27
+ target: [
28
+ projects.sourceConnectionId,
29
+ projects.sourceExternalRepositoryId
30
+ ]
31
+ }).returning();
32
+ } catch (error) {
33
+ if (isUniqueViolation(error, PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT)) {
34
+ throw new ProjectSlugConflictError(params.slug);
35
+ }
36
+ throw error;
37
+ }
38
+ if (!projectRow) {
39
+ const [conflict] = await tx.select().from(projects).where(and(eq(projects.sourceConnectionId, params.sourceConnectionId), eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId))).limit(1);
40
+ if (conflict) {
41
+ throw new ProjectAlreadyExistsError(conflict.id, params.sourceConnectionId, params.sourceExternalRepositoryId);
42
+ }
30
43
  }
44
+ }
45
+ if (!projectRow) {
31
46
  throw new Error('Insert returned no rows');
32
47
  }
33
48
  return toProject(projectRow);
@@ -35,6 +50,39 @@ export async function createProject(params) {
35
50
  recordProjectCreated();
36
51
  return project;
37
52
  }
53
+ export async function updateProject(params, options = {}) {
54
+ const executor = options.tx ?? db();
55
+ const [existingRow] = await executor.select().from(projects).where(eq(projects.id, params.projectId)).limit(1);
56
+ if (!existingRow) return undefined;
57
+ const nextName = params.name ?? existingRow.name;
58
+ const nextSlug = params.slug ?? existingRow.slug;
59
+ if (nextName === existingRow.name && nextSlug === existingRow.slug) {
60
+ return {
61
+ project: toProject(existingRow),
62
+ changed: false
63
+ };
64
+ }
65
+ try {
66
+ const [row] = await executor.update(projects).set({
67
+ ...params.name !== undefined ? {
68
+ name: params.name
69
+ } : {},
70
+ ...params.slug !== undefined ? {
71
+ slug: params.slug
72
+ } : {},
73
+ updatedAt: new Date()
74
+ }).where(eq(projects.id, params.projectId)).returning();
75
+ return row ? {
76
+ project: toProject(row),
77
+ changed: true
78
+ } : undefined;
79
+ } catch (error) {
80
+ if (isUniqueViolation(error, PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT)) {
81
+ throw new ProjectSlugConflictError(nextSlug);
82
+ }
83
+ throw error;
84
+ }
85
+ }
38
86
  export async function getProjectById(id) {
39
87
  const rows = await db().select().from(projects).where(eq(projects.id, id)).limit(1);
40
88
  const row = rows[0];
@@ -47,12 +95,61 @@ export async function getProjectBySource(params) {
47
95
  if (!row) return undefined;
48
96
  return toProject(row);
49
97
  }
98
+ export async function updateProjectSourceRepository(params) {
99
+ const executor = params.tx ?? db();
100
+ const rows = await executor.update(projects).set({
101
+ sourceRepositoryOwner: params.sourceRepositoryOwner,
102
+ sourceRepositoryName: params.sourceRepositoryName,
103
+ sourceDefaultBranch: params.sourceDefaultBranch,
104
+ updatedAt: new Date()
105
+ }).where(and(eq(projects.workspaceId, params.workspaceId), eq(projects.sourceConnectionId, params.sourceConnectionId), eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId))).returning();
106
+ const row = rows[0];
107
+ if (!row) return undefined;
108
+ return toProject(row);
109
+ }
50
110
  export async function requireProjectForWorkspace(params) {
51
111
  const project = await getProjectById(params.projectId);
52
112
  if (!project) throw new ProjectNotFoundError(params.projectId);
53
113
  if (project.workspaceId !== params.workspaceId) throw new ProjectNotFoundError(params.projectId);
54
114
  return project;
55
115
  }
116
+ export async function updateProjectSourceMetadata(params) {
117
+ await db().update(projects).set({
118
+ sourceConnectionId: params.sourceConnectionId,
119
+ sourceExternalRepositoryId: params.sourceExternalRepositoryId,
120
+ sourceRepositoryOwner: params.sourceRepositoryOwner,
121
+ sourceRepositoryName: params.sourceRepositoryName,
122
+ sourceDefaultBranch: params.sourceDefaultBranch,
123
+ updatedAt: new Date()
124
+ }).where(and(eq(projects.workspaceId, params.workspaceId), eq(projects.id, params.projectId)));
125
+ }
126
+ export async function resolveCheckoutTarget(params) {
127
+ const selection = {
128
+ projectId: projects.id,
129
+ connectionId: projects.sourceConnectionId,
130
+ externalRepositoryId: projects.sourceExternalRepositoryId
131
+ };
132
+ if ('project' in params.target) {
133
+ const [project] = await db().select(selection).from(projects).where(and(eq(projects.workspaceId, params.workspaceId), eq(projects.id, params.target.project))).limit(1);
134
+ return project;
135
+ }
136
+ const separator = params.target.repository.indexOf('/');
137
+ if (separator === 0 || separator === params.target.repository.length - 1) return undefined;
138
+ const repository = separator === -1 ? {
139
+ connectionId: params.target.connection ?? params.defaults.connectionId,
140
+ owner: params.defaults.owner,
141
+ name: params.target.repository
142
+ } : params.target.repository.indexOf('/', separator + 1) === -1 ? {
143
+ connectionId: params.target.connection ?? params.defaults.connectionId,
144
+ owner: params.target.repository.slice(0, separator),
145
+ name: params.target.repository.slice(separator + 1)
146
+ } : undefined;
147
+ if (repository === undefined) return undefined;
148
+ const matches = await db().select(selection).from(projects).where(and(eq(projects.workspaceId, params.workspaceId), eq(projects.sourceConnectionId, repository.connectionId), sql`lower(${projects.sourceRepositoryOwner}) = lower(${repository.owner})`, sql`lower(${projects.sourceRepositoryName}) = lower(${repository.name})`)).limit(2);
149
+ // Owner/name isn't a unique key: renamed or reconnected repositories can leave
150
+ // more than one project row matching. Fail closed instead of picking arbitrarily.
151
+ return matches.length === 1 ? matches[0] : undefined;
152
+ }
56
153
  export async function listProjects(params) {
57
154
  const conditions = [
58
155
  eq(projects.workspaceId, params.workspaceId)
@@ -60,7 +157,8 @@ export async function listProjects(params) {
60
157
  const cursorCondition = cursorWhere(params.cursor);
61
158
  if (cursorCondition) conditions.push(cursorCondition);
62
159
  if (params.search) {
63
- conditions.push(ilike(projects.name, `%${escapeIlikePattern(params.search)}%`));
160
+ const searchPattern = `%${escapeIlikePattern(params.search)}%`;
161
+ conditions.push(or(ilike(projects.name, searchPattern), ilike(projects.slug, searchPattern)));
64
162
  }
65
163
  const rows = await db().select().from(projects).where(and(...conditions)).orderBy(desc(projects.createdAt), desc(projects.id)).limit(params.limit + 1);
66
164
  const hasMore = rows.length > params.limit;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/projects.ts"],"sourcesContent":["import {and, count, desc, eq, ilike, inArray, lt, or, type SQL} from 'drizzle-orm';\nimport type {Project} from '#core/entities/project.js';\nimport {ProjectAlreadyExistsError, ProjectNotFoundError} from '#core/errors.js';\nimport {recordProjectCreated} from '#metrics/instance.js';\nimport {db} from './db.js';\nimport {projects, toProject} from './schema/projects.js';\n\nexport interface CreateProjectParams {\n workspaceId: string;\n sourceConnectionId: string;\n sourceExternalRepositoryId: string;\n name: string;\n}\n\nexport interface ProjectCursor {\n createdAt: Date;\n id: string;\n}\n\nexport interface ListProjectsParams {\n workspaceId: string;\n limit: number;\n cursor?: ProjectCursor | undefined;\n search?: string | undefined;\n}\n\nfunction escapeIlikePattern(value: string): string {\n return value.replace(/[\\\\%_]/g, (char) => `\\\\${char}`);\n}\n\nexport interface ListProjectsResult {\n projects: Project[];\n nextCursor: ProjectCursor | null;\n}\n\nexport type AdminProjectSummary = Pick<\n Project,\n 'id' | 'workspaceId' | 'name' | 'createdAt' | 'updatedAt'\n>;\n\nexport interface ListAdminProjectsParams {\n limit: number;\n cursor?: ProjectCursor | undefined;\n projectId?: string | undefined;\n search?: string | undefined;\n}\n\nexport interface ListAdminProjectsResult {\n projects: AdminProjectSummary[];\n nextCursor: ProjectCursor | null;\n}\n\nfunction cursorWhere(cursor: ProjectCursor | undefined): SQL | undefined {\n if (!cursor) return undefined;\n return or(\n lt(projects.createdAt, cursor.createdAt),\n and(eq(projects.createdAt, cursor.createdAt), lt(projects.id, cursor.id)),\n );\n}\n\nexport async function createProject(params: CreateProjectParams): Promise<Project> {\n const project = await db().transaction(async (tx) => {\n const [projectRow] = await tx\n .insert(projects)\n .values({\n workspaceId: params.workspaceId,\n sourceConnectionId: params.sourceConnectionId,\n sourceExternalRepositoryId: params.sourceExternalRepositoryId,\n name: params.name,\n })\n .onConflictDoNothing({\n target: [projects.sourceConnectionId, projects.sourceExternalRepositoryId],\n })\n .returning();\n\n if (!projectRow) {\n const [conflict] = await tx\n .select()\n .from(projects)\n .where(\n and(\n eq(projects.sourceConnectionId, params.sourceConnectionId),\n eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId),\n ),\n )\n .limit(1);\n if (conflict) {\n throw new ProjectAlreadyExistsError(\n conflict.id,\n params.sourceConnectionId,\n params.sourceExternalRepositoryId,\n );\n }\n throw new Error('Insert returned no rows');\n }\n\n return toProject(projectRow);\n });\n recordProjectCreated();\n return project;\n}\n\nexport async function getProjectById(id: string): Promise<Project | undefined> {\n const rows = await db().select().from(projects).where(eq(projects.id, id)).limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProject(row);\n}\n\nexport interface GetProjectBySourceParams {\n workspaceId: string;\n sourceConnectionId: string;\n sourceExternalRepositoryId: string;\n}\n\nexport async function getProjectBySource(\n params: GetProjectBySourceParams,\n): Promise<Project | undefined> {\n const rows = await db()\n .select()\n .from(projects)\n .where(\n and(\n eq(projects.workspaceId, params.workspaceId),\n eq(projects.sourceConnectionId, params.sourceConnectionId),\n eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProject(row);\n}\n\nexport async function requireProjectForWorkspace(params: {\n projectId: string;\n workspaceId: string;\n}): Promise<Project> {\n const project = await getProjectById(params.projectId);\n if (!project) throw new ProjectNotFoundError(params.projectId);\n if (project.workspaceId !== params.workspaceId) throw new ProjectNotFoundError(params.projectId);\n return project;\n}\n\nexport async function listProjects(params: ListProjectsParams): Promise<ListProjectsResult> {\n const conditions = [eq(projects.workspaceId, params.workspaceId)];\n const cursorCondition = cursorWhere(params.cursor);\n if (cursorCondition) conditions.push(cursorCondition);\n if (params.search) {\n conditions.push(ilike(projects.name, `%${escapeIlikePattern(params.search)}%`));\n }\n\n const rows = await db()\n .select()\n .from(projects)\n .where(and(...conditions))\n .orderBy(desc(projects.createdAt), desc(projects.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n\n return {\n projects: pageRows.map(toProject),\n nextCursor: hasMore && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function listAdminProjects(\n params: ListAdminProjectsParams,\n): Promise<ListAdminProjectsResult> {\n const conditions: SQL[] = [];\n const cursorCondition = cursorWhere(params.cursor);\n if (cursorCondition) conditions.push(cursorCondition);\n if (params.projectId) conditions.push(eq(projects.id, params.projectId));\n if (params.search) {\n conditions.push(ilike(projects.name, `%${escapeIlikePattern(params.search)}%`));\n }\n\n const rows = await db()\n .select({\n id: projects.id,\n workspaceId: projects.workspaceId,\n name: projects.name,\n createdAt: projects.createdAt,\n updatedAt: projects.updatedAt,\n })\n .from(projects)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(projects.createdAt), desc(projects.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n\n return {\n projects: pageRows,\n nextCursor: hasMore && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function getProjectCount(): Promise<number> {\n const [row] = await db().select({value: count()}).from(projects);\n return row?.value ?? 0;\n}\n\nexport async function getWorkspaceProjectCounts(params: {\n workspaceIds: string[];\n}): Promise<Array<{workspaceId: string; count: number}>> {\n const rows = await db()\n .select({workspaceId: projects.workspaceId, count: count()})\n .from(projects)\n .where(inArray(projects.workspaceId, params.workspaceIds))\n .groupBy(projects.workspaceId);\n\n const countsByWorkspace = new Map(rows.map((row) => [row.workspaceId, Number(row.count)]));\n return params.workspaceIds.map((workspaceId) => ({\n workspaceId,\n count: countsByWorkspace.get(workspaceId) ?? 0,\n }));\n}\n"],"names":["and","count","desc","eq","ilike","inArray","lt","or","ProjectAlreadyExistsError","ProjectNotFoundError","recordProjectCreated","db","projects","toProject","escapeIlikePattern","value","replace","char","cursorWhere","cursor","undefined","createdAt","id","createProject","params","project","transaction","tx","projectRow","insert","values","workspaceId","sourceConnectionId","sourceExternalRepositoryId","name","onConflictDoNothing","target","returning","conflict","select","from","where","limit","Error","getProjectById","rows","row","getProjectBySource","requireProjectForWorkspace","projectId","listProjects","conditions","cursorCondition","push","search","orderBy","hasMore","length","pageRows","slice","last","at","map","nextCursor","listAdminProjects","updatedAt","getProjectCount","getWorkspaceProjectCounts","workspaceIds","groupBy","countsByWorkspace","Map","Number","get"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/projects.ts"],"sourcesContent":["import {isUniqueViolation} from '@shipfox/node-drizzle';\nimport {and, count, desc, eq, ilike, inArray, lt, or, type SQL, sql} from 'drizzle-orm';\nimport type {Project} from '#core/entities/project.js';\nimport {\n ProjectAlreadyExistsError,\n ProjectNotFoundError,\n ProjectSlugConflictError,\n} from '#core/errors.js';\nimport {recordProjectCreated} from '#metrics/instance.js';\nimport {db, type Executor} from './db.js';\nimport {projects, toProject} from './schema/projects.js';\n\ntype ProjectDatabase = ReturnType<typeof db>;\ntype ProjectTransaction = Parameters<Parameters<ProjectDatabase['transaction']>[0]>[0];\n\nexport interface CreateProjectParams {\n workspaceId: string;\n sourceConnectionId: string;\n sourceExternalRepositoryId: string;\n name: string;\n slug: string;\n}\n\nexport interface UpdateProjectParams {\n projectId: string;\n name?: string | undefined;\n slug?: string | undefined;\n}\n\nexport interface UpdateProjectResult {\n project: Project;\n changed: boolean;\n}\n\nexport interface ProjectCursor {\n createdAt: Date;\n id: string;\n}\n\nexport interface ListProjectsParams {\n workspaceId: string;\n limit: number;\n cursor?: ProjectCursor | undefined;\n search?: string | undefined;\n}\n\nfunction escapeIlikePattern(value: string): string {\n return value.replace(/[\\\\%_]/g, (char) => `\\\\${char}`);\n}\n\nexport interface ListProjectsResult {\n projects: Project[];\n nextCursor: ProjectCursor | null;\n}\n\nexport type AdminProjectSummary = Pick<\n Project,\n 'id' | 'workspaceId' | 'name' | 'createdAt' | 'updatedAt'\n>;\n\nexport interface ListAdminProjectsParams {\n limit: number;\n cursor?: ProjectCursor | undefined;\n projectId?: string | undefined;\n search?: string | undefined;\n}\n\nexport interface ListAdminProjectsResult {\n projects: AdminProjectSummary[];\n nextCursor: ProjectCursor | null;\n}\n\nconst PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT = 'projects_workspace_slug_unique';\n\nfunction cursorWhere(cursor: ProjectCursor | undefined): SQL | undefined {\n if (!cursor) return undefined;\n return or(\n lt(projects.createdAt, cursor.createdAt),\n and(eq(projects.createdAt, cursor.createdAt), lt(projects.id, cursor.id)),\n );\n}\n\nexport async function createProject(params: CreateProjectParams): Promise<Project> {\n const project = await db().transaction(async (tx) => {\n let projectRow: typeof projects.$inferSelect | undefined;\n for (let attempt = 0; attempt < 2 && !projectRow; attempt += 1) {\n try {\n [projectRow] = await tx\n .insert(projects)\n .values({\n workspaceId: params.workspaceId,\n sourceConnectionId: params.sourceConnectionId,\n sourceExternalRepositoryId: params.sourceExternalRepositoryId,\n name: params.name,\n slug: params.slug,\n })\n .onConflictDoNothing({\n target: [projects.sourceConnectionId, projects.sourceExternalRepositoryId],\n })\n .returning();\n } catch (error) {\n if (isUniqueViolation(error, PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT)) {\n throw new ProjectSlugConflictError(params.slug);\n }\n throw error;\n }\n\n if (!projectRow) {\n const [conflict] = await tx\n .select()\n .from(projects)\n .where(\n and(\n eq(projects.sourceConnectionId, params.sourceConnectionId),\n eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId),\n ),\n )\n .limit(1);\n if (conflict) {\n throw new ProjectAlreadyExistsError(\n conflict.id,\n params.sourceConnectionId,\n params.sourceExternalRepositoryId,\n );\n }\n }\n }\n\n if (!projectRow) {\n throw new Error('Insert returned no rows');\n }\n\n return toProject(projectRow);\n });\n recordProjectCreated();\n return project;\n}\n\nexport async function updateProject(\n params: UpdateProjectParams,\n options: {tx?: ProjectDatabase | ProjectTransaction | undefined} = {},\n): Promise<UpdateProjectResult | undefined> {\n const executor = options.tx ?? db();\n\n const [existingRow] = await executor\n .select()\n .from(projects)\n .where(eq(projects.id, params.projectId))\n .limit(1);\n if (!existingRow) return undefined;\n\n const nextName = params.name ?? existingRow.name;\n const nextSlug = params.slug ?? existingRow.slug;\n if (nextName === existingRow.name && nextSlug === existingRow.slug) {\n return {project: toProject(existingRow), changed: false};\n }\n\n try {\n const [row] = await executor\n .update(projects)\n .set({\n ...(params.name !== undefined ? {name: params.name} : {}),\n ...(params.slug !== undefined ? {slug: params.slug} : {}),\n updatedAt: new Date(),\n })\n .where(eq(projects.id, params.projectId))\n .returning();\n\n return row ? {project: toProject(row), changed: true} : undefined;\n } catch (error) {\n if (isUniqueViolation(error, PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT)) {\n throw new ProjectSlugConflictError(nextSlug);\n }\n throw error;\n }\n}\n\nexport async function getProjectById(id: string): Promise<Project | undefined> {\n const rows = await db().select().from(projects).where(eq(projects.id, id)).limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProject(row);\n}\n\nexport interface GetProjectBySourceParams {\n workspaceId: string;\n sourceConnectionId: string;\n sourceExternalRepositoryId: string;\n}\n\nexport async function getProjectBySource(\n params: GetProjectBySourceParams,\n): Promise<Project | undefined> {\n const rows = await db()\n .select()\n .from(projects)\n .where(\n and(\n eq(projects.workspaceId, params.workspaceId),\n eq(projects.sourceConnectionId, params.sourceConnectionId),\n eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toProject(row);\n}\n\nexport interface UpdateProjectSourceRepositoryParams extends GetProjectBySourceParams {\n tx?: Executor;\n sourceRepositoryOwner: string;\n sourceRepositoryName: string;\n sourceDefaultBranch: string;\n}\n\nexport async function updateProjectSourceRepository(\n params: UpdateProjectSourceRepositoryParams,\n): Promise<Project | undefined> {\n const executor = params.tx ?? db();\n const rows = await executor\n .update(projects)\n .set({\n sourceRepositoryOwner: params.sourceRepositoryOwner,\n sourceRepositoryName: params.sourceRepositoryName,\n sourceDefaultBranch: params.sourceDefaultBranch,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(projects.workspaceId, params.workspaceId),\n eq(projects.sourceConnectionId, params.sourceConnectionId),\n eq(projects.sourceExternalRepositoryId, params.sourceExternalRepositoryId),\n ),\n )\n .returning();\n\n const row = rows[0];\n if (!row) return undefined;\n return toProject(row);\n}\n\nexport async function requireProjectForWorkspace(params: {\n projectId: string;\n workspaceId: string;\n}): Promise<Project> {\n const project = await getProjectById(params.projectId);\n if (!project) throw new ProjectNotFoundError(params.projectId);\n if (project.workspaceId !== params.workspaceId) throw new ProjectNotFoundError(params.projectId);\n return project;\n}\n\nexport interface ResolveCheckoutTargetParams {\n workspaceId: string;\n defaults: {\n connectionId: string;\n owner: string;\n };\n target: {project: string} | {connection?: string | undefined; repository: string};\n}\n\nexport interface ResolvedCheckoutTarget {\n projectId: string;\n connectionId: string;\n externalRepositoryId: string;\n}\n\nexport interface UpdateProjectSourceMetadataParams {\n workspaceId: string;\n projectId: string;\n sourceConnectionId: string;\n sourceExternalRepositoryId: string;\n sourceRepositoryOwner: string;\n sourceRepositoryName: string;\n sourceDefaultBranch: string;\n}\n\nexport async function updateProjectSourceMetadata(\n params: UpdateProjectSourceMetadataParams,\n): Promise<void> {\n await db()\n .update(projects)\n .set({\n sourceConnectionId: params.sourceConnectionId,\n sourceExternalRepositoryId: params.sourceExternalRepositoryId,\n sourceRepositoryOwner: params.sourceRepositoryOwner,\n sourceRepositoryName: params.sourceRepositoryName,\n sourceDefaultBranch: params.sourceDefaultBranch,\n updatedAt: new Date(),\n })\n .where(and(eq(projects.workspaceId, params.workspaceId), eq(projects.id, params.projectId)));\n}\n\nexport async function resolveCheckoutTarget(\n params: ResolveCheckoutTargetParams,\n): Promise<ResolvedCheckoutTarget | undefined> {\n const selection = {\n projectId: projects.id,\n connectionId: projects.sourceConnectionId,\n externalRepositoryId: projects.sourceExternalRepositoryId,\n };\n\n if ('project' in params.target) {\n const [project] = await db()\n .select(selection)\n .from(projects)\n .where(\n and(eq(projects.workspaceId, params.workspaceId), eq(projects.id, params.target.project)),\n )\n .limit(1);\n return project;\n }\n\n const separator = params.target.repository.indexOf('/');\n if (separator === 0 || separator === params.target.repository.length - 1) return undefined;\n\n const repository =\n separator === -1\n ? {\n connectionId: params.target.connection ?? params.defaults.connectionId,\n owner: params.defaults.owner,\n name: params.target.repository,\n }\n : params.target.repository.indexOf('/', separator + 1) === -1\n ? {\n connectionId: params.target.connection ?? params.defaults.connectionId,\n owner: params.target.repository.slice(0, separator),\n name: params.target.repository.slice(separator + 1),\n }\n : undefined;\n if (repository === undefined) return undefined;\n\n const matches = await db()\n .select(selection)\n .from(projects)\n .where(\n and(\n eq(projects.workspaceId, params.workspaceId),\n eq(projects.sourceConnectionId, repository.connectionId),\n sql`lower(${projects.sourceRepositoryOwner}) = lower(${repository.owner})`,\n sql`lower(${projects.sourceRepositoryName}) = lower(${repository.name})`,\n ),\n )\n .limit(2);\n\n // Owner/name isn't a unique key: renamed or reconnected repositories can leave\n // more than one project row matching. Fail closed instead of picking arbitrarily.\n return matches.length === 1 ? matches[0] : undefined;\n}\n\nexport async function listProjects(params: ListProjectsParams): Promise<ListProjectsResult> {\n const conditions = [eq(projects.workspaceId, params.workspaceId)];\n const cursorCondition = cursorWhere(params.cursor);\n if (cursorCondition) conditions.push(cursorCondition);\n if (params.search) {\n const searchPattern = `%${escapeIlikePattern(params.search)}%`;\n conditions.push(\n or(ilike(projects.name, searchPattern), ilike(projects.slug, searchPattern)) as SQL,\n );\n }\n\n const rows = await db()\n .select()\n .from(projects)\n .where(and(...conditions))\n .orderBy(desc(projects.createdAt), desc(projects.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n\n return {\n projects: pageRows.map(toProject),\n nextCursor: hasMore && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function listAdminProjects(\n params: ListAdminProjectsParams,\n): Promise<ListAdminProjectsResult> {\n const conditions: SQL[] = [];\n const cursorCondition = cursorWhere(params.cursor);\n if (cursorCondition) conditions.push(cursorCondition);\n if (params.projectId) conditions.push(eq(projects.id, params.projectId));\n if (params.search) {\n conditions.push(ilike(projects.name, `%${escapeIlikePattern(params.search)}%`));\n }\n\n const rows = await db()\n .select({\n id: projects.id,\n workspaceId: projects.workspaceId,\n name: projects.name,\n createdAt: projects.createdAt,\n updatedAt: projects.updatedAt,\n })\n .from(projects)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(projects.createdAt), desc(projects.id))\n .limit(params.limit + 1);\n\n const hasMore = rows.length > params.limit;\n const pageRows = hasMore ? rows.slice(0, params.limit) : rows;\n const last = pageRows.at(-1);\n\n return {\n projects: pageRows,\n nextCursor: hasMore && last ? {createdAt: last.createdAt, id: last.id} : null,\n };\n}\n\nexport async function getProjectCount(): Promise<number> {\n const [row] = await db().select({value: count()}).from(projects);\n return row?.value ?? 0;\n}\n\nexport async function getWorkspaceProjectCounts(params: {\n workspaceIds: string[];\n}): Promise<Array<{workspaceId: string; count: number}>> {\n const rows = await db()\n .select({workspaceId: projects.workspaceId, count: count()})\n .from(projects)\n .where(inArray(projects.workspaceId, params.workspaceIds))\n .groupBy(projects.workspaceId);\n\n const countsByWorkspace = new Map(rows.map((row) => [row.workspaceId, Number(row.count)]));\n return params.workspaceIds.map((workspaceId) => ({\n workspaceId,\n count: countsByWorkspace.get(workspaceId) ?? 0,\n }));\n}\n"],"names":["isUniqueViolation","and","count","desc","eq","ilike","inArray","lt","or","sql","ProjectAlreadyExistsError","ProjectNotFoundError","ProjectSlugConflictError","recordProjectCreated","db","projects","toProject","escapeIlikePattern","value","replace","char","PROJECTS_WORKSPACE_SLUG_UNIQUE_CONSTRAINT","cursorWhere","cursor","undefined","createdAt","id","createProject","params","project","transaction","tx","projectRow","attempt","insert","values","workspaceId","sourceConnectionId","sourceExternalRepositoryId","name","slug","onConflictDoNothing","target","returning","error","conflict","select","from","where","limit","Error","updateProject","options","executor","existingRow","projectId","nextName","nextSlug","changed","row","update","set","updatedAt","Date","getProjectById","rows","getProjectBySource","updateProjectSourceRepository","sourceRepositoryOwner","sourceRepositoryName","sourceDefaultBranch","requireProjectForWorkspace","updateProjectSourceMetadata","resolveCheckoutTarget","selection","connectionId","externalRepositoryId","separator","repository","indexOf","length","connection","defaults","owner","slice","matches","listProjects","conditions","cursorCondition","push","search","searchPattern","orderBy","hasMore","pageRows","last","at","map","nextCursor","listAdminProjects","getProjectCount","getWorkspaceProjectCounts","workspaceIds","groupBy","countsByWorkspace","Map","Number","get"],"mappings":"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"}
@@ -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";
@@ -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
  {