full comments system

This commit is contained in:
Oliver Bryan
2026-01-21 19:10:28 +00:00
parent 0d2195cab4
commit 8f87fc8acf
28 changed files with 1451 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import {
ISSUE_DESCRIPTION_MAX_LENGTH,
ISSUE_COMMENT_MAX_LENGTH,
ISSUE_STATUS_MAX_LENGTH,
ISSUE_TITLE_MAX_LENGTH,
ORG_DESCRIPTION_MAX_LENGTH,
@@ -111,6 +112,25 @@ export const IssuesReplaceStatusRequestSchema = z.object({
export type IssuesReplaceStatusRequest = z.infer<typeof IssuesReplaceStatusRequestSchema>;
export const IssueCommentCreateRequestSchema = z.object({
issueId: z.number().int().positive("issueId must be a positive integer"),
body: z.string().min(1, "Comment is required").max(ISSUE_COMMENT_MAX_LENGTH),
});
export type IssueCommentCreateRequest = z.infer<typeof IssueCommentCreateRequestSchema>;
export const IssueCommentDeleteRequestSchema = z.object({
id: z.number().int().positive("id must be a positive integer"),
});
export type IssueCommentDeleteRequest = z.infer<typeof IssueCommentDeleteRequestSchema>;
export const IssueCommentsByIssueQuerySchema = z.object({
issueId: z.coerce.number().int().positive("issueId must be a positive integer"),
});
export type IssueCommentsByIssueQuery = z.infer<typeof IssueCommentsByIssueQuerySchema>;
// organisation schemas
export const OrgCreateRequestSchema = z.object({
@@ -375,6 +395,22 @@ export const IssueResponseSchema = z.object({
export type IssueResponseType = z.infer<typeof IssueResponseSchema>;
export const IssueCommentRecordSchema = z.object({
id: z.number(),
issueId: z.number(),
userId: z.number(),
body: z.string(),
createdAt: z.string().nullable().optional(),
updatedAt: z.string().nullable().optional(),
});
export const IssueCommentResponseSchema = z.object({
Comment: IssueCommentRecordSchema,
User: UserResponseSchema,
});
export type IssueCommentResponseType = z.infer<typeof IssueCommentResponseSchema>;
export const OrganisationRecordSchema = z.object({
id: z.number(),
name: z.string(),

View File

@@ -12,3 +12,4 @@ export const PROJECT_SLUG_MAX_LENGTH = 64;
export const ISSUE_TITLE_MAX_LENGTH = 64;
export const ISSUE_DESCRIPTION_MAX_LENGTH = 2048;
export const ISSUE_STATUS_MAX_LENGTH = 24;
export const ISSUE_COMMENT_MAX_LENGTH = 2048;

View File

@@ -2,8 +2,12 @@ export type {
ApiError,
AuthResponse,
IssueCreateRequest,
IssueCommentCreateRequest,
IssueCommentDeleteRequest,
IssueCommentsByIssueQuery,
IssueDeleteRequest,
IssueResponseType,
IssueCommentResponseType,
IssuesByProjectQuery,
IssuesReplaceStatusRequest,
IssuesStatusCountQuery,
@@ -47,7 +51,12 @@ export {
ApiErrorSchema,
AuthResponseSchema,
IssueCreateRequestSchema,
IssueCommentCreateRequestSchema,
IssueCommentDeleteRequestSchema,
IssueCommentsByIssueQuerySchema,
IssueDeleteRequestSchema,
IssueCommentResponseSchema,
IssueCommentRecordSchema,
IssueRecordSchema,
IssueResponseSchema,
IssuesByProjectQuerySchema,
@@ -93,6 +102,7 @@ export {
} from "./api-schemas";
export {
ISSUE_DESCRIPTION_MAX_LENGTH,
ISSUE_COMMENT_MAX_LENGTH,
ISSUE_STATUS_MAX_LENGTH,
ISSUE_TITLE_MAX_LENGTH,
ORG_DESCRIPTION_MAX_LENGTH,
@@ -108,6 +118,9 @@ export type {
IconStyle,
IssueAssigneeInsert,
IssueAssigneeRecord,
IssueCommentInsert,
IssueCommentRecord,
IssueCommentResponse,
IssueInsert,
IssueRecord,
IssueResponse,
@@ -138,6 +151,9 @@ export {
IssueAssignee,
IssueAssigneeInsertSchema,
IssueAssigneeSelectSchema,
IssueComment,
IssueCommentInsertSchema,
IssueCommentSelectSchema,
IssueInsertSchema,
IssueSelectSchema,
iconStyles,

View File

@@ -3,6 +3,7 @@ import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
import {
ISSUE_DESCRIPTION_MAX_LENGTH,
ISSUE_COMMENT_MAX_LENGTH,
ISSUE_STATUS_MAX_LENGTH,
ISSUE_TITLE_MAX_LENGTH,
ORG_DESCRIPTION_MAX_LENGTH,
@@ -151,6 +152,19 @@ export const IssueAssignee = pgTable(
(t) => [uniqueIndex("unique_issue_user").on(t.issueId, t.userId)],
);
export const IssueComment = pgTable("IssueComment", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
issueId: integer()
.notNull()
.references(() => Issue.id, { onDelete: "cascade" }),
userId: integer()
.notNull()
.references(() => User.id, { onDelete: "cascade" }),
body: varchar({ length: ISSUE_COMMENT_MAX_LENGTH }).notNull(),
createdAt: timestamp({ withTimezone: false }).defaultNow(),
updatedAt: timestamp({ withTimezone: false }).defaultNow(),
});
// Zod schemas
export const UserSelectSchema = createSelectSchema(User);
export const UserInsertSchema = createInsertSchema(User);
@@ -173,6 +187,9 @@ export const IssueInsertSchema = createInsertSchema(Issue);
export const IssueAssigneeSelectSchema = createSelectSchema(IssueAssignee);
export const IssueAssigneeInsertSchema = createInsertSchema(IssueAssignee);
export const IssueCommentSelectSchema = createSelectSchema(IssueComment);
export const IssueCommentInsertSchema = createInsertSchema(IssueComment);
export const SessionSelectSchema = createSelectSchema(Session);
export const SessionInsertSchema = createInsertSchema(Session);
@@ -203,6 +220,9 @@ export type IssueInsert = z.infer<typeof IssueInsertSchema>;
export type IssueAssigneeRecord = z.infer<typeof IssueAssigneeSelectSchema>;
export type IssueAssigneeInsert = z.infer<typeof IssueAssigneeInsertSchema>;
export type IssueCommentRecord = z.infer<typeof IssueCommentSelectSchema>;
export type IssueCommentInsert = z.infer<typeof IssueCommentInsertSchema>;
export type SessionRecord = z.infer<typeof SessionSelectSchema>;
export type SessionInsert = z.infer<typeof SessionInsertSchema>;
@@ -217,6 +237,11 @@ export type IssueResponse = {
Assignees: UserRecord[];
};
export type IssueCommentResponse = {
Comment: IssueCommentRecord;
User: UserRecord;
};
export type ProjectResponse = {
Project: ProjectRecord;
Organisation: OrganisationRecord;