Project.blob -> Project.key

This commit is contained in:
Oliver Bryan
2025-12-29 06:17:40 +00:00
parent 54493f7c60
commit f534bc6dec
16 changed files with 509 additions and 71 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE "Project" RENAME COLUMN "blob" TO "key";

View File

@@ -0,0 +1,431 @@
{
"id": "e65db5fa-6d8f-43f1-aea1-154a085302bc",
"prevId": "c7a99155-1dc7-414d-88b6-8f485daa0c58",
"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
},
"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_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
},
"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": {}
}
}

View File

@@ -43,6 +43,13 @@
"when": 1766433489198, "when": 1766433489198,
"tag": "0005_great_timeslip", "tag": "0005_great_timeslip",
"breakpoints": true "breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1766988874206,
"tag": "0006_wise_kree",
"breakpoints": true
} }
] ]
} }

View File

@@ -2,11 +2,11 @@ import { Issue, Organisation, Project, User } from "@issue/shared";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../client"; import { db } from "../client";
export async function createProject(blob: string, name: string, creatorId: number, organisationId: number) { export async function createProject(key: string, name: string, creatorId: number, organisationId: number) {
const [project] = await db const [project] = await db
.insert(Project) .insert(Project)
.values({ .values({
blob, key,
name, name,
creatorId, creatorId,
organisationId, organisationId,
@@ -17,7 +17,7 @@ export async function createProject(blob: string, name: string, creatorId: numbe
export async function updateProject( export async function updateProject(
projectId: number, projectId: number,
updates: { blob?: string; name?: string; creatorId?: number; organisationId?: number }, updates: { key?: string; name?: string; creatorId?: number; organisationId?: number },
) { ) {
const [project] = await db.update(Project).set(updates).where(eq(Project.id, projectId)).returning(); const [project] = await db.update(Project).set(updates).where(eq(Project.id, projectId)).returning();
return project; return project;
@@ -35,8 +35,8 @@ export async function getProjectByID(projectId: number) {
return project; return project;
} }
export async function getProjectByBlob(projectBlob: string) { export async function getProjectByKey(projectKey: string) {
const [project] = await db.select().from(Project).where(eq(Project.blob, projectBlob)); const [project] = await db.select().from(Project).where(eq(Project.key, projectKey));
return project; return project;
} }

View File

@@ -20,7 +20,7 @@ const main = async () => {
"/issue/create": withCors(withAuth(routes.issueCreate)), "/issue/create": withCors(withAuth(routes.issueCreate)),
"/issue/update": withCors(withAuth(routes.issueUpdate)), "/issue/update": withCors(withAuth(routes.issueUpdate)),
"/issue/delete": withCors(withAuth(routes.issueDelete)), "/issue/delete": withCors(withAuth(routes.issueDelete)),
"/issues/:projectBlob": withCors(withAuth(routes.issuesInProject)), "/issues/:projectKey": withCors(withAuth(routes.issuesInProject)),
"/issues/all": withCors(withAuth(routes.issues)), "/issues/all": withCors(withAuth(routes.issues)),
"/organisation/create": withCors(withAuth(routes.organisationCreate)), "/organisation/create": withCors(withAuth(routes.organisationCreate)),

View File

@@ -4,7 +4,7 @@ import authRegister from "./auth/register";
import issueCreate from "./issue/create"; import issueCreate from "./issue/create";
import issueDelete from "./issue/delete"; import issueDelete from "./issue/delete";
import issueUpdate from "./issue/update"; import issueUpdate from "./issue/update";
import issuesInProject from "./issues/[projectBlob]"; import issuesInProject from "./issues/[projectKey]";
import issues from "./issues/all"; import issues from "./issues/all";
import organisationAddMember from "./organisation/add-member"; import organisationAddMember from "./organisation/add-member";
import organisationById from "./organisation/by-id"; import organisationById from "./organisation/by-id";

View File

@@ -1,24 +1,24 @@
import type { BunRequest } from "bun"; import type { BunRequest } from "bun";
import { createIssue, getProjectByID, getProjectByBlob } from "../../db/queries"; import { createIssue, getProjectByID, getProjectByKey } from "../../db/queries";
// /issue/create?projectId=1&title=Testing&description=Description // /issue/create?projectId=1&title=Testing&description=Description
// OR // OR
// /issue/create?projectBlob=projectBlob&title=Testing&description=Description // /issue/create?projectKey=projectKey&title=Testing&description=Description
export default async function issueCreate(req: BunRequest) { export default async function issueCreate(req: BunRequest) {
const url = new URL(req.url); const url = new URL(req.url);
const projectId = url.searchParams.get("projectId"); const projectId = url.searchParams.get("projectId");
const projectBlob = url.searchParams.get("projectBlob"); const projectKey = url.searchParams.get("projectKey");
let project = null; let project = null;
if (projectId) { if (projectId) {
project = await getProjectByID(Number(projectId)); project = await getProjectByID(Number(projectId));
} else if (projectBlob) { } else if (projectKey) {
project = await getProjectByBlob(projectBlob); project = await getProjectByKey(projectKey);
} else { } else {
return new Response("missing project blob or project id", { status: 400 }); return new Response("missing project key or project id", { status: 400 });
} }
if (!project) { if (!project) {
return new Response(`project not found: provided ${projectId ?? projectBlob}`, { status: 404 }); return new Response(`project not found: provided ${projectId ?? projectKey}`, { status: 404 });
} }
const title = url.searchParams.get("title") || "Untitled Issue"; const title = url.searchParams.get("title") || "Untitled Issue";

View File

@@ -1,12 +1,12 @@
import type { BunRequest } from "bun"; import type { BunRequest } from "bun";
import { getIssuesWithAssigneeByProject, getProjectByBlob } from "../../db/queries"; import { getIssuesWithAssigneeByProject, getProjectByKey } from "../../db/queries";
export default async function issuesInProject(req: BunRequest<"/issues/:projectBlob">) { export default async function issuesInProject(req: BunRequest<"/issues/:projectKey">) {
const { projectBlob } = req.params; const { projectKey } = req.params;
const project = await getProjectByBlob(projectBlob); const project = await getProjectByKey(projectKey);
if (!project) { if (!project) {
return new Response(`project not found: provided ${projectBlob}`, { status: 404 }); return new Response(`project not found: provided ${projectKey}`, { status: 404 });
} }
const issues = await getIssuesWithAssigneeByProject(project.id); const issues = await getIssuesWithAssigneeByProject(project.id);

View File

@@ -1,25 +1,25 @@
import type { BunRequest } from "bun"; import type { BunRequest } from "bun";
import { createProject, getProjectByBlob, getUserById } from "../../db/queries"; import { createProject, getProjectByKey, getUserById } from "../../db/queries";
// /project/create?blob=BLOB&name=Testing&creatorId=1&organisationId=1 // /project/create?key=KEY&name=Testing&creatorId=1&organisationId=1
export default async function projectCreate(req: BunRequest) { export default async function projectCreate(req: BunRequest) {
const url = new URL(req.url); const url = new URL(req.url);
const blob = url.searchParams.get("blob"); const key = url.searchParams.get("key");
const name = url.searchParams.get("name"); const name = url.searchParams.get("name");
const creatorId = url.searchParams.get("creatorId"); const creatorId = url.searchParams.get("creatorId");
const organisationId = url.searchParams.get("organisationId"); const organisationId = url.searchParams.get("organisationId");
if (!blob || !name || !creatorId || !organisationId) { if (!key || !name || !creatorId || !organisationId) {
return new Response( return new Response(
`missing parameters: ${!blob ? "blob " : ""}${!name ? "name " : ""}${!creatorId ? "creatorId " : ""}${!organisationId ? "organisationId" : ""}`, `missing parameters: ${!key ? "key " : ""}${!name ? "name " : ""}${!creatorId ? "creatorId " : ""}${!organisationId ? "organisationId" : ""}`,
{ status: 400 }, { status: 400 },
); );
} }
// check if project with blob already exists in the organisation // check if project with key already exists in the organisation
const existingProject = await getProjectByBlob(blob); const existingProject = await getProjectByKey(key);
if (existingProject?.organisationId === parseInt(organisationId, 10)) { if (existingProject?.organisationId === parseInt(organisationId, 10)) {
return new Response(`project with blob ${blob} already exists`, { status: 400 }); return new Response(`project with key ${key} already exists`, { status: 400 });
} }
const creator = await getUserById(parseInt(creatorId, 10)); const creator = await getUserById(parseInt(creatorId, 10));
@@ -27,7 +27,7 @@ export default async function projectCreate(req: BunRequest) {
return new Response(`creator with id ${creatorId} not found`, { status: 404 }); return new Response(`creator with id ${creatorId} not found`, { status: 404 });
} }
const project = await createProject(blob, name, creator.id, parseInt(organisationId, 10)); const project = await createProject(key, name, creator.id, parseInt(organisationId, 10));
return Response.json(project); return Response.json(project);
} }

View File

@@ -1,11 +1,11 @@
import type { BunRequest } from "bun"; import type { BunRequest } from "bun";
import { getProjectByBlob, getProjectByID, getUserById, updateProject } from "../../db/queries"; import { getProjectByID, getProjectByKey, getUserById, updateProject } from "../../db/queries";
// /project/update?id=1&blob=NEW&name=new%20name&creatorId=1&organisationId=1 // /project/update?id=1&key=NEW&name=new%20name&creatorId=1&organisationId=1
export default async function projectUpdate(req: BunRequest) { export default async function projectUpdate(req: BunRequest) {
const url = new URL(req.url); const url = new URL(req.url);
const id = url.searchParams.get("id"); const id = url.searchParams.get("id");
const blob = url.searchParams.get("blob") || undefined; const key = url.searchParams.get("key") || undefined;
const name = url.searchParams.get("name") || undefined; const name = url.searchParams.get("name") || undefined;
const creatorId = url.searchParams.get("creatorId") || undefined; const creatorId = url.searchParams.get("creatorId") || undefined;
const organisationId = url.searchParams.get("organisationId") || undefined; const organisationId = url.searchParams.get("organisationId") || undefined;
@@ -19,15 +19,15 @@ export default async function projectUpdate(req: BunRequest) {
return new Response(`project with id ${id} does not exist`, { status: 404 }); return new Response(`project with id ${id} does not exist`, { status: 404 });
} }
if (!blob && !name && !creatorId && !organisationId) { if (!key && !name && !creatorId && !organisationId) {
return new Response(`at least one of blob, name, creatorId, or organisationId must be provided`, { return new Response(`at least one of key, name, creatorId, or organisationId must be provided`, {
status: 400, status: 400,
}); });
} }
const projectWithBlob = blob ? await getProjectByBlob(blob) : null; const projectWithKey = key ? await getProjectByKey(key) : null;
if (projectWithBlob && projectWithBlob.id !== Number(id)) { if (projectWithKey && projectWithKey.id !== Number(id)) {
return new Response(`a project with blob "${blob}" already exists`, { status: 400 }); return new Response(`a project with key "${key}" already exists`, { status: 400 });
} }
const newCreator = creatorId ? await getUserById(Number(creatorId)) : null; const newCreator = creatorId ? await getUserById(Number(creatorId)) : null;
@@ -36,8 +36,8 @@ export default async function projectUpdate(req: BunRequest) {
} }
const project = await updateProject(Number(id), { const project = await updateProject(Number(id), {
blob: blob, key,
name: name, name,
creatorId: newCreator?.id, creatorId: newCreator?.id,
organisationId: organisationId ? Number(organisationId) : undefined, organisationId: organisationId ? Number(organisationId) : undefined,
}); });

View File

@@ -130,7 +130,7 @@ function Index() {
useEffect(() => { useEffect(() => {
if (!selectedProject) return; if (!selectedProject) return;
fetch(`${serverURL}/issues/${selectedProject.Project.blob}`, { headers: getAuthHeaders() }) fetch(`${serverURL}/issues/${selectedProject.Project.key}`, { headers: getAuthHeaders() })
.then((res) => res.json()) .then((res) => res.json())
.then((data: IssueResponse[]) => { .then((data: IssueResponse[]) => {
setIssues(data); setIssues(data);

View File

@@ -12,7 +12,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { cn, getAuthHeaders } from "@/lib/utils"; import { cn, getAuthHeaders } from "@/lib/utils";
const blobify = (value: string) => const keyify = (value: string) =>
value value
.toUpperCase() .toUpperCase()
.replace(/[^A-Z0-9]/g, "") .replace(/[^A-Z0-9]/g, "")
@@ -33,11 +33,11 @@ export function CreateProject({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [blob, setBlob] = useState(""); const [key, setKey] = useState("");
const [nameTouched, setNameTouched] = useState(false); const [nameTouched, setNameTouched] = useState(false);
const [blobTouched, setBlobTouched] = useState(false); const [keyTouched, setKeyTouched] = useState(false);
const [blobManuallyEdited, setBlobManuallyEdited] = useState(false); const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);
const [submitAttempted, setSubmitAttempted] = useState(false); const [submitAttempted, setSubmitAttempted] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
@@ -48,20 +48,20 @@ export function CreateProject({
[nameTouched, submitAttempted, name], [nameTouched, submitAttempted, name],
); );
const blobInvalid = useMemo(() => { const keyInvalid = useMemo(() => {
if (!(blobTouched || submitAttempted)) return ""; if (!(keyTouched || submitAttempted)) return "";
if (blob.trim() === "") return "Cannot be empty"; if (key.trim() === "") return "Cannot be empty";
if (blob.length > 4) return "Must be 4 or less characters"; if (key.length > 4) return "Must be 4 or less characters";
return ""; return "";
}, [blobTouched, submitAttempted, blob]); }, [keyTouched, submitAttempted, key]);
const reset = () => { const reset = () => {
setName(""); setName("");
setBlob(""); setKey("");
setNameTouched(false); setNameTouched(false);
setBlobTouched(false); setKeyTouched(false);
setBlobManuallyEdited(false); setKeyManuallyEdited(false);
setSubmitAttempted(false); setSubmitAttempted(false);
setSubmitting(false); setSubmitting(false);
@@ -80,7 +80,7 @@ export function CreateProject({
setError(null); setError(null);
setSubmitAttempted(true); setSubmitAttempted(true);
if (name.trim() === "" || blob.length > 4) { if (name.trim() === "" || key.length > 4) {
return; return;
} }
@@ -97,7 +97,7 @@ export function CreateProject({
setSubmitting(true); setSubmitting(true);
try { try {
const url = new URL(`${serverURL}/project/create`); const url = new URL(`${serverURL}/project/create`);
url.searchParams.set("blob", blob); url.searchParams.set("key", key);
url.searchParams.set("name", name.trim()); url.searchParams.set("name", name.trim());
url.searchParams.set("creatorId", `${userId}`); url.searchParams.set("creatorId", `${userId}`);
url.searchParams.set("organisationId", `${organisationId}`); url.searchParams.set("organisationId", `${organisationId}`);
@@ -161,8 +161,8 @@ export function CreateProject({
const nextName = e.target.value; const nextName = e.target.value;
setName(nextName); setName(nextName);
if (!blobManuallyEdited) { if (!keyManuallyEdited) {
setBlob(blobify(nextName)); setKey(keyify(nextName));
} }
}} }}
onBlur={() => setNameTouched(true)} onBlur={() => setNameTouched(true)}
@@ -180,23 +180,23 @@ export function CreateProject({
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="project-blob">Blob</Label> <Label htmlFor="project-key">Key</Label>
<Input <Input
id="project-blob" id="project-key"
name="blob" name="key"
value={blob} value={key}
onChange={(e) => { onChange={(e) => {
setBlob(blobify(e.target.value)); setKey(keyify(e.target.value));
setBlobManuallyEdited(true); setKeyManuallyEdited(true);
}} }}
onBlur={() => setBlobTouched(true)} onBlur={() => setKeyTouched(true)}
aria-invalid={blobInvalid !== ""} aria-invalid={keyInvalid !== ""}
placeholder="DEMO" placeholder="DEMO"
required required
/> />
<div className="flex items-end justify-end w-full text-xs -mb-4 -mt-2"> <div className="flex items-end justify-end w-full text-xs -mb-4 -mt-2">
{blobInvalid !== "" ? ( {keyInvalid !== "" ? (
<Label className="text-destructive text-sm">{blobInvalid}</Label> <Label className="text-destructive text-sm">{keyInvalid}</Label>
) : ( ) : (
<Label className="opacity-0 text-sm">a</Label> <Label className="opacity-0 text-sm">a</Label>
)} )}
@@ -219,7 +219,7 @@ export function CreateProject({
</DialogClose> </DialogClose>
<Button <Button
type="submit" type="submit"
disabled={submitting || nameInvalid !== "" || blobInvalid !== ""} disabled={submitting || nameInvalid !== "" || keyInvalid !== ""}
> >
{submitting ? "Creating..." : "Create"} {submitting ? "Creating..." : "Create"}
</Button> </Button>

View File

@@ -18,7 +18,7 @@ export function IssueDetailPane({
<div className="flex flex-row items-center justify-end border-b h-[25px]"> <div className="flex flex-row items-center justify-end border-b h-[25px]">
<span className="w-full"> <span className="w-full">
<p className="text-sm w-fit px-1"> <p className="text-sm w-fit px-1">
{issueID(project.Project.blob, issueData.Issue.number)} {issueID(project.Project.key, issueData.Issue.number)}
</p> </p>
</span> </span>

View File

@@ -5,8 +5,8 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function issueID(blob: string, num: number) { export function issueID(key: string, num: number) {
return `${blob}-${num.toString().padStart(3, "0")}`; return `${key}-${num.toString().padStart(3, "0")}`;
} }
export function getAuthHeaders(): HeadersInit { export function getAuthHeaders(): HeadersInit {

View File

@@ -34,7 +34,7 @@ export const OrganisationMember = pgTable("OrganisationMember", {
export const Project = pgTable("Project", { export const Project = pgTable("Project", {
id: integer().primaryKey().generatedAlwaysAsIdentity(), id: integer().primaryKey().generatedAlwaysAsIdentity(),
blob: varchar({ length: 4 }).notNull(), key: varchar({ length: 4 }).notNull(),
name: varchar({ length: 256 }).notNull(), name: varchar({ length: 256 }).notNull(),
organisationId: integer() organisationId: integer()
.notNull() .notNull()

View File

@@ -1,4 +1,3 @@
- user settings/profile page - user settings/profile page
- create issue - create issue
- add/invite user(s) to org - add/invite user(s) to org
- rename Project.blob to Project.key