Compare commits

..

23 Commits

Author SHA1 Message Date
Henry
0a2f27a7e1 chore: translations 2026-02-01 21:03:32 +00:00
Henry
514692e200 refactor: centre permissions modal 2026-02-01 20:53:08 +00:00
Henry
247aa54a5c feat: allow delete:list as default 2026-01-30 14:55:21 +00:00
Henry
fc41d17bcb feat: allow admins to assign the admin role to other users 2026-01-29 22:07:50 +00:00
Henry
a2ef6609f2 feat: disable UI elements if user does not have permissions 2026-01-29 22:05:00 +00:00
Henry
2cb7c55d13 feat: set roleId when inviting new members 2026-01-29 21:07:50 +00:00
Henry
e3640542b3 feat: allow users to delete entities they have created 2026-01-29 20:50:38 +00:00
Henry
62725ffa38 feat: clear all permission overrides 2026-01-28 23:18:44 +00:00
Henry
95bfe41910 feat: reset role defaults 2026-01-28 23:00:10 +00:00
Henry
3369092509 feat: allow editing individual permissions 2026-01-28 22:42:04 +00:00
Henry
f210921d54 feat: order members by role and createdAt 2026-01-28 21:45:19 +00:00
Henry
303ca1409f feat: enable updating member roles 2026-01-28 21:32:46 +00:00
Henry
2a5e8c7651 feat: add permissions page to settings 2026-01-28 20:11:50 +00:00
Henry
0b734bd460 feat: assert permission for remaining routes 2026-01-28 12:03:21 +00:00
Henry
7239f958c0 feat: assert board permissions 2026-01-26 17:23:03 +00:00
Henry
3f81b9daa3 feat: assert permissions for lists 2026-01-26 16:53:58 +00:00
Henry
e86e59c817 feat: add card permissions 2026-01-26 16:44:53 +00:00
Henry
090028d827 feat: setup basic router interactions 2026-01-26 14:45:40 +00:00
Henry
9415daecd3 feat: add repo funcs 2026-01-26 14:38:44 +00:00
Henry
087f7fe216 feat: setup default permissions 2026-01-26 14:34:45 +00:00
Henry
5f84f7b47d feat: add publicId to workspace roles 2026-01-26 14:05:24 +00:00
Henry
398a94b566 chore: regen migration 2026-01-26 13:40:52 +00:00
Henry
e026757fac feat: setup schema for workspace roles 2026-01-26 13:23:25 +00:00
17 changed files with 48 additions and 6732 deletions

View File

@@ -47,7 +47,6 @@
"@trpc/react-query": "catalog:", "@trpc/react-query": "catalog:",
"@trpc/server": "catalog:", "@trpc/server": "catalog:",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^12.26.2",
"geist": "^1.3.1", "geist": "^1.3.1",
"jose": "^6.1.2", "jose": "^6.1.2",
"next": "15.5.9", "next": "15.5.9",

View File

