feat: finish swapping drizzle for supabase

This commit is contained in:
Henry
2024-04-17 18:18:33 +01:00
parent bfb5e49981
commit 4e1e99cbac
12 changed files with 189 additions and 277 deletions

View File

@@ -9,7 +9,7 @@ export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string() }))
.mutation(async ({ ctx, input }) => {
const { data } = await ctx.supabase.auth.signInWithOtp({
const { data } = await ctx.db.auth.signInWithOtp({
email: input.email,
options: {
emailRedirectTo: '/boards',

View File

@@ -10,7 +10,7 @@ export const boardRouter = createTRPCRouter({
all: publicProcedure
.input(z.object({ workspacePublicId: z.string().min(12) }))
.query(async ({ ctx, input }) => {
const workspace = await ctx.supabase
const workspace = await ctx.db
.from('workspace')
.select(`id`)
.eq('publicId', input.workspacePublicId)
@@ -19,7 +19,7 @@ export const boardRouter = createTRPCRouter({
if (!workspace.data) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('board')
.select(`
publicId,
@@ -33,7 +33,7 @@ export const boardRouter = createTRPCRouter({
byId: publicProcedure
.input(z.object({ id: z.string().min(12) }))
.query(async ({ ctx, input }) => {
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('board')
.select(`
publicId,
@@ -100,7 +100,7 @@ export const boardRouter = createTRPCRouter({
if (!userId) return;
const workspace = await ctx.supabase
const workspace = await ctx.db
.from('workspace')
.select(`id`)
.eq('publicId', input.workspacePublicId)
@@ -109,7 +109,7 @@ export const boardRouter = createTRPCRouter({
if (!workspace.data) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('board')
.insert({
publicId: generateUID(),
@@ -135,7 +135,7 @@ export const boardRouter = createTRPCRouter({
if (!userId) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('board')
.update({ name: input.name })
.eq('publicId', input.boardId);
@@ -152,7 +152,7 @@ export const boardRouter = createTRPCRouter({
if (!userId) return;
const board = await ctx.supabase
const board = await ctx.db
.from('board')
.select(`
id,
@@ -168,20 +168,20 @@ export const boardRouter = createTRPCRouter({
const deletedAt = new Date().toISOString();
await ctx.supabase
await ctx.db
.from('board')
.update({ deletedAt, deletedBy: userId })
.eq('id', board.data.id)
.is('deletedAt', null);
if (listIds.length) {
await ctx.supabase
await ctx.db
.from('list')
.update({ deletedAt, deletedBy: userId })
.eq('boardId', board.data.id)
.is('deletedAt', null);
await ctx.supabase
await ctx.db
.from('card')
.update({ deletedAt, deletedBy: userId })
.in('listId', listIds)

View File

@@ -1,7 +1,4 @@
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 {
@@ -24,7 +21,7 @@ export const cardRouter = createTRPCRouter({
if (!userId) return;
const list = await ctx.supabase
const list = await ctx.db
.from('list')
.select(`id, cards:card (index)`)
.eq('publicId', input.listPublicId)
@@ -36,7 +33,7 @@ export const cardRouter = createTRPCRouter({
const latestCard = list.data.cards.length && list.data.cards[0]
const newCard = await ctx.supabase
const newCard = await ctx.db
.from('card')
.insert({
publicId: generateUID(),
@@ -52,7 +49,7 @@ export const cardRouter = createTRPCRouter({
const newCardId = newCard.data?.id;
if (newCardId && input.labelsPublicIds.length) {
const labels = await ctx.supabase
const labels = await ctx.db
.from('label')
.select(`id`)
.eq('publicId', input.labelsPublicIds);
@@ -61,13 +58,13 @@ export const cardRouter = createTRPCRouter({
const labelsInsert = labels.data.map((label) => ({ cardId: newCardId, labelId: label.id }));
await ctx.supabase
await ctx.db
.from('_card_labels')
.insert(labelsInsert);
}
if (newCardId && input.memberPublicIds.length) {
const members = await ctx.supabase
const members = await ctx.db
.from('workspace_members')
.select(`id`)
.eq('publicId', input.memberPublicIds);
@@ -76,7 +73,7 @@ export const cardRouter = createTRPCRouter({
const membersInsert = members.data.map((member) => ({ cardId: newCardId, workspaceMemberId: member.id }));
await ctx.supabase
await ctx.db
.from('_card_workspace_members')
.insert(membersInsert);
}
@@ -95,14 +92,14 @@ export const cardRouter = createTRPCRouter({
if (!userId) return;
const card = await ctx.supabase
const card = await ctx.db
.from('card')
.select(`id`)
.eq('publicId', input.cardPublicId)
.limit(1)
.single();
const label = await ctx.supabase
const label = await ctx.db
.from('label')
.select(`id`)
.eq('publicId', input.labelPublicId)
@@ -111,7 +108,7 @@ export const cardRouter = createTRPCRouter({
if (!card.data || !label.data) return;
const existingLabel = await ctx.supabase
const existingLabel = await ctx.db
.from('_card_labels')
.select()
.eq('cardId', card.data.id)
@@ -120,7 +117,7 @@ export const cardRouter = createTRPCRouter({
.single();
if (existingLabel.data) {
await ctx.supabase
await ctx.db
.from('_card_labels')
.delete()
.eq('cardId', card.data.id)
@@ -129,7 +126,7 @@ export const cardRouter = createTRPCRouter({
return { newLabel: false };
}
await ctx.supabase
await ctx.db
.from('_card_labels')
.insert({
cardId: card.data.id,
@@ -150,14 +147,14 @@ export const cardRouter = createTRPCRouter({
if (!userId) return;
const card = await ctx.supabase
const card = await ctx.db
.from('card')
.select(`id`)
.eq('publicId', input.cardPublicId)
.limit(1)
.single();
const member = await ctx.supabase
const member = await ctx.db
.from('workspace_members')
.select(`id`)
.eq('publicId', input.workspaceMemberPublicId)
@@ -166,7 +163,7 @@ export const cardRouter = createTRPCRouter({
if (!card.data || !member.data) return;
const existingMember = await ctx.supabase
const existingMember = await ctx.db
.from('_card_workspace_members')
.select()
.eq('cardId', card.data.id)
@@ -175,7 +172,7 @@ export const cardRouter = createTRPCRouter({
.single();
if (existingMember.data) {
await ctx.supabase
await ctx.db
.from('_card_workspace_members')
.delete()
.eq('cardId', card.data.id)
@@ -184,7 +181,7 @@ export const cardRouter = createTRPCRouter({
return { newMember: false };
}
await ctx.supabase
await ctx.db
.from('_card_workspace_members')
.insert({
cardId: card.data.id,
@@ -196,7 +193,7 @@ export const cardRouter = createTRPCRouter({
byId: publicProcedure
.input(z.object({ id: z.string().min(12) }))
.query(async ({ ctx, input }) => {
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('card')
.select(`
publicId,
@@ -262,7 +259,7 @@ export const cardRouter = createTRPCRouter({
if (!userId) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('card')
.update({ title: input.title, description: input.description })
.eq('publicId', input.cardId)
@@ -275,22 +272,37 @@ export const cardRouter = createTRPCRouter({
z.object({
cardPublicId: z.string().min(12),
}))
.mutation(({ ctx, input }) => {
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
where: eq(cards.publicId, input.cardPublicId),
})
const card = await ctx.db
.from('card')
.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
.input(
@@ -299,69 +311,54 @@ export const cardRouter = createTRPCRouter({
newListId: z.string().min(12),
newIndex: z.number().optional(),
}))
.mutation(({ ctx, input }) => {
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId) return;
return ctx.db.transaction(async (tx) => {
const card = await tx.query.cards.findFirst({
with: {
list: true,
},
where: and(eq(cards.publicId, input.cardId), isNull(cards.deletedAt)),
const card = await ctx.db
.from('card')
.select(`id, index, list (id)`)
.eq('publicId', input.cardId)
.is('deletedAt', null)
.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;
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)));
}
})
return { success: !!error }
})
});

View File

@@ -87,7 +87,7 @@ export const importRouter = createTRPCRouter({
if (!userId) return;
const newImport = await ctx.supabase
const newImport = await ctx.db
.from('import')
.insert({
publicId: generateUID(),
@@ -103,7 +103,7 @@ export const importRouter = createTRPCRouter({
let boardsCreated = 0;
const workspace = await ctx.supabase
const workspace = await ctx.db
.from('workspace')
.select(`id`)
.eq('publicId', input.workspacePublicId)
@@ -130,7 +130,7 @@ export const importRouter = createTRPCRouter({
}))
}
const newBoard = await ctx.supabase
const newBoard = await ctx.db
.from('board')
.insert({
publicId: generateUID(),
@@ -150,7 +150,7 @@ export const importRouter = createTRPCRouter({
let listIndex = 0;
for (const list of formattedData.lists) {
const newList = await ctx.supabase
const newList = await ctx.db
.from('list')
.insert({
publicId: generateUID(),
@@ -177,7 +177,7 @@ export const importRouter = createTRPCRouter({
importId: newImportId
}))
await ctx.supabase
await ctx.db
.from('card')
.insert(cardsInsert);
}
@@ -188,7 +188,7 @@ export const importRouter = createTRPCRouter({
}
if (boardsCreated > 0 && newImportId) {
await ctx.supabase
await ctx.db
.from('import')
.update({ status: 'success' })
.eq('importId', newImportId);

View File

@@ -20,7 +20,7 @@ export const labelRouter = createTRPCRouter({
if (!userId) return;
const card = await ctx.supabase
const card = await ctx.db
.from('card')
.select(`id, list (boardId)`)
.eq('publicId', input.cardPublicId)
@@ -30,7 +30,7 @@ export const labelRouter = createTRPCRouter({
if (!card.data?.list) return;
const newLabel = await ctx.supabase
const newLabel = await ctx.db
.from('label')
.insert({
publicId: generateUID(),
@@ -45,7 +45,7 @@ export const labelRouter = createTRPCRouter({
if (!newLabel.data) return;
await ctx.supabase
await ctx.db
.from('_card_labels')
.insert({
cardId: card.data.id,

View File

@@ -19,7 +19,7 @@ export const listRouter = createTRPCRouter({
if (!userId) return;
const board = await ctx.supabase
const board = await ctx.db
.from('board')
.select(`id, lists:list (index)`)
.eq('publicId', input.boardPublicId)
@@ -32,7 +32,7 @@ export const listRouter = createTRPCRouter({
const latestListIndex = board.data.lists[0]?.index
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('list')
.insert({
publicId: generateUID(),
@@ -61,7 +61,7 @@ export const listRouter = createTRPCRouter({
if (!userId) return;
const list = await ctx.supabase
const list = await ctx.db
.from('list')
.select(`id, boardId`)
.eq('publicId', input.listId)
@@ -70,7 +70,7 @@ export const listRouter = createTRPCRouter({
if (!list?.data) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.rpc('reorder_list', {
board_id: list.data.boardId,
list_id: list.data.id,
@@ -90,7 +90,7 @@ export const listRouter = createTRPCRouter({
if (!userId) return;
const list = await ctx.supabase
const list = await ctx.db
.from('list')
.select(`id, boardId, index`)
.eq('publicId', input.listPublicId)
@@ -101,19 +101,19 @@ export const listRouter = createTRPCRouter({
const deletedAt = new Date().toISOString();
await ctx.supabase
await ctx.db
.from('list')
.update({ deletedAt, deletedBy: userId })
.eq('id', list.data.id)
.is('deletedAt', null);
await ctx.supabase
await ctx.db
.from('card')
.update({ deletedAt, deletedBy: userId })
.eq('listId', list.data.id)
.is('deletedAt', null);
const { data } = await ctx.supabase
const { data } = await ctx.db
.rpc('shift_list_index', {
board_id: list.data.boardId,
list_index: list.data.index
@@ -132,7 +132,7 @@ export const listRouter = createTRPCRouter({
if (!userId) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('list')
.update({ name: input.name })
.eq('publicId', input.listPublicId)

View File

@@ -13,7 +13,7 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('workspace_members')
.select(`
role,
@@ -34,7 +34,7 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return;
const { data } = await ctx.supabase
const { data } = await ctx.db
.from('workspace')
.select(`
publicId,
@@ -66,7 +66,7 @@ export const workspaceRouter = createTRPCRouter({
if (!userId) return;
const workspace = await ctx.supabase
const workspace = await ctx.db
.from('workspace')
.insert({
publicId: generateUID(),
@@ -86,7 +86,7 @@ export const workspaceRouter = createTRPCRouter({
if (!newWorkspaceId) return;
await ctx.supabase
await ctx.db
.from('workspace_members')
.insert({
publicId: generateUID(),

View File

@@ -11,10 +11,9 @@ import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import superjson from "superjson";
import { ZodError } from "zod";
import { db } from "~/server/db";
import createClient from "~/utils/supabase/api";
import { type Database } from "~/types/database.types";
import { SupabaseClient } from '@supabase/supabase-js';
import { type SupabaseClient } from '@supabase/supabase-js';
/**
* 1. CONTEXT
@@ -30,8 +29,7 @@ type User = {
interface CreateContextOptions {
user: User | null;
supabase: SupabaseClient<Database>
db: any;
db: SupabaseClient<Database>
}
/**
@@ -48,8 +46,7 @@ interface CreateContextOptions {
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
user: opts.user,
supabase: opts.supabase,
db,
db: opts.db,
};
};
@@ -60,11 +57,11 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
* @see https://trpc.io/docs/context
*/
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 });
};
/**

View File

@@ -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')
}
},
}
],
});

View File

@@ -12,8 +12,6 @@ import {
bigint,
} from "drizzle-orm/pg-core";
import { type AdapterAccount } from "@auth/core/adapters";
export const importSourceEnum = pgEnum('source', ['trello']);
export const importStatusEnum = pgEnum('status', ['started', 'success', 'failed']);
export const memberRoleEnum = pgEnum('role', ['admin', 'member', 'guest']);
@@ -236,7 +234,6 @@ export const users = pgTable("user", {
});
export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
boards: many(boards),
cards: many(cards),
imports: many(imports),
@@ -244,61 +241,6 @@ export const usersRelations = relations(users, ({ many }) => ({
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(
"workspace",
{

View File

@@ -600,7 +600,17 @@ export type Database = {
[_ in never]: never
}
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: {
board_id: number
list_id: number
@@ -609,6 +619,13 @@ export type Database = {
}
Returns: undefined
}
shift_card_index: {
Args: {
list_id: number
card_index: number
}
Returns: undefined
}
shift_list_index: {
Args: {
board_id: number

View File

@@ -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
LANGUAGE SQL
AS $$
@@ -13,6 +13,41 @@ AS $$
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)
RETURNS VOID
LANGUAGE SQL
@@ -20,4 +55,14 @@ AS $$
UPDATE list
SET index = index - 1
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;
$$;