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 * as activityRepo from "~/server/db/repository/cardActivity.repo";
|
||||
import * as boardRepo from "~/server/db/repository/board.repo";
|
||||
import * as cardRepo from "~/server/db/repository/card.repo";
|
||||
import * as listRepo from "~/server/db/repository/list.repo";
|
||||
@@ -203,17 +204,39 @@ export const boardRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
if (listIds.length) {
|
||||
await listRepo.softDeleteAllByBoardId(ctx.db, {
|
||||
const deletedLists = await listRepo.softDeleteAllByBoardId(ctx.db, {
|
||||
boardId: board.id,
|
||||
deletedAt,
|
||||
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,
|
||||
deletedAt,
|
||||
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 };
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TRPCError } from "@trpc/server";
|
||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||
|
||||
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 listRepo from "~/server/db/repository/list.repo";
|
||||
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
||||
@@ -82,6 +83,12 @@ export const cardRouter = createTRPCRouter({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.created",
|
||||
cardId: newCard.id,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
if (newCardId && input.labelPublicIds.length) {
|
||||
const labels = await labelRepo.getAllByPublicIds(
|
||||
ctx.db,
|
||||
@@ -99,7 +106,25 @@ export const cardRouter = createTRPCRouter({
|
||||
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) {
|
||||
@@ -119,10 +144,26 @@ export const cardRouter = createTRPCRouter({
|
||||
workspaceMemberId: member.id,
|
||||
}));
|
||||
|
||||
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
||||
ctx.db,
|
||||
membersInsert,
|
||||
);
|
||||
const cardMembers =
|
||||
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
||||
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;
|
||||
@@ -177,12 +218,40 @@ export const cardRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
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 };
|
||||
}),
|
||||
@@ -238,12 +307,43 @@ export const cardRouter = createTRPCRouter({
|
||||
);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
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 };
|
||||
}),
|
||||
@@ -306,6 +406,18 @@ export const cardRouter = createTRPCRouter({
|
||||
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(
|
||||
ctx.db,
|
||||
{ title: input.title, description: input.description },
|
||||
@@ -318,6 +430,32 @@ export const cardRouter = createTRPCRouter({
|
||||
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;
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
@@ -359,17 +497,29 @@ export const cardRouter = createTRPCRouter({
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
|
||||
await cardRepo.softDelete(ctx.db, {
|
||||
const deletedCard = await cardRepo.softDelete(ctx.db, {
|
||||
cardId: card.id,
|
||||
deletedAt,
|
||||
deletedBy: userId,
|
||||
});
|
||||
|
||||
if (!deletedCard)
|
||||
throw new TRPCError({
|
||||
message: `Failed to delete card`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
|
||||
await cardRepo.shiftIndex(ctx.db, {
|
||||
listId: card.list.id,
|
||||
cardIndex: card.index,
|
||||
});
|
||||
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.archived",
|
||||
cardId: card.id,
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
reorder: protectedProcedure
|
||||
@@ -435,7 +585,7 @@ export const cardRouter = createTRPCRouter({
|
||||
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
|
||||
}
|
||||
|
||||
const result = await cardRepo.reorder(ctx.db, {
|
||||
const { success } = await cardRepo.reorder(ctx.db, {
|
||||
currentListId: currentList.id,
|
||||
newListId: newList.id,
|
||||
currentIndex,
|
||||
@@ -443,6 +593,38 @@ export const cardRouter = createTRPCRouter({
|
||||
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 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 listRepo from "~/server/db/repository/list.repo";
|
||||
import * as workspaceRepo from "~/server/db/repository/workspace.repo";
|
||||
@@ -196,9 +197,26 @@ export const importRouter = createTRPCRouter({
|
||||
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++;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TRPCError } from "@trpc/server";
|
||||
|
||||
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 boardRepo from "~/server/db/repository/board.repo";
|
||||
import * as listRepo from "~/server/db/repository/list.repo";
|
||||
@@ -142,18 +143,38 @@ export const listRouter = createTRPCRouter({
|
||||
|
||||
const deletedAt = new Date().toISOString();
|
||||
|
||||
await listRepo.softDeleteById(ctx.db, {
|
||||
const deletedList = await listRepo.softDeleteById(ctx.db, {
|
||||
listId: list.id,
|
||||
deletedAt,
|
||||
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],
|
||||
deletedAt,
|
||||
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, {
|
||||
boardId: list.boardId,
|
||||
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,
|
||||
"tag": "0004_rainy_archangel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "5",
|
||||
"when": 1730813108528,
|
||||
"tag": "0005_blue_marvex",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -36,9 +36,12 @@ export const bulkCreateCardLabelRelationships = async (
|
||||
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 (
|
||||
@@ -48,11 +51,12 @@ export const bulkCreateCardWorkspaceMemberRelationships = async (
|
||||
workspaceMemberId: number;
|
||||
}[],
|
||||
) => {
|
||||
const result = db
|
||||
const { data } = await db
|
||||
.from("_card_workspace_members")
|
||||
.insert(cardWorkspaceMemberRelationshipInput);
|
||||
.insert(cardWorkspaceMemberRelationshipInput)
|
||||
.select();
|
||||
|
||||
return result;
|
||||
return data;
|
||||
};
|
||||
|
||||
export const update = async (
|
||||
@@ -69,7 +73,11 @@ export const update = async (
|
||||
.from("card")
|
||||
.update({ title: cardInput.title, description: cardInput.description })
|
||||
.eq("publicId", args.cardPublicId)
|
||||
.is("deletedAt", null);
|
||||
.is("deletedAt", null)
|
||||
.select(`id, publicId, title, description`)
|
||||
.order("id", { ascending: true })
|
||||
.limit(1)
|
||||
.single();
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -95,7 +103,7 @@ export const getByPublicId = async (
|
||||
) => {
|
||||
const { data } = await db
|
||||
.from("card")
|
||||
.select(`id`)
|
||||
.select(`id, publicId, title, description`)
|
||||
.eq("publicId", cardPublicId)
|
||||
.limit(1)
|
||||
.single();
|
||||
@@ -130,7 +138,7 @@ export const bulkCreate = async (
|
||||
importId?: number;
|
||||
}[],
|
||||
) => {
|
||||
const data = await db.from("card").insert(cardInput);
|
||||
const { data } = await db.from("card").insert(cardInput).select(`id`);
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -139,10 +147,15 @@ export const createCardLabelRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
||||
) => {
|
||||
const { data } = await db.from("_card_labels").insert({
|
||||
cardId: cardLabelRelationshipInput.cardId,
|
||||
labelId: cardLabelRelationshipInput.labelId,
|
||||
});
|
||||
const { data } = await db
|
||||
.from("_card_labels")
|
||||
.insert({
|
||||
cardId: cardLabelRelationshipInput.cardId,
|
||||
labelId: cardLabelRelationshipInput.labelId,
|
||||
})
|
||||
.select()
|
||||
.limit(1)
|
||||
.single();
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -166,12 +179,12 @@ export const createCardMemberRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
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,
|
||||
workspaceMemberId: cardMemberRelationshipInput.memberId,
|
||||
});
|
||||
|
||||
return data;
|
||||
return { success: !error };
|
||||
};
|
||||
|
||||
export const getWithListAndMembersByPublicId = async (
|
||||
@@ -247,7 +260,7 @@ export const reorder = async (
|
||||
cardId: number;
|
||||
},
|
||||
) => {
|
||||
const result = await db.rpc("reorder_cards", {
|
||||
const { error } = await db.rpc("reorder_cards", {
|
||||
current_list_id: args.currentListId,
|
||||
new_list_id: args.newListId,
|
||||
current_index: args.currentIndex,
|
||||
@@ -255,7 +268,7 @@ export const reorder = async (
|
||||
card_id: args.cardId,
|
||||
});
|
||||
|
||||
return result;
|
||||
return { success: !error };
|
||||
};
|
||||
|
||||
export const shiftIndex = async (
|
||||
@@ -296,12 +309,16 @@ export const softDelete = async (
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
const { data } = await db
|
||||
.from("card")
|
||||
.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 (
|
||||
@@ -312,39 +329,46 @@ export const softDeleteAllByListIds = async (
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
const { data } = await db
|
||||
.from("card")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.in("listId", args.listIds)
|
||||
.is("deletedAt", null);
|
||||
.is("deletedAt", null)
|
||||
.select(`id`);
|
||||
|
||||
return result;
|
||||
return data;
|
||||
};
|
||||
|
||||
export const hardDeleteCardMemberRelationship = async (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; memberId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
const { error } = await db
|
||||
.from("_card_workspace_members")
|
||||
.delete()
|
||||
.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 (
|
||||
db: SupabaseClient<Database>,
|
||||
args: { cardId: number; labelId: number },
|
||||
) => {
|
||||
const result = await db
|
||||
const { data } = await db
|
||||
.from("_card_labels")
|
||||
.delete()
|
||||
.eq("cardId", args.cardId)
|
||||
.eq("labelId", args.labelId);
|
||||
.eq("labelId", args.labelId)
|
||||
.select()
|
||||
.limit(1)
|
||||
.single();
|
||||
|
||||
return result;
|
||||
return { data };
|
||||
};
|
||||
|
||||
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;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
const { data } = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.eq("boardId", args.boardId)
|
||||
.is("deletedAt", null);
|
||||
.is("deletedAt", null)
|
||||
.select(`id`)
|
||||
.order("id", { ascending: true });
|
||||
|
||||
return result;
|
||||
return data;
|
||||
};
|
||||
|
||||
export const softDeleteById = async (
|
||||
@@ -144,11 +146,15 @@ export const softDeleteById = async (
|
||||
deletedBy: string;
|
||||
},
|
||||
) => {
|
||||
const result = await db
|
||||
const { data } = await db
|
||||
.from("list")
|
||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||
.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",
|
||||
"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", {
|
||||
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