feat: finish swapping drizzle for supabase
This commit is contained in:
@@ -9,7 +9,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
login: publicProcedure
|
login: publicProcedure
|
||||||
.input(z.object({ email: z.string() }))
|
.input(z.object({ email: z.string() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { data } = await ctx.supabase.auth.signInWithOtp({
|
const { data } = await ctx.db.auth.signInWithOtp({
|
||||||
email: input.email,
|
email: input.email,
|
||||||
options: {
|
options: {
|
||||||
emailRedirectTo: '/boards',
|
emailRedirectTo: '/boards',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
all: publicProcedure
|
all: publicProcedure
|
||||||
.input(z.object({ workspacePublicId: z.string().min(12) }))
|
.input(z.object({ workspacePublicId: z.string().min(12) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const workspace = await ctx.supabase
|
const workspace = await ctx.db
|
||||||
.from('workspace')
|
.from('workspace')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.workspacePublicId)
|
.eq('publicId', input.workspacePublicId)
|
||||||
@@ -19,7 +19,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!workspace.data) return;
|
if (!workspace.data) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.select(`
|
.select(`
|
||||||
publicId,
|
publicId,
|
||||||
@@ -33,7 +33,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
byId: publicProcedure
|
byId: publicProcedure
|
||||||
.input(z.object({ id: z.string().min(12) }))
|
.input(z.object({ id: z.string().min(12) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.select(`
|
.select(`
|
||||||
publicId,
|
publicId,
|
||||||
@@ -100,7 +100,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const workspace = await ctx.supabase
|
const workspace = await ctx.db
|
||||||
.from('workspace')
|
.from('workspace')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.workspacePublicId)
|
.eq('publicId', input.workspacePublicId)
|
||||||
@@ -109,7 +109,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!workspace.data) return;
|
if (!workspace.data) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -135,7 +135,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.update({ name: input.name })
|
.update({ name: input.name })
|
||||||
.eq('publicId', input.boardId);
|
.eq('publicId', input.boardId);
|
||||||
@@ -152,7 +152,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const board = await ctx.supabase
|
const board = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.select(`
|
.select(`
|
||||||
id,
|
id,
|
||||||
@@ -168,20 +168,20 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.update({ deletedAt, deletedBy: userId })
|
.update({ deletedAt, deletedBy: userId })
|
||||||
.eq('id', board.data.id)
|
.eq('id', board.data.id)
|
||||||
.is('deletedAt', null);
|
.is('deletedAt', null);
|
||||||
|
|
||||||
if (listIds.length) {
|
if (listIds.length) {
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.update({ deletedAt, deletedBy: userId })
|
.update({ deletedAt, deletedBy: userId })
|
||||||
.eq('boardId', board.data.id)
|
.eq('boardId', board.data.id)
|
||||||
.is('deletedAt', null);
|
.is('deletedAt', null);
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.update({ deletedAt, deletedBy: userId })
|
.update({ deletedAt, deletedBy: userId })
|
||||||
.in('listId', listIds)
|
.in('listId', listIds)
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { and, desc, eq, isNull, inArray, sql } from "drizzle-orm";
|
|
||||||
|
|
||||||
import { cards, cardsToLabels, cardToWorkspaceMembers, labels, lists, workspaceMembers } from "~/server/db/schema";
|
|
||||||
import { generateUID } from "~/utils/generateUID";
|
import { generateUID } from "~/utils/generateUID";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +21,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const list = await ctx.supabase
|
const list = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.select(`id, cards:card (index)`)
|
.select(`id, cards:card (index)`)
|
||||||
.eq('publicId', input.listPublicId)
|
.eq('publicId', input.listPublicId)
|
||||||
@@ -36,7 +33,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const latestCard = list.data.cards.length && list.data.cards[0]
|
const latestCard = list.data.cards.length && list.data.cards[0]
|
||||||
|
|
||||||
const newCard = await ctx.supabase
|
const newCard = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -52,7 +49,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
const newCardId = newCard.data?.id;
|
const newCardId = newCard.data?.id;
|
||||||
|
|
||||||
if (newCardId && input.labelsPublicIds.length) {
|
if (newCardId && input.labelsPublicIds.length) {
|
||||||
const labels = await ctx.supabase
|
const labels = await ctx.db
|
||||||
.from('label')
|
.from('label')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.labelsPublicIds);
|
.eq('publicId', input.labelsPublicIds);
|
||||||
@@ -61,13 +58,13 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const labelsInsert = labels.data.map((label) => ({ cardId: newCardId, labelId: label.id }));
|
const labelsInsert = labels.data.map((label) => ({ cardId: newCardId, labelId: label.id }));
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_labels')
|
.from('_card_labels')
|
||||||
.insert(labelsInsert);
|
.insert(labelsInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCardId && input.memberPublicIds.length) {
|
if (newCardId && input.memberPublicIds.length) {
|
||||||
const members = await ctx.supabase
|
const members = await ctx.db
|
||||||
.from('workspace_members')
|
.from('workspace_members')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.memberPublicIds);
|
.eq('publicId', input.memberPublicIds);
|
||||||
@@ -76,7 +73,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const membersInsert = members.data.map((member) => ({ cardId: newCardId, workspaceMemberId: member.id }));
|
const membersInsert = members.data.map((member) => ({ cardId: newCardId, workspaceMemberId: member.id }));
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_workspace_members')
|
.from('_card_workspace_members')
|
||||||
.insert(membersInsert);
|
.insert(membersInsert);
|
||||||
}
|
}
|
||||||
@@ -95,14 +92,14 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const card = await ctx.supabase
|
const card = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.cardPublicId)
|
.eq('publicId', input.cardPublicId)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
const label = await ctx.supabase
|
const label = await ctx.db
|
||||||
.from('label')
|
.from('label')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.labelPublicId)
|
.eq('publicId', input.labelPublicId)
|
||||||
@@ -111,7 +108,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!card.data || !label.data) return;
|
if (!card.data || !label.data) return;
|
||||||
|
|
||||||
const existingLabel = await ctx.supabase
|
const existingLabel = await ctx.db
|
||||||
.from('_card_labels')
|
.from('_card_labels')
|
||||||
.select()
|
.select()
|
||||||
.eq('cardId', card.data.id)
|
.eq('cardId', card.data.id)
|
||||||
@@ -120,7 +117,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (existingLabel.data) {
|
if (existingLabel.data) {
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_labels')
|
.from('_card_labels')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('cardId', card.data.id)
|
.eq('cardId', card.data.id)
|
||||||
@@ -129,7 +126,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
return { newLabel: false };
|
return { newLabel: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_labels')
|
.from('_card_labels')
|
||||||
.insert({
|
.insert({
|
||||||
cardId: card.data.id,
|
cardId: card.data.id,
|
||||||
@@ -150,14 +147,14 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const card = await ctx.supabase
|
const card = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.cardPublicId)
|
.eq('publicId', input.cardPublicId)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
const member = await ctx.supabase
|
const member = await ctx.db
|
||||||
.from('workspace_members')
|
.from('workspace_members')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.workspaceMemberPublicId)
|
.eq('publicId', input.workspaceMemberPublicId)
|
||||||
@@ -166,7 +163,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!card.data || !member.data) return;
|
if (!card.data || !member.data) return;
|
||||||
|
|
||||||
const existingMember = await ctx.supabase
|
const existingMember = await ctx.db
|
||||||
.from('_card_workspace_members')
|
.from('_card_workspace_members')
|
||||||
.select()
|
.select()
|
||||||
.eq('cardId', card.data.id)
|
.eq('cardId', card.data.id)
|
||||||
@@ -175,7 +172,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (existingMember.data) {
|
if (existingMember.data) {
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_workspace_members')
|
.from('_card_workspace_members')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('cardId', card.data.id)
|
.eq('cardId', card.data.id)
|
||||||
@@ -184,7 +181,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
return { newMember: false };
|
return { newMember: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_workspace_members')
|
.from('_card_workspace_members')
|
||||||
.insert({
|
.insert({
|
||||||
cardId: card.data.id,
|
cardId: card.data.id,
|
||||||
@@ -196,7 +193,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
byId: publicProcedure
|
byId: publicProcedure
|
||||||
.input(z.object({ id: z.string().min(12) }))
|
.input(z.object({ id: z.string().min(12) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.select(`
|
.select(`
|
||||||
publicId,
|
publicId,
|
||||||
@@ -262,7 +259,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.update({ title: input.title, description: input.description })
|
.update({ title: input.title, description: input.description })
|
||||||
.eq('publicId', input.cardId)
|
.eq('publicId', input.cardId)
|
||||||
@@ -275,22 +272,37 @@ export const cardRouter = createTRPCRouter({
|
|||||||
z.object({
|
z.object({
|
||||||
cardPublicId: z.string().min(12),
|
cardPublicId: z.string().min(12),
|
||||||
}))
|
}))
|
||||||
.mutation(({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const userId = ctx.user?.id;
|
const userId = ctx.user?.id;
|
||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
return ctx.db.transaction(async (tx) => {
|
const card = await ctx.db
|
||||||
const card = await tx.query.cards.findFirst({
|
.from('card')
|
||||||
where: eq(cards.publicId, input.cardPublicId),
|
.select(`id, index, listId`)
|
||||||
})
|
.eq('publicId', input.cardPublicId)
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
if (!card) return;
|
if (!card.data) return;
|
||||||
|
|
||||||
await tx.update(cards).set({ deletedAt: new Date(), deletedBy: userId}).where(eq(cards.publicId, input.cardPublicId));
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await tx.execute(sql`UPDATE ${cards} SET ${cards.index} = ${cards.index} - 1 WHERE ${cards.listId} = ${card.listId} AND ${cards.index} > ${card.index} AND ${cards.deletedAt} IS NULL;`);
|
await ctx.db
|
||||||
})
|
.from('card')
|
||||||
|
.update({ deletedAt, deletedBy: userId})
|
||||||
|
.eq('publicId', input.cardPublicId);
|
||||||
|
|
||||||
|
await ctx.db
|
||||||
|
.from('card')
|
||||||
|
.update({ deletedAt, deletedBy: userId})
|
||||||
|
.eq('publicId', input.cardPublicId);
|
||||||
|
|
||||||
|
await ctx.db
|
||||||
|
.rpc('shift_card_index', {
|
||||||
|
list_id: card.data.listId,
|
||||||
|
card_index: card.data.index,
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
reorder: publicProcedure
|
reorder: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -299,69 +311,54 @@ export const cardRouter = createTRPCRouter({
|
|||||||
newListId: z.string().min(12),
|
newListId: z.string().min(12),
|
||||||
newIndex: z.number().optional(),
|
newIndex: z.number().optional(),
|
||||||
}))
|
}))
|
||||||
.mutation(({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const userId = ctx.user?.id;
|
const userId = ctx.user?.id;
|
||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
return ctx.db.transaction(async (tx) => {
|
const card = await ctx.db
|
||||||
const card = await tx.query.cards.findFirst({
|
.from('card')
|
||||||
with: {
|
.select(`id, index, list (id)`)
|
||||||
list: true,
|
.eq('publicId', input.cardId)
|
||||||
},
|
.is('deletedAt', null)
|
||||||
where: and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)),
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (!card.data) return;
|
||||||
|
|
||||||
|
const currentList = card.data.list;
|
||||||
|
const currentIndex = card.data.index;
|
||||||
|
|
||||||
|
let newIndex = input.newIndex;
|
||||||
|
|
||||||
|
const newList = await ctx.db
|
||||||
|
.from('list')
|
||||||
|
.select(`id, cards:card (index)`)
|
||||||
|
.eq('publicId', input.newListId)
|
||||||
|
.is('deletedAt', null)
|
||||||
|
.order('index', { foreignTable: 'card', ascending: false })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (!newList.data) return;
|
||||||
|
|
||||||
|
if (newIndex === undefined) {
|
||||||
|
const lastCardIndex = newList.data.cards.length ? newList.data.cards[0]?.index : undefined;
|
||||||
|
|
||||||
|
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentList?.id || !newList?.data.id) return;
|
||||||
|
|
||||||
|
const { error } = await ctx.db
|
||||||
|
.rpc('reorder_cards', {
|
||||||
|
current_list_id: currentList.id,
|
||||||
|
new_list_id: newList.data.id,
|
||||||
|
current_index: currentIndex,
|
||||||
|
new_index: newIndex,
|
||||||
|
card_id: card.data.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!card) return;
|
return { success: !!error }
|
||||||
|
|
||||||
const currentList = card.list;
|
|
||||||
const currentIndex = card.index;
|
|
||||||
|
|
||||||
let newIndex = input.newIndex;
|
|
||||||
|
|
||||||
const newList = await tx.query.lists.findFirst({
|
|
||||||
with: {
|
|
||||||
cards: {
|
|
||||||
orderBy: [desc(cards.index)],
|
|
||||||
limit: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
where: and(eq(lists.publicId, input.newListId), isNull(cards.deletedAt)),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!newList) return;
|
|
||||||
|
|
||||||
if (newIndex === undefined) {
|
|
||||||
const lastCardIndex = newList.cards.length ? newList.cards[0]?.index : undefined;
|
|
||||||
|
|
||||||
newIndex = lastCardIndex !== undefined ? lastCardIndex + 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentList?.id || !newList?.id) return;
|
|
||||||
|
|
||||||
|
|
||||||
if (currentList.id === newList.id) {
|
|
||||||
await tx.execute(sql`
|
|
||||||
UPDATE ${cards}
|
|
||||||
SET index =
|
|
||||||
CASE
|
|
||||||
WHEN ${cards.index} = ${currentIndex} THEN ${newIndex}
|
|
||||||
WHEN ${currentIndex} < ${newIndex} AND ${cards.index} > ${currentIndex} AND ${cards.index} <= ${newIndex} THEN ${cards.index} - 1
|
|
||||||
WHEN ${currentIndex} > ${newIndex} AND ${cards.index} >= ${newIndex} AND ${cards.index} < ${currentIndex} THEN ${cards.index} + 1
|
|
||||||
ELSE ${cards.index}
|
|
||||||
END
|
|
||||||
WHERE ${cards.listId} = ${currentList.id} AND ${cards.deletedAt} IS NULL;
|
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
await tx.execute(sql`UPDATE ${cards} SET index = index + 1 WHERE ${cards.listId} = ${newList.id} AND ${cards.index} >= ${newIndex} AND ${cards.deletedAt} IS NULL;`)
|
|
||||||
|
|
||||||
await tx.execute(sql`UPDATE ${cards} SET index = index - 1 WHERE ${cards.listId} = ${currentList.id} AND ${cards.index} >= ${currentIndex} AND ${cards.deletedAt} IS NULL;`)
|
|
||||||
|
|
||||||
await tx
|
|
||||||
.update(cards)
|
|
||||||
.set({ listId: newList.id, index: newIndex })
|
|
||||||
.where(and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -87,7 +87,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const newImport = await ctx.supabase
|
const newImport = await ctx.db
|
||||||
.from('import')
|
.from('import')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -103,7 +103,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
|
|
||||||
let boardsCreated = 0;
|
let boardsCreated = 0;
|
||||||
|
|
||||||
const workspace = await ctx.supabase
|
const workspace = await ctx.db
|
||||||
.from('workspace')
|
.from('workspace')
|
||||||
.select(`id`)
|
.select(`id`)
|
||||||
.eq('publicId', input.workspacePublicId)
|
.eq('publicId', input.workspacePublicId)
|
||||||
@@ -130,7 +130,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBoard = await ctx.supabase
|
const newBoard = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -150,7 +150,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
let listIndex = 0;
|
let listIndex = 0;
|
||||||
|
|
||||||
for (const list of formattedData.lists) {
|
for (const list of formattedData.lists) {
|
||||||
const newList = await ctx.supabase
|
const newList = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -177,7 +177,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
importId: newImportId
|
importId: newImportId
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.insert(cardsInsert);
|
.insert(cardsInsert);
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ export const importRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (boardsCreated > 0 && newImportId) {
|
if (boardsCreated > 0 && newImportId) {
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('import')
|
.from('import')
|
||||||
.update({ status: 'success' })
|
.update({ status: 'success' })
|
||||||
.eq('importId', newImportId);
|
.eq('importId', newImportId);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const labelRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const card = await ctx.supabase
|
const card = await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.select(`id, list (boardId)`)
|
.select(`id, list (boardId)`)
|
||||||
.eq('publicId', input.cardPublicId)
|
.eq('publicId', input.cardPublicId)
|
||||||
@@ -30,7 +30,7 @@ export const labelRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!card.data?.list) return;
|
if (!card.data?.list) return;
|
||||||
|
|
||||||
const newLabel = await ctx.supabase
|
const newLabel = await ctx.db
|
||||||
.from('label')
|
.from('label')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -45,7 +45,7 @@ export const labelRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!newLabel.data) return;
|
if (!newLabel.data) return;
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('_card_labels')
|
.from('_card_labels')
|
||||||
.insert({
|
.insert({
|
||||||
cardId: card.data.id,
|
cardId: card.data.id,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const board = await ctx.supabase
|
const board = await ctx.db
|
||||||
.from('board')
|
.from('board')
|
||||||
.select(`id, lists:list (index)`)
|
.select(`id, lists:list (index)`)
|
||||||
.eq('publicId', input.boardPublicId)
|
.eq('publicId', input.boardPublicId)
|
||||||
@@ -32,7 +32,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const latestListIndex = board.data.lists[0]?.index
|
const latestListIndex = board.data.lists[0]?.index
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -61,7 +61,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const list = await ctx.supabase
|
const list = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.select(`id, boardId`)
|
.select(`id, boardId`)
|
||||||
.eq('publicId', input.listId)
|
.eq('publicId', input.listId)
|
||||||
@@ -70,7 +70,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!list?.data) return;
|
if (!list?.data) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.rpc('reorder_list', {
|
.rpc('reorder_list', {
|
||||||
board_id: list.data.boardId,
|
board_id: list.data.boardId,
|
||||||
list_id: list.data.id,
|
list_id: list.data.id,
|
||||||
@@ -90,7 +90,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const list = await ctx.supabase
|
const list = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.select(`id, boardId, index`)
|
.select(`id, boardId, index`)
|
||||||
.eq('publicId', input.listPublicId)
|
.eq('publicId', input.listPublicId)
|
||||||
@@ -101,19 +101,19 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.update({ deletedAt, deletedBy: userId })
|
.update({ deletedAt, deletedBy: userId })
|
||||||
.eq('id', list.data.id)
|
.eq('id', list.data.id)
|
||||||
.is('deletedAt', null);
|
.is('deletedAt', null);
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('card')
|
.from('card')
|
||||||
.update({ deletedAt, deletedBy: userId })
|
.update({ deletedAt, deletedBy: userId })
|
||||||
.eq('listId', list.data.id)
|
.eq('listId', list.data.id)
|
||||||
.is('deletedAt', null);
|
.is('deletedAt', null);
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.rpc('shift_list_index', {
|
.rpc('shift_list_index', {
|
||||||
board_id: list.data.boardId,
|
board_id: list.data.boardId,
|
||||||
list_index: list.data.index
|
list_index: list.data.index
|
||||||
@@ -132,7 +132,7 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('list')
|
.from('list')
|
||||||
.update({ name: input.name })
|
.update({ name: input.name })
|
||||||
.eq('publicId', input.listPublicId)
|
.eq('publicId', input.listPublicId)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('workspace_members')
|
.from('workspace_members')
|
||||||
.select(`
|
.select(`
|
||||||
role,
|
role,
|
||||||
@@ -34,7 +34,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const { data } = await ctx.supabase
|
const { data } = await ctx.db
|
||||||
.from('workspace')
|
.from('workspace')
|
||||||
.select(`
|
.select(`
|
||||||
publicId,
|
publicId,
|
||||||
@@ -66,7 +66,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
const workspace = await ctx.supabase
|
const workspace = await ctx.db
|
||||||
.from('workspace')
|
.from('workspace')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
@@ -86,7 +86,7 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (!newWorkspaceId) return;
|
if (!newWorkspaceId) return;
|
||||||
|
|
||||||
await ctx.supabase
|
await ctx.db
|
||||||
.from('workspace_members')
|
.from('workspace_members')
|
||||||
.insert({
|
.insert({
|
||||||
publicId: generateUID(),
|
publicId: generateUID(),
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
|
|||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import createClient from "~/utils/supabase/api";
|
import createClient from "~/utils/supabase/api";
|
||||||
import { type Database } from "~/types/database.types";
|
import { type Database } from "~/types/database.types";
|
||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { type SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. CONTEXT
|
* 1. CONTEXT
|
||||||
@@ -30,8 +29,7 @@ type User = {
|
|||||||
|
|
||||||
interface CreateContextOptions {
|
interface CreateContextOptions {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
supabase: SupabaseClient<Database>
|
db: SupabaseClient<Database>
|
||||||
db: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,8 +46,7 @@ interface CreateContextOptions {
|
|||||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||||
return {
|
return {
|
||||||
user: opts.user,
|
user: opts.user,
|
||||||
supabase: opts.supabase,
|
db: opts.db,
|
||||||
db,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,11 +57,11 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
|||||||
* @see https://trpc.io/docs/context
|
* @see https://trpc.io/docs/context
|
||||||
*/
|
*/
|
||||||
export const createTRPCContext = async (_opts: CreateNextContextOptions) => {
|
export const createTRPCContext = async (_opts: CreateNextContextOptions) => {
|
||||||
const supabase = createClient(_opts.req, _opts.res);
|
const db = createClient(_opts.req, _opts.res);
|
||||||
|
|
||||||
const { data: { user } } = await supabase.auth.getUser()
|
const { data: { user } } = await db.auth.getUser()
|
||||||
|
|
||||||
return createInnerTRPCContext({ user, supabase });
|
return createInnerTRPCContext({ user, db });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import { render } from '@react-email/render';
|
|
||||||
import { MagicLinkEmail } from "~/email/emails/magic-link";
|
|
||||||
|
|
||||||
import { DrizzleAdapter } from "@auth/drizzle-adapter";
|
|
||||||
import NextAuth, { type DefaultSession } from "next-auth";
|
|
||||||
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import { pgTable} from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
|
|
||||||
* object and keep type safety.
|
|
||||||
*
|
|
||||||
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
|
|
||||||
*/
|
|
||||||
declare module "next-auth" {
|
|
||||||
interface Session extends DefaultSession {
|
|
||||||
user: {
|
|
||||||
id: string;
|
|
||||||
// ...other properties
|
|
||||||
// role: UserRole;
|
|
||||||
} & DefaultSession["user"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface User {
|
|
||||||
// // ...other properties
|
|
||||||
// // role: UserRole;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const {
|
|
||||||
handlers: { GET, POST },
|
|
||||||
auth,
|
|
||||||
} = NextAuth({
|
|
||||||
callbacks: {
|
|
||||||
session: ({ session, user }) => ({
|
|
||||||
...session,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
name: session.user.name,
|
|
||||||
email: session.user.email,
|
|
||||||
image: session.user.image,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
adapter: DrizzleAdapter(db, pgTable),
|
|
||||||
pages: {
|
|
||||||
signIn: "/auth/login",
|
|
||||||
// signOut: "/auth/signout",
|
|
||||||
// error: "/auth/error",
|
|
||||||
verifyRequest: "/auth/verify",
|
|
||||||
},
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
id: 'email',
|
|
||||||
type: 'email',
|
|
||||||
from: process.env.EMAIL_FROM ?? '',
|
|
||||||
server: {},
|
|
||||||
maxAge: 24 * 60 * 60,
|
|
||||||
name: 'Email',
|
|
||||||
options: {},
|
|
||||||
async sendVerificationRequest({ identifier: email, url }) {
|
|
||||||
const magicLinkEmail = MagicLinkEmail({ loginUrl: url });
|
|
||||||
const response = await fetch(process.env.EMAIL_URL ?? '', {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${process.env.EMAIL_TOKEN}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
from: process.env.EMAIL_FROM,
|
|
||||||
to: [email],
|
|
||||||
subject: 'Login via email',
|
|
||||||
html: render(magicLinkEmail),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error('Error sending magic login')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -12,8 +12,6 @@ import {
|
|||||||
bigint,
|
bigint,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
import { type AdapterAccount } from "@auth/core/adapters";
|
|
||||||
|
|
||||||
export const importSourceEnum = pgEnum('source', ['trello']);
|
export const importSourceEnum = pgEnum('source', ['trello']);
|
||||||
export const importStatusEnum = pgEnum('status', ['started', 'success', 'failed']);
|
export const importStatusEnum = pgEnum('status', ['started', 'success', 'failed']);
|
||||||
export const memberRoleEnum = pgEnum('role', ['admin', 'member', 'guest']);
|
export const memberRoleEnum = pgEnum('role', ['admin', 'member', 'guest']);
|
||||||
@@ -236,7 +234,6 @@ export const users = pgTable("user", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const usersRelations = relations(users, ({ many }) => ({
|
export const usersRelations = relations(users, ({ many }) => ({
|
||||||
accounts: many(accounts),
|
|
||||||
boards: many(boards),
|
boards: many(boards),
|
||||||
cards: many(cards),
|
cards: many(cards),
|
||||||
imports: many(imports),
|
imports: many(imports),
|
||||||
@@ -244,61 +241,6 @@ export const usersRelations = relations(users, ({ many }) => ({
|
|||||||
workspaces: many(workspaces),
|
workspaces: many(workspaces),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const accounts = pgTable(
|
|
||||||
"account",
|
|
||||||
{
|
|
||||||
userId: uuid("userId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id),
|
|
||||||
type: varchar("type", { length: 255 })
|
|
||||||
.$type<AdapterAccount["type"]>()
|
|
||||||
.notNull(),
|
|
||||||
provider: varchar("provider", { length: 255 }).notNull(),
|
|
||||||
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
|
||||||
refresh_token: text("refresh_token"),
|
|
||||||
access_token: text("access_token"),
|
|
||||||
expires_at: integer("expires_at"),
|
|
||||||
token_type: varchar("token_type", { length: 255 }),
|
|
||||||
scope: varchar("scope", { length: 255 }),
|
|
||||||
id_token: text("id_token"),
|
|
||||||
session_state: varchar("session_state", { length: 255 }),
|
|
||||||
},
|
|
||||||
(account) => ({
|
|
||||||
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const accountsRelations = relations(accounts, ({ one }) => ({
|
|
||||||
user: one(users, { fields: [accounts.userId], references: [users.id] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const sessions = pgTable(
|
|
||||||
"session",
|
|
||||||
{
|
|
||||||
sessionToken: varchar("sessionToken", { length: 255 })
|
|
||||||
.notNull()
|
|
||||||
.primaryKey(),
|
|
||||||
userId: uuid("userId").notNull().references(() => users.id),
|
|
||||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const sessionsRelations = relations(sessions, ({ one }) => ({
|
|
||||||
user: one(users, { fields: [sessions.userId], references: [users.id] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const verificationTokens = pgTable(
|
|
||||||
"verificationToken",
|
|
||||||
{
|
|
||||||
identifier: varchar("identifier", { length: 255 }).notNull(),
|
|
||||||
token: varchar("token", { length: 255 }).notNull(),
|
|
||||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
|
||||||
},
|
|
||||||
(vt) => ({
|
|
||||||
compoundKey: primaryKey(vt.identifier, vt.token),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const workspaces = pgTable(
|
export const workspaces = pgTable(
|
||||||
"workspace",
|
"workspace",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -600,7 +600,17 @@ export type Database = {
|
|||||||
[_ in never]: never
|
[_ in never]: never
|
||||||
}
|
}
|
||||||
Functions: {
|
Functions: {
|
||||||
reorder_list: {
|
reorder_cards: {
|
||||||
|
Args: {
|
||||||
|
card_id: number
|
||||||
|
current_list_id: number
|
||||||
|
new_list_id: number
|
||||||
|
current_index: number
|
||||||
|
new_index: number
|
||||||
|
}
|
||||||
|
Returns: undefined
|
||||||
|
}
|
||||||
|
reorder_lists: {
|
||||||
Args: {
|
Args: {
|
||||||
board_id: number
|
board_id: number
|
||||||
list_id: number
|
list_id: number
|
||||||
@@ -609,6 +619,13 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: undefined
|
Returns: undefined
|
||||||
}
|
}
|
||||||
|
shift_card_index: {
|
||||||
|
Args: {
|
||||||
|
list_id: number
|
||||||
|
card_index: number
|
||||||
|
}
|
||||||
|
Returns: undefined
|
||||||
|
}
|
||||||
shift_list_index: {
|
shift_list_index: {
|
||||||
Args: {
|
Args: {
|
||||||
board_id: number
|
board_id: number
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
CREATE OR REPLACE FUNCTION reorder_list(board_id BIGINT, list_id BIGINT, current_index INT, new_index INT)
|
CREATE OR REPLACE FUNCTION reorder_lists(board_id BIGINT, list_id BIGINT, current_index INT, new_index INT)
|
||||||
RETURNS VOID
|
RETURNS VOID
|
||||||
LANGUAGE SQL
|
LANGUAGE SQL
|
||||||
AS $$
|
AS $$
|
||||||
@@ -13,6 +13,41 @@ AS $$
|
|||||||
WHERE "boardId" = board_id;
|
WHERE "boardId" = board_id;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION reorder_cards(card_id BIGINT, current_list_id BIGINT, new_list_id BIGINT, current_index INT, new_index INT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE PLPGSQL
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
card_index INT;
|
||||||
|
BEGIN
|
||||||
|
SELECT index INTO card_index FROM card WHERE "listId" = current_list_id AND id = card_id AND "deletedAt" IS NULL;
|
||||||
|
|
||||||
|
IF current_list_id = new_list_id THEN
|
||||||
|
UPDATE card
|
||||||
|
SET index =
|
||||||
|
CASE
|
||||||
|
WHEN index = current_index THEN new_index
|
||||||
|
WHEN current_index < new_index AND index > current_index AND index <= new_index THEN index - 1
|
||||||
|
WHEN current_index > new_index AND index >= new_index AND index < current_index THEN index + 1
|
||||||
|
ELSE index
|
||||||
|
END
|
||||||
|
WHERE "listId" = current_list_id AND "deletedAt" IS NULL;
|
||||||
|
ELSE
|
||||||
|
UPDATE card
|
||||||
|
SET index = index + 1
|
||||||
|
WHERE "listId" = new_list_id AND index >= new_index AND "deletedAt" IS NULL;
|
||||||
|
|
||||||
|
UPDATE card
|
||||||
|
SET index = index - 1
|
||||||
|
WHERE "listId" = current_list_id AND index >= current_index AND "deletedAt" IS NULL;
|
||||||
|
|
||||||
|
UPDATE card
|
||||||
|
SET "listId" = new_list_id, index = new_index
|
||||||
|
WHERE id = card_id AND "deletedAt" IS NULL;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION shift_list_index(board_id BIGINT, list_index INT)
|
CREATE OR REPLACE FUNCTION shift_list_index(board_id BIGINT, list_index INT)
|
||||||
RETURNS VOID
|
RETURNS VOID
|
||||||
LANGUAGE SQL
|
LANGUAGE SQL
|
||||||
@@ -20,4 +55,14 @@ AS $$
|
|||||||
UPDATE list
|
UPDATE list
|
||||||
SET index = index - 1
|
SET index = index - 1
|
||||||
WHERE "boardId" = board_id AND index > list_index AND "deletedAt" IS NULL;
|
WHERE "boardId" = board_id AND index > list_index AND "deletedAt" IS NULL;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION shift_card_index(list_id BIGINT, card_index INT)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE SQL
|
||||||
|
AS $$
|
||||||
|
UPDATE card
|
||||||
|
SET index = index - 1
|
||||||
|
WHERE "listId" = list_id AND index > card_index AND "deletedAt" IS NULL;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user