setDescription(e.target.value)}
- validate={(v) =>
- v.trim().length > 2048 ? "Too long (2048 character limit)" : undefined
- }
+ validate={(v) => {
+ if (v.trim().length > ORG_DESCRIPTION_MAX_LENGTH) {
+ return `Too long (${ORG_DESCRIPTION_MAX_LENGTH} character limit)`;
+ }
+ return undefined;
+ }}
submitAttempted={submitAttempted}
placeholder="What is this organisation for?"
+ maxLength={ORG_DESCRIPTION_MAX_LENGTH}
/>
@@ -179,9 +187,10 @@ export function CreateOrganisation({
disabled={
submitting ||
name.trim() === "" ||
- name.trim().length > 16 ||
+ name.trim().length > ORG_NAME_MAX_LENGTH ||
slug.trim() === "" ||
- slug.trim().length > 16
+ slug.trim().length > ORG_SLUG_MAX_LENGTH ||
+ description.trim().length > ORG_DESCRIPTION_MAX_LENGTH
}
>
{submitting ? "Creating..." : "Create"}
diff --git a/packages/frontend/src/components/create-project.tsx b/packages/frontend/src/components/create-project.tsx
index 21c2a4b..0a468a4 100644
--- a/packages/frontend/src/components/create-project.tsx
+++ b/packages/frontend/src/components/create-project.tsx
@@ -1,4 +1,4 @@
-import type { ProjectRecord } from "@issue/shared";
+import { PROJECT_NAME_MAX_LENGTH, type ProjectRecord } from "@issue/shared";
import { type FormEvent, useState } from "react";
import { useAuthenticatedSession } from "@/components/session-provider";
import { Button } from "@/components/ui/button";
@@ -61,7 +61,12 @@ export function CreateProject({
setError(null);
setSubmitAttempted(true);
- if (name.trim() === "" || key.trim() === "" || key.length > 4) {
+ if (
+ name.trim() === "" ||
+ name.trim().length > PROJECT_NAME_MAX_LENGTH ||
+ key.trim() === "" ||
+ key.length > 4
+ ) {
return;
}
@@ -115,7 +120,7 @@ export function CreateProject({
)}
-
+
Create Project
@@ -132,9 +137,16 @@ export function CreateProject({
setKey(keyify(nextName));
}
}}
- validate={(v) => (v.trim() === "" ? "Cannot be empty" : undefined)}
+ validate={(v) => {
+ if (v.trim() === "") return "Cannot be empty";
+ if (v.trim().length > PROJECT_NAME_MAX_LENGTH) {
+ return `Too long (${PROJECT_NAME_MAX_LENGTH} character limit)`;
+ }
+ return undefined;
+ }}
submitAttempted={submitAttempted}
placeholder="Demo Project"
+ maxLength={PROJECT_NAME_MAX_LENGTH}
/>
PROJECT_NAME_MAX_LENGTH && submitAttempted) ||
((key.trim() === "" || key.length > 4) && submitAttempted)
}
>
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 54cbf03..755d396 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -42,6 +42,16 @@ export {
UserSelectSchema,
} from "./schema";
+export const ORG_NAME_MAX_LENGTH = 256;
+export const ORG_DESCRIPTION_MAX_LENGTH = 1024;
+export const ORG_SLUG_MAX_LENGTH = 64;
+
+export const PROJECT_NAME_MAX_LENGTH = 256;
+export const PROJECT_DESCRIPTION_MAX_LENGTH = 1024;
+export const PROJECT_SLUG_MAX_LENGTH = 64;
+
+export const ISSUE_TITLE_MAX_LENGTH = 256;
+export const ISSUE_DESCRIPTION_MAX_LENGTH = 2048;
export const ISSUE_STATUS_MAX_LENGTH = 24;
export { calculateBreakTimeMs, calculateWorkTimeMs, isTimerRunning } from "./utils/time-tracking";
diff --git a/packages/shared/src/schema.ts b/packages/shared/src/schema.ts
index 2b6f5c6..b60836d 100644
--- a/packages/shared/src/schema.ts
+++ b/packages/shared/src/schema.ts
@@ -1,6 +1,17 @@
import { integer, pgTable, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
+import {
+ ISSUE_DESCRIPTION_MAX_LENGTH,
+ ISSUE_STATUS_MAX_LENGTH,
+ ISSUE_TITLE_MAX_LENGTH,
+ ORG_DESCRIPTION_MAX_LENGTH,
+ ORG_NAME_MAX_LENGTH,
+ ORG_SLUG_MAX_LENGTH,
+ PROJECT_DESCRIPTION_MAX_LENGTH,
+ PROJECT_NAME_MAX_LENGTH,
+ PROJECT_SLUG_MAX_LENGTH,
+} from "./index";
export const User = pgTable("User", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
@@ -14,10 +25,13 @@ export const User = pgTable("User", {
export const Organisation = pgTable("Organisation", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
- name: varchar({ length: 256 }).notNull(),
- description: varchar({ length: 1024 }),
- slug: varchar({ length: 64 }).notNull().unique(),
- statuses: varchar({ length: 64 }).array().notNull().default(["TO DO", "IN PROGRESS", "REVIEW", "DONE"]),
+ name: varchar({ length: ORG_NAME_MAX_LENGTH }).notNull(),
+ description: varchar({ length: ORG_DESCRIPTION_MAX_LENGTH }),
+ slug: varchar({ length: ORG_SLUG_MAX_LENGTH }).notNull().unique(),
+ statuses: varchar({ length: ISSUE_STATUS_MAX_LENGTH })
+ .array()
+ .notNull()
+ .default(["TO DO", "IN PROGRESS", "REVIEW", "DONE", "ARCHIVED", "MERGED"]),
createdAt: timestamp({ withTimezone: false }).defaultNow(),
updatedAt: timestamp({ withTimezone: false }).defaultNow(),
});
@@ -37,7 +51,9 @@ export const OrganisationMember = pgTable("OrganisationMember", {
export const Project = pgTable("Project", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
key: varchar({ length: 4 }).notNull(),
- name: varchar({ length: 256 }).notNull(),
+ name: varchar({ length: PROJECT_NAME_MAX_LENGTH }).notNull(),
+ description: varchar({ length: PROJECT_DESCRIPTION_MAX_LENGTH }),
+ slug: varchar({ length: PROJECT_SLUG_MAX_LENGTH }).notNull().unique(),
organisationId: integer()
.notNull()
.references(() => Organisation.id),
@@ -79,9 +95,9 @@ export const Issue = pgTable(
number: integer("number").notNull(),
- title: varchar({ length: 256 }).notNull(),
- description: varchar({ length: 2048 }).notNull(),
- status: varchar({ length: 64 }).notNull().default("TO DO"),
+ title: varchar({ length: ISSUE_TITLE_MAX_LENGTH }).notNull(),
+ description: varchar({ length: ISSUE_DESCRIPTION_MAX_LENGTH }).notNull(),
+ status: varchar({ length: ISSUE_STATUS_MAX_LENGTH }).notNull().default("TO DO"),
creatorId: integer()
.notNull()