mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
Issue.creatorId + implementation
This commit is contained in:
2
packages/backend/drizzle/0009_closed_metal_master.sql
Normal file
2
packages/backend/drizzle/0009_closed_metal_master.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "Issue" ADD COLUMN "creatorId" integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "Issue" ADD CONSTRAINT "Issue_creatorId_User_id_fk" FOREIGN KEY ("creatorId") REFERENCES "public"."User"("id") ON DELETE no action ON UPDATE no action;
|
||||
456
packages/backend/drizzle/meta/0009_snapshot.json
Normal file
456
packages/backend/drizzle/meta/0009_snapshot.json
Normal file
@@ -0,0 +1,456 @@
|
||||
{
|
||||
"id": "6abfed17-620d-4113-90e2-013e71a9a8d7",
|
||||
"prevId": "808cb9e7-dabb-4186-8fc5-2d1b7635ffa8",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.Issue": {
|
||||
"name": "Issue",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "Issue_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"projectId": {
|
||||
"name": "projectId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"number": {
|
||||
"name": "number",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "varchar(256)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "varchar(2048)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"creatorId": {
|
||||
"name": "creatorId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"assigneeId": {
|
||||
"name": "assigneeId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"unique_project_issue_number": {
|
||||
"name": "unique_project_issue_number",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "projectId",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "number",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"Issue_projectId_Project_id_fk": {
|
||||
"name": "Issue_projectId_Project_id_fk",
|
||||
"tableFrom": "Issue",
|
||||
"tableTo": "Project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"Issue_creatorId_User_id_fk": {
|
||||
"name": "Issue_creatorId_User_id_fk",
|
||||
"tableFrom": "Issue",
|
||||
"tableTo": "User",
|
||||
"columnsFrom": [
|
||||
"creatorId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"Issue_assigneeId_User_id_fk": {
|
||||
"name": "Issue_assigneeId_User_id_fk",
|
||||
"tableFrom": "Issue",
|
||||
"tableTo": "User",
|
||||
"columnsFrom": [
|
||||
"assigneeId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.Organisation": {
|
||||
"name": "Organisation",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "Organisation_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(256)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "varchar(1024)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"Organisation_slug_unique": {
|
||||
"name": "Organisation_slug_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.OrganisationMember": {
|
||||
"name": "OrganisationMember",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "OrganisationMember_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"organisationId": {
|
||||
"name": "organisationId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"OrganisationMember_organisationId_Organisation_id_fk": {
|
||||
"name": "OrganisationMember_organisationId_Organisation_id_fk",
|
||||
"tableFrom": "OrganisationMember",
|
||||
"tableTo": "Organisation",
|
||||
"columnsFrom": [
|
||||
"organisationId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"OrganisationMember_userId_User_id_fk": {
|
||||
"name": "OrganisationMember_userId_User_id_fk",
|
||||
"tableFrom": "OrganisationMember",
|
||||
"tableTo": "User",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.Project": {
|
||||
"name": "Project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "Project_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "varchar(4)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(256)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"organisationId": {
|
||||
"name": "organisationId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"creatorId": {
|
||||
"name": "creatorId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"Project_organisationId_Organisation_id_fk": {
|
||||
"name": "Project_organisationId_Organisation_id_fk",
|
||||
"tableFrom": "Project",
|
||||
"tableTo": "Organisation",
|
||||
"columnsFrom": [
|
||||
"organisationId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"Project_creatorId_User_id_fk": {
|
||||
"name": "Project_creatorId_User_id_fk",
|
||||
"tableFrom": "Project",
|
||||
"tableTo": "User",
|
||||
"columnsFrom": [
|
||||
"creatorId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.User": {
|
||||
"name": "User",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "User_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(256)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"passwordHash": {
|
||||
"name": "passwordHash",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatarURL": {
|
||||
"name": "avatarURL",
|
||||
"type": "varchar(512)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updatedAt": {
|
||||
"name": "updatedAt",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"User_username_unique": {
|
||||
"name": "User_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,13 @@
|
||||
"when": 1767250186823,
|
||||
"tag": "0008_certain_sharon_ventura",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1767704274330,
|
||||
"tag": "0009_closed_metal_master",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Issue, User } from "@issue/shared";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { aliasedTable, and, eq, sql } from "drizzle-orm";
|
||||
import { db } from "../client";
|
||||
|
||||
export async function createIssue(
|
||||
projectId: number,
|
||||
title: string,
|
||||
description: string,
|
||||
creatorId: number,
|
||||
assigneeId?: number,
|
||||
) {
|
||||
// prevents two issues with the same unique number
|
||||
@@ -27,6 +28,7 @@ export async function createIssue(
|
||||
title,
|
||||
description,
|
||||
number: nextNumber,
|
||||
creatorId,
|
||||
assigneeId,
|
||||
})
|
||||
.returning();
|
||||
@@ -64,10 +66,18 @@ export async function getIssueByNumber(projectId: number, number: number) {
|
||||
return issue;
|
||||
}
|
||||
|
||||
export async function getIssuesWithAssigneeByProject(projectId: number) {
|
||||
export async function getIssuesWithUsersByProject(projectId: number) {
|
||||
const Creator = aliasedTable(User, "Creator");
|
||||
const Assignee = aliasedTable(User, "Assignee");
|
||||
|
||||
return await db
|
||||
.select()
|
||||
.select({
|
||||
Issue: Issue,
|
||||
Creator: Creator,
|
||||
Assignee: Assignee,
|
||||
})
|
||||
.from(Issue)
|
||||
.where(eq(Issue.projectId, projectId))
|
||||
.leftJoin(User, eq(Issue.assigneeId, User.id));
|
||||
.innerJoin(Creator, eq(Issue.creatorId, Creator.id))
|
||||
.leftJoin(Assignee, eq(Issue.assigneeId, Assignee.id));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { BunRequest } from "bun";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createIssue, getProjectByID, getProjectByKey } from "../../db/queries";
|
||||
|
||||
// /issue/create?projectId=1&title=Testing&description=Description
|
||||
// OR
|
||||
// /issue/create?projectKey=projectKey&title=Testing&description=Description
|
||||
export default async function issueCreate(req: BunRequest) {
|
||||
export default async function issueCreate(req: AuthedRequest) {
|
||||
const url = new URL(req.url);
|
||||
const projectId = url.searchParams.get("projectId");
|
||||
const projectKey = url.searchParams.get("projectKey");
|
||||
@@ -24,7 +24,7 @@ export default async function issueCreate(req: BunRequest) {
|
||||
const title = url.searchParams.get("title") || "Untitled Issue";
|
||||
const description = url.searchParams.get("description") || "";
|
||||
|
||||
const issue = await createIssue(project.id, title, description);
|
||||
const issue = await createIssue(project.id, title, description, req.userId);
|
||||
|
||||
return Response.json(issue);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { getIssuesWithAssigneeByProject, getProjectByID } from "../../db/queries";
|
||||
import { getIssuesWithUsersByProject, getProjectByID } from "../../db/queries";
|
||||
|
||||
export default async function issuesByProject(req: AuthedRequest) {
|
||||
const url = new URL(req.url);
|
||||
@@ -9,7 +9,7 @@ export default async function issuesByProject(req: AuthedRequest) {
|
||||
if (!project) {
|
||||
return new Response(`project not found: provided ${projectId}`, { status: 404 });
|
||||
}
|
||||
const issues = await getIssuesWithAssigneeByProject(project.id);
|
||||
const issues = await getIssuesWithUsersByProject(project.id);
|
||||
|
||||
return Response.json(issues);
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ export const createDemoData = async () => {
|
||||
project.id,
|
||||
`Issue ${i} in ${projectName}`,
|
||||
`This is a description for issue ${i} in ${projectName}.`,
|
||||
config.creator.id,
|
||||
assignee,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,17 +27,22 @@ export function IssueDetailPane({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full p-2 gap-2">
|
||||
<div className="flex flex-col w-full p-2 py-1 gap-2">
|
||||
<h1 className="text-md">{issueData.Issue.title}</h1>
|
||||
<p className="text-sm">{issueData.Issue.description}</p>
|
||||
|
||||
{issueData.User && (
|
||||
{issueData.Assignee && (
|
||||
<div className="flex items-center gap-2">
|
||||
Assignee:
|
||||
{issueData.User ? <SmallUserDisplay user={issueData.User} /> : "Unassigned"}
|
||||
<SmallUserDisplay user={issueData.Assignee} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 px-2 py-1 border-t text-sm text-muted-foreground">
|
||||
Created by:
|
||||
<SmallUserDisplay user={issueData.Creator} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,11 +51,11 @@ export function IssuesTable({
|
||||
)}
|
||||
{(columns.assignee == null || columns.assignee === true) && (
|
||||
<TableCell className={"flex items-center justify-end px-1 py-0 h-[32px]"}>
|
||||
{issueData.User && (
|
||||
{issueData.Assignee && (
|
||||
<Avatar
|
||||
name={issueData.User?.name}
|
||||
username={issueData.User?.username}
|
||||
avatarURL={issueData.User?.avatarURL}
|
||||
name={issueData.Assignee?.name}
|
||||
username={issueData.Assignee?.username}
|
||||
avatarURL={issueData.Assignee?.avatarURL}
|
||||
textClass="text-xs"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -58,6 +58,9 @@ export const Issue = pgTable(
|
||||
title: varchar({ length: 256 }).notNull(),
|
||||
description: varchar({ length: 2048 }).notNull(),
|
||||
|
||||
creatorId: integer()
|
||||
.notNull()
|
||||
.references(() => User.id),
|
||||
assigneeId: integer().references(() => User.id),
|
||||
},
|
||||
(t) => [
|
||||
@@ -103,7 +106,8 @@ export type IssueInsert = z.infer<typeof IssueInsertSchema>;
|
||||
|
||||
export type IssueResponse = {
|
||||
Issue: IssueRecord;
|
||||
User?: UserRecord;
|
||||
Creator: UserRecord;
|
||||
Assignee: UserRecord | null;
|
||||
};
|
||||
|
||||
export type ProjectResponse = {
|
||||
|
||||
Reference in New Issue
Block a user