Skip to content

Commit d962be8

Browse files
authored
SIMSBIOHUB-891: Backend for Ticket System (#345)
* backend changes update and delete ticket comments cleanup repository cleanup cleanup indexes and seeds fix tests code smell fix tests linter * pr comments * change post to put
1 parent d8a8219 commit d962be8

40 files changed

+4578
-1
lines changed

api/src/models/comment.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ export const Comment = z.object({
55
comment: z.string()
66
});
77
export type Comment = z.infer<typeof Comment>;
8+
9+
export interface UpdateComment {
10+
comment: string;
11+
}

api/src/models/ticket-comment.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { z } from 'zod';
2+
3+
export const TicketComment = z.object({
4+
ticket_comment_id: z.string().uuid(),
5+
ticket_id: z.string().uuid(),
6+
user_identifier: z.string(),
7+
create_date: z.string(),
8+
comment: z.string()
9+
});
10+
11+
export type TicketComment = z.infer<typeof TicketComment>;
12+
13+
export const CreateTicketCommentRequest = z.object({
14+
comment: z.string().min(1).max(3000)
15+
});
16+
17+
export type CreateTicketCommentRequest = z.infer<typeof CreateTicketCommentRequest>;
18+
19+
export const UpdateTicketCommentRequest = z.object({
20+
comment: z.string().min(1).max(3000)
21+
});
22+
23+
export type UpdateTicketCommentRequest = z.infer<typeof UpdateTicketCommentRequest>;
24+
25+
export const CreateTicketComment = z.object({
26+
ticketId: z.string().uuid(),
27+
comment: z.string().min(1).max(3000)
28+
});
29+
30+
export type CreateTicketComment = z.infer<typeof CreateTicketComment>;
31+
32+
export const UpdateTicketComment = z.object({
33+
ticketId: z.string().uuid(),
34+
ticketCommentId: z.string().uuid(),
35+
comment: z.string().min(1).max(3000)
36+
});
37+
38+
export type UpdateTicketComment = z.infer<typeof UpdateTicketComment>;

api/src/models/ticket-reference.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { z } from 'zod';
2+
3+
export const TicketRelationshipType = z.enum([
4+
'blocks',
5+
'blocked_by',
6+
'duplicates',
7+
'duplicate_of',
8+
'relates_to',
9+
'resolves',
10+
'resolved_by'
11+
]);
12+
13+
export type TicketRelationshipType = z.infer<typeof TicketRelationshipType>;
14+
15+
export const TicketReference = z.object({
16+
ticket_reference_id: z.string().uuid(),
17+
source_ticket_id: z.string().uuid(),
18+
source_ticket_slug: z.string().regex(/^\d{8}$/),
19+
source_ticket_subject: z.string(),
20+
target_ticket_id: z.string().uuid(),
21+
target_ticket_slug: z.string().regex(/^\d{8}$/),
22+
target_ticket_subject: z.string(),
23+
relationship: TicketRelationshipType,
24+
user_identifier: z.string(),
25+
create_date: z.string()
26+
});
27+
28+
export type TicketReference = z.infer<typeof TicketReference>;
29+
30+
export const CreateTicketReference = z.object({
31+
source_ticket_id: z.string().uuid(),
32+
target_ticket_id: z.string().uuid(),
33+
relationship: TicketRelationshipType
34+
});
35+
36+
export type CreateTicketReference = z.infer<typeof CreateTicketReference>;
37+
38+
export const CreateTicketReferenceRequest = z.object({
39+
target_ticket_id: z.string().uuid(),
40+
relationship: TicketRelationshipType
41+
});
42+
43+
export type CreateTicketReferenceRequest = z.infer<typeof CreateTicketReferenceRequest>;

api/src/models/ticket-status.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { z } from 'zod';
2+
3+
export const TicketStatus = z.object({
4+
ticket_status_id: z.string().uuid(),
5+
ticket_id: z.string().uuid(),
6+
user_identifier: z.string(),
7+
create_date: z.string(),
8+
status: z.enum(['open', 'closed'])
9+
});
10+
11+
export type TicketStatus = z.infer<typeof TicketStatus>;

api/src/models/ticket.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { z } from 'zod';
2+
import { TicketComment } from './ticket-comment';
3+
import { TicketReference } from './ticket-reference';
4+
import { TicketStatus as TicketStatusRecord } from './ticket-status';
5+
6+
export const TicketPriority = z.enum(['low', 'medium', 'high', 'critical']);
7+
export type TicketPriority = z.infer<typeof TicketPriority>;
8+
9+
export const TicketStatus = z.enum(['open', 'closed']);
10+
export type TicketStatus = z.infer<typeof TicketStatus>;
11+
12+
export interface TicketFilters {
13+
team_id?: string;
14+
status?: TicketStatus;
15+
}
16+
17+
export const Ticket = z.object({
18+
ticket_id: z.string().uuid(),
19+
ticket_slug: z.string().regex(/^\d{8}$/),
20+
subject: z.string(),
21+
description: z.string().nullable(),
22+
team_id: z.string().uuid(),
23+
create_date: z.string(),
24+
priority: TicketPriority,
25+
status: TicketStatus
26+
});
27+
28+
export type Ticket = z.infer<typeof Ticket>;
29+
30+
export const TicketSlug = Ticket.pick({ ticket_slug: true });
31+
export type TicketSlug = z.infer<typeof TicketSlug>;
32+
33+
export const CreateTicketRequest = z.object({
34+
subject: z.string(),
35+
description: z.string().nullable(),
36+
priority: TicketPriority
37+
});
38+
39+
export type CreateTicketRequest = z.infer<typeof CreateTicketRequest>;
40+
41+
export type CreateTicketPayload = CreateTicketRequest & {
42+
team_id: string;
43+
ticket_slug: string;
44+
};
45+
46+
export const UpdateTicketRequest = z.object({
47+
subject: z.string().optional(),
48+
description: z.string().nullable().optional(),
49+
priority: TicketPriority.optional(),
50+
status: TicketStatus.optional()
51+
});
52+
53+
export type UpdateTicketRequest = z.infer<typeof UpdateTicketRequest>;
54+
55+
export const UpdateTicketStatusRequest = z.object({
56+
status: TicketStatus
57+
});
58+
59+
export type UpdateTicketStatusRequest = z.infer<typeof UpdateTicketStatusRequest>;
60+
61+
export * from './ticket-comment';
62+
export * from './ticket-reference';
63+
export * from './ticket-status';
64+
65+
export const TicketWithHistory = Ticket.extend({
66+
statuses: z.array(TicketStatusRecord),
67+
comments: z.array(TicketComment),
68+
references: z.array(TicketReference)
69+
});
70+
71+
export type TicketWithHistory = z.infer<typeof TicketWithHistory>;

api/src/openapi/schemas/ticket.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { OpenAPIV3 } from 'openapi-types';
2+
import { paginationResponseSchema } from './pagination';
3+
4+
const TicketPriorityEnum = ['low', 'medium', 'high', 'critical'];
5+
const TicketStatusEnum = ['open', 'closed'];
6+
7+
export const TicketCommentSchema: OpenAPIV3.SchemaObject = {
8+
type: 'object',
9+
required: ['ticket_comment_id', 'ticket_id', 'user_identifier', 'create_date', 'comment'],
10+
properties: {
11+
ticket_comment_id: { type: 'string', format: 'uuid' },
12+
ticket_id: { type: 'string', format: 'uuid' },
13+
user_identifier: { type: 'string' },
14+
create_date: { type: 'string', format: 'date-time' },
15+
comment: { type: 'string' }
16+
}
17+
};
18+
19+
export const TicketReferenceSchema: OpenAPIV3.SchemaObject = {
20+
type: 'object',
21+
required: [
22+
'ticket_reference_id',
23+
'source_ticket_id',
24+
'source_ticket_slug',
25+
'source_ticket_subject',
26+
'target_ticket_id',
27+
'target_ticket_slug',
28+
'target_ticket_subject',
29+
'relationship',
30+
'user_identifier',
31+
'create_date'
32+
],
33+
properties: {
34+
ticket_reference_id: { type: 'string', format: 'uuid' },
35+
source_ticket_id: { type: 'string', format: 'uuid' },
36+
source_ticket_slug: { type: 'string', minLength: 8, maxLength: 8, pattern: String.raw`^\d{8}$` },
37+
source_ticket_subject: { type: 'string', maxLength: 100 },
38+
target_ticket_id: { type: 'string', format: 'uuid' },
39+
target_ticket_slug: { type: 'string', minLength: 8, maxLength: 8, pattern: String.raw`^\d{8}$` },
40+
target_ticket_subject: { type: 'string', maxLength: 100 },
41+
relationship: {
42+
type: 'string',
43+
enum: ['blocks', 'blocked_by', 'duplicates', 'duplicate_of', 'relates_to', 'resolves', 'resolved_by']
44+
},
45+
user_identifier: { type: 'string' },
46+
create_date: { type: 'string', format: 'date-time' }
47+
}
48+
};
49+
50+
export const TicketSchema: OpenAPIV3.SchemaObject = {
51+
type: 'object',
52+
required: ['ticket_id', 'ticket_slug', 'subject', 'team_id', 'create_date', 'priority', 'status'],
53+
properties: {
54+
ticket_id: { type: 'string', format: 'uuid' },
55+
ticket_slug: { type: 'string', minLength: 8, maxLength: 8, pattern: String.raw`^\d{8}$` },
56+
subject: { type: 'string', maxLength: 100 },
57+
description: { type: 'string', maxLength: 2000, nullable: true },
58+
team_id: { type: 'string', format: 'uuid' },
59+
create_date: { type: 'string', format: 'date-time' },
60+
priority: { type: 'string', enum: TicketPriorityEnum },
61+
status: { type: 'string', enum: TicketStatusEnum }
62+
}
63+
};
64+
65+
export const TicketWithHistorySchema: OpenAPIV3.SchemaObject = {
66+
type: 'object',
67+
required: [
68+
'ticket_id',
69+
'ticket_slug',
70+
'subject',
71+
'team_id',
72+
'create_date',
73+
'priority',
74+
'status',
75+
'statuses',
76+
'comments',
77+
'references'
78+
],
79+
properties: {
80+
ticket_id: { type: 'string', format: 'uuid' },
81+
ticket_slug: { type: 'string', minLength: 8, maxLength: 8, pattern: String.raw`^\d{8}$` },
82+
subject: { type: 'string', maxLength: 100 },
83+
description: { type: 'string', maxLength: 2000, nullable: true },
84+
team_id: { type: 'string', format: 'uuid' },
85+
create_date: { type: 'string', format: 'date-time' },
86+
priority: { type: 'string', enum: TicketPriorityEnum },
87+
status: { type: 'string', enum: TicketStatusEnum },
88+
statuses: {
89+
description: 'Status change history for the ticket.',
90+
type: 'array',
91+
items: {
92+
type: 'object',
93+
required: ['ticket_status_id', 'ticket_id', 'user_identifier', 'create_date', 'status'],
94+
properties: {
95+
ticket_status_id: { type: 'string', format: 'uuid' },
96+
ticket_id: { type: 'string', format: 'uuid' },
97+
user_identifier: { type: 'string' },
98+
create_date: { type: 'string', format: 'date-time' },
99+
status: { type: 'string', enum: TicketStatusEnum }
100+
}
101+
}
102+
},
103+
comments: {
104+
type: 'array',
105+
items: TicketCommentSchema
106+
},
107+
references: {
108+
type: 'array',
109+
items: TicketReferenceSchema
110+
}
111+
}
112+
};
113+
114+
export const CreateTicketRequestSchema: OpenAPIV3.SchemaObject = {
115+
type: 'object',
116+
additionalProperties: false,
117+
required: ['subject', 'description', 'priority'],
118+
properties: {
119+
subject: { type: 'string', maxLength: 100 },
120+
description: { type: 'string', maxLength: 2000, nullable: true },
121+
priority: { type: 'string', enum: TicketPriorityEnum }
122+
}
123+
};
124+
125+
export const UpdateTicketRequestSchema: OpenAPIV3.SchemaObject = {
126+
type: 'object',
127+
additionalProperties: false,
128+
properties: {
129+
subject: { type: 'string', maxLength: 100 },
130+
description: { type: 'string', maxLength: 2000, nullable: true },
131+
priority: { type: 'string', enum: TicketPriorityEnum },
132+
status: { type: 'string', enum: TicketStatusEnum }
133+
}
134+
};
135+
136+
export const UpdateTicketStatusRequestSchema: OpenAPIV3.SchemaObject = {
137+
type: 'object',
138+
additionalProperties: false,
139+
required: ['status'],
140+
properties: {
141+
status: { type: 'string', enum: TicketStatusEnum }
142+
}
143+
};
144+
145+
export const CreateTicketCommentRequestSchema: OpenAPIV3.SchemaObject = {
146+
type: 'object',
147+
additionalProperties: false,
148+
required: ['comment'],
149+
properties: {
150+
comment: { type: 'string', minLength: 1, maxLength: 3000 }
151+
}
152+
};
153+
154+
export const UpdateTicketCommentRequestSchema: OpenAPIV3.SchemaObject = {
155+
type: 'object',
156+
additionalProperties: false,
157+
required: ['comment'],
158+
properties: {
159+
comment: { type: 'string', minLength: 1, maxLength: 3000 }
160+
}
161+
};
162+
163+
export const CreateTicketReferenceRequestSchema: OpenAPIV3.SchemaObject = {
164+
type: 'object',
165+
additionalProperties: false,
166+
required: ['target_ticket_id', 'relationship'],
167+
properties: {
168+
target_ticket_id: { type: 'string', format: 'uuid' },
169+
relationship: {
170+
type: 'string',
171+
enum: ['blocks', 'blocked_by', 'duplicates', 'duplicate_of', 'relates_to', 'resolves', 'resolved_by']
172+
}
173+
}
174+
};
175+
176+
export const TicketStatusSchema: OpenAPIV3.SchemaObject = {
177+
type: 'object',
178+
required: ['ticket_status_id', 'ticket_id', 'user_identifier', 'create_date', 'status'],
179+
properties: {
180+
ticket_status_id: { type: 'string', format: 'uuid' },
181+
ticket_id: { type: 'string', format: 'uuid' },
182+
user_identifier: { type: 'string' },
183+
create_date: { type: 'string', format: 'date-time' },
184+
status: { type: 'string', enum: TicketStatusEnum }
185+
}
186+
};
187+
188+
export const TicketListResponseSchema: OpenAPIV3.SchemaObject = {
189+
type: 'object',
190+
required: ['tickets', 'pagination'],
191+
properties: {
192+
tickets: {
193+
type: 'array',
194+
items: TicketSchema
195+
},
196+
pagination: paginationResponseSchema
197+
}
198+
};

0 commit comments

Comments
 (0)