feat: setup card activity
This commit is contained in:
@@ -3,6 +3,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
|
|
||||||
|
import * as activityRepo from "~/server/db/repository/cardActivity.repo";
|
||||||
import * as boardRepo from "~/server/db/repository/board.repo";
|
import * as boardRepo from "~/server/db/repository/board.repo";
|
||||||
import * as cardRepo from "~/server/db/repository/card.repo";
|
import * as cardRepo from "~/server/db/repository/card.repo";
|
||||||
import * as listRepo from "~/server/db/repository/list.repo";
|
import * as listRepo from "~/server/db/repository/list.repo";
|
||||||
@@ -203,17 +204,39 @@ export const boardRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (listIds.length) {
|
if (listIds.length) {
|
||||||
await listRepo.softDeleteAllByBoardId(ctx.db, {
|
const deletedLists = await listRepo.softDeleteAllByBoardId(ctx.db, {
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
if (!deletedLists?.length) {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to delete lists`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedCards = await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||||
listIds,
|
listIds,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!deletedCards?.length) {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to delete cards`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const activities = deletedCards.map((card) => ({
|
||||||
|
type: "card.archived" as const,
|
||||||
|
createdBy: userId,
|
||||||
|
cardId: card.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await activityRepo.bulkCreate(ctx.db, activities);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
|
|
||||||
import * as cardRepo from "~/server/db/repository/card.repo";
|
import * as cardRepo from "~/server/db/repository/card.repo";
|
||||||
|
import * as cardActivityRepo from "~/server/db/repository/cardActivity.repo";
|
||||||
import * as labelRepo from "~/server/db/repository/label.repo";
|
import * as labelRepo from "~/server/db/repository/label.repo";
|
||||||
import * as listRepo from "~/server/db/repository/list.repo";
|
import * as listRepo from "~/server/db/repository/list.repo";
|
||||||
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
||||||
@@ -82,6 +83,12 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.created",
|
||||||
|
cardId: newCard.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
if (newCardId && input.labelPublicIds.length) {
|
if (newCardId && input.labelPublicIds.length) {
|
||||||
const labels = await labelRepo.getAllByPublicIds(
|
const labels = await labelRepo.getAllByPublicIds(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
@@ -99,7 +106,25 @@ export const cardRouter = createTRPCRouter({
|
|||||||
labelId: label.id,
|
labelId: label.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await cardRepo.bulkCreateCardLabelRelationships(ctx.db, labelsInsert);
|
const cardLabels = await cardRepo.bulkCreateCardLabelRelationships(
|
||||||
|
ctx.db,
|
||||||
|
labelsInsert,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cardLabels?.length)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to create card label relationships`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardActivitesInsert = cardLabels.map((cardLabel) => ({
|
||||||
|
type: "card.updated.label.added" as const,
|
||||||
|
cardId: cardLabel.cardId,
|
||||||
|
labelId: cardLabel.labelId,
|
||||||
|
createdBy: userId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCardId && input.memberPublicIds.length) {
|
if (newCardId && input.memberPublicIds.length) {
|
||||||
@@ -119,10 +144,26 @@ export const cardRouter = createTRPCRouter({
|
|||||||
workspaceMemberId: member.id,
|
workspaceMemberId: member.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
const cardMembers =
|
||||||
ctx.db,
|
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
||||||
membersInsert,
|
ctx.db,
|
||||||
);
|
membersInsert,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cardMembers?.length)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to create card member relationships`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardActivitesInsert = cardMembers.map((cardMember) => ({
|
||||||
|
type: "card.updated.member.added" as const,
|
||||||
|
cardId: cardMember.cardId,
|
||||||
|
workspaceMemberId: cardMember.workspaceMemberId,
|
||||||
|
createdBy: userId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newCard;
|
return newCard;
|
||||||
@@ -177,12 +218,40 @@ export const cardRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingLabel) {
|
if (existingLabel) {
|
||||||
await cardRepo.hardDeleteCardLabelRelationship(ctx.db, cardLabelIds);
|
const deletedCardLabelRelationship =
|
||||||
|
await cardRepo.hardDeleteCardLabelRelationship(ctx.db, cardLabelIds);
|
||||||
|
|
||||||
|
if (!deletedCardLabelRelationship)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to remove label from card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.updated.label.removed" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
labelId: label.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
return { newLabel: false };
|
return { newLabel: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
await cardRepo.createCardLabelRelationship(ctx.db, cardLabelIds);
|
const newCardLabelRelationship =
|
||||||
|
await cardRepo.createCardLabelRelationship(ctx.db, cardLabelIds);
|
||||||
|
|
||||||
|
if (!newCardLabelRelationship)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to add label to card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.updated.label.added" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
labelId: label.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
return { newLabel: true };
|
return { newLabel: true };
|
||||||
}),
|
}),
|
||||||
@@ -238,12 +307,43 @@ export const cardRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingMember) {
|
if (existingMember) {
|
||||||
await cardRepo.hardDeleteCardMemberRelationship(ctx.db, cardMemberIds);
|
const deletedCardMemberRelationship =
|
||||||
|
await cardRepo.hardDeleteCardMemberRelationship(
|
||||||
|
ctx.db,
|
||||||
|
cardMemberIds,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!deletedCardMemberRelationship?.success)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to remove member from card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.updated.member.removed" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
workspaceMemberId: member.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
return { newMember: false };
|
return { newMember: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
await cardRepo.createCardMemberRelationship(ctx.db, cardMemberIds);
|
const newCardMemberRelationship =
|
||||||
|
await cardRepo.createCardMemberRelationship(ctx.db, cardMemberIds);
|
||||||
|
|
||||||
|
if (!newCardMemberRelationship?.success)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to add member to card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.updated.member.added" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
workspaceMemberId: member.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
return { newMember: true };
|
return { newMember: true };
|
||||||
}),
|
}),
|
||||||
@@ -306,6 +406,18 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const existingCard = await cardRepo.getByPublicId(
|
||||||
|
ctx.db,
|
||||||
|
input.cardPublicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existingCard) {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const result = await cardRepo.update(
|
const result = await cardRepo.update(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
{ title: input.title, description: input.description },
|
{ title: input.title, description: input.description },
|
||||||
@@ -318,6 +430,32 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const activities = [];
|
||||||
|
|
||||||
|
if (existingCard.title !== input.title) {
|
||||||
|
activities.push({
|
||||||
|
type: "card.updated.title" as const,
|
||||||
|
cardId: result.id,
|
||||||
|
createdBy: userId,
|
||||||
|
fromTitle: existingCard.title,
|
||||||
|
toTitle: input.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCard.description !== input.description) {
|
||||||
|
activities.push({
|
||||||
|
type: "card.updated.description" as const,
|
||||||
|
cardId: result.id,
|
||||||
|
createdBy: userId,
|
||||||
|
fromDescription: existingCard.description ?? undefined,
|
||||||
|
toDescription: input.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activities.length > 0) {
|
||||||
|
await cardActivityRepo.bulkCreate(ctx.db, activities);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
@@ -359,17 +497,29 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await cardRepo.softDelete(ctx.db, {
|
const deletedCard = await cardRepo.softDelete(ctx.db, {
|
||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!deletedCard)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to delete card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
await cardRepo.shiftIndex(ctx.db, {
|
await cardRepo.shiftIndex(ctx.db, {
|
||||||
listId: card.list.id,
|
listId: card.list.id,
|
||||||
cardIndex: card.index,
|
cardIndex: card.index,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(ctx.db, {
|
||||||
|
type: "card.archived",
|
||||||
|
cardId: card.id,
|
||||||
|
createdBy: userId,
|
||||||
|
});
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
reorder: protectedProcedure
|
reorder: protectedProcedure
|
||||||
@@ -435,7 +585,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
|
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await cardRepo.reorder(ctx.db, {
|
const { success } = await cardRepo.reorder(ctx.db, {
|
||||||
currentListId: currentList.id,
|
currentListId: currentList.id,
|
||||||
newListId: newList.id,
|
newListId: newList.id,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
@@ -443,6 +593,38 @@ export const cardRouter = createTRPCRouter({
|
|||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: !!result.error };
|
if (!success)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to reorder card`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const activities = [];
|
||||||
|
|
||||||
|
if (currentIndex !== newIndex) {
|
||||||
|
activities.push({
|
||||||
|
type: "card.updated.index" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
createdBy: userId,
|
||||||
|
fromIndex: currentIndex,
|
||||||
|
toIndex: newIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentList.id !== newList.id) {
|
||||||
|
activities.push({
|
||||||
|
type: "card.updated.list" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
createdBy: userId,
|
||||||
|
fromListId: currentList.id,
|
||||||
|
toListId: newList.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activities.length > 0) {
|
||||||
|
await cardActivityRepo.bulkCreate(ctx.db, activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success };
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { generateUID } from "~/utils/generateUID";
|
|||||||
|
|
||||||
import * as boardRepo from "~/server/db/repository/board.repo";
|
import * as boardRepo from "~/server/db/repository/board.repo";
|
||||||
import * as cardRepo from "~/server/db/repository/card.repo";
|
import * as cardRepo from "~/server/db/repository/card.repo";
|
||||||
|
import * as cardActivityRepo from "~/server/db/repository/cardActivity.repo";
|
||||||
import * as importRepo from "~/server/db/repository/import.repo";
|
import * as importRepo from "~/server/db/repository/import.repo";
|
||||||
import * as listRepo from "~/server/db/repository/list.repo";
|
import * as listRepo from "~/server/db/repository/list.repo";
|
||||||
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
||||||
@@ -196,9 +197,26 @@ export const importRouter = createTRPCRouter({
|
|||||||
importId: newImportId,
|
importId: newImportId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await cardRepo.bulkCreate(ctx.db, cardsInsert);
|
const createdCards = await cardRepo.bulkCreate(
|
||||||
|
ctx.db,
|
||||||
|
cardsInsert,
|
||||||
|
);
|
||||||
|
|
||||||
await ctx.db.from("card").insert(cardsInsert);
|
if (!createdCards?.length)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: "Failed to create new cards",
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const activities = createdCards.map((card) => ({
|
||||||
|
type: "card.created" as const,
|
||||||
|
cardId: card.id,
|
||||||
|
createdBy: userId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (createdCards.length > 0) {
|
||||||
|
await cardActivityRepo.bulkCreate(ctx.db, activities);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listIndex++;
|
listIndex++;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
|
|
||||||
|
import * as activityRepo from "~/server/db/repository/cardActivity.repo";
|
||||||
import * as cardRepo from "~/server/db/repository/card.repo";
|
import * as cardRepo from "~/server/db/repository/card.repo";
|
||||||
import * as boardRepo from "~/server/db/repository/board.repo";
|
import * as boardRepo from "~/server/db/repository/board.repo";
|
||||||
import * as listRepo from "~/server/db/repository/list.repo";
|
import * as listRepo from "~/server/db/repository/list.repo";
|
||||||
@@ -142,18 +143,38 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await listRepo.softDeleteById(ctx.db, {
|
const deletedList = await listRepo.softDeleteById(ctx.db, {
|
||||||
listId: list.id,
|
listId: list.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
if (!deletedList)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to delete list`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const deletedCards = await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||||
listIds: [list.id],
|
listIds: [list.id],
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!deletedCards?.length)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Failed to delete cards`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
|
const activities = deletedCards.map((card) => ({
|
||||||
|
type: "card.archived" as const,
|
||||||
|
createdBy: userId,
|
||||||
|
cardId: card.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await activityRepo.bulkCreate(ctx.db, activities);
|
||||||
|
|
||||||
await listRepo.shiftIndex(ctx.db, {
|
await listRepo.shiftIndex(ctx.db, {
|
||||||
boardId: list.boardId,
|
boardId: list.boardId,
|
||||||
listIndex: list.id,
|
listIndex: list.id,
|
||||||
|
|||||||
55
src/server/db/migrations/0005_blue_marvex.sql
Normal file
55
src/server/db/migrations/0005_blue_marvex.sql
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "card_activity_type" AS ENUM('card.created', 'card.updated.title', 'card.updated.description', 'card.updated.index', 'card.updated.list', 'card.updated.label.added', 'card.updated.label.removed', 'card.updated.member.added', 'card.updated.member.removed', 'card.archived');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "card_activity" (
|
||||||
|
"id" bigserial PRIMARY KEY NOT NULL,
|
||||||
|
"publicId" varchar(12) NOT NULL,
|
||||||
|
"type" "card_activity_type" NOT NULL,
|
||||||
|
"cardId" bigint NOT NULL,
|
||||||
|
"fromIndex" integer,
|
||||||
|
"toIndex" integer,
|
||||||
|
"fromListId" bigint,
|
||||||
|
"toListId" bigint,
|
||||||
|
"labelId" bigint,
|
||||||
|
"workspaceMemberId" bigint,
|
||||||
|
"fromTitle" varchar(255),
|
||||||
|
"toTitle" varchar(255),
|
||||||
|
"fromDescription" text,
|
||||||
|
"toDescription" text,
|
||||||
|
"createdBy" uuid NOT NULL,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "card_activity_publicId_unique" UNIQUE("publicId")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card_activity" ADD CONSTRAINT "card_activity_cardId_card_id_fk" FOREIGN KEY ("cardId") REFERENCES "card"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card_activity" ADD CONSTRAINT "card_activity_toListId_list_id_fk" FOREIGN KEY ("toListId") REFERENCES "list"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card_activity" ADD CONSTRAINT "card_activity_labelId_label_id_fk" FOREIGN KEY ("labelId") REFERENCES "label"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card_activity" ADD CONSTRAINT "card_activity_workspaceMemberId_workspace_members_id_fk" FOREIGN KEY ("workspaceMemberId") REFERENCES "workspace_members"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card_activity" ADD CONSTRAINT "card_activity_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
1233
src/server/db/migrations/meta/0005_snapshot.json
Normal file
1233
src/server/db/migrations/meta/0005_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,13 @@
|
|||||||
"when": 1730205607613,
|
"when": 1730205607613,
|
||||||
"tag": "0004_rainy_archangel",
|
"tag": "0004_rainy_archangel",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1730813108528,
|
||||||
|
"tag": "0005_blue_marvex",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,12 @@ export const bulkCreateCardLabelRelationships = async (
|
|||||||
labelId: number;
|
labelId: number;
|
||||||
}[],
|
}[],
|
||||||
) => {
|
) => {
|
||||||
const result = db.from("_card_labels").insert(cardLabelRelationshipInput);
|
const { data } = await db
|
||||||
|
.from("_card_labels")
|
||||||
|
.insert(cardLabelRelationshipInput)
|
||||||
|
.select();
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bulkCreateCardWorkspaceMemberRelationships = async (
|
export const bulkCreateCardWorkspaceMemberRelationships = async (
|
||||||
@@ -48,11 +51,12 @@ export const bulkCreateCardWorkspaceMemberRelationships = async (
|
|||||||
workspaceMemberId: number;
|
workspaceMemberId: number;
|
||||||
}[],
|
}[],
|
||||||
) => {
|
) => {
|
||||||
const result = db
|
const { data } = await db
|
||||||
.from("_card_workspace_members")
|
.from("_card_workspace_members")
|
||||||
.insert(cardWorkspaceMemberRelationshipInput);
|
.insert(cardWorkspaceMemberRelationshipInput)
|
||||||
|
.select();
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const update = async (
|
export const update = async (
|
||||||
@@ -69,7 +73,11 @@ export const update = async (
|
|||||||
.from("card")
|
.from("card")
|
||||||
.update({ title: cardInput.title, description: cardInput.description })
|
.update({ title: cardInput.title, description: cardInput.description })
|
||||||
.eq("publicId", args.cardPublicId)
|
.eq("publicId", args.cardPublicId)
|
||||||
.is("deletedAt", null);
|
.is("deletedAt", null)
|
||||||
|
.select(`id, publicId, title, description`)
|
||||||
|
.order("id", { ascending: true })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -95,7 +103,7 @@ export const getByPublicId = async (
|
|||||||
) => {
|
) => {
|
||||||
const { data } = await db
|
const { data } = await db
|
||||||
.from("card")
|
.from("card")
|
||||||
.select(`id`)
|
.select(`id, publicId, title, description`)
|
||||||
.eq("publicId", cardPublicId)
|
.eq("publicId", cardPublicId)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.single();
|
.single();
|
||||||
@@ -130,7 +138,7 @@ export const bulkCreate = async (
|
|||||||
importId?: number;
|
importId?: number;
|
||||||
}[],
|
}[],
|
||||||
) => {
|
) => {
|
||||||
const data = await db.from("card").insert(cardInput);
|
const { data } = await db.from("card").insert(cardInput).select(`id`);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -139,10 +147,15 @@ export const createCardLabelRelationship = async (
|
|||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
||||||
) => {
|
) => {
|
||||||
const { data } = await db.from("_card_labels").insert({
|
const { data } = await db
|
||||||
cardId: cardLabelRelationshipInput.cardId,
|
.from("_card_labels")
|
||||||
labelId: cardLabelRelationshipInput.labelId,
|
.insert({
|
||||||
});
|
cardId: cardLabelRelationshipInput.cardId,
|
||||||
|
labelId: cardLabelRelationshipInput.labelId,
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -166,12 +179,12 @@ export const createCardMemberRelationship = async (
|
|||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
cardMemberRelationshipInput: { cardId: number; memberId: number },
|
cardMemberRelationshipInput: { cardId: number; memberId: number },
|
||||||
) => {
|
) => {
|
||||||
const { data } = await db.from("_card_workspace_members").insert({
|
const { error } = await db.from("_card_workspace_members").insert({
|
||||||
cardId: cardMemberRelationshipInput.cardId,
|
cardId: cardMemberRelationshipInput.cardId,
|
||||||
workspaceMemberId: cardMemberRelationshipInput.memberId,
|
workspaceMemberId: cardMemberRelationshipInput.memberId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return { success: !error };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWithListAndMembersByPublicId = async (
|
export const getWithListAndMembersByPublicId = async (
|
||||||
@@ -247,7 +260,7 @@ export const reorder = async (
|
|||||||
cardId: number;
|
cardId: number;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const result = await db.rpc("reorder_cards", {
|
const { error } = await db.rpc("reorder_cards", {
|
||||||
current_list_id: args.currentListId,
|
current_list_id: args.currentListId,
|
||||||
new_list_id: args.newListId,
|
new_list_id: args.newListId,
|
||||||
current_index: args.currentIndex,
|
current_index: args.currentIndex,
|
||||||
@@ -255,7 +268,7 @@ export const reorder = async (
|
|||||||
card_id: args.cardId,
|
card_id: args.cardId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return { success: !error };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const shiftIndex = async (
|
export const shiftIndex = async (
|
||||||
@@ -296,12 +309,16 @@ export const softDelete = async (
|
|||||||
deletedBy: string;
|
deletedBy: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { data } = await db
|
||||||
.from("card")
|
.from("card")
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
.eq("id", args.cardId);
|
.eq("id", args.cardId)
|
||||||
|
.select(`id`)
|
||||||
|
.order("id", { ascending: true })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const softDeleteAllByListIds = async (
|
export const softDeleteAllByListIds = async (
|
||||||
@@ -312,39 +329,46 @@ export const softDeleteAllByListIds = async (
|
|||||||
deletedBy: string;
|
deletedBy: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { data } = await db
|
||||||
.from("card")
|
.from("card")
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
.in("listId", args.listIds)
|
.in("listId", args.listIds)
|
||||||
.is("deletedAt", null);
|
.is("deletedAt", null)
|
||||||
|
.select(`id`);
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hardDeleteCardMemberRelationship = async (
|
export const hardDeleteCardMemberRelationship = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
args: { cardId: number; memberId: number },
|
args: { cardId: number; memberId: number },
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { error } = await db
|
||||||
.from("_card_workspace_members")
|
.from("_card_workspace_members")
|
||||||
.delete()
|
.delete()
|
||||||
.eq("cardId", args.cardId)
|
.eq("cardId", args.cardId)
|
||||||
.eq("workspaceMemberId", args.memberId);
|
.eq("workspaceMemberId", args.memberId)
|
||||||
|
.select()
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return result;
|
return { success: !error };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hardDeleteCardLabelRelationship = async (
|
export const hardDeleteCardLabelRelationship = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
args: { cardId: number; labelId: number },
|
args: { cardId: number; labelId: number },
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { data } = await db
|
||||||
.from("_card_labels")
|
.from("_card_labels")
|
||||||
.delete()
|
.delete()
|
||||||
.eq("cardId", args.cardId)
|
.eq("cardId", args.cardId)
|
||||||
.eq("labelId", args.labelId);
|
.eq("labelId", args.labelId)
|
||||||
|
.select()
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return result;
|
return { data };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hardDeleteAllCardLabelRelationships = async (
|
export const hardDeleteAllCardLabelRelationships = async (
|
||||||
|
|||||||
77
src/server/db/repository/cardActivity.repo.ts
Normal file
77
src/server/db/repository/cardActivity.repo.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { generateUID } from "~/utils/generateUID";
|
||||||
|
import { type Database } from "~/types/database.types";
|
||||||
|
import { type SupabaseClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
export const create = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
activityInput: {
|
||||||
|
type: Database["public"]["Enums"]["card_activity_type"];
|
||||||
|
cardId: number;
|
||||||
|
fromIndex?: number;
|
||||||
|
toIndex?: number;
|
||||||
|
fromListId?: number;
|
||||||
|
toListId?: number;
|
||||||
|
labelId?: number;
|
||||||
|
workspaceMemberId?: number;
|
||||||
|
fromTitle?: string;
|
||||||
|
toTitle?: string;
|
||||||
|
fromDescription?: string;
|
||||||
|
toDescription?: string;
|
||||||
|
createdBy: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { data } = await db
|
||||||
|
.from("card_activity")
|
||||||
|
.insert({
|
||||||
|
publicId: generateUID(),
|
||||||
|
type: activityInput.type,
|
||||||
|
cardId: activityInput.cardId,
|
||||||
|
fromListId: activityInput.fromListId,
|
||||||
|
toListId: activityInput.toListId,
|
||||||
|
fromIndex: activityInput.fromIndex,
|
||||||
|
toIndex: activityInput.toIndex,
|
||||||
|
labelId: activityInput.labelId,
|
||||||
|
workspaceMemberId: activityInput.workspaceMemberId,
|
||||||
|
fromTitle: activityInput.fromTitle,
|
||||||
|
toTitle: activityInput.toTitle,
|
||||||
|
fromDescription: activityInput.fromDescription,
|
||||||
|
toDescription: activityInput.toDescription,
|
||||||
|
createdBy: activityInput.createdBy,
|
||||||
|
})
|
||||||
|
.select(`id`)
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bulkCreate = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
activityInputs: {
|
||||||
|
type: Database["public"]["Enums"]["card_activity_type"];
|
||||||
|
cardId: number;
|
||||||
|
fromIndex?: number;
|
||||||
|
toIndex?: number;
|
||||||
|
fromListId?: number;
|
||||||
|
toListId?: number;
|
||||||
|
labelId?: number;
|
||||||
|
workspaceMemberId?: number;
|
||||||
|
fromTitle?: string;
|
||||||
|
toTitle?: string;
|
||||||
|
fromDescription?: string;
|
||||||
|
toDescription?: string;
|
||||||
|
createdBy: string;
|
||||||
|
}[],
|
||||||
|
) => {
|
||||||
|
const activitiesWithPublicIds = activityInputs.map((activity) => ({
|
||||||
|
...activity,
|
||||||
|
publicId: generateUID(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { data } = await db
|
||||||
|
.from("card_activity")
|
||||||
|
.insert(activitiesWithPublicIds)
|
||||||
|
.select("id");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -127,13 +127,15 @@ export const softDeleteAllByBoardId = async (
|
|||||||
deletedBy: string;
|
deletedBy: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { data } = await db
|
||||||
.from("list")
|
.from("list")
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
.eq("boardId", args.boardId)
|
.eq("boardId", args.boardId)
|
||||||
.is("deletedAt", null);
|
.is("deletedAt", null)
|
||||||
|
.select(`id`)
|
||||||
|
.order("id", { ascending: true });
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const softDeleteById = async (
|
export const softDeleteById = async (
|
||||||
@@ -144,11 +146,15 @@ export const softDeleteById = async (
|
|||||||
deletedBy: string;
|
deletedBy: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const result = await db
|
const { data } = await db
|
||||||
.from("list")
|
.from("list")
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
.eq("id", args.listId)
|
.eq("id", args.listId)
|
||||||
.is("deletedAt", null);
|
.is("deletedAt", null)
|
||||||
|
.select(`id`)
|
||||||
|
.order("id", { ascending: true })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
return result;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ export const memberStatusEnum = pgEnum("member_status", [
|
|||||||
"active",
|
"active",
|
||||||
"removed",
|
"removed",
|
||||||
]);
|
]);
|
||||||
|
export const activityTypeEnum = pgEnum("card_activity_type", [
|
||||||
|
"card.created",
|
||||||
|
"card.updated.title",
|
||||||
|
"card.updated.description",
|
||||||
|
"card.updated.index",
|
||||||
|
"card.updated.list",
|
||||||
|
"card.updated.label.added",
|
||||||
|
"card.updated.label.removed",
|
||||||
|
"card.updated.member.added",
|
||||||
|
"card.updated.member.removed",
|
||||||
|
"card.archived",
|
||||||
|
]);
|
||||||
|
|
||||||
export const boards = pgTable("board", {
|
export const boards = pgTable("board", {
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
@@ -324,3 +336,55 @@ export const usersToWorkspacesRelations = relations(
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const cardActivities = pgTable("card_activity", {
|
||||||
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
|
type: activityTypeEnum("type").notNull(),
|
||||||
|
cardId: bigint("cardId", { mode: "number" })
|
||||||
|
.notNull()
|
||||||
|
.references(() => cards.id, { onDelete: "cascade" }),
|
||||||
|
fromIndex: integer("fromIndex"),
|
||||||
|
toIndex: integer("toIndex"),
|
||||||
|
fromListId: bigint("fromListId", { mode: "number" }),
|
||||||
|
toListId: bigint("toListId", { mode: "number" }).references(() => lists.id),
|
||||||
|
labelId: bigint("labelId", { mode: "number" }).references(() => labels.id),
|
||||||
|
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }).references(
|
||||||
|
() => workspaceMembers.id,
|
||||||
|
),
|
||||||
|
fromTitle: varchar("fromTitle", { length: 255 }),
|
||||||
|
toTitle: varchar("toTitle", { length: 255 }),
|
||||||
|
fromDescription: text("fromDescription"),
|
||||||
|
toDescription: text("toDescription"),
|
||||||
|
createdBy: uuid("createdBy")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
|
||||||
|
card: one(cards, {
|
||||||
|
fields: [cardActivities.cardId],
|
||||||
|
references: [cards.id],
|
||||||
|
}),
|
||||||
|
fromList: one(lists, {
|
||||||
|
fields: [cardActivities.fromListId],
|
||||||
|
references: [lists.id],
|
||||||
|
}),
|
||||||
|
toList: one(lists, {
|
||||||
|
fields: [cardActivities.toListId],
|
||||||
|
references: [lists.id],
|
||||||
|
}),
|
||||||
|
label: one(labels, {
|
||||||
|
fields: [cardActivities.labelId],
|
||||||
|
references: [labels.id],
|
||||||
|
}),
|
||||||
|
workspaceMember: one(workspaceMembers, {
|
||||||
|
fields: [cardActivities.workspaceMemberId],
|
||||||
|
references: [workspaceMembers.id],
|
||||||
|
}),
|
||||||
|
createdBy: one(users, {
|
||||||
|
fields: [cardActivities.createdBy],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user