@@ -4,8 +4,6 @@ import {
HiLink, HiLink,
HiOutlineDocumentDuplicate, HiOutlineDocumentDuplicate,
HiOutlineTrash, HiOutlineTrash,
HiOutlineStar,
HiStar,
} from "react-icons/hi2"; } from "react-icons/hi2";
import Dropdown from "~/components/Dropdown"; import Dropdown from "~/components/Dropdown";
@@ -18,86 +16,39 @@ export default function BoardDropdown({
isTemplate, isTemplate,
isLoading, isLoading,
boardPublicId, boardPublicId,
isFavorite, workspacePublicId,
boardName,
}: { }: {
isTemplate: boolean; isTemplate: boolean;
isLoading: boolean; isLoading: boolean;
boardPublicId: string; boardPublicId: string;
isFavorite?: boolean; workspacePublicId: string;
boardName?: string;
}) { }) {
const { openModal } = useModal(); const { openModal } = useModal();
const { canEditBoard, canDeleteBoard, canCreateBoard } = usePermissions(); const { canEditBoard, canDeleteBoard, canCreateBoard } = usePermissions();
const { showPopup } = usePopup();
const utils = api.useUtils();
const handleToggleFavorite = () => {
updateBoard.mutate({
boardPublicId,
favorite: !isFavorite,
});
};
const updateBoard = api.board.update.useMutation({
onSuccess: (data, variables) => {
void utils.board.all.invalidate();
void utils.board.byId.invalidate();
// Show popup notification
if (variables.favorite !== undefined) {
showPopup({
header: variables.favorite
? t`Added to favorites`
: t`Removed from favorites`,
message: variables.favorite
? t`${boardName ?? "Board"} has been added to your favorites.`
: t`${boardName ?? "Board"} has been removed from your favorites.`,
icon: "success",
});
}
},
onError: () => {
showPopup({
header: t`Unable to update board`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
});
const items = [ const items = [
...(isTemplate && canCreateBoard ...(isTemplate
? [ ? []
: [
{ {
label: t`Make template`, label: t`Make template`,
action: () => openModal("CREATE_TEMPLATE"), action: canCreateBoard ? () => openModal("CREATE_TEMPLATE") : undefined,
icon: ( icon: (
<HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" /> <HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" />
), ),
disabled: !canCreateBoard,
}, },
] ...(canEditBoard
: []), ? [
...(!isTemplate && canEditBoard {
? [ label: t`Edit board URL`,
{ action: () => openModal("UPDATE_BOARD_SLUG"),
label: t`Edit board URL`, icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
action: () => openModal("UPDATE_BOARD_SLUG"), },
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />, ]
}, : []),
] ]),
: []),
{
label: isFavorite
? t`Remove from favorites`
: t`Add to favorites`,
action: handleToggleFavorite,
icon: isFavorite ? (
<HiStar className="h-[16px] w-[16px] text-dark-900" />
) : (
<HiOutlineStar className="h-[16px] w-[16px] text-dark-900" />
),
},
...(canDeleteBoard ...(canDeleteBoard
? [ ? [
{ {
@@ -108,7 +59,6 @@ export default function BoardDropdown({
] ]
: []), : []),
]; ];
if (items.length === 0) { if (items.length === 0) {
return null; return null;

View File

@@ -493,8 +493,6 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
isLoading={!boardData} isLoading={!boardData}
boardPublicId={boardId ?? ""} boardPublicId={boardId ?? ""}
workspacePublicId={workspace.publicId} workspacePublicId={workspace.publicId}
isFavorite={boardData?.favorite}
boardName={boardData?.name}
/> />
</div> </div>
</div> </div>
@@ -596,12 +594,13 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
? `/templates/${boardId}/cards/${card.publicId}` ? `/templates/${boardId}/cards/${card.publicId}`
: `/cards/${card.publicId}` : `/cards/${card.publicId}`
} }
className={`mb-2 flex !cursor-pointer flex-col ${card.publicId.startsWith( className={`mb-2 flex !cursor-pointer flex-col ${
"PLACEHOLDER", card.publicId.startsWith(
) "PLACEHOLDER",
? "pointer-events-none" )
: "" ? "pointer-events-none"
}`} : ""
}`}
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}

View File

@@ -1,7 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import { HiOutlineRectangleStack, HiOutlineStar, HiStar } from "react-icons/hi2"; import { HiOutlineRectangleStack } from "react-icons/hi2";
import { motion } from "framer-motion";
import Button from "~/components/Button"; import Button from "~/components/Button";
import PatternedBackground from "~/components/PatternedBackground"; import PatternedBackground from "~/components/PatternedBackground";
import { Tooltip } from "~/components/Tooltip"; import { Tooltip } from "~/components/Tooltip";
@@ -15,13 +15,6 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
const { openModal } = useModal(); const { openModal } = useModal();
const { canCreateBoard } = usePermissions(); const { canCreateBoard } = usePermissions();
const utils = api.useUtils();
const updateBoard = api.board.update.useMutation({
onSuccess: () => {
void utils.board.all.invalidate();
},
});
const { data, isLoading } = api.board.all.useQuery( const { data, isLoading } = api.board.all.useQuery(
{ {
workspacePublicId: workspace.publicId, workspacePublicId: workspace.publicId,
@@ -30,20 +23,6 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
{ enabled: workspace.publicId ? true : false }, { enabled: workspace.publicId ? true : false },
); );
const handleToggleFavorite = (
e: React.MouseEvent,
boardPublicId: string,
currentFavorite: boolean | undefined
) => {
e.preventDefault();
e.stopPropagation();
updateBoard.mutate({
boardPublicId,
favorite: !currentFavorite,
});
};
if (isLoading) if (isLoading)
return ( return (
<div className="3xl:grid-cols-4 grid h-fit w-full grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3"> <div className="3xl:grid-cols-4 grid h-fit w-full grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3">
@@ -83,51 +62,20 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
); );
return ( return (
<motion.div <div className="3xl:grid-cols-4 grid h-fit w-full grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3">
className="3xl:grid-cols-4 grid h-fit w-full grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3"
layout
>
{data?.map((board) => ( {data?.map((board) => (
<motion.div <Link
key={board.publicId} key={board.publicId}
layout href={`${isTemplate ? "templates" : "boards"}/${board.publicId}`}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
layout: {
type: "spring",
stiffness: 300,
damping: 30,
mass: 1
},
opacity: { duration: 0.2 },
scale: { duration: 0.2 }
}}
> >
<Link <div className="align-center relative mr-5 flex h-[150px] w-full items-center justify-center rounded-md border border-dashed border-light-400 bg-light-50 shadow-sm hover:bg-light-200 dark:border-dark-600 dark:bg-dark-50 dark:hover:bg-dark-100">
href={`${isTemplate ? "templates" : "boards"}/${board.publicId}`} <PatternedBackground />
> <p className="px-4 text-[14px] font-bold text-neutral-700 dark:text-dark-1000">
<div className="group relative mr-5 flex h-[150px] w-full items-center justify-center rounded-md border border-dashed border-light-400 bg-light-50 shadow-sm hover:bg-light-200 dark:border-dark-600 dark:bg-dark-50 dark:hover:bg-dark-100"> {board.name}
<PatternedBackground /> </p>
<button </div>
onClick={(e) => handleToggleFavorite(e, board.publicId, board.favorite)} </Link>
className={`absolute right-3 top-3 z-10 rounded p-1 transition-all hover:bg-light-300 dark:hover:bg-dark-200 ${board.favorite ? "" : "md:opacity-0 md:group-hover:opacity-100"
}`}
aria-label={board.favorite ? "Remove from favorites" : "Add to favorites"}
>
{board.favorite ? (
<HiStar className="h-5 w-5 text-neutral-700 dark:text-dark-1000" />
) : (
<HiOutlineStar className="h-5 w-5 text-neutral-700 dark:text-dark-800" />
)}
</button>
<p className="px-4 text-[14px] font-bold text-neutral-700 dark:text-dark-1000">
{board.name}
</p>
</div>
</Link>
</motion.div>
))} ))}
</motion.div> </div>
); );
} }

View File

@@ -1,6 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import { PageHead } from "~/components/PageHead"; import { PageHead } from "~/components/PageHead";
import PatternedBackground from "~/components/PatternedBackground"; import PatternedBackground from "~/components/PatternedBackground";
import { api } from "~/utils/api"; import { api } from "~/utils/api";

View File

@@ -60,12 +60,9 @@ export const boardRouter = createTRPCRouter({
await assertPermission(ctx.db, userId, workspace.id, "board:view"); await assertPermission(ctx.db, userId, workspace.id, "board:view");
const result = boardRepo.getAllByWorkspaceId( const result = boardRepo.getAllByWorkspaceId(ctx.db, workspace.id, {
ctx.db, type: input.type,
workspace.id, });
userId,
{ type: input.type }
);
return result; return result;
}), }),
@@ -132,7 +129,6 @@ export const boardRouter = createTRPCRouter({
const result = await boardRepo.getByPublicId( const result = await boardRepo.getByPublicId(
ctx.db, ctx.db,
input.boardPublicId, input.boardPublicId,
userId,
{ {
members: input.members ?? [], members: input.members ?? [],
labels: input.labels ?? [], labels: input.labels ?? [],
@@ -279,7 +275,6 @@ export const boardRouter = createTRPCRouter({
const sourceBoard = await boardRepo.getByPublicId( const sourceBoard = await boardRepo.getByPublicId(
ctx.db, ctx.db,
input.sourceBoardPublicId, input.sourceBoardPublicId,
userId,
{ {
members: [], members: [],
labels: [], labels: [],
@@ -404,10 +399,9 @@ export const boardRouter = createTRPCRouter({
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/) .regex(/^(?![-]+$)[a-zA-Z0-9-]+$/)
.optional(), .optional(),
visibility: z.enum(["public", "private"]).optional(), visibility: z.enum(["public", "private"]).optional(),
favorite: z.boolean().optional()
}), }),
) )
.output(z.object({ success: z.boolean() }).or(z.custom<Awaited<ReturnType<typeof boardRepo.update>>>())) .output(z.custom<Awaited<ReturnType<typeof boardRepo.update>>>())
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id; const userId = ctx.user?.id;
@@ -436,23 +430,6 @@ export const boardRouter = createTRPCRouter({
board.createdBy ?? null, board.createdBy ?? null,
); );
// Handle favorite toggle separately
if (input.favorite !== undefined) {
if (input.favorite) {
await boardRepo.addUserFavorite(ctx.db, userId, board.id);
} else {
await boardRepo.removeUserFavorite(ctx.db, userId, board.id);
}
}
// Handle other updates (name, slug, visibility)
const hasOtherUpdates = input.name || input.slug || input.visibility !== undefined;
if (!hasOtherUpdates) {
// Only favorite was updated, return success
return { success: true };
}
if (input.slug) { if (input.slug) {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable( const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db, ctx.db,

View File

@@ -1,21 +0,0 @@
CREATE TABLE IF NOT EXISTS "user_board_favorites" (
"userId" uuid NOT NULL,
"boardId" bigint NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "user_board_favorites_userId_boardId_pk" PRIMARY KEY("userId","boardId")
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "user_board_favorites" ADD CONSTRAINT "user_board_favorites_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "user_board_favorites" ADD CONSTRAINT "user_board_favorites_boardId_board_id_fk" FOREIGN KEY ("boardId") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_board_favorite_user_idx" ON "user_board_favorites" USING btree ("userId");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_board_favorite_board_idx" ON "user_board_favorites" USING btree ("boardId");

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"id": "c24a8e89-c100-4483-9f0b-391201327ac5", "id": "c24a8e89-c100-4483-9f0b-391201327ac5",
"prevId": "3b54938c-c8d2-41c1-b4ba-a377719891bc", "prevId": "4e1ebb8b-bd52-48c5-87d6-8e6e5eb8bbde",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"tables": { "tables": {

File diff suppressed because it is too large Load Diff

View File

@@ -169,13 +169,6 @@
"when": 1769435949476, "when": 1769435949476,
"tag": "20260126135909_AddWorkspaceRoles", "tag": "20260126135909_AddWorkspaceRoles",
"breakpoints": true "breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1769983198190,
"tag": "20260201215958_AddUserBoardFavouritesTable",
"breakpoints": true
} }
] ]
} }

View File

@@ -26,7 +26,6 @@ import {
comments, comments,
labels, labels,
lists, lists,
userBoardFavorites,
workspaceMembers, workspaceMembers,
} from "@kan/db/schema"; } from "@kan/db/schema";
import { generateUID } from "@kan/shared/utils"; import { generateUID } from "@kan/shared/utils";
@@ -40,24 +39,17 @@ export const getCount = async (db: dbClient) => {
return result[0]?.count ?? 0; return result[0]?.count ?? 0;
}; };
export const getAllByWorkspaceId = async ( export const getAllByWorkspaceId = (
db: dbClient, db: dbClient,
workspaceId: number, workspaceId: number,
userId: string,
opts?: { type?: "regular" | "template" }, opts?: { type?: "regular" | "template" },
) => { ) => {
const boardsData = await db.query.boards.findMany({ return db.query.boards.findMany({
columns: { columns: {
publicId: true, publicId: true,
name: true, name: true,
}, },
with: { with: {
userFavorites: {
where: eq(userBoardFavorites.userId, userId),
columns: {
userId: true,
},
},
lists: { lists: {
columns: { columns: {
publicId: true, publicId: true,
@@ -80,21 +72,6 @@ export const getAllByWorkspaceId = async (
opts?.type ? eq(boards.type, opts.type) : undefined, opts?.type ? eq(boards.type, opts.type) : undefined,
), ),
}); });
// Transform and sort: favorites first, then alphabetically
return boardsData
.map((board) => ({
...board,
favorite: board.userFavorites.length > 0,
userFavorites: undefined,
}))
.sort((a, b) => {
// Sort favorites first
if (a.favorite && !b.favorite) return -1;
if (!a.favorite && b.favorite) return 1;
// Then alphabetically by name
return a.name.localeCompare(b.name);
});
}; };
export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => { export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => {
@@ -145,7 +122,6 @@ const buildDueDateWhere = (filters: DueDateFilter[]) => {
export const getByPublicId = async ( export const getByPublicId = async (
db: dbClient, db: dbClient,
boardPublicId: string, boardPublicId: string,
userId: string,
filters: { filters: {
members: string[]; members: string[];
labels: string[]; labels: string[];
@@ -197,12 +173,6 @@ export const getByPublicId = async (
visibility: true, visibility: true,
}, },
with: { with: {
userFavorites: {
where: eq(userBoardFavorites.userId, userId),
columns: {
userId: true,
},
},
workspace: { workspace: {
columns: { columns: {
publicId: true, publicId: true,
@@ -355,8 +325,6 @@ export const getByPublicId = async (
const formattedResult = { const formattedResult = {
...board, ...board,
favorite: board.userFavorites.length > 0,
userFavorites: undefined,
lists: board.lists.map((list) => ({ lists: board.lists.map((list) => ({
...list, ...list,
cards: list.cards.map((card) => ({ cards: list.cards.map((card) => ({
@@ -956,34 +924,3 @@ export const createFromSnapshot = async (
return newBoard; return newBoard;
}); });
}; };
export const addUserFavorite = async (
db: dbClient,
userId: string,
boardId: number,
) => {
return db
.insert(userBoardFavorites)
.values({
userId,
boardId,
})
.onConflictDoNothing()
.returning();
};
export const removeUserFavorite = async (
db: dbClient,
userId: string,
boardId: number,
) => {
return db
.delete(userBoardFavorites)
.where(
and(
eq(userBoardFavorites.userId, userId),
eq(userBoardFavorites.boardId, boardId)
)
)
.returning();
};

View File

@@ -93,7 +93,7 @@ export const create = async (
index: index, index: index,
dueDate: cardInput.dueDate ?? null, dueDate: cardInput.dueDate ?? null,
}) })
.returning({ id: cards.id, listId: cards.listId, publicId: cards.publicId }); .returning({ id: cards.id, listId: cards.listId });
if (!result[0]) throw new Error("Unable to create card"); if (!result[0]) throw new Error("Unable to create card");

View File

@@ -6,7 +6,6 @@ import {
ilike, ilike,
inArray, inArray,
isNull, isNull,
asc,
or, or,
sql, sql,
} from "drizzle-orm"; } from "drizzle-orm";
@@ -186,8 +185,6 @@ export const getByPublicIdWithMembers = (
columns: { columns: {
id: true, id: true,
publicId: true, publicId: true,
name: true,
slug: true,
showEmailsToMembers: true, showEmailsToMembers: true,
}, },
with: { with: {
@@ -251,7 +248,6 @@ export const getBySlugWithBoards = (db: dbClient, workspaceSlug: string) => {
name: true, name: true,
}, },
where: and(isNull(boards.deletedAt), eq(boards.visibility, "public")), where: and(isNull(boards.deletedAt), eq(boards.visibility, "public")),
orderBy: [asc(boards.name)]
}, },
}, },
where: and( where: and(

View File

@@ -2,11 +2,9 @@ import { relations, sql } from "drizzle-orm";
import { import {
bigint, bigint,
bigserial, bigserial,
boolean,
index, index,
pgEnum, pgEnum,
pgTable, pgTable,
primaryKey,
text, text,
timestamp, timestamp,
uniqueIndex, uniqueIndex,
@@ -69,7 +67,6 @@ export const boards = pgTable(
).enableRLS(); ).enableRLS();
export const boardsRelations = relations(boards, ({ one, many }) => ({ export const boardsRelations = relations(boards, ({ one, many }) => ({
userFavorites: many(userBoardFavorites),
createdBy: one(users, { createdBy: one(users, {
fields: [boards.createdBy], fields: [boards.createdBy],
references: [users.id], references: [users.id],
@@ -94,21 +91,3 @@ export const boardsRelations = relations(boards, ({ one, many }) => ({
relationName: "boardWorkspace", relationName: "boardWorkspace",
}), }),
})); }));
export const userBoardFavorites = pgTable(
"user_board_favorites",
{
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
boardId: bigint("boardId", { mode: "number" })
.notNull()
.references(() => boards.id, { onDelete: "cascade" }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
},
(table) => ({
pk: primaryKey({ columns: [table.userId, table.boardId] }),
userIdx: index("user_board_favorite_user_idx").on(table.userId),
boardIdx: index("user_board_favorite_board_idx").on(table.boardId),
}),
);

View File

@@ -8,7 +8,7 @@ import {
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { apikey } from "./auth"; import { apikey } from "./auth";
import { boards, userBoardFavorites } from "./boards"; import { boards } from "./boards";
import { cards } from "./cards"; import { cards } from "./cards";
import { imports } from "./imports"; import { imports } from "./imports";
import { lists } from "./lists"; import { lists } from "./lists";
@@ -84,14 +84,3 @@ export const usersToWorkspacesRelations = relations(
}), }),
}), }),
); );
export const userBoardFavoritesRelations = relations(userBoardFavorites, ({ one }) => ({
user: one(users, {
fields: [userBoardFavorites.userId],
references: [users.id],
}),
board: one(boards, {
fields: [userBoardFavorites.boardId],
references: [boards.id],
}),
}));

38
pnpm-lock.yaml generated
View File

@@ -163,9 +163,6 @@ importers:
date-fns: date-fns:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
framer-motion:
specifier: ^12.26.2
version: 12.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
geist: geist:
specifier: ^1.3.1 specifier: ^1.3.1
version: 1.4.2(next@15.5.9(@babel/core@7.28.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) version: 1.4.2(next@15.5.9(@babel/core@7.28.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -5485,20 +5482,6 @@ packages:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'} engines: {node: '>=0.4.x'}
framer-motion@12.26.2:
resolution: {integrity: sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fs-extra@10.1.0: fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -6611,12 +6594,6 @@ packages:
moo@0.5.2: moo@0.5.2:
resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
motion-dom@12.26.2:
resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==}
motion-utils@12.24.10:
resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==}
mri@1.2.0: mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -13978,15 +13955,6 @@ snapshots:
format@0.2.2: {} format@0.2.2: {}
framer-motion@12.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
motion-dom: 12.26.2
motion-utils: 12.24.10
tslib: 2.8.1
optionalDependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
fs-extra@10.1.0: fs-extra@10.1.0:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -15536,12 +15504,6 @@ snapshots:
moo@0.5.2: {} moo@0.5.2: {}
motion-dom@12.26.2:
dependencies:
motion-utils: 12.24.10
motion-utils@12.24.10: {}
mri@1.2.0: {} mri@1.2.0: {}
ms@2.1.3: {} ms@2.1.3: {}