@shipfox/api-definitions 5.0.0 → 6.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 (92) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +36 -0
  3. package/dist/core/entities/workflow-definition.d.ts +2 -5
  4. package/dist/core/entities/workflow-definition.d.ts.map +1 -1
  5. package/dist/core/entities/workflow-definition.js.map +1 -1
  6. package/dist/core/entities/workflow-model.d.ts +1 -137
  7. package/dist/core/entities/workflow-model.d.ts.map +1 -1
  8. package/dist/core/entities/workflow-model.js.map +1 -1
  9. package/dist/core/integrations.d.ts +25 -0
  10. package/dist/core/integrations.d.ts.map +1 -0
  11. package/dist/core/integrations.js +30 -0
  12. package/dist/core/integrations.js.map +1 -0
  13. package/dist/core/sync-definitions.d.ts +2 -2
  14. package/dist/core/sync-definitions.d.ts.map +1 -1
  15. package/dist/core/sync-definitions.js +23 -14
  16. package/dist/core/sync-definitions.js.map +1 -1
  17. package/dist/core/workflow-model/constants.d.ts +1 -1
  18. package/dist/core/workflow-model/constants.d.ts.map +1 -1
  19. package/dist/core/workflow-model/constants.js +1 -1
  20. package/dist/core/workflow-model/constants.js.map +1 -1
  21. package/dist/core/workflow-model/normalize-job-checkout.d.ts +2 -2
  22. package/dist/core/workflow-model/normalize-job-checkout.d.ts.map +1 -1
  23. package/dist/core/workflow-model/normalize-job-checkout.js +2 -6
  24. package/dist/core/workflow-model/normalize-job-checkout.js.map +1 -1
  25. package/dist/core/workflow-model/normalize-job-success.d.ts +2 -1
  26. package/dist/core/workflow-model/normalize-job-success.d.ts.map +1 -1
  27. package/dist/core/workflow-model/normalize-job-success.js +2 -1
  28. package/dist/core/workflow-model/normalize-job-success.js.map +1 -1
  29. package/dist/db/db.d.ts +2 -2
  30. package/dist/db/schema/sync-states.d.ts +1 -1
  31. package/dist/index.d.ts +5 -7
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +10 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/presentation/index.d.ts +1 -1
  36. package/dist/presentation/index.d.ts.map +1 -1
  37. package/dist/presentation/index.js +1 -1
  38. package/dist/presentation/index.js.map +1 -1
  39. package/dist/presentation/inter-module.d.ts +4 -0
  40. package/dist/presentation/inter-module.d.ts.map +1 -0
  41. package/dist/presentation/inter-module.js +25 -0
  42. package/dist/presentation/inter-module.js.map +1 -0
  43. package/dist/presentation/routes/create-definition.d.ts +5 -6
  44. package/dist/presentation/routes/create-definition.d.ts.map +1 -1
  45. package/dist/presentation/routes/create-definition.js +7 -19
  46. package/dist/presentation/routes/create-definition.js.map +1 -1
  47. package/dist/presentation/routes/get-definition.d.ts +2 -1
  48. package/dist/presentation/routes/get-definition.d.ts.map +1 -1
  49. package/dist/presentation/routes/get-definition.js +26 -35
  50. package/dist/presentation/routes/get-definition.js.map +1 -1
  51. package/dist/presentation/routes/index.d.ts +1 -2
  52. package/dist/presentation/routes/index.d.ts.map +1 -1
  53. package/dist/presentation/routes/index.js +5 -6
  54. package/dist/presentation/routes/index.js.map +1 -1
  55. package/dist/presentation/routes/list-definitions.d.ts +2 -1
  56. package/dist/presentation/routes/list-definitions.d.ts.map +1 -1
  57. package/dist/presentation/routes/list-definitions.js +36 -45
  58. package/dist/presentation/routes/list-definitions.js.map +1 -1
  59. package/dist/presentation/routes/project-access.d.ts +10 -0
  60. package/dist/presentation/routes/project-access.d.ts.map +1 -0
  61. package/dist/presentation/routes/project-access.js +17 -0
  62. package/dist/presentation/routes/project-access.js.map +1 -0
  63. package/dist/temporal/activities/sync-activities.d.ts +3 -9
  64. package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
  65. package/dist/temporal/activities/sync-activities.js +6 -10
  66. package/dist/temporal/activities/sync-activities.js.map +1 -1
  67. package/dist/tsconfig.test.tsbuildinfo +1 -1
  68. package/package.json +16 -14
  69. package/src/core/entities/workflow-definition.ts +3 -6
  70. package/src/core/entities/workflow-model.ts +1 -162
  71. package/src/core/integrations.ts +39 -0
  72. package/src/core/sync-definitions.test.ts +19 -17
  73. package/src/core/sync-definitions.ts +26 -20
  74. package/src/core/workflow-model/constants.ts +1 -1
  75. package/src/core/workflow-model/normalize-job-checkout.ts +2 -5
  76. package/src/core/workflow-model/normalize-job-success.ts +2 -1
  77. package/src/db/definitions.test.ts +68 -7
  78. package/src/index.ts +12 -31
  79. package/src/presentation/index.ts +1 -1
  80. package/src/presentation/inter-module.ts +25 -0
  81. package/src/presentation/routes/create-definition.test.ts +59 -63
  82. package/src/presentation/routes/create-definition.ts +15 -34
  83. package/src/presentation/routes/get-definition.test.ts +14 -8
  84. package/src/presentation/routes/get-definition.ts +25 -28
  85. package/src/presentation/routes/index.test.ts +8 -1
  86. package/src/presentation/routes/index.ts +5 -7
  87. package/src/presentation/routes/list-definitions.test.ts +8 -7
  88. package/src/presentation/routes/list-definitions.ts +33 -36
  89. package/src/presentation/routes/project-access.ts +16 -0
  90. package/src/temporal/activities/sync-activities.test.ts +9 -6
  91. package/src/temporal/activities/sync-activities.ts +14 -29
  92. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,29 +1,19 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
