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

@@ -5,6 +5,9 @@ import authRegister from "./auth/register";
import issueCreate from "./issue/create";
import issueDelete from "./issue/delete";
import issueUpdate from "./issue/update";
import issueCommentCreate from "./issue-comment/create";
import issueCommentDelete from "./issue-comment/delete";
import issueCommentsByIssue from "./issue-comments/by-issue";
import issues from "./issues/all";
import issuesByProject from "./issues/by-project";
import issuesReplaceStatus from "./issues/replace-status";
@@ -54,6 +57,10 @@ export const routes = {
issueDelete,
issueUpdate,
issueCommentCreate,
issueCommentDelete,
issueCommentsByIssue,
issuesByProject,
issues,
issuesReplaceStatus,

View File

@@ -0,0 +1,34 @@
import { IssueCommentCreateRequestSchema } from "@sprint/shared";
import type { AuthedRequest } from "../../auth/middleware";
import {
createIssueComment,
getIssueByID,
getIssueOrganisationId,
getOrganisationMemberRole,
} from "../../db/queries";
import { errorResponse, parseJsonBody } from "../../validation";
export default async function issueCommentCreate(req: AuthedRequest) {
const parsed = await parseJsonBody(req, IssueCommentCreateRequestSchema);
if ("error" in parsed) return parsed.error;
const { issueId, body } = parsed.data;
const issue = await getIssueByID(issueId);
if (!issue) {
return errorResponse(`issue not found: ${issueId}`, "ISSUE_NOT_FOUND", 404);
}
const organisationId = await getIssueOrganisationId(issueId);
if (!organisationId) {
return errorResponse(`organisation not found for issue ${issueId}`, "ORG_NOT_FOUND", 404);
}
const member = await getOrganisationMemberRole(organisationId, req.userId);
if (!member) {
return errorResponse("forbidden", "FORBIDDEN", 403);
}
const comment = await createIssueComment(issueId, req.userId, body);
return Response.json(comment);
}

View File

@@ -0,0 +1,39 @@
import { IssueCommentDeleteRequestSchema } from "@sprint/shared";
import type { AuthedRequest } from "../../auth/middleware";
import {
deleteIssueComment,
getIssueCommentById,
getIssueOrganisationId,
getOrganisationMemberRole,
} from "../../db/queries";
import { errorResponse, parseJsonBody } from "../../validation";
export default async function issueCommentDelete(req: AuthedRequest) {
const parsed = await parseJsonBody(req, IssueCommentDeleteRequestSchema);
if ("error" in parsed) return parsed.error;
const { id } = parsed.data;
const comment = await getIssueCommentById(id);
if (!comment) {
return errorResponse(`comment not found: ${id}`, "COMMENT_NOT_FOUND", 404);
}
if (comment.userId !== req.userId) {
return errorResponse("forbidden", "FORBIDDEN", 403);
}
const organisationId = await getIssueOrganisationId(comment.issueId);
if (!organisationId) {
return errorResponse("organisation not found", "ORG_NOT_FOUND", 404);
}
const member = await getOrganisationMemberRole(organisationId, req.userId);
if (!member) {
return errorResponse("forbidden", "FORBIDDEN", 403);
}
await deleteIssueComment(id);
return Response.json({ success: true });
}

View File

@@ -0,0 +1,35 @@
import { IssueCommentsByIssueQuerySchema } from "@sprint/shared";
import type { AuthedRequest } from "../../auth/middleware";
import {
getIssueByID,
getIssueCommentsByIssueId,
getIssueOrganisationId,
getOrganisationMemberRole,
} from "../../db/queries";
import { errorResponse, parseQueryParams } from "../../validation";
export default async function issueCommentsByIssue(req: AuthedRequest) {
const url = new URL(req.url);
const parsed = parseQueryParams(url, IssueCommentsByIssueQuerySchema);
if ("error" in parsed) return parsed.error;
const { issueId } = parsed.data;
const issue = await getIssueByID(issueId);
if (!issue) {
return errorResponse(`issue not found: ${issueId}`, "ISSUE_NOT_FOUND", 404);
}
const organisationId = await getIssueOrganisationId(issueId);
if (!organisationId) {
return errorResponse(`organisation not found for issue ${issueId}`, "ORG_NOT_FOUND", 404);
}
const member = await getOrganisationMemberRole(organisationId, req.userId);
if (!member) {
return errorResponse("forbidden", "FORBIDDEN", 403);
}
const comments = await getIssueCommentsByIssueId(issueId);
return Response.json(comments);
}