feat: card checklists (#141)
* feat: setup checklist schema * feat: scaffold checklist router * feat: add create new checklist modal * feat: create new checklist item * feat: toggle checklist item completed state * feat: update checklist name * feat: delete checklist * feat: show checklists progress on cards * feat: tweak light mode styling * feat: focus item form on creation of new checklist * feat: tweak new checklist item form styling * feat: add checklist activity * feat: show checklist progress on public board page * feat: display checklist items on public card modal * chore: add translations
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { boardRouter } from "./routers/board";
|
||||
import { cardRouter } from "./routers/card";
|
||||
import { checklistRouter } from "./routers/checklist";
|
||||
import { feedbackRouter } from "./routers/feedback";
|
||||
import { importRouter } from "./routers/import";
|
||||
import { integrationRouter } from "./routers/integration";
|
||||
@@ -13,6 +14,7 @@ import { createTRPCRouter } from "./trpc";
|
||||
export const appRouter = createTRPCRouter({
|
||||
board: boardRouter,
|
||||
card: cardRouter,
|
||||
checklist: checklistRouter,
|
||||
feedback: feedbackRouter,
|
||||
label: labelRouter,
|
||||
list: listRouter,
|
||||
|
||||
410
packages/api/src/routers/checklist.ts
Normal file
410
packages/api/src/routers/checklist.ts
Normal file
@@ -0,0 +1,410 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||
import * as checklistRepo from "@kan/db/repository/checklist.repo";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { assertUserInWorkspace } from "../utils/auth";
|
||||
|
||||
const checklistSchema = z.object({
|
||||
publicId: z.string().length(12),
|
||||
name: z.string().min(1).max(255),
|
||||
});
|
||||
|
||||
const checklistItemSchema = z.object({
|
||||
publicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500),
|
||||
completed: z.boolean(),
|
||||
});
|
||||
|
||||
export const checklistRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Add a checklist to a card",
|
||||
method: "POST",
|
||||
path: "/cards/{cardPublicId}/checklists",
|
||||
description: "Adds a checklist to a card",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
cardPublicId: z.string().length(12),
|
||||
name: z.string().min(1).max(255),
|
||||
}),
|
||||
)
|
||||
.output(checklistSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const card = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
|
||||
ctx.db,
|
||||
input.cardPublicId,
|
||||
);
|
||||
|
||||
if (!card)
|
||||
throw new TRPCError({
|
||||
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(ctx.db, userId, card.workspaceId);
|
||||
|
||||
const newChecklist = await checklistRepo.create(ctx.db, {
|
||||
name: input.name,
|
||||
createdBy: userId,
|
||||
cardId: card.id,
|
||||
});
|
||||
|
||||
if (!newChecklist?.id)
|
||||
throw new TRPCError({
|
||||
message: `Failed to create checklist`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.added",
|
||||
cardId: card.id,
|
||||
toTitle: newChecklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return newChecklist;
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
checklistPublicId: z.string().length(12),
|
||||
name: z.string().min(1).max(255),
|
||||
}),
|
||||
)
|
||||
.output(z.object({ publicId: z.string().length(12), name: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const checklist = await checklistRepo.getChecklistByPublicId(
|
||||
ctx.db,
|
||||
input.checklistPublicId,
|
||||
);
|
||||
if (!checklist)
|
||||
throw new TRPCError({
|
||||
message: `Checklist with public ID ${input.checklistPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(
|
||||
ctx.db,
|
||||
userId,
|
||||
checklist.card.list.board.workspace.id,
|
||||
);
|
||||
|
||||
const previousName = checklist.name;
|
||||
|
||||
const updated = await checklistRepo.updateChecklistById(ctx.db, {
|
||||
id: checklist.id,
|
||||
name: input.name,
|
||||
});
|
||||
|
||||
if (!updated)
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.renamed",
|
||||
cardId: checklist.cardId,
|
||||
fromTitle: previousName,
|
||||
toTitle: updated.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return updated;
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Delete a checklist",
|
||||
method: "DELETE",
|
||||
path: "/checklists/{checklistPublicId}",
|
||||
description: "Deletes a checklist by its public ID",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(z.object({ checklistPublicId: z.string().length(12) }))
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const checklist = await checklistRepo.getChecklistByPublicId(
|
||||
ctx.db,
|
||||
input.checklistPublicId,
|
||||
);
|
||||
if (!checklist)
|
||||
throw new TRPCError({
|
||||
message: `Checklist with public ID ${input.checklistPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(
|
||||
ctx.db,
|
||||
userId,
|
||||
checklist.card.list.board.workspace.id,
|
||||
);
|
||||
|
||||
await checklistRepo.softDeleteAllItemsByChecklistId(ctx.db, {
|
||||
checklistId: checklist.id,
|
||||
deletedAt: new Date(),
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
const deleted = await checklistRepo.softDeleteById(ctx.db, {
|
||||
id: checklist.id,
|
||||
deletedAt: new Date(),
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
if (!deleted)
|
||||
throw new TRPCError({
|
||||
message: `Failed to delete checklist`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.deleted",
|
||||
cardId: checklist.cardId,
|
||||
fromTitle: checklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
createItem: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Add an item to a checklist",
|
||||
method: "POST",
|
||||
path: "/checklists/{checklistPublicId}/items",
|
||||
description: "Adds an item to a checklist",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
checklistPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500),
|
||||
}),
|
||||
)
|
||||
.output(checklistItemSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const checklist = await checklistRepo.getChecklistByPublicId(
|
||||
ctx.db,
|
||||
input.checklistPublicId,
|
||||
);
|
||||
|
||||
if (!checklist)
|
||||
throw new TRPCError({
|
||||
message: `Checklist with public ID ${input.checklistPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(
|
||||
ctx.db,
|
||||
userId,
|
||||
checklist.card.list.board.workspace.id,
|
||||
);
|
||||
|
||||
const newChecklistItem = await checklistRepo.createItem(ctx.db, {
|
||||
title: input.title,
|
||||
createdBy: userId,
|
||||
checklistId: checklist.id,
|
||||
});
|
||||
|
||||
if (!newChecklistItem?.id)
|
||||
throw new TRPCError({
|
||||
message: `Failed to create checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.item.added",
|
||||
cardId: checklist.cardId,
|
||||
toTitle: newChecklistItem.title,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return newChecklistItem;
|
||||
}),
|
||||
updateItem: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Update a checklist item",
|
||||
method: "PUT",
|
||||
path: "/checklists/items/{checklistItemPublicId}",
|
||||
description: "Updates a checklist item (title/completed)",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
checklistItemPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500).optional(),
|
||||
completed: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.output(checklistItemSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
|
||||
ctx.db,
|
||||
input.checklistItemPublicId,
|
||||
);
|
||||
|
||||
if (!item)
|
||||
throw new TRPCError({
|
||||
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(
|
||||
ctx.db,
|
||||
userId,
|
||||
item.checklist.card.list.board.workspace.id,
|
||||
);
|
||||
|
||||
const previousTitle = item.title;
|
||||
|
||||
const updated = await checklistRepo.updateItemById(ctx.db, {
|
||||
id: item.id,
|
||||
title: input.title,
|
||||
completed: input.completed,
|
||||
});
|
||||
|
||||
if (!updated)
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
// Log completion toggle
|
||||
if (input.completed !== undefined) {
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: input.completed
|
||||
? "card.updated.checklist.item.completed"
|
||||
: "card.updated.checklist.item.uncompleted",
|
||||
cardId: item.checklist.cardId,
|
||||
toTitle: updated.title,
|
||||
createdBy: userId,
|
||||
});
|
||||
}
|
||||
|
||||
// Log title change
|
||||
if (input.title !== undefined && input.title !== previousTitle) {
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.item.updated",
|
||||
cardId: item.checklist.cardId,
|
||||
fromTitle: previousTitle,
|
||||
toTitle: updated.title,
|
||||
createdBy: userId,
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
}),
|
||||
deleteItem: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Delete a checklist item",
|
||||
method: "DELETE",
|
||||
path: "/checklists/items/{checklistItemPublicId}",
|
||||
description: "Deletes a checklist item",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(z.object({ checklistItemPublicId: z.string().length(12) }))
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
|
||||
ctx.db,
|
||||
input.checklistItemPublicId,
|
||||
);
|
||||
if (!item)
|
||||
throw new TRPCError({
|
||||
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertUserInWorkspace(
|
||||
ctx.db,
|
||||
userId,
|
||||
item.checklist.card.list.board.workspace.id,
|
||||
);
|
||||
|
||||
const deleted = await checklistRepo.softDeleteItemById(ctx.db, {
|
||||
id: item.id,
|
||||
deletedAt: new Date(),
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
if (!deleted)
|
||||
throw new TRPCError({
|
||||
message: `Failed to delete item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.item.deleted",
|
||||
cardId: item.checklist.cardId,
|
||||
fromTitle: item.title,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
84
packages/db/migrations/20250806202106_glossy_luminals.sql
Normal file
84
packages/db/migrations/20250806202106_glossy_luminals.sql
Normal file
@@ -0,0 +1,84 @@
|
||||
CREATE TABLE IF NOT EXISTS "card_checklist_item" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"publicId" varchar(12) NOT NULL,
|
||||
"title" varchar(500) NOT NULL,
|
||||
"completed" boolean DEFAULT false NOT NULL,
|
||||
"index" integer NOT NULL,
|
||||
"checklistId" bigint NOT NULL,
|
||||
"createdBy" uuid,
|
||||
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||
"updatedAt" timestamp,
|
||||
"deletedAt" timestamp,
|
||||
"deletedBy" uuid,
|
||||
CONSTRAINT "card_checklist_item_publicId_unique" UNIQUE("publicId")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "card_checklist_item" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "card_checklist" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"publicId" varchar(12) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"index" integer NOT NULL,
|
||||
"cardId" bigint NOT NULL,
|
||||
"createdBy" uuid,
|
||||
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||
"updatedAt" timestamp,
|
||||
"deletedAt" timestamp,
|
||||
"deletedBy" uuid,
|
||||
CONSTRAINT "card_checklist_publicId_unique" UNIQUE("publicId")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "card_checklist" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "integration" (
|
||||
"provider" varchar(255) NOT NULL,
|
||||
"userId" uuid NOT NULL,
|
||||
"accessToken" varchar(255) NOT NULL,
|
||||
"refreshToken" varchar(255),
|
||||
"expiresAt" timestamp NOT NULL,
|
||||
"createdAt" timestamp NOT NULL,
|
||||
"updatedAt" timestamp,
|
||||
CONSTRAINT "integration_pkey" PRIMARY KEY("userId","provider")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "integration" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_checklistId_card_checklist_id_fk" FOREIGN KEY ("checklistId") REFERENCES "public"."card_checklist"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_deletedBy_user_id_fk" FOREIGN KEY ("deletedBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_cardId_card_id_fk" FOREIGN KEY ("cardId") REFERENCES "public"."card"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_deletedBy_user_id_fk" FOREIGN KEY ("deletedBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "integration" ADD CONSTRAINT "integration_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
@@ -0,0 +1,8 @@
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.added' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.renamed' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.deleted' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.added' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.updated' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.completed' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.uncompleted' BEFORE 'card.archived';--> statement-breakpoint
|
||||
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.deleted' BEFORE 'card.archived';
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "91f9a2fa-31e2-4f3a-bdb9-852292fd7501",
|
||||
"prevId": "058e8521-2814-46f0-a33f-1bbd86f450ce",
|
||||
"prevId": "d170c8e0-bf75-4c71-abd9-0ecbeeb14003",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -92,13 +92,9 @@
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsFrom": ["userId"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -245,13 +241,9 @@
|
||||
"apiKey_userId_user_id_fk": {
|
||||
"name": "apiKey_userId_user_id_fk",
|
||||
"tableFrom": "apiKey",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsFrom": ["userId"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -320,13 +312,9 @@
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsFrom": ["userId"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -335,9 +323,7 @@
|
||||
"uniqueConstraints": {
|
||||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"columns": [
|
||||
"token"
|
||||
],
|
||||
"columns": ["token"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -523,52 +509,36 @@
|
||||
"board_createdBy_user_id_fk": {
|
||||
"name": "board_createdBy_user_id_fk",
|
||||
"tableFrom": "board",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"board_deletedBy_user_id_fk": {
|
||||
"name": "board_deletedBy_user_id_fk",
|
||||
"tableFrom": "board",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"board_importId_import_id_fk": {
|
||||
"name": "board_importId_import_id_fk",
|
||||
"tableFrom": "board",
|
||||
"columnsFrom": [
|
||||
"importId"
|
||||
],
|
||||
"columnsFrom": ["importId"],
|
||||
"tableTo": "import",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "no action"
|
||||
},
|
||||
"board_workspaceId_workspace_id_fk": {
|
||||
"name": "board_workspaceId_workspace_id_fk",
|
||||
"tableFrom": "board",
|
||||
"columnsFrom": [
|
||||
"workspaceId"
|
||||
],
|
||||
"columnsFrom": ["workspaceId"],
|
||||
"tableTo": "workspace",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -577,9 +547,7 @@
|
||||
"uniqueConstraints": {
|
||||
"board_publicId_unique": {
|
||||
"name": "board_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -713,91 +681,63 @@
|
||||
"card_activity_cardId_card_id_fk": {
|
||||
"name": "card_activity_cardId_card_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"cardId"
|
||||
],
|
||||
"columnsFrom": ["cardId"],
|
||||
"tableTo": "card",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_activity_fromListId_list_id_fk": {
|
||||
"name": "card_activity_fromListId_list_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"fromListId"
|
||||
],
|
||||
"columnsFrom": ["fromListId"],
|
||||
"tableTo": "list",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_activity_toListId_list_id_fk": {
|
||||
"name": "card_activity_toListId_list_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"toListId"
|
||||
],
|
||||
"columnsFrom": ["toListId"],
|
||||
"tableTo": "list",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_activity_labelId_label_id_fk": {
|
||||
"name": "card_activity_labelId_label_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"labelId"
|
||||
],
|
||||
"columnsFrom": ["labelId"],
|
||||
"tableTo": "label",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_activity_workspaceMemberId_workspace_members_id_fk": {
|
||||
"name": "card_activity_workspaceMemberId_workspace_members_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"workspaceMemberId"
|
||||
],
|
||||
"columnsFrom": ["workspaceMemberId"],
|
||||
"tableTo": "workspace_members",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"card_activity_createdBy_user_id_fk": {
|
||||
"name": "card_activity_createdBy_user_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"card_activity_commentId_card_comments_id_fk": {
|
||||
"name": "card_activity_commentId_card_comments_id_fk",
|
||||
"tableFrom": "card_activity",
|
||||
"columnsFrom": [
|
||||
"commentId"
|
||||
],
|
||||
"columnsFrom": ["commentId"],
|
||||
"tableTo": "card_comments",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -806,9 +746,7 @@
|
||||
"uniqueConstraints": {
|
||||
"card_activity_publicId_unique": {
|
||||
"name": "card_activity_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -838,26 +776,18 @@
|
||||
"_card_workspace_members_cardId_card_id_fk": {
|
||||
"name": "_card_workspace_members_cardId_card_id_fk",
|
||||
"tableFrom": "_card_workspace_members",
|
||||
"columnsFrom": [
|
||||
"cardId"
|
||||
],
|
||||
"columnsFrom": ["cardId"],
|
||||
"tableTo": "card",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"_card_workspace_members_workspaceMemberId_workspace_members_id_fk": {
|
||||
"name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk",
|
||||
"tableFrom": "_card_workspace_members",
|
||||
"columnsFrom": [
|
||||
"workspaceMemberId"
|
||||
],
|
||||
"columnsFrom": ["workspaceMemberId"],
|
||||
"tableTo": "workspace_members",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -865,10 +795,7 @@
|
||||
"compositePrimaryKeys": {
|
||||
"_card_workspace_members_cardId_workspaceMemberId_pk": {
|
||||
"name": "_card_workspace_members_cardId_workspaceMemberId_pk",
|
||||
"columns": [
|
||||
"cardId",
|
||||
"workspaceMemberId"
|
||||
]
|
||||
"columns": ["cardId", "workspaceMemberId"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
@@ -959,52 +886,36 @@
|
||||
"card_createdBy_user_id_fk": {
|
||||
"name": "card_createdBy_user_id_fk",
|
||||
"tableFrom": "card",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"card_deletedBy_user_id_fk": {
|
||||
"name": "card_deletedBy_user_id_fk",
|
||||
"tableFrom": "card",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"card_listId_list_id_fk": {
|
||||
"name": "card_listId_list_id_fk",
|
||||
"tableFrom": "card",
|
||||
"columnsFrom": [
|
||||
"listId"
|
||||
],
|
||||
"columnsFrom": ["listId"],
|
||||
"tableTo": "list",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_importId_import_id_fk": {
|
||||
"name": "card_importId_import_id_fk",
|
||||
"tableFrom": "card",
|
||||
"columnsFrom": [
|
||||
"importId"
|
||||
],
|
||||
"columnsFrom": ["importId"],
|
||||
"tableTo": "import",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "no action"
|
||||
}
|
||||
@@ -1013,9 +924,7 @@
|
||||
"uniqueConstraints": {
|
||||
"card_publicId_unique": {
|
||||
"name": "card_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1045,26 +954,18 @@
|
||||
"_card_labels_cardId_card_id_fk": {
|
||||
"name": "_card_labels_cardId_card_id_fk",
|
||||
"tableFrom": "_card_labels",
|
||||
"columnsFrom": [
|
||||
"cardId"
|
||||
],
|
||||
"columnsFrom": ["cardId"],
|
||||
"tableTo": "card",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"_card_labels_labelId_label_id_fk": {
|
||||
"name": "_card_labels_labelId_label_id_fk",
|
||||
"tableFrom": "_card_labels",
|
||||
"columnsFrom": [
|
||||
"labelId"
|
||||
],
|
||||
"columnsFrom": ["labelId"],
|
||||
"tableTo": "label",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
@@ -1072,10 +973,7 @@
|
||||
"compositePrimaryKeys": {
|
||||
"_card_labels_cardId_labelId_pk": {
|
||||
"name": "_card_labels_cardId_labelId_pk",
|
||||
"columns": [
|
||||
"cardId",
|
||||
"labelId"
|
||||
]
|
||||
"columns": ["cardId", "labelId"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
@@ -1148,39 +1046,27 @@
|
||||
"card_comments_cardId_card_id_fk": {
|
||||
"name": "card_comments_cardId_card_id_fk",
|
||||
"tableFrom": "card_comments",
|
||||
"columnsFrom": [
|
||||
"cardId"
|
||||
],
|
||||
"columnsFrom": ["cardId"],
|
||||
"tableTo": "card",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"card_comments_createdBy_user_id_fk": {
|
||||
"name": "card_comments_createdBy_user_id_fk",
|
||||
"tableFrom": "card_comments",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"card_comments_deletedBy_user_id_fk": {
|
||||
"name": "card_comments_deletedBy_user_id_fk",
|
||||
"tableFrom": "card_comments",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1189,9 +1075,7 @@
|
||||
"uniqueConstraints": {
|
||||
"card_comments_publicId_unique": {
|
||||
"name": "card_comments_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1253,13 +1137,9 @@
|
||||
"feedback_createdBy_user_id_fk": {
|
||||
"name": "feedback_createdBy_user_id_fk",
|
||||
"tableFrom": "feedback",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1319,13 +1199,9 @@
|
||||
"import_createdBy_user_id_fk": {
|
||||
"name": "import_createdBy_user_id_fk",
|
||||
"tableFrom": "import",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1334,9 +1210,7 @@
|
||||
"uniqueConstraints": {
|
||||
"import_publicId_unique": {
|
||||
"name": "import_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1421,52 +1295,36 @@
|
||||
"label_createdBy_user_id_fk": {
|
||||
"name": "label_createdBy_user_id_fk",
|
||||
"tableFrom": "label",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"label_boardId_board_id_fk": {
|
||||
"name": "label_boardId_board_id_fk",
|
||||
"tableFrom": "label",
|
||||
"columnsFrom": [
|
||||
"boardId"
|
||||
],
|
||||
"columnsFrom": ["boardId"],
|
||||
"tableTo": "board",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"label_importId_import_id_fk": {
|
||||
"name": "label_importId_import_id_fk",
|
||||
"tableFrom": "label",
|
||||
"columnsFrom": [
|
||||
"importId"
|
||||
],
|
||||
"columnsFrom": ["importId"],
|
||||
"tableTo": "import",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "no action"
|
||||
},
|
||||
"label_deletedBy_user_id_fk": {
|
||||
"name": "label_deletedBy_user_id_fk",
|
||||
"tableFrom": "label",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1475,9 +1333,7 @@
|
||||
"uniqueConstraints": {
|
||||
"label_publicId_unique": {
|
||||
"name": "label_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1562,52 +1418,36 @@
|
||||
"list_createdBy_user_id_fk": {
|
||||
"name": "list_createdBy_user_id_fk",
|
||||
"tableFrom": "list",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"list_deletedBy_user_id_fk": {
|
||||
"name": "list_deletedBy_user_id_fk",
|
||||
"tableFrom": "list",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"list_boardId_board_id_fk": {
|
||||
"name": "list_boardId_board_id_fk",
|
||||
"tableFrom": "list",
|
||||
"columnsFrom": [
|
||||
"boardId"
|
||||
],
|
||||
"columnsFrom": ["boardId"],
|
||||
"tableTo": "board",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"list_importId_import_id_fk": {
|
||||
"name": "list_importId_import_id_fk",
|
||||
"tableFrom": "list",
|
||||
"columnsFrom": [
|
||||
"importId"
|
||||
],
|
||||
"columnsFrom": ["importId"],
|
||||
"tableTo": "import",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "no action"
|
||||
}
|
||||
@@ -1616,9 +1456,7 @@
|
||||
"uniqueConstraints": {
|
||||
"list_publicId_unique": {
|
||||
"name": "list_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1688,9 +1526,7 @@
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"columns": ["email"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1722,9 +1558,7 @@
|
||||
"uniqueConstraints": {
|
||||
"workspace_slugs_slug_unique": {
|
||||
"name": "workspace_slugs_slug_unique",
|
||||
"columns": [
|
||||
"slug"
|
||||
],
|
||||
"columns": ["slug"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1818,39 +1652,27 @@
|
||||
"workspace_members_userId_user_id_fk": {
|
||||
"name": "workspace_members_userId_user_id_fk",
|
||||
"tableFrom": "workspace_members",
|
||||
"columnsFrom": [
|
||||
"userId"
|
||||
],
|
||||
"columnsFrom": ["userId"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"workspace_members_workspaceId_workspace_id_fk": {
|
||||
"name": "workspace_members_workspaceId_workspace_id_fk",
|
||||
"tableFrom": "workspace_members",
|
||||
"columnsFrom": [
|
||||
"workspaceId"
|
||||
],
|
||||
"columnsFrom": ["workspaceId"],
|
||||
"tableTo": "workspace",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"workspace_members_deletedBy_user_id_fk": {
|
||||
"name": "workspace_members_deletedBy_user_id_fk",
|
||||
"tableFrom": "workspace_members",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1859,9 +1681,7 @@
|
||||
"uniqueConstraints": {
|
||||
"workspace_members_publicId_unique": {
|
||||
"name": "workspace_members_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1948,26 +1768,18 @@
|
||||
"workspace_createdBy_user_id_fk": {
|
||||
"name": "workspace_createdBy_user_id_fk",
|
||||
"tableFrom": "workspace",
|
||||
"columnsFrom": [
|
||||
"createdBy"
|
||||
],
|
||||
"columnsFrom": ["createdBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
},
|
||||
"workspace_deletedBy_user_id_fk": {
|
||||
"name": "workspace_deletedBy_user_id_fk",
|
||||
"tableFrom": "workspace",
|
||||
"columnsFrom": [
|
||||
"deletedBy"
|
||||
],
|
||||
"columnsFrom": ["deletedBy"],
|
||||
"tableTo": "user",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": ["id"],
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "set null"
|
||||
}
|
||||
@@ -1976,16 +1788,12 @@
|
||||
"uniqueConstraints": {
|
||||
"workspace_publicId_unique": {
|
||||
"name": "workspace_publicId_unique",
|
||||
"columns": [
|
||||
"publicId"
|
||||
],
|
||||
"columns": ["publicId"],
|
||||
"nullsNotDistinct": false
|
||||
},
|
||||
"workspace_slug_unique": {
|
||||
"name": "workspace_slug_unique",
|
||||
"columns": [
|
||||
"slug"
|
||||
],
|
||||
"columns": ["slug"],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
},
|
||||
@@ -1998,10 +1806,7 @@
|
||||
"public.board_visibility": {
|
||||
"name": "board_visibility",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"private",
|
||||
"public"
|
||||
]
|
||||
"values": ["private", "public"]
|
||||
},
|
||||
"public.card_activity_type": {
|
||||
"name": "card_activity_type",
|
||||
@@ -2025,53 +1830,32 @@
|
||||
"public.source": {
|
||||
"name": "source",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"trello"
|
||||
]
|
||||
"values": ["trello"]
|
||||
},
|
||||
"public.status": {
|
||||
"name": "status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"started",
|
||||
"success",
|
||||
"failed"
|
||||
]
|
||||
"values": ["started", "success", "failed"]
|
||||
},
|
||||
"public.role": {
|
||||
"name": "role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"member",
|
||||
"guest"
|
||||
]
|
||||
"values": ["admin", "member", "guest"]
|
||||
},
|
||||
"public.member_status": {
|
||||
"name": "member_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"invited",
|
||||
"active",
|
||||
"removed"
|
||||
]
|
||||
"values": ["invited", "active", "removed"]
|
||||
},
|
||||
"public.slug_type": {
|
||||
"name": "slug_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"reserved",
|
||||
"premium"
|
||||
]
|
||||
"values": ["reserved", "premium"]
|
||||
},
|
||||
"public.workspace_plan": {
|
||||
"name": "workspace_plan",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"free",
|
||||
"pro",
|
||||
"enterprise"
|
||||
]
|
||||
"values": ["free", "pro", "enterprise"]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
@@ -2084,4 +1868,4 @@
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2415
packages/db/migrations/meta/20250806202106_snapshot.json
Normal file
2415
packages/db/migrations/meta/20250806202106_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2423
packages/db/migrations/meta/20250813141748_snapshot.json
Normal file
2423
packages/db/migrations/meta/20250813141748_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,20 @@
|
||||
"when": 1749585889984,
|
||||
"tag": "20250610200449_ReaddIntegrationTable",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1754511666265,
|
||||
"tag": "20250806202106_glossy_luminals",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1755094668761,
|
||||
"tag": "20250813141748_AddChecklistActivityTypes",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
cards,
|
||||
cardsToLabels,
|
||||
cardToWorkspaceMembers,
|
||||
checklistItems,
|
||||
checklists,
|
||||
labels,
|
||||
lists,
|
||||
workspaceMembers,
|
||||
@@ -164,6 +166,27 @@ export const getByPublicId = async (
|
||||
},
|
||||
},
|
||||
},
|
||||
checklists: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
name: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklists.deletedAt),
|
||||
orderBy: asc(checklists.index),
|
||||
with: {
|
||||
items: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
title: true,
|
||||
completed: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklistItems.deletedAt),
|
||||
orderBy: asc(checklistItems.index),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
where: and(
|
||||
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
|
||||
@@ -280,6 +303,27 @@ export const getBySlug = async (
|
||||
},
|
||||
},
|
||||
},
|
||||
checklists: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
name: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklists.deletedAt),
|
||||
orderBy: asc(checklists.index),
|
||||
with: {
|
||||
items: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
title: true,
|
||||
completed: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklistItems.deletedAt),
|
||||
orderBy: asc(checklistItems.index),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
where: and(
|
||||
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
cards,
|
||||
cardsToLabels,
|
||||
cardToWorkspaceMembers,
|
||||
checklistItems,
|
||||
checklists,
|
||||
labels,
|
||||
lists,
|
||||
workspaceMembers,
|
||||
@@ -300,6 +302,27 @@ export const getWithListAndMembersByPublicId = async (
|
||||
},
|
||||
},
|
||||
},
|
||||
checklists: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
name: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklists.deletedAt),
|
||||
orderBy: asc(checklists.index),
|
||||
with: {
|
||||
items: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
title: true,
|
||||
completed: true,
|
||||
index: true,
|
||||
},
|
||||
where: isNull(checklistItems.deletedAt),
|
||||
orderBy: asc(checklistItems.index),
|
||||
},
|
||||
},
|
||||
},
|
||||
list: {
|
||||
columns: {
|
||||
publicId: true,
|
||||
|
||||
216
packages/db/src/repository/checklist.repo.ts
Normal file
216
packages/db/src/repository/checklist.repo.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { and, desc, eq, isNull } from "drizzle-orm";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import { checklistItems, checklists } from "@kan/db/schema";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
|
||||
export const create = async (
|
||||
db: dbClient,
|
||||
checklistInput: {
|
||||
cardId: number;
|
||||
name: string;
|
||||
createdBy: string;
|
||||
},
|
||||
) => {
|
||||
return db.transaction(async (tx) => {
|
||||
const card = await tx.query.checklists.findFirst({
|
||||
where: and(
|
||||
eq(checklists.cardId, checklistInput.cardId),
|
||||
isNull(checklists.deletedAt),
|
||||
),
|
||||
orderBy: desc(checklists.index),
|
||||
});
|
||||
|
||||
const [result] = await tx
|
||||
.insert(checklists)
|
||||
.values({
|
||||
publicId: generateUID(),
|
||||
name: checklistInput.name,
|
||||
createdBy: checklistInput.createdBy,
|
||||
cardId: checklistInput.cardId,
|
||||
index: card ? card.index + 1 : 0,
|
||||
})
|
||||
.returning({
|
||||
id: checklists.id,
|
||||
publicId: checklists.publicId,
|
||||
name: checklists.name,
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
export const createItem = async (
|
||||
db: dbClient,
|
||||
checklistItemInput: {
|
||||
checklistId: number;
|
||||
title: string;
|
||||
createdBy: string;
|
||||
},
|
||||
) => {
|
||||
return db.transaction(async (tx) => {
|
||||
const lastItem = await tx.query.checklistItems.findFirst({
|
||||
where: and(
|
||||
eq(checklistItems.checklistId, checklistItemInput.checklistId),
|
||||
isNull(checklistItems.deletedAt),
|
||||
),
|
||||
orderBy: desc(checklistItems.index),
|
||||
});
|
||||
|
||||
const [result] = await tx
|
||||
.insert(checklistItems)
|
||||
.values({
|
||||
publicId: generateUID(),
|
||||
title: checklistItemInput.title,
|
||||
createdBy: checklistItemInput.createdBy,
|
||||
checklistId: checklistItemInput.checklistId,
|
||||
index: lastItem ? lastItem.index + 1 : 0,
|
||||
completed: false,
|
||||
})
|
||||
.returning({
|
||||
id: checklistItems.id,
|
||||
publicId: checklistItems.publicId,
|
||||
title: checklistItems.title,
|
||||
completed: checklistItems.completed,
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
export const getChecklistByPublicId = async (
|
||||
db: dbClient,
|
||||
checklistPublicId: string,
|
||||
) => {
|
||||
const checklist = await db.query.checklists.findFirst({
|
||||
where: and(
|
||||
eq(checklists.publicId, checklistPublicId),
|
||||
isNull(checklists.deletedAt),
|
||||
),
|
||||
with: {
|
||||
card: {
|
||||
with: {
|
||||
list: {
|
||||
with: {
|
||||
board: {
|
||||
with: {
|
||||
workspace: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return checklist;
|
||||
};
|
||||
|
||||
export const getChecklistItemByPublicIdWithChecklist = async (
|
||||
db: dbClient,
|
||||
checklistItemPublicId: string,
|
||||
) => {
|
||||
const item = await db.query.checklistItems.findFirst({
|
||||
where: and(
|
||||
eq(checklistItems.publicId, checklistItemPublicId),
|
||||
isNull(checklistItems.deletedAt),
|
||||
),
|
||||
with: {
|
||||
checklist: {
|
||||
with: {
|
||||
card: {
|
||||
with: {
|
||||
list: {
|
||||
with: {
|
||||
board: {
|
||||
with: { workspace: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
export const updateItemById = async (
|
||||
db: dbClient,
|
||||
args: { id: number; title?: string; completed?: boolean },
|
||||
) => {
|
||||
const [result] = await db
|
||||
.update(checklistItems)
|
||||
.set({
|
||||
...(args.title !== undefined ? { title: args.title } : {}),
|
||||
...(args.completed !== undefined ? { completed: args.completed } : {}),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(checklistItems.id, args.id))
|
||||
.returning({
|
||||
publicId: checklistItems.publicId,
|
||||
title: checklistItems.title,
|
||||
completed: checklistItems.completed,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const softDeleteItemById = async (
|
||||
db: dbClient,
|
||||
args: { id: number; deletedAt: Date; deletedBy: string },
|
||||
) => {
|
||||
const [result] = await db
|
||||
.update(checklistItems)
|
||||
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.where(eq(checklistItems.id, args.id))
|
||||
.returning({ id: checklistItems.id });
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const softDeleteAllItemsByChecklistId = async (
|
||||
db: dbClient,
|
||||
args: { checklistId: number; deletedAt: Date; deletedBy: string },
|
||||
) => {
|
||||
const result = await db
|
||||
.update(checklistItems)
|
||||
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.where(
|
||||
and(
|
||||
eq(checklistItems.checklistId, args.checklistId),
|
||||
isNull(checklistItems.deletedAt),
|
||||
),
|
||||
)
|
||||
.returning({ id: checklistItems.id });
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const softDeleteById = async (
|
||||
db: dbClient,
|
||||
args: { id: number; deletedAt: Date; deletedBy: string },
|
||||
) => {
|
||||
const [result] = await db
|
||||
.update(checklists)
|
||||
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.where(eq(checklists.id, args.id))
|
||||
.returning({ id: checklists.id });
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const updateChecklistById = async (
|
||||
db: dbClient,
|
||||
args: { id: number; name: string },
|
||||
) => {
|
||||
const [result] = await db
|
||||
.update(checklists)
|
||||
.set({ name: args.name, updatedAt: new Date() })
|
||||
.where(eq(checklists.id, args.id))
|
||||
.returning({ publicId: checklists.publicId, name: checklists.name });
|
||||
return result;
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { checklists } from "./checklists";
|
||||
import { imports } from "./imports";
|
||||
import { labels } from "./labels";
|
||||
import { lists } from "./lists";
|
||||
@@ -31,6 +32,15 @@ export const activityTypes = [
|
||||
"card.updated.comment.added",
|
||||
"card.updated.comment.updated",
|
||||
"card.updated.comment.deleted",
|
||||
// Checklist activities
|
||||
"card.updated.checklist.added",
|
||||
"card.updated.checklist.renamed",
|
||||
"card.updated.checklist.deleted",
|
||||
"card.updated.checklist.item.added",
|
||||
"card.updated.checklist.item.updated",
|
||||
"card.updated.checklist.item.completed",
|
||||
"card.updated.checklist.item.uncompleted",
|
||||
"card.updated.checklist.item.deleted",
|
||||
"card.archived",
|
||||
] as const;
|
||||
|
||||
@@ -84,6 +94,7 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||
}),
|
||||
comments: many(comments),
|
||||
activities: many(cardActivities),
|
||||
checklists: many(checklists),
|
||||
}));
|
||||
|
||||
export const cardActivities = pgTable("card_activity", {
|
||||
|
||||
90
packages/db/src/schema/checklists.ts
Normal file
90
packages/db/src/schema/checklists.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
boolean,
|
||||
integer,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { cards } from "./cards";
|
||||
import { users } from "./users";
|
||||
|
||||
export const checklists = pgTable("card_checklist", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
index: integer("index").notNull(),
|
||||
cardId: bigint("cardId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => cards.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy").references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
}).enableRLS();
|
||||
|
||||
export const checklistsRelations = relations(checklists, ({ one, many }) => ({
|
||||
card: one(cards, {
|
||||
fields: [checklists.cardId],
|
||||
references: [cards.id],
|
||||
relationName: "checklistsCard",
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [checklists.createdBy],
|
||||
references: [users.id],
|
||||
relationName: "checklistsCreatedByUser",
|
||||
}),
|
||||
deletedBy: one(users, {
|
||||
fields: [checklists.deletedBy],
|
||||
references: [users.id],
|
||||
relationName: "checklistsDeletedByUser",
|
||||
}),
|
||||
items: many(checklistItems),
|
||||
}));
|
||||
|
||||
export const checklistItems = pgTable("card_checklist_item", {
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
title: varchar("title", { length: 500 }).notNull(),
|
||||
completed: boolean("completed").notNull().default(false),
|
||||
index: integer("index").notNull(),
|
||||
checklistId: bigint("checklistId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => checklists.id, { onDelete: "cascade" }),
|
||||
createdBy: uuid("createdBy").references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
deletedAt: timestamp("deletedAt"),
|
||||
deletedBy: uuid("deletedBy").references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
}).enableRLS();
|
||||
|
||||
export const checklistItemsRelations = relations(checklistItems, ({ one }) => ({
|
||||
checklist: one(checklists, {
|
||||
fields: [checklistItems.checklistId],
|
||||
references: [checklists.id],
|
||||
relationName: "checklistItemsChecklist",
|
||||
}),
|
||||
createdBy: one(users, {
|
||||
fields: [checklistItems.createdBy],
|
||||
references: [users.id],
|
||||
relationName: "checklistItemsCreatedByUser",
|
||||
}),
|
||||
deletedBy: one(users, {
|
||||
fields: [checklistItems.deletedBy],
|
||||
references: [users.id],
|
||||
relationName: "checklistItemsDeletedByUser",
|
||||
}),
|
||||
}));
|
||||
@@ -2,6 +2,7 @@ export * from "./auth";
|
||||
export * from "./boards";
|
||||
export * from "./auth";
|
||||
export * from "./cards";
|
||||
export * from "./checklists";
|
||||
export * from "./feedback";
|
||||
export * from "./imports";
|
||||
export * from "./labels";
|
||||
|
||||
Reference in New Issue
Block a user