3
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
4
  import type {FastifyInstance} from 'fastify';
3
5
  import Fastify from 'fastify';
4
6
  import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
5
- import {buildCreateDefinitionRoute, createDefinitionRoute} from './create-definition.js';
6
-
7
- const projectAccessState = vi.hoisted(() => ({workspaceId: '', sourceConnectionId: ''}));
8
-
9
- vi.mock('@shipfox/api-projects', () => ({
10
- ProjectNotFoundError: class ProjectNotFoundError extends Error {},
11
- requireProjectAccess: vi.fn(({projectId}) =>
12
- Promise.resolve({
13
- project: {
14
- id: projectId,
15
- workspaceId: projectAccessState.workspaceId,
16
- sourceConnectionId: projectAccessState.sourceConnectionId,
17
- },
18
- workspaceId: projectAccessState.workspaceId,
19
- }),
20
- ),
21
- }));
7
+ import {buildCreateDefinitionRoute} from './create-definition.js';
8
+
9
+ const getProjectById = vi.fn();
10
+ const projects = {getProjectById} as Pick<ProjectsModuleClient, 'getProjectById'>;
22
11
 
23
12
  describe('POST /api/definitions', () => {
24
13
  let app: FastifyInstance;
25
14
  let workspaceId: string;
26
15
  let projectId: string;
16
+ let sourceConnectionId: string;
27
17
 
28
18
  beforeAll(async () => {
29
19
  app = Fastify();
@@ -40,15 +30,20 @@ describe('POST /api/definitions', () => {
40
30
  );
41
31
  done();
42
32
  });
43
- app.post('/api/definitions', createDefinitionRoute);
33
+ app.post(
34
+ '/api/definitions',
35
+ buildCreateDefinitionRoute({projects: projects as ProjectsModuleClient}),
36
+ );
44
37
  await app.ready();
45
38
  });
46
39
 
47
40
  beforeEach(() => {
48
41
  workspaceId = crypto.randomUUID();
49
42
  projectId = crypto.randomUUID();
50
- projectAccessState.workspaceId = workspaceId;
51
- projectAccessState.sourceConnectionId = crypto.randomUUID();
43
+ sourceConnectionId = crypto.randomUUID();
44
+ getProjectById.mockResolvedValue({
45
+ project: {id: projectId, workspaceId, sourceConnectionId},
46
+ } as never);
52
47
  });
53
48
 
54
49
  const validYaml = `
@@ -82,7 +77,7 @@ jobs:
82
77
  });
83
78
 
84
79
  test('skips connection snapshot loading when YAML has no integrations', async () => {
85
- const loadWorkspaceConnectionSnapshot = vi.fn(() => Promise.resolve(new Map()));
80
+ const getAgentToolsContext = vi.fn();
86
81
  const appWithOptions = Fastify();
87
82
  appWithOptions.setValidatorCompiler(validatorCompiler);
88
83
  appWithOptions.setSerializerCompiler(serializerCompiler);
@@ -100,8 +95,11 @@ jobs:
100
95
  appWithOptions.post(
101
96
  '/api/definitions',
102
97
  buildCreateDefinitionRoute({
103
- agentToolSelectionCatalogs: new Map(),
104
- loadWorkspaceConnectionSnapshot,
98
+ projects: projects as ProjectsModuleClient,
99
+ integrations: {getAgentToolsContext} as Pick<
100
+ IntegrationsModuleClient,
101
+ 'getAgentToolsContext'
102
+ > as IntegrationsModuleClient,
105
103
  }),
106
104
  );
107
105
  await appWithOptions.ready();
@@ -117,35 +115,39 @@ jobs:
117
115
  });
118
116
 
119
117
  expect(res.statusCode).toBe(200);
120
- expect(loadWorkspaceConnectionSnapshot).not.toHaveBeenCalled();
118
+ expect(getAgentToolsContext).not.toHaveBeenCalled();
121
119
  });
122
120
 
123
121
  test('uses the project source connection as the default integration connection', async () => {
124
- const loadWorkspaceConnectionSnapshot = vi.fn(() =>
125
- Promise.resolve(
126
- new Map([
127
- [
128
- 'github-main',
129
- {
130
- id: projectAccessState.sourceConnectionId,
131
- provider: 'github',
132
- capabilities: ['agent_tools'] as const,
133
- },
134
- ],
135
- ]),
136
- ),
137
- );
138
- const getIntegrationConnectionById = vi.fn(() =>
122
+ const getAgentToolsContext = vi.fn(() =>
139
123
  Promise.resolve({
140
- id: projectAccessState.sourceConnectionId,
141
- workspaceId,
142
- provider: 'github',
143
- externalAccountId: 'installation-1',
144
- slug: 'github-main',
145
- displayName: 'GitHub',
146
- lifecycleStatus: 'active' as const,
147
- createdAt: new Date(),
148
- updatedAt: new Date(),
124
+ selectionCatalogs: [
125
+ {
126
+ provider: 'github',
127
+ selectors: [
128
+ {
129
+ token: 'issue_read',
130
+ kind: 'family' as const,
131
+ sensitivity: 'read' as const,
132
+ sensitive: false,
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ catalogs: [],
138
+ workspaceConnections: [
139
+ {
140
+ id: sourceConnectionId,
141
+ slug: 'github-main',
142
+ provider: 'github',
143
+ capabilities: ['agent_tools' as const],
144
+ },
145
+ ],
146
+ defaultConnection: {
147
+ id: sourceConnectionId,
148
+ slug: 'github-main',
149
+ provider: 'github',
150
+ },
149
151
  }),
150
152
  );
151
153
  const appWithOptions = Fastify();
@@ -165,18 +167,11 @@ jobs:
165
167
  appWithOptions.post(
166
168
  '/api/definitions',
167
169
  buildCreateDefinitionRoute({
168
- agentToolSelectionCatalogs: new Map([
169
- [
170
- 'github',
171
- {
172
- selectors: [
173
- {token: 'issue_read', kind: 'family', sensitivity: 'read', sensitive: false},
174
- ],
175
- },
176
- ],
177
- ]),
178
- loadWorkspaceConnectionSnapshot,
179
- getIntegrationConnectionById,
170
+ projects: projects as ProjectsModuleClient,
171
+ integrations: {getAgentToolsContext} as Pick<
172
+ IntegrationsModuleClient,
173
+ 'getAgentToolsContext'
174
+ > as IntegrationsModuleClient,
180
175
  }),
181
176
  );
182
177
  await appWithOptions.ready();
@@ -201,9 +196,10 @@ jobs:
201
196
  });
202
197
 
203
198
  expect(res.statusCode).toBe(200);
204
- expect(getIntegrationConnectionById).toHaveBeenCalledWith(
205
- projectAccessState.sourceConnectionId,
206
- );
199
+ expect(getAgentToolsContext).toHaveBeenCalledWith({
200
+ workspaceId,
201
+ defaultConnectionId: sourceConnectionId,
202
+ });
207
203
  });
208
204
 
209
205
  test('invalid YAML syntax returns 400', async () => {
@@ -3,27 +3,24 @@ import {
3
3
  definitionResponseSchema,
4
4
  definitionValidationErrorSchema,
5
5
  } from '@shipfox/api-definitions-dto';
6
- import type {
7
- AgentToolSelectionCatalogs,
8
- GetIntegrationConnectionByIdFn,
9
- LoadWorkspaceConnectionSnapshot,
10
- } from '@shipfox/api-integration-core';
11
- import {ProjectNotFoundError, requireProjectAccess} from '@shipfox/api-projects';
6
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
7
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
12
8
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
13
9
  import {z} from 'zod';
14
10
  import {DefinitionParseError} from '#core/errors.js';
15
11
  import {hasAgentStepIntegrations} from '#core/has-agent-step-integrations.js';
12
+ import {loadIntegrationValidationContext} from '#core/integrations.js';
16
13
  import {parseDefinition} from '#core/parse-definition.js';
17
14
  import {upsertDefinition} from '#db/definitions.js';
18
15
  import {toDefinitionDto} from '#presentation/dto/index.js';
16
+ import {requireProjectAccess} from './project-access.js';
19
17
 
20
18
  export interface CreateDefinitionRouteOptions {
21
- agentToolSelectionCatalogs?: AgentToolSelectionCatalogs | undefined;
22
- loadWorkspaceConnectionSnapshot?: LoadWorkspaceConnectionSnapshot | undefined;
23
- getIntegrationConnectionById?: GetIntegrationConnectionByIdFn | undefined;
19
+ projects: ProjectsModuleClient;
20
+ integrations?: IntegrationsModuleClient;
24
21
  }
25
22
 
26
- export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions = {}) {
23
+ export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions) {
27
24
  return defineRoute({
28
25
  method: 'POST',
29
26
  path: '/',
@@ -47,36 +44,22 @@ export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions
47
44
  status: 400,
48
45
  });
49
46
  }
50
- if (error instanceof ProjectNotFoundError) {
51
- throw new ClientError(error.message, 'project-not-found', {status: 404});
52
- }
53
47
  throw error;
54
48
  },
55
49
  handler: async (request) => {
56
50
  const {project_id: projectId, config_path, source, yaml: yamlString, sha, ref} = request.body;
57
- const {project} = await requireProjectAccess({request, projectId});
51
+ const project = await requireProjectAccess(request, projectId, options.projects);
58
52
 
59
53
  const structurallyParsed = parseDefinition(yamlString);
60
- const {
61
- agentToolSelectionCatalogs,
62
- loadWorkspaceConnectionSnapshot,
63
- getIntegrationConnectionById,
64
- } = options;
54
+ const {integrations} = options;
65
55
  const parsed =
66
- agentToolSelectionCatalogs !== undefined &&
67
- loadWorkspaceConnectionSnapshot !== undefined &&
68
- hasAgentStepIntegrations(structurallyParsed.document)
56
+ integrations !== undefined && hasAgentStepIntegrations(structurallyParsed.document)
69
57
  ? parseDefinition(yamlString, {
70
- integrationValidationContext: {
71
- agentToolSelectionCatalogs,
72
- workspaceConnectionSnapshot: await loadWorkspaceConnectionSnapshot(
73
- project.workspaceId,
74
- ),
75
- defaultConnectionSlug:
76
- getIntegrationConnectionById === undefined
77
- ? undefined
78
- : (await getIntegrationConnectionById(project.sourceConnectionId))?.slug,
79
- },
58
+ integrationValidationContext: await loadIntegrationValidationContext(
59
+ integrations,
60
+ project.workspaceId,
61
+ project.sourceConnectionId,
62
+ ),
80
63
  })
81
64
  : structurallyParsed;
82
65
 
@@ -97,5 +80,3 @@ export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions
97
80
  },
98
81
  });
99
82
  }
100
-
101
- export const createDefinitionRoute = buildCreateDefinitionRoute();
@@ -1,21 +1,27 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
3
  import type {FastifyInstance} from 'fastify';
3
4
  import Fastify from 'fastify';
4
5
  import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
5
6
  import {definitionFactory} from '#test/index.js';
6
- import {getDefinitionRoute} from './get-definition.js';
7
+ import {buildGetDefinitionRoute} from './get-definition.js';
7
8
 
8
9
  const projectAccessState = vi.hoisted(() => ({workspaceId: ''}));
9
10
 
10
- vi.mock('@shipfox/api-projects', () => ({
11
- ProjectNotFoundError: class ProjectNotFoundError extends Error {},
12
- requireProjectAccess: vi.fn(({projectId}) =>
11
+ const projects = {
12
+ getProjectById: vi.fn(({projectId}) =>
13
13
  Promise.resolve({
14
- project: {id: projectId, workspaceId: projectAccessState.workspaceId},
15
- workspaceId: projectAccessState.workspaceId,
14
+ project: {
15
+ id: projectId,
16
+ workspaceId: projectAccessState.workspaceId,
17
+ sourceConnectionId: crypto.randomUUID(),
18
+ sourceExternalRepositoryId: 'repo',
19
+ name: 'Project',
20
+ },
16
21
  }),
17
22
  ),
18
- }));
23
+ requireProjectForWorkspace: vi.fn(),
24
+ } as unknown as ProjectsModuleClient;
19
25
 
20
26
  describe('GET /api/definitions/:id', () => {
21
27
  let app: FastifyInstance;
@@ -36,7 +42,7 @@ describe('GET /api/definitions/:id', () => {
36
42
  );
37
43
  done();
38
44
  });
39
- app.get('/api/definitions/:id', getDefinitionRoute);
45
+ app.get('/api/definitions/:id', buildGetDefinitionRoute(projects));
40
46
  await app.ready();
41
47
  });
42
48
 
@@ -1,37 +1,34 @@
1
1
  import {definitionResponseSchema} from '@shipfox/api-definitions-dto';
2
- import {ProjectNotFoundError, requireProjectAccess} from '@shipfox/api-projects';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
3
3
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
4
4
  import {z} from 'zod';
5
5
  import {getDefinitionById} from '#db/definitions.js';
6
6
  import {toDefinitionDto} from '#presentation/dto/index.js';
7
+ import {requireProjectAccess} from './project-access.js';
7
8
 
8
- export const getDefinitionRoute = defineRoute({
9
- method: 'GET',
10
- path: '/:id',
11
- description: 'Get a definition by ID',
12
- schema: {
13
- params: z.object({
14
- id: z.string().uuid(),
15
- }),
16
- response: {
17
- 200: definitionResponseSchema,
9
+ export function buildGetDefinitionRoute(projects: ProjectsModuleClient) {
10
+ return defineRoute({
11
+ method: 'GET',
12
+ path: '/:id',
13
+ description: 'Get a definition by ID',
14
+ schema: {
15
+ params: z.object({
16
+ id: z.string().uuid(),
17
+ }),
18
+ response: {
19
+ 200: definitionResponseSchema,
20
+ },
18
21
  },
19
- },
20
- errorHandler: (error) => {
21
- if (error instanceof ProjectNotFoundError) {
22
- throw new ClientError('Definition not found', 'not-found', {status: 404});
23
- }
24
- throw error;
25
- },
26
- handler: async (request) => {
27
- const {id} = request.params;
28
- const definition = await getDefinitionById(id);
22
+ handler: async (request) => {
23
+ const {id} = request.params;
24
+ const definition = await getDefinitionById(id);
29
25
 
30
- if (!definition) {
31
- throw new ClientError('Definition not found', 'not-found', {status: 404});
32
- }
33
- await requireProjectAccess({request, projectId: definition.projectId});
26
+ if (!definition) {
27
+ throw new ClientError('Definition not found', 'not-found', {status: 404});
28
+ }
29
+ await requireProjectAccess(request, definition.projectId, projects);
34
30
 
35
- return toDefinitionDto(definition);
36
- },
37
- });
31
+ return toDefinitionDto(definition);
32
+ },
33
+ });
34
+ }
@@ -1,7 +1,14 @@
1
1
  import {AUTH_USER, buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
3
  import {type AuthMethod, ClientError, closeApp, createApp} from '@shipfox/node-fastify';
3
4
  import type {FastifyRequest} from 'fastify';
4
- import {definitionRoutes} from './index.js';
5
+ import {createDefinitionRoutes} from './index.js';
6
+
7
+ const projects = {
8
+ getProjectById: vi.fn(),
9
+ requireProjectForWorkspace: vi.fn(),
10
+ } as unknown as ProjectsModuleClient;
11
+ const definitionRoutes = createDefinitionRoutes({projects});
5
12
 
6
13
  const fakeUserAuth: AuthMethod = {
7
14
  name: AUTH_USER,
@@ -4,25 +4,23 @@ import {
4
4
  buildCreateDefinitionRoute,
5
5
  type CreateDefinitionRouteOptions,
6
6
  } from './create-definition.js';
7
- import {getDefinitionRoute} from './get-definition.js';
8
- import {listDefinitionsRoute} from './list-definitions.js';
7
+ import {buildGetDefinitionRoute} from './get-definition.js';
8
+ import {buildListDefinitionsRoute} from './list-definitions.js';
9
9
  import {validateDefinitionRoute} from './validate-definition.js';
10
10
 
11
11
  export interface DefinitionRouteOptions extends CreateDefinitionRouteOptions {}
12
12
 
13
- export function createDefinitionRoutes(options: DefinitionRouteOptions = {}): RouteGroup[] {
13
+ export function createDefinitionRoutes(options: DefinitionRouteOptions): RouteGroup[] {
14
14
  return [
15
15
  {
16
16
  prefix: '/definitions',
17
17
  auth: AUTH_USER,
18
18
  routes: [
19
19
  buildCreateDefinitionRoute(options),
20
- listDefinitionsRoute,
21
- getDefinitionRoute,
20
+ buildListDefinitionsRoute(options.projects),
21
+ buildGetDefinitionRoute(options.projects),
22
22
  validateDefinitionRoute,
23
23
  ],
24
24
  },
25
25
  ];
26
26
  }
27
-
28
- export const definitionRoutes = createDefinitionRoutes();
@@ -1,11 +1,12 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
3
  import {encodeStringIdCursor} from '@shipfox/node-drizzle';
3
4
  import type {FastifyInstance} from 'fastify';
4
5
  import Fastify from 'fastify';
5
6
  import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
6
7
  import {markDefinitionSyncState} from '#db/index.js';
7
8
  import {definitionFactory} from '#test/index.js';
8
- import {listDefinitionsRoute} from './list-definitions.js';
9
+ import {buildListDefinitionsRoute} from './list-definitions.js';
9
10
 
10
11
  const projectAccessState = vi.hoisted(() => ({
11
12
  workspaceId: '',
@@ -13,20 +14,20 @@ const projectAccessState = vi.hoisted(() => ({
13
14
  sourceExternalRepositoryId: '',
14
15
  }));
15
16
 
16
- vi.mock('@shipfox/api-projects', () => ({
17
- ProjectNotFoundError: class ProjectNotFoundError extends Error {},
18
- requireProjectAccess: vi.fn(({projectId}) =>
17
+ const projects = {
18
+ getProjectById: vi.fn(({projectId}) =>
19
19
  Promise.resolve({
20
20
  project: {
21
21
  id: projectId,
22
22
  workspaceId: projectAccessState.workspaceId,
23
23
  sourceConnectionId: projectAccessState.sourceConnectionId,
24
24
  sourceExternalRepositoryId: projectAccessState.sourceExternalRepositoryId,
25
+ name: 'Project',
25
26
  },
26
- workspaceId: projectAccessState.workspaceId,
27
27
  }),
28
28
  ),
29
- }));
29
+ requireProjectForWorkspace: vi.fn(),
30
+ } as unknown as ProjectsModuleClient;
30
31
 
31
32
  describe('GET /api/definitions', () => {
32
33
  let app: FastifyInstance;
@@ -50,7 +51,7 @@ describe('GET /api/definitions', () => {
50
51
  );
51
52
  done();
52
53
  });
53
- app.get('/api/definitions', listDefinitionsRoute);
54
+ app.get('/api/definitions', buildListDefinitionsRoute(projects));
54
55
  await app.ready();
55
56
  });
56
57
 
@@ -2,48 +2,45 @@ import {
2
2
  definitionListQuerySchema,
3
3
  definitionListResponseSchema,
4
4
  } from '@shipfox/api-definitions-dto';
5
- import {ProjectNotFoundError, requireProjectAccess} from '@shipfox/api-projects';
5
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
6
6
  import {decodeStringIdCursor, encodeStringIdCursor} from '@shipfox/node-drizzle';
7
7
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
8
8
  import {listDefinitions} from '#db/definitions.js';
9
9
  import {getLatestDefinitionSyncState} from '#db/sync-states.js';
10
10
  import {toDefinitionDto, toDefinitionSyncSummaryDto} from '#presentation/dto/index.js';
11
+ import {requireProjectAccess} from './project-access.js';
11
12
 
12
- export const listDefinitionsRoute = defineRoute({
13
- method: 'GET',
14
- path: '/',
15
- description: 'List all definitions for a project',
16
- schema: {
17
- querystring: definitionListQuerySchema,
18
- response: {
19
- 200: definitionListResponseSchema,
13
+ export function buildListDefinitionsRoute(projects: ProjectsModuleClient) {
14
+ return defineRoute({
15
+ method: 'GET',
16
+ path: '/',
17
+ description: 'List all definitions for a project',
18
+ schema: {
19
+ querystring: definitionListQuerySchema,
20
+ response: {
21
+ 200: definitionListResponseSchema,
22
+ },
20
23
  },
21
- },
22
- errorHandler: (error) => {
23
- if (error instanceof ProjectNotFoundError) {
24
- throw new ClientError(error.message, 'project-not-found', {status: 404});
25
- }
26
- throw error;
27
- },
28
- handler: async (request) => {
29
- const {project_id: projectId, limit, cursor} = request.query;
30
- const decodedCursor = decodeStringIdCursor(cursor);
31
- if (cursor && !decodedCursor) {
32
- throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
33
- }
24
+ handler: async (request) => {
25
+ const {project_id: projectId, limit, cursor} = request.query;
26
+ const decodedCursor = decodeStringIdCursor(cursor);
27
+ if (cursor && !decodedCursor) {
28
+ throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
29
+ }
34
30
 
35
- const {project} = await requireProjectAccess({request, projectId});
36
- const result = await listDefinitions({projectId, limit, cursor: decodedCursor});
37
- const syncState = await getLatestDefinitionSyncState({
38
- projectId,
39
- sourceConnectionId: project.sourceConnectionId,
40
- sourceExternalRepositoryId: project.sourceExternalRepositoryId,
41
- });
31
+ const project = await requireProjectAccess(request, projectId, projects);
32
+ const result = await listDefinitions({projectId, limit, cursor: decodedCursor});
33
+ const syncState = await getLatestDefinitionSyncState({
34
+ projectId,
35
+ sourceConnectionId: project.sourceConnectionId,
36
+ sourceExternalRepositoryId: project.sourceExternalRepositoryId,
37
+ });
42
38
 
43
- return {
44
- definitions: result.definitions.map(toDefinitionDto),
45
- sync: toDefinitionSyncSummaryDto(syncState),
46
- next_cursor: result.nextCursor ? encodeStringIdCursor(result.nextCursor) : null,
47
- };
48
- },
49
- });
39
+ return {
40
+ definitions: result.definitions.map(toDefinitionDto),
41
+ sync: toDefinitionSyncSummaryDto(syncState),
42
+ next_cursor: result.nextCursor ? encodeStringIdCursor(result.nextCursor) : null,
43
+ };
44
+ },
45
+ });
46
+ }
@@ -0,0 +1,16 @@
1
+ import {requireWorkspaceAccess} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
3
+ import {ClientError} from '@shipfox/node-fastify';
4
+ import type {FastifyRequest} from 'fastify';
5
+
6
+ export async function requireProjectAccess(
7
+ request: FastifyRequest,
8
+ projectId: string,
9
+ projects: ProjectsModuleClient,
10
+ ) {
11
+ const {project} = await projects.getProjectById({projectId});
12
+ if (project === null)
13
+ throw new ClientError('Project not found', 'project-not-found', {status: 404});
14
+ requireWorkspaceAccess({request, workspaceId: project.workspaceId});
15
+ return project;
16
+ }
@@ -1,7 +1,6 @@
1
- import {
2
- IntegrationProviderError,
3
- type IntegrationSourceControlService,
4
- } from '@shipfox/api-integration-core';
1
+ import type {IntegrationSourceControlService} from '@shipfox/api-integration-core';
2
+ import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto';
3
+ import {createInterModuleKnownError} from '@shipfox/inter-module';
5
4
  import {ApplicationFailure} from '@temporalio/common';
6
5
  import {sql} from 'drizzle-orm';
7
6
  import {db, definitionSyncStates} from '#db/index.js';
@@ -149,7 +148,11 @@ describe('definition sync activities', () => {
149
148
  sourceControl({
150
149
  resolveRepository: vi.fn(() => {
151
150
  return Promise.reject(
152
- new IntegrationProviderError('timeout', 'GitHub request timed out'),
151
+ createInterModuleKnownError(
152
+ integrationsInterModuleContract.methods.resolveSourceRepository,
153
+ 'provider-failure',
154
+ {reason: 'timeout'},
155
+ ),
153
156
  );
154
157
  }),
155
158
  }),
@@ -165,7 +168,7 @@ describe('definition sync activities', () => {
165
168
  await expect(result).rejects.toMatchObject({
166
169
  nonRetryable: false,
167
170
  type: 'provider-timeout',
168
- message: 'GitHub request timed out',
171
+ message: 'integrations.resolveSourceRepository: provider-failure',
169
172
  });
170
173
  });
171
174
  });