@shipfox/api-projects-dto 10.0.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-projects-dto",
3
3
  "license": "MIT",
4
- "version": "10.0.0",
4
+ "version": "12.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "zod": "^4.4.3",
26
- "@shipfox/api-common-dto": "9.2.0",
27
- "@shipfox/inter-module": "0.2.2"
26
+ "@shipfox/api-common-dto": "12.0.0",
27
+ "@shipfox/inter-module": "0.2.3"
28
28
  },
29
29
  "imports": {
30
30
  "#*": "./dist/*"
@@ -1,10 +1,11 @@
1
- import {displayNameSchema} from '@shipfox/api-common-dto';
1
+ import {displayNameSchema, slugSchema} from '@shipfox/api-common-dto';
2
2
  import {z} from 'zod';
3
3
  import {projectResponseSchema} from '../schemas/project.js';
4
4
 
5
5
  export const e2eCreateProjectBodySchema = z.object({
6
6
  workspace_id: z.string().uuid(),
7
7
  name: displayNameSchema,
8
+ slug: slugSchema.optional(),
8
9
  source_connection_id: z.string().uuid().optional(),
9
10
  source_external_repository_id: z.string().min(1).max(255).optional(),
10
11
  });
@@ -0,0 +1,23 @@
1
+ import {projectCreatedEventSchema} from './events.js';
2
+
3
+ const legacyProjectCreatedEvent = {
4
+ actorId: 'actor-1',
5
+ workspaceId: 'workspace-1',
6
+ projectId: 'project-1',
7
+ sourceConnectionId: 'connection-1',
8
+ sourceExternalRepositoryId: 'repository-1',
9
+ };
10
+
11
+ describe('project created event schema', () => {
12
+ test('accepts legacy in-flight payloads without a slug', () => {
13
+ expect(projectCreatedEventSchema.parse(legacyProjectCreatedEvent)).toEqual(
14
+ legacyProjectCreatedEvent,
15
+ );
16
+ });
17
+
18
+ test('preserves slugs on new payloads', () => {
19
+ expect(
20
+ projectCreatedEventSchema.parse({...legacyProjectCreatedEvent, slug: 'project'}),
21
+ ).toMatchObject({slug: 'project'});
22
+ });
23
+ });
package/src/events.ts CHANGED
@@ -3,17 +3,36 @@ import {z} from 'zod';
3
3
  const nonEmptyStringSchema = z.string().nonempty();
4
4
 
5
5
  export const PROJECT_CREATED = 'projects.project.created' as const;
6
+ export const PROJECT_UPDATED = 'projects.project.updated' as const;
6
7
  export const PROJECT_SOURCE_BOUND = 'projects.project.source_bound' as const;
7
8
  export const PROJECT_SOURCE_COMMIT_OBSERVED = 'projects.project.source_commit_observed' as const;
8
9
 
9
- export const projectCreatedEventSchema = z.object({
10
+ const projectCreatedEventWithoutSlugSchema = z.object({
10
11
  actorId: nonEmptyStringSchema,
11
12
  workspaceId: nonEmptyStringSchema,
12
13
  projectId: nonEmptyStringSchema,
13
14
  sourceConnectionId: nonEmptyStringSchema,
14
15
  sourceExternalRepositoryId: nonEmptyStringSchema,
15
16
  });
16
- export type ProjectCreatedEvent = z.infer<typeof projectCreatedEventSchema>;
17
+
18
+ const projectCreatedEventWithSlugSchema = projectCreatedEventWithoutSlugSchema.extend({
19
+ slug: nonEmptyStringSchema,
20
+ });
21
+
22
+ export const projectCreatedEventSchema = z.union([
23
+ projectCreatedEventWithSlugSchema,
24
+ projectCreatedEventWithoutSlugSchema,
25
+ ]);
26
+ export type ProjectCreatedEvent = z.infer<typeof projectCreatedEventWithSlugSchema>;
27
+
28
+ export const projectUpdatedEventSchema = z.object({
29
+ actorId: nonEmptyStringSchema,
30
+ workspaceId: nonEmptyStringSchema,
31
+ projectId: nonEmptyStringSchema,
32
+ name: nonEmptyStringSchema,
33
+ slug: nonEmptyStringSchema,
34
+ });
35
+ export type ProjectUpdatedEvent = z.infer<typeof projectUpdatedEventSchema>;
17
36
 
18
37
  export const projectSourceBoundEventSchema = z.object({
19
38
  actorId: nonEmptyStringSchema,
@@ -40,12 +59,14 @@ export type ProjectSourceCommitObservedEvent = z.infer<
40
59
 
41
60
  export interface ProjectsEventMap {
42
61
  [PROJECT_CREATED]: ProjectCreatedEvent;
62
+ [PROJECT_UPDATED]: ProjectUpdatedEvent;
43
63
  [PROJECT_SOURCE_BOUND]: ProjectSourceBoundEvent;
44
64
  [PROJECT_SOURCE_COMMIT_OBSERVED]: ProjectSourceCommitObservedEvent;
45
65
  }
46
66
 
47
67
  export const projectsEventSchemas = {
48
68
  [PROJECT_CREATED]: projectCreatedEventSchema,
69
+ [PROJECT_UPDATED]: projectUpdatedEventSchema,
49
70
  [PROJECT_SOURCE_BOUND]: projectSourceBoundEventSchema,
50
71
  [PROJECT_SOURCE_COMMIT_OBSERVED]: projectSourceCommitObservedEventSchema,
51
72
  } satisfies Record<keyof ProjectsEventMap, z.ZodType>;
@@ -9,11 +9,43 @@ describe('projectsInterModuleContract', () => {
9
9
  workspaceId: '00000000-0000-4000-8000-000000000002',
10
10
  sourceConnectionId: '00000000-0000-4000-8000-000000000003',
11
11
  sourceExternalRepositoryId: 'shipfox/project',
12
+ sourceRepositoryOwner: 'shipfox',
12
13
  name: 'Project',
13
14
  },
14
15
  });
15
16
 
16
17
  expect(result.project?.id).toBe(projectId);
18
+ expect(result.project?.sourceRepositoryOwner).toBe('shipfox');
19
+ });
20
+
21
+ test('accepts checkout targets addressed by project or repository', () => {
22
+ const workspaceId = '00000000-0000-4000-8000-000000000001';
23
+ const connectionId = '00000000-0000-4000-8000-000000000002';
24
+
25
+ expect(
26
+ projectsInterModuleContract.methods.resolveCheckoutTarget.input.parse({
27
+ workspaceId,
28
+ defaults: {connectionId, owner: 'acme'},
29
+ target: {project: '00000000-0000-4000-8000-000000000003'},
30
+ }).target,
31
+ ).toEqual({project: '00000000-0000-4000-8000-000000000003'});
32
+ expect(
33
+ projectsInterModuleContract.methods.resolveCheckoutTarget.input.parse({
34
+ workspaceId,
35
+ defaults: {connectionId, owner: 'acme'},
36
+ target: {connection: connectionId, repository: 'acme/api'},
37
+ }).target,
38
+ ).toEqual({connection: connectionId, repository: 'acme/api'});
39
+ });
40
+
41
+ test('defines the checkout authorization failure', () => {
42
+ const details = {};
43
+
44
+ expect(
45
+ projectsInterModuleContract.methods.resolveCheckoutTarget.errors[
46
+ 'checkout-repository-not-authorized'
47
+ ].parse(details),
48
+ ).toEqual(details);
17
49
  });
18
50
 
19
51
  test.each([
@@ -7,12 +7,25 @@ const projectSchema = z.object({
7
7
  workspaceId: idSchema,
8
8
  sourceConnectionId: idSchema,
9
9
  sourceExternalRepositoryId: z.string(),
10
+ sourceRepositoryOwner: z.string().min(1).nullable().optional(),
10
11
  name: z.string(),
11
12
  });
12
13
  const workspaceProjectCountSchema = z.object({
13
14
  workspaceId: idSchema,
14
15
  count: z.number().int().nonnegative(),
15
16
  });
17
+ const checkoutTargetSchema = z.union([
18
+ z.strictObject({project: idSchema}),
19
+ z.strictObject({
20
+ connection: idSchema.optional(),
21
+ repository: z.string().min(1),
22
+ }),
23
+ ]);
24
+ const resolvedCheckoutTargetSchema = z.object({
25
+ projectId: idSchema,
26
+ connectionId: idSchema,
27
+ externalRepositoryId: z.string(),
28
+ });
16
29
 
17
30
  /** Producer-owned project lookup and workspace ownership operations. */
18
31
  export const projectsInterModuleContract = defineInterModuleContract({
@@ -22,6 +35,14 @@ export const projectsInterModuleContract = defineInterModuleContract({
22
35
  input: z.object({projectId: idSchema}),
23
36
  output: z.object({project: projectSchema.nullable()}),
24
37
  },
38
+ getProjectBySource: {
39
+ input: z.object({
40
+ workspaceId: idSchema,
41
+ sourceConnectionId: idSchema,
42
+ sourceExternalRepositoryId: z.string(),
43
+ }),
44
+ output: z.object({project: projectSchema.nullable()}),
45
+ },
25
46
  requireProjectForWorkspace: {
26
47
  input: z.object({projectId: idSchema, workspaceId: idSchema}),
27
48
  output: z.object({project: projectSchema}),
@@ -34,6 +55,17 @@ export const projectsInterModuleContract = defineInterModuleContract({
34
55
  input: z.object({workspaceIds: z.array(idSchema).min(1).max(100)}),
35
56
  output: z.object({counts: z.array(workspaceProjectCountSchema)}),
36
57
  },
58
+ resolveCheckoutTarget: {
59
+ input: z.object({
60
+ workspaceId: idSchema,
61
+ defaults: z.object({connectionId: idSchema, owner: z.string().min(1)}),
62
+ target: checkoutTargetSchema,
63
+ }),
64
+ output: resolvedCheckoutTargetSchema,
65
+ errors: {
66
+ 'checkout-repository-not-authorized': z.object({}),
67
+ },
68
+ },
37
69
  },
38
70
  });
39
71
 
@@ -1,8 +1,14 @@
1
1
  export {
2
+ type AdminProjectSummaryDto,
3
+ adminProjectSummaryDtoSchema,
2
4
  type CreateProjectBodyDto,
3
5
  createProjectBodySchema,
6
+ type ListAdminProjectsQueryDto,
7
+ type ListAdminProjectsResponseDto,
4
8
  type ListProjectsQueryDto,
5
9
  type ListProjectsResponseDto,
10
+ listAdminProjectsQuerySchema,
11
+ listAdminProjectsResponseSchema,
6
12
  listProjectsQuerySchema,
7
13
  listProjectsResponseSchema,
8
14
  type ProjectDto,
@@ -11,4 +17,6 @@ export {
11
17
  projectDtoSchema,
12
18
  projectResponseSchema,
13
19
  projectSourceDtoSchema,
20
+ type UpdateProjectBodyDto,
21
+ updateProjectBodySchema,
14
22
  } from './project.js';
@@ -1,4 +1,4 @@
1
- import {displayNameSchema} from '@shipfox/api-common-dto';
1
+ import {displayNameSchema, slugSchema} from '@shipfox/api-common-dto';
2
2
  import {z} from 'zod';
3
3
 
4
4
  export const projectSourceDtoSchema = z.object({
@@ -10,6 +10,7 @@ export type ProjectSourceDto = z.infer<typeof projectSourceDtoSchema>;
10
10
  export const createProjectBodySchema = z.object({
11
11
  workspace_id: z.string().uuid(),
12
12
  name: displayNameSchema,
13
+ slug: slugSchema,
13
14
  source: z.object({
14
15
  connection_id: z.string().uuid(),
15
16
  external_repository_id: z.string().min(1).max(255),
@@ -18,10 +19,20 @@ export const createProjectBodySchema = z.object({
18
19
 
19
20
  export type CreateProjectBodyDto = z.infer<typeof createProjectBodySchema>;
20
21
 
22
+ export const updateProjectBodySchema = z
23
+ .object({
24
+ name: displayNameSchema.optional(),
25
+ slug: slugSchema.optional(),
26
+ })
27
+ .partial();
28
+
29
+ export type UpdateProjectBodyDto = z.infer<typeof updateProjectBodySchema>;
30
+
21
31
  export const projectDtoSchema = z.object({
22
32
  id: z.string().uuid(),
23
33
  workspace_id: z.string().uuid(),
24
34
  name: z.string(),
35
+ slug: slugSchema,
25
36
  source: projectSourceDtoSchema,
26
37
  created_at: z.string(),
27
38
  updated_at: z.string(),
@@ -48,3 +59,30 @@ export const listProjectsResponseSchema = z.object({
48
59
  });
49
60
 
50
61
  export type ListProjectsResponseDto = z.infer<typeof listProjectsResponseSchema>;
62
+
63
+ export const adminProjectSummaryDtoSchema = z.object({
64
+ id: z.string().uuid(),
65
+ name: z.string(),
66
+ status: z.literal('active'),
67
+ workspace_id: z.string().uuid(),
68
+ created_at: z.string(),
69
+ updated_at: z.string(),
70
+ });
71
+
72
+ export type AdminProjectSummaryDto = z.infer<typeof adminProjectSummaryDtoSchema>;
73
+
74
+ export const listAdminProjectsQuerySchema = z.object({
75
+ project_id: z.string().uuid().optional(),
76
+ limit: z.coerce.number().int().min(1).max(100).default(50),
77
+ cursor: z.string().optional(),
78
+ search: z.string().min(1).max(100).optional(),
79
+ });
80
+
81
+ export type ListAdminProjectsQueryDto = z.infer<typeof listAdminProjectsQuerySchema>;
82
+
83
+ export const listAdminProjectsResponseSchema = z.object({
84
+ projects: z.array(adminProjectSummaryDtoSchema),
85
+ next_cursor: z.string().nullable(),
86
+ });
87
+
88
+ export type ListAdminProjectsResponseDto = z.infer<typeof listAdminProjectsResponseSchema>;