mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
route + query updates for "assignees" update
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Issue, User } from "@sprint/shared";
|
||||
import { Issue, IssueAssignee, type IssueResponse, User, type UserRecord } from "@sprint/shared";
|
||||
import { aliasedTable, and, eq, inArray, sql } from "drizzle-orm";
|
||||
import { db } from "../client";
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function createIssue(
|
||||
description: string,
|
||||
creatorId: number,
|
||||
sprintId?: number,
|
||||
assigneeId?: number,
|
||||
assigneeIds?: number[],
|
||||
status?: string,
|
||||
) {
|
||||
// prevents two issues with the same unique number
|
||||
@@ -22,7 +22,6 @@ export async function createIssue(
|
||||
|
||||
const nextNumber = (lastIssue?.max || 0) + 1;
|
||||
|
||||
// 2. create new issue
|
||||
const [newIssue] = await tx
|
||||
.insert(Issue)
|
||||
.values({
|
||||
@@ -32,11 +31,23 @@ export async function createIssue(
|
||||
number: nextNumber,
|
||||
creatorId,
|
||||
sprintId,
|
||||
assigneeId,
|
||||
...(status && { status }),
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!newIssue) {
|
||||
throw new Error("failed to create issue");
|
||||
}
|
||||
|
||||
if (assigneeIds && assigneeIds.length > 0) {
|
||||
await tx.insert(IssueAssignee).values(
|
||||
assigneeIds.map((userId) => ({
|
||||
issueId: newIssue.id,
|
||||
userId,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return newIssue;
|
||||
});
|
||||
}
|
||||
@@ -51,13 +62,27 @@ export async function updateIssue(
|
||||
title?: string;
|
||||
description?: string;
|
||||
sprintId?: number | null;
|
||||
assigneeId?: number | null;
|
||||
status?: string;
|
||||
},
|
||||
) {
|
||||
return await db.update(Issue).set(updates).where(eq(Issue.id, id)).returning();
|
||||
}
|
||||
|
||||
export async function setIssueAssignees(issueId: number, userIds: number[]) {
|
||||
return await db.transaction(async (tx) => {
|
||||
await tx.delete(IssueAssignee).where(eq(IssueAssignee.issueId, issueId));
|
||||
|
||||
if (userIds.length > 0) {
|
||||
await tx.insert(IssueAssignee).values(
|
||||
userIds.map((userId) => ({
|
||||
issueId,
|
||||
userId,
|
||||
})),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getIssues() {
|
||||
return await db.select().from(Issue);
|
||||
}
|
||||
@@ -119,18 +144,41 @@ export async function replaceIssueStatus(organisationId: number, oldStatus: stri
|
||||
return { updated: result.rowCount ?? 0 };
|
||||
}
|
||||
|
||||
export async function getIssuesWithUsersByProject(projectId: number) {
|
||||
export async function getIssuesWithUsersByProject(projectId: number): Promise<IssueResponse[]> {
|
||||
const Creator = aliasedTable(User, "Creator");
|
||||
const Assignee = aliasedTable(User, "Assignee");
|
||||
|
||||
return await db
|
||||
const issuesWithCreators = await db
|
||||
.select({
|
||||
Issue: Issue,
|
||||
Creator: Creator,
|
||||
Assignee: Assignee,
|
||||
})
|
||||
.from(Issue)
|
||||
.where(eq(Issue.projectId, projectId))
|
||||
.innerJoin(Creator, eq(Issue.creatorId, Creator.id))
|
||||
.leftJoin(Assignee, eq(Issue.assigneeId, Assignee.id));
|
||||
.innerJoin(Creator, eq(Issue.creatorId, Creator.id));
|
||||
|
||||
const issueIds = issuesWithCreators.map((i) => i.Issue.id);
|
||||
const assigneesData =
|
||||
issueIds.length > 0
|
||||
? await db
|
||||
.select({
|
||||
issueId: IssueAssignee.issueId,
|
||||
User: User,
|
||||
})
|
||||
.from(IssueAssignee)
|
||||
.innerJoin(User, eq(IssueAssignee.userId, User.id))
|
||||
.where(inArray(IssueAssignee.issueId, issueIds))
|
||||
: [];
|
||||
|
||||
const assigneesByIssue = new Map<number, UserRecord[]>();
|
||||
for (const a of assigneesData) {
|
||||
const existing = assigneesByIssue.get(a.issueId) || [];
|
||||
existing.push(a.User);
|
||||
assigneesByIssue.set(a.issueId, existing);
|
||||
}
|
||||
|
||||
return issuesWithCreators.map((row) => ({
|
||||
Issue: row.Issue,
|
||||
Creator: row.Creator,
|
||||
Assignees: assigneesByIssue.get(row.Issue.id) || [],
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default async function issueCreate(req: AuthedRequest) {
|
||||
const parsed = await parseJsonBody(req, IssueCreateRequestSchema);
|
||||
if ("error" in parsed) return parsed.error;
|
||||
|
||||
const { projectId, title, description = "", status, assigneeId, sprintId } = parsed.data;
|
||||
const { projectId, title, description = "", status, assigneeIds, sprintId } = parsed.data;
|
||||
|
||||
const project = await getProjectByID(projectId);
|
||||
if (!project) {
|
||||
@@ -20,7 +20,7 @@ export default async function issueCreate(req: AuthedRequest) {
|
||||
description,
|
||||
req.userId,
|
||||
sprintId ?? undefined,
|
||||
assigneeId ?? undefined,
|
||||
assigneeIds,
|
||||
status,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,32 +1,43 @@
|
||||
import { IssueUpdateRequestSchema } from "@sprint/shared";
|
||||
import { type IssueRecord, IssueUpdateRequestSchema } from "@sprint/shared";
|
||||
import type { BunRequest } from "bun";
|
||||
import { updateIssue } from "../../db/queries";
|
||||
import { getIssueByID, setIssueAssignees, updateIssue } from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function issueUpdate(req: BunRequest) {
|
||||
const parsed = await parseJsonBody(req, IssueUpdateRequestSchema);
|
||||
if ("error" in parsed) return parsed.error;
|
||||
|
||||
const { id, title, description, status, assigneeId, sprintId } = parsed.data;
|
||||
const { id, title, description, status, assigneeIds, sprintId } = parsed.data;
|
||||
|
||||
// check that at least one field is being updated
|
||||
if (
|
||||
title === undefined &&
|
||||
description === undefined &&
|
||||
status === undefined &&
|
||||
assigneeId === undefined &&
|
||||
assigneeIds === undefined &&
|
||||
sprintId === undefined
|
||||
) {
|
||||
return errorResponse("no updates provided", "NO_UPDATES", 400);
|
||||
}
|
||||
|
||||
const issue = await updateIssue(id, {
|
||||
const hasIssueFieldUpdates =
|
||||
title !== undefined || description !== undefined || status !== undefined || sprintId !== undefined;
|
||||
|
||||
let issue: IssueRecord | undefined;
|
||||
if (hasIssueFieldUpdates) {
|
||||
[issue] = await updateIssue(id, {
|
||||
title,
|
||||
description,
|
||||
sprintId,
|
||||
assigneeId,
|
||||
status,
|
||||
});
|
||||
} else {
|
||||
issue = await getIssueByID(id);
|
||||
}
|
||||
|
||||
if (assigneeIds !== undefined) {
|
||||
await setIssueAssignees(id, assigneeIds ?? []);
|
||||
}
|
||||
|
||||
return Response.json(issue);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const IssueCreateRequestSchema = z.object({
|
||||
title: z.string().min(1, "title is required").max(ISSUE_TITLE_MAX_LENGTH),
|
||||
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).default(""),
|
||||
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
||||
assigneeId: z.number().int().positive().nullable().optional(),
|
||||
assigneeIds: z.array(z.number().int().positive()).optional(),
|
||||
sprintId: z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ export const IssueUpdateRequestSchema = z.object({
|
||||
title: z.string().min(1).max(ISSUE_TITLE_MAX_LENGTH).optional(),
|
||||
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).optional(),
|
||||
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
||||
assigneeId: z.number().int().positive().nullable().optional(),
|
||||
assigneeIds: z.array(z.number().int().positive()).nullable().optional(),
|
||||
sprintId: z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -328,14 +328,13 @@ export const IssueRecordSchema = z.object({
|
||||
description: z.string(),
|
||||
status: z.string(),
|
||||
creatorId: z.number(),
|
||||
assigneeId: z.number().nullable(),
|
||||
sprintId: z.number().nullable(),
|
||||
});
|
||||
|
||||
export const IssueResponseSchema = z.object({
|
||||
Issue: IssueRecordSchema,
|
||||
Creator: UserResponseSchema,
|
||||
Assignee: UserResponseSchema.nullable(),
|
||||
Assignees: z.array(UserResponseSchema),
|
||||
});
|
||||
|
||||
export type IssueResponseType = z.infer<typeof IssueResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user