refactor: update card repo to use drizzle

This commit is contained in:
Henry
2025-04-24 22:36:29 +01:00
parent 520a376b0f
commit 0c3ab8b890
2 changed files with 369 additions and 270 deletions

View File

@@ -1,9 +1,16 @@
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import { and, eq, isNull, sql } from "drizzle-orm"; import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
import type { dbClient } from "@kan/db/client"; import type { dbClient } from "@kan/db/client";
import type { Database } from "@kan/db/types/database.types"; import type { Database } from "@kan/db/types/database.types";
import * as schema from "@kan/db/schema"; import {
cardActivities,
cards,
cardsToLabels,
cardToWorkspaceMembers,
lists,
workspaceMembers,
} from "@kan/db/schema";
import { generateUID } from "@kan/shared/utils"; import { generateUID } from "@kan/shared/utils";
export const create = async ( export const create = async (
@@ -23,9 +30,9 @@ export const create = async (
id: true, id: true,
}, },
where: and( where: and(
eq(schema.cards.listId, cardInput.listId), eq(cards.listId, cardInput.listId),
eq(schema.cards.index, cardInput.index), eq(cards.index, cardInput.index),
isNull(schema.cards.deletedAt), isNull(cards.deletedAt),
), ),
}); });
@@ -44,7 +51,7 @@ export const create = async (
} }
const result = await tx const result = await tx
.insert(schema.cards) .insert(cards)
.values({ .values({
publicId: generateUID(), publicId: generateUID(),
title: cardInput.title, title: cardInput.title,
@@ -53,11 +60,11 @@ export const create = async (
listId: cardInput.listId, listId: cardInput.listId,
index: cardInput.index, index: cardInput.index,
}) })
.returning({ id: schema.cards.id }); .returning({ id: cards.id });
if (!result[0]) return tx.rollback(); if (!result[0]) return tx.rollback();
await tx.insert(schema.cardActivities).values({ await tx.insert(cardActivities).values({
publicId: generateUID(), publicId: generateUID(),
cardId: result[0].id, cardId: result[0].id,
type: "card.created", type: "card.created",
@@ -69,37 +76,37 @@ export const create = async (
}; };
export const bulkCreateCardLabelRelationships = async ( export const bulkCreateCardLabelRelationships = async (
db: SupabaseClient<Database>, db: dbClient,
cardLabelRelationshipInput: { cardLabelRelationshipInput: {
cardId: number; cardId: number;
labelId: number; labelId: number;
}[], }[],
) => { ) => {
const { data } = await db const [result] = await db
.from("_card_labels") .insert(cardsToLabels)
.insert(cardLabelRelationshipInput) .values(cardLabelRelationshipInput)
.select(); .returning();
return data; return result;
}; };
export const bulkCreateCardWorkspaceMemberRelationships = async ( export const bulkCreateCardWorkspaceMemberRelationships = async (
db: SupabaseClient<Database>, db: dbClient,
cardWorkspaceMemberRelationshipInput: { cardWorkspaceMemberRelationshipInput: {
cardId: number; cardId: number;
workspaceMemberId: number; workspaceMemberId: number;
}[], }[],
) => { ) => {
const { data } = await db const [result] = await db
.from("_card_workspace_members") .insert(cardToWorkspaceMembers)
.insert(cardWorkspaceMemberRelationshipInput) .values(cardWorkspaceMemberRelationshipInput)
.select(); .returning();
return data; return result;
}; };
export const update = async ( export const update = async (
db: SupabaseClient<Database>, db: dbClient,
cardInput: { cardInput: {
title: string; title: string;
description: string; description: string;
@@ -108,65 +115,70 @@ export const update = async (
cardPublicId: string; cardPublicId: string;
}, },
) => { ) => {
const { data } = await db const [result] = await db
.from("card") .update(cards)
.update({ title: cardInput.title, description: cardInput.description }) .set({
.eq("publicId", args.cardPublicId) title: cardInput.title,
.is("deletedAt", null) description: cardInput.description,
.select(`id, publicId, title, description`) })
.order("id", { ascending: true }) .where(and(eq(cards.publicId, args.cardPublicId), isNull(cards.deletedAt)))
.limit(1) .returning({
.single(); id: cards.id,
publicId: cards.publicId,
title: cards.title,
description: cards.description,
});
return data; return result;
}; };
export const getCardWithListByPublicId = async ( export const getCardWithListByPublicId = (
db: SupabaseClient<Database>, db: dbClient,
cardPublicId: string, cardPublicId: string,
) => { ) => {
const { data } = await db return db.query.cards.findFirst({
.from("card") columns: {
.select(`id, index, list (id, boardId)`) id: true,
.eq("publicId", cardPublicId) index: true,
.is("deletedAt", null) },
.limit(1) with: {
.single(); list: {
columns: {
return data; id: true,
boardId: true,
},
},
},
where: and(eq(cards.publicId, cardPublicId), isNull(cards.deletedAt)),
});
}; };
export const getByPublicId = async ( export const getByPublicId = (db: dbClient, cardPublicId: string) => {
db: SupabaseClient<Database>, return db.query.cards.findFirst({
cardPublicId: string, columns: {
) => { id: true,
const { data } = await db publicId: true,
.from("card") title: true,
.select(`id, publicId, title, description`) description: true,
.eq("publicId", cardPublicId) },
.limit(1) where: eq(cards.publicId, cardPublicId),
.single(); });
return data;
}; };
export const getCardLabelRelationship = async ( export const getCardLabelRelationship = async (
db: SupabaseClient<Database>, db: dbClient,
args: { cardId: number; labelId: number }, args: { cardId: number; labelId: number },
) => { ) => {
const { data } = await db return db.query.cardsToLabels.findFirst({
.from("_card_labels") where: and(
.select() eq(cardsToLabels.cardId, args.cardId),
.eq("cardId", args.cardId) eq(cardsToLabels.labelId, args.labelId),
.eq("labelId", args.labelId) ),
.limit(1) });
.single();
return data;
}; };
export const bulkCreate = async ( export const bulkCreate = async (
db: SupabaseClient<Database>, db: dbClient,
cardInput: { cardInput: {
publicId: string; publicId: string;
title: string; title: string;
@@ -177,178 +189,246 @@ export const bulkCreate = async (
importId?: number; importId?: number;
}[], }[],
) => { ) => {
const { data } = await db.from("card").insert(cardInput).select(`id`); const [result] = await db.insert(cards).values(cardInput).returning({
id: cards.id,
});
return data; return result;
}; };
export const createCardLabelRelationship = async ( export const createCardLabelRelationship = async (
db: SupabaseClient<Database>, db: dbClient,
cardLabelRelationshipInput: { cardId: number; labelId: number }, cardLabelRelationshipInput: { cardId: number; labelId: number },
) => { ) => {
const { data } = await db const [result] = await db
.from("_card_labels") .insert(cardsToLabels)
.insert({ .values({
cardId: cardLabelRelationshipInput.cardId, cardId: cardLabelRelationshipInput.cardId,
labelId: cardLabelRelationshipInput.labelId, labelId: cardLabelRelationshipInput.labelId,
}) })
.select() .returning();
.limit(1)
.single();
return data; return result;
}; };
export const bulkCreateCardLabelRelationship = async ( export const bulkCreateCardLabelRelationship = async (
db: SupabaseClient<Database>, db: dbClient,
cardLabelRelationshipInput: { cardId: number; labelId: number }[], cardLabelRelationshipInput: { cardId: number; labelId: number }[],
) => { ) => {
const { data } = await db const [result] = await db
.from("_card_labels") .insert(cardsToLabels)
.insert(cardLabelRelationshipInput) .values(cardLabelRelationshipInput)
.select(); .returning();
return data; return result;
}; };
export const getCardMemberRelationship = async ( export const getCardMemberRelationship = (
db: SupabaseClient<Database>, db: dbClient,
args: { cardId: number; memberId: number }, args: { cardId: number; memberId: number },
) => { ) => {
const { data } = await db return db.query.cardToWorkspaceMembers.findFirst({
.from("_card_workspace_members") where: and(
.select() eq(cardToWorkspaceMembers.cardId, args.cardId),
.eq("cardId", args.cardId) eq(cardToWorkspaceMembers.workspaceMemberId, args.memberId),
.eq("workspaceMemberId", args.memberId) ),
.limit(1) });
.single();
return data;
}; };
export const createCardMemberRelationship = async ( export const createCardMemberRelationship = async (
db: SupabaseClient<Database>, db: dbClient,
cardMemberRelationshipInput: { cardId: number; memberId: number }, cardMemberRelationshipInput: { cardId: number; memberId: number },
) => { ) => {
const { error } = await db.from("_card_workspace_members").insert({ const [result] = await db
cardId: cardMemberRelationshipInput.cardId, .insert(cardToWorkspaceMembers)
workspaceMemberId: cardMemberRelationshipInput.memberId, .values({
}); cardId: cardMemberRelationshipInput.cardId,
workspaceMemberId: cardMemberRelationshipInput.memberId,
})
.returning();
return { success: !error }; return { success: !!result };
}; };
export const getWithListAndMembersByPublicId = async ( export const getWithListAndMembersByPublicId = async (
db: SupabaseClient<Database>, db: dbClient,
cardPublicId: string, cardPublicId: string,
) => { ) => {
const { data } = await db const card = await db.query.cards.findFirst({
.from("card") columns: {
.select( publicId: true,
` title: true,
publicId, description: true,
title, },
description, with: {
labels:label ( labels: {
publicId, with: {
name, label: {
colourCode columns: {
), publicId: true,
list ( name: true,
publicId, colourCode: true,
name, },
board ( },
publicId, },
name, },
labels:label ( list: {
publicId, columns: {
colourCode, publicId: true,
name name: true,
), },
lists:list ( with: {
publicId, board: {
name columns: {
), publicId: true,
workspace ( name: true,
publicId, },
members:workspace_members ( with: {
publicId, labels: {
user!workspace_members_userId_user_id_fk ( columns: {
id, publicId: true,
name, colourCode: true,
email, name: true,
image },
) },
) lists: {
) columns: {
) publicId: true,
), name: true,
members:workspace_members ( },
publicId, where: isNull(lists.deletedAt),
user!workspace_members_userId_user_id_fk ( orderBy: asc(lists.index),
id, },
name workspace: {
) columns: {
), publicId: true,
activities:card_activity ( },
publicId, with: {
type, members: {
createdAt, columns: {
fromIndex, publicId: true,
toIndex, },
fromTitle, with: {
toTitle, user: {
fromDescription, columns: {
toDescription, id: true,
fromList:list!card_activity_fromListId_list_id_fk ( name: true,
publicId, email: true,
name, image: true,
index },
), },
toList:list!card_activity_toListId_list_id_fk ( },
publicId, where: isNull(workspaceMembers.deletedAt),
name, },
index },
), },
label!card_activity_labelId_label_id_fk ( },
publicId, },
name },
), // https://github.com/drizzle-team/drizzle-orm/issues/2903
member:workspace_members!card_activity_workspaceMemberId_workspace_members_id_fk ( // where: isNull(lists.deletedAt),
publicId, },
user!workspace_members_userId_user_id_fk ( members: {
id, with: {
name, member: {
email columns: {
) publicId: true,
), },
user!card_activity_createdBy_user_id_fk ( with: {
id, user: {
name, columns: {
email id: true,
), name: true,
comment:card_comments!card_activity_commentId_card_comments_id_fk ( },
publicId, },
comment, },
createdBy, // https://github.com/drizzle-team/drizzle-orm/issues/2903
updatedAt // where: isNull(workspaceMembers.deletedAt),
) },
) },
`, },
) activities: {
.eq("publicId", cardPublicId) columns: {
.is("deletedAt", null) publicId: true,
.is("list.board.lists.deletedAt", null) type: true,
.is("list.board.workspace.members.deletedAt", null) createdAt: true,
.is("activities.comment.deletedAt", null) fromIndex: true,
.order("index", { referencedTable: "list.board.lists", ascending: true }) toIndex: true,
.is("members.deletedAt", null) fromTitle: true,
.limit(1) toTitle: true,
.single(); fromDescription: true,
toDescription: true,
},
with: {
fromList: {
columns: {
publicId: true,
name: true,
index: true,
},
},
toList: {
columns: {
publicId: true,
name: true,
index: true,
},
},
label: {
columns: {
publicId: true,
name: true,
},
},
member: {
columns: {
publicId: true,
},
with: {
user: {
columns: {
id: true,
name: true,
email: true,
},
},
},
},
user: {
columns: {
id: true,
name: true,
email: true,
},
},
comment: {
columns: {
publicId: true,
comment: true,
createdBy: true,
updatedAt: true,
},
// https://github.com/drizzle-team/drizzle-orm/issues/2903
// where: isNull(comments.deletedAt),
},
},
},
},
where: and(eq(cards.publicId, cardPublicId), isNull(cards.deletedAt)),
});
return data; if (!card) return null;
const formattedResult = {
...card,
labels: card.labels.map((label) => label.label),
members: card.members.map((member) => member.member),
};
return formattedResult;
}; };
// Move to update - should take two arguments cardId and index
export const reorder = async ( export const reorder = async (
db: SupabaseClient<Database>, db: SupabaseClient<Database>,
args: { args: {
@@ -370,6 +450,7 @@ export const reorder = async (
return { success: !error }; return { success: !error };
}; };
// Again should be handled in update transaction
export const shiftIndex = async ( export const shiftIndex = async (
db: SupabaseClient<Database>, db: SupabaseClient<Database>,
args: { args: {
@@ -385,6 +466,7 @@ export const shiftIndex = async (
return data; return data;
}; };
// Handled in update transaction
export const pushIndex = async ( export const pushIndex = async (
db: SupabaseClient<Database>, db: SupabaseClient<Database>,
args: { args: {
@@ -401,77 +483,85 @@ export const pushIndex = async (
}; };
export const softDelete = async ( export const softDelete = async (
db: SupabaseClient<Database>, db: dbClient,
args: { args: {
cardId: number; cardId: number;
deletedAt: string; deletedAt: Date;
deletedBy: string; deletedBy: string;
}, },
) => { ) => {
const { data } = await db const [result] = await db
.from("card") .update(cards)
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.eq("id", args.cardId) .where(eq(cards.id, args.cardId))
.select(`id`) .returning({
.order("id", { ascending: true }) id: cards.id,
.limit(1) });
.single();
return result;
return data; };
};
export const softDeleteAllByListIds = async (
export const softDeleteAllByListIds = async ( db: dbClient,
db: SupabaseClient<Database>, args: {
args: { listIds: number[];
listIds: number[]; deletedAt: Date;
deletedAt: string; deletedBy: string;
deletedBy: string; },
}, ) => {
) => { const updatedCards = await db
const { data } = await db .update(cards)
.from("card") .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) .where(and(inArray(cards.listId, args.listIds), isNull(cards.deletedAt)))
.in("listId", args.listIds) .returning({
.is("deletedAt", null) id: cards.id,
.select(`id`); });
return data; return updatedCards;
}; };
export const hardDeleteCardMemberRelationship = async ( export const hardDeleteCardMemberRelationship = async (
db: SupabaseClient<Database>, db: dbClient,
args: { cardId: number; memberId: number }, args: { cardId: number; memberId: number },
) => { ) => {
const { error } = await db const [result] = await db
.from("_card_workspace_members") .delete(cardToWorkspaceMembers)
.delete() .where(
.eq("cardId", args.cardId) and(
.eq("workspaceMemberId", args.memberId) eq(cardToWorkspaceMembers.cardId, args.cardId),
.select(); eq(cardToWorkspaceMembers.workspaceMemberId, args.memberId),
),
return { success: !error }; )
}; .returning();
export const hardDeleteCardLabelRelationship = async ( return { success: !!result };
db: SupabaseClient<Database>, };
args: { cardId: number; labelId: number },
) => { export const hardDeleteCardLabelRelationship = async (
const { data } = await db db: dbClient,
.from("_card_labels") args: { cardId: number; labelId: number },
.delete() ) => {
.eq("cardId", args.cardId) const [result] = await db
.eq("labelId", args.labelId) .delete(cardsToLabels)
.select() .where(
.single(); and(
eq(cardsToLabels.cardId, args.cardId),
return { data }; eq(cardsToLabels.labelId, args.labelId),
}; ),
)
export const hardDeleteAllCardLabelRelationships = async ( .returning();
db: SupabaseClient<Database>,
labelId: number, return { data: result };
) => { };
const result = await db.from("_card_labels").delete().eq("labelId", labelId);
export const hardDeleteAllCardLabelRelationships = async (
db: dbClient,
labelId: number,
) => {
const [result] = await db
.delete(cardsToLabels)
.where(eq(cardsToLabels.labelId, labelId))
.returning();
return result; return result;
}; };

View File

@@ -77,6 +77,7 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
references: [imports.id], references: [imports.id],
}), }),
comments: many(comments), comments: many(comments),
activities: many(cardActivities),
})); }));
export const cardActivities = pgTable("card_activity", { export const cardActivities = pgTable("card_activity", {
@@ -132,10 +133,18 @@ export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({
fields: [cardActivities.workspaceMemberId], fields: [cardActivities.workspaceMemberId],
references: [workspaceMembers.id], references: [workspaceMembers.id],
}), }),
createdBy: one(users, { user: one(users, {
fields: [cardActivities.createdBy], fields: [cardActivities.createdBy],
references: [users.id], references: [users.id],
}), }),
member: one(workspaceMembers, {
fields: [cardActivities.workspaceMemberId],
references: [workspaceMembers.id],
}),
comment: one(comments, {
fields: [cardActivities.commentId],
references: [comments.id],
}),
})); }));
export const cardsToLabels = pgTable( export const cardsToLabels = pgTable(