From 7e339c09c86cafef56ea3bf915c8665b9e6e0c95 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sat, 18 May 2024 16:57:00 +0200 Subject: [PATCH] feat: add home board for users (#505) * feat: add home board for users * fix: format issues * fix: deepsource issue * chore: address pull request feedback * fix: typecheck issue --- .../{(default) => (home)}/_definition.ts | 2 +- .../{(default) => (home)}/layout.tsx | 0 .../(content)/{(default) => (home)}/page.tsx | 0 .../app/[locale]/boards/(content)/_client.tsx | 4 +- .../[locale]/boards/(content)/_context.tsx | 2 +- .../[locale]/boards/(content)/_creator.tsx | 12 +- .../boards/[name]/settings/_danger.tsx | 4 +- .../boards/[name]/settings/_general.tsx | 5 +- .../boards/[name]/settings/_shared.tsx | 2 +- apps/nextjs/src/app/[locale]/boards/_types.ts | 2 +- .../src/app/[locale]/manage/about/page.tsx | 4 +- .../_components/board-card-menu-dropdown.tsx | 43 +- .../src/app/[locale]/manage/boards/page.tsx | 35 +- apps/nextjs/src/app/[locale]/manage/page.tsx | 4 +- .../app/[locale]/manage/tools/logs/page.tsx | 4 +- .../manage/users/[userId]/general/page.tsx | 4 +- .../app/[locale]/manage/users/create/page.tsx | 4 +- .../src/app/[locale]/manage/users/page.tsx | 4 +- .../board/modals/board-rename-modal.tsx | 2 +- .../src/components/user-avatar-menu.tsx | 6 +- apps/nextjs/src/metadata.ts | 1 + packages/api/src/router/board.ts | 43 +- packages/api/src/router/test/board.spec.ts | 13 +- packages/api/src/router/test/user.spec.ts | 5 + .../{mysql.config.ts.ts => mysql.config.ts} | 0 .../mysql/0001_wild_alex_wilder.sql | 2 + .../migrations/mysql/meta/0001_snapshot.json | 1166 +++++++++++++++++ .../db/migrations/mysql/meta/_journal.json | 7 + .../sqlite/0001_mixed_titanium_man.sql | 33 + .../migrations/sqlite/meta/0001_snapshot.json | 1114 ++++++++++++++++ .../db/migrations/sqlite/meta/_journal.json | 7 + packages/db/schema/mysql.ts | 7 + packages/db/schema/sqlite.ts | 7 + packages/db/test/db-mock.ts | 9 +- packages/translation/src/lang/en.ts | 12 +- packages/widgets/src/server/runner.tsx | 2 +- 36 files changed, 2509 insertions(+), 62 deletions(-) rename apps/nextjs/src/app/[locale]/boards/(content)/{(default) => (home)}/_definition.ts (81%) rename apps/nextjs/src/app/[locale]/boards/(content)/{(default) => (home)}/layout.tsx (100%) rename apps/nextjs/src/app/[locale]/boards/(content)/{(default) => (home)}/page.tsx (100%) create mode 100644 apps/nextjs/src/metadata.ts rename packages/db/configs/{mysql.config.ts.ts => mysql.config.ts} (100%) create mode 100644 packages/db/migrations/mysql/0001_wild_alex_wilder.sql create mode 100644 packages/db/migrations/mysql/meta/0001_snapshot.json create mode 100644 packages/db/migrations/sqlite/0001_mixed_titanium_man.sql create mode 100644 packages/db/migrations/sqlite/meta/0001_snapshot.json diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/(default)/_definition.ts b/apps/nextjs/src/app/[locale]/boards/(content)/(home)/_definition.ts similarity index 81% rename from apps/nextjs/src/app/[locale]/boards/(content)/(default)/_definition.ts rename to apps/nextjs/src/app/[locale]/boards/(content)/(home)/_definition.ts index 79a6a42c2..2357d7424 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/(default)/_definition.ts +++ b/apps/nextjs/src/app/[locale]/boards/(content)/(home)/_definition.ts @@ -4,6 +4,6 @@ import { createBoardContentPage } from "../_creator"; export default createBoardContentPage<{ locale: string }>({ async getInitialBoardAsync() { - return await api.board.getDefaultBoard(); + return await api.board.getHomeBoard(); }, }); diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/(default)/layout.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/(home)/layout.tsx similarity index 100% rename from apps/nextjs/src/app/[locale]/boards/(content)/(default)/layout.tsx rename to apps/nextjs/src/app/[locale]/boards/(content)/(home)/layout.tsx diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/(default)/page.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/(home)/page.tsx similarity index 100% rename from apps/nextjs/src/app/[locale]/boards/(content)/(default)/page.tsx rename to apps/nextjs/src/app/[locale]/boards/(content)/(home)/page.tsx diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx index 3faac9877..1a7fe0cc1 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx @@ -19,8 +19,8 @@ export const updateBoardName = (name: string | null) => { }; type UpdateCallback = ( - prev: RouterOutputs["board"]["getDefaultBoard"], -) => RouterOutputs["board"]["getDefaultBoard"]; + prev: RouterOutputs["board"]["getHomeBoard"], +) => RouterOutputs["board"]["getHomeBoard"]; export const useUpdateBoard = () => { const utils = clientApi.useUtils(); diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx index 5fbb6886d..ae12b472f 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx @@ -16,7 +16,7 @@ import { clientApi } from "@homarr/api/client"; import { updateBoardName } from "./_client"; const BoardContext = createContext<{ - board: RouterOutputs["board"]["getDefaultBoard"]; + board: RouterOutputs["board"]["getHomeBoard"]; isReady: boolean; markAsReady: (id: string) => void; } | null>(null); diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index badc80a07..55eada4b2 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; import { TRPCError } from "@trpc/server"; -import { capitalize } from "@homarr/common"; - // Placed here because gridstack styles are used for board content import "~/styles/gridstack.scss"; +import { getI18n } from "@homarr/translation/server"; + +import { createMetaTitle } from "~/metadata"; import { createBoardLayout } from "../_layout-creator"; import type { Board } from "../_types"; import { ClientBoard } from "./_client"; @@ -38,9 +39,14 @@ export const createBoardContentPage = < }): Promise => { try { const board = await getInitialBoard(params); + const t = await getI18n(); return { - title: board.metaTitle ?? `${capitalize(board.name)} board | Homarr`, + title: + board.metaTitle ?? + createMetaTitle( + t("board.content.metaTitle", { boardName: board.name }), + ), icons: { icon: board.faviconImageUrl ? board.faviconImageUrl : undefined, }, diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_danger.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_danger.tsx index 6635d0ba3..a54b65e58 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_danger.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_danger.tsx @@ -52,7 +52,7 @@ export const DangerZoneSettingsContent = () => { { onSettled() { void utils.board.getBoardByName.invalidate({ name: board.name }); - void utils.board.getDefaultBoard.invalidate(); + void utils.board.getHomeBoard.invalidate(); }, }, ); @@ -64,7 +64,7 @@ export const DangerZoneSettingsContent = () => { changeVisibility, t, utils.board.getBoardByName, - utils.board.getDefaultBoard, + utils.board.getHomeBoard, visibility, openConfirmModal, ]); diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx index a47624d9e..9ecc9141b 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx @@ -21,6 +21,7 @@ import { useZodForm } from "@homarr/form"; import { useI18n } from "@homarr/translation/client"; import { validation } from "@homarr/validation"; +import { createMetaTitle } from "~/metadata"; import type { Board } from "../../_types"; import { useUpdateBoard } from "../../(content)/_client"; import { useSavePartialSettingsMutation } from "./_shared"; @@ -105,7 +106,9 @@ export const GeneralSettingsContent = ({ board }: Props) => { } {...form.getInputProps("metaTitle")} /> diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_shared.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_shared.tsx index 1097a8e13..9fa888be2 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_shared.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_shared.tsx @@ -7,7 +7,7 @@ export const useSavePartialSettingsMutation = (board: Board) => { return clientApi.board.savePartialBoardSettings.useMutation({ onSettled() { void utils.board.getBoardByName.invalidate({ name: board.name }); - void utils.board.getDefaultBoard.invalidate(); + void utils.board.getHomeBoard.invalidate(); }, }); }; diff --git a/apps/nextjs/src/app/[locale]/boards/_types.ts b/apps/nextjs/src/app/[locale]/boards/_types.ts index dc5828171..4596bff1a 100644 --- a/apps/nextjs/src/app/[locale]/boards/_types.ts +++ b/apps/nextjs/src/app/[locale]/boards/_types.ts @@ -1,7 +1,7 @@ import type { RouterOutputs } from "@homarr/api"; import type { WidgetKind } from "@homarr/definitions"; -export type Board = RouterOutputs["board"]["getDefaultBoard"]; +export type Board = RouterOutputs["board"]["getHomeBoard"]; export type Section = Board["sections"][number]; export type Item = Section["items"][number]; diff --git a/apps/nextjs/src/app/[locale]/manage/about/page.tsx b/apps/nextjs/src/app/[locale]/manage/about/page.tsx index 2805c565c..170fa7f1e 100644 --- a/apps/nextjs/src/app/[locale]/manage/about/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/about/page.tsx @@ -21,6 +21,7 @@ import { setStaticParamsLocale } from "next-international/server"; import { getScopedI18n, getStaticParams } from "@homarr/translation/server"; +import { createMetaTitle } from "~/metadata"; import { getPackageAttributesAsync } from "~/versions/package-reader"; import contributorsData from "../../../../../../../static-data/contributors.json"; import translatorsData from "../../../../../../../static-data/translators.json"; @@ -29,10 +30,9 @@ import classes from "./about.module.css"; export async function generateMetadata() { const t = await getScopedI18n("management"); - const metaTitle = `${t("metaTitle")} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle")), }; } diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx index 6a97c81ca..51709545e 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx @@ -3,7 +3,7 @@ import { useCallback } from "react"; import Link from "next/link"; import { Menu } from "@mantine/core"; -import { IconSettings, IconTrash } from "@tabler/icons-react"; +import { IconHome, IconSettings, IconTrash } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; @@ -40,7 +40,13 @@ export const BoardCardMenuDropdown = ({ const { openConfirmModal } = useConfirmModal(); - const { mutateAsync, isPending } = clientApi.board.deleteBoard.useMutation({ + const setHomeBoardMutation = clientApi.board.setHomeBoard.useMutation({ + onSettled: async () => { + // Revalidate all as it's part of the user settings, /boards page and board manage page + await revalidatePathActionAsync("/"); + }, + }); + const deleteBoardMutation = clientApi.board.deleteBoard.useMutation({ onSettled: async () => { await revalidatePathActionAsync("/manage/boards"); }, @@ -54,23 +60,36 @@ export const BoardCardMenuDropdown = ({ }), // eslint-disable-next-line no-restricted-syntax onConfirm: async () => { - await mutateAsync({ + await deleteBoardMutation.mutateAsync({ id: board.id, }); }, }); - }, [board.id, board.name, mutateAsync, openConfirmModal, t]); + }, [board.id, board.name, deleteBoardMutation, openConfirmModal, t]); + + const handleSetHomeBoard = useCallback(async () => { + await setHomeBoardMutation.mutateAsync({ id: board.id }); + }, [board.id, setHomeBoardMutation]); return ( + } + > + {t("setHomeBoard.label")} + {hasChangeAccess && ( - } - > - {t("settings.label")} - + <> + + } + > + {t("settings.label")} + + )} {hasFullAccess && ( <> @@ -80,7 +99,7 @@ export const BoardCardMenuDropdown = ({ c="red.7" leftSection={} onClick={handleDeletion} - disabled={isPending} + disabled={deleteBoardMutation.isPending} > {t("delete.label")} diff --git a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx index ac254468d..b4bec8053 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { ActionIcon, + Badge, Button, Card, CardSection, @@ -13,7 +14,12 @@ import { Title, Tooltip, } from "@mantine/core"; -import { IconDotsVertical, IconLock, IconWorld } from "@tabler/icons-react"; +import { + IconDotsVertical, + IconHomeFilled, + IconLock, + IconWorld, +} from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; @@ -71,12 +77,27 @@ const BoardCard = async ({ board }: BoardCardProps) => { - {board.creator && ( - - - {board.creator?.name} - - )} + + {board.isHome && ( + + } + > + {t("action.setHomeBoard.badge.label")} + + + )} + + {board.creator && ( + + + {board.creator?.name} + + )} + diff --git a/apps/nextjs/src/app/[locale]/manage/page.tsx b/apps/nextjs/src/app/[locale]/manage/page.tsx index 6c1bf2b84..d16a8184b 100644 --- a/apps/nextjs/src/app/[locale]/manage/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/page.tsx @@ -5,6 +5,7 @@ import { IconArrowRight } from "@tabler/icons-react"; import { api } from "@homarr/api/server"; import { getScopedI18n } from "@homarr/translation/server"; +import { createMetaTitle } from "~/metadata"; import { HeroBanner } from "./_components/hero-banner"; interface LinkProps { @@ -16,10 +17,9 @@ interface LinkProps { export async function generateMetadata() { const t = await getScopedI18n("management"); - const metaTitle = `${t("metaTitle")} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle")), }; } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx index f290e4e85..d6cf53924 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx @@ -7,13 +7,13 @@ import "@xterm/xterm/css/xterm.css"; import dynamic from "next/dynamic"; import { fullHeightWithoutHeaderAndFooter } from "~/constants"; +import { createMetaTitle } from "~/metadata"; export async function generateMetadata() { const t = await getScopedI18n("management"); - const metaTitle = `${t("metaTitle")} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle")), }; } diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx index cb6bdcd6f..9b964dd45 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx @@ -10,6 +10,7 @@ import { DangerZoneRoot, } from "~/components/manage/danger-zone"; import { catchTrpcNotFound } from "~/errors/trpc-not-found"; +import { createMetaTitle } from "~/metadata"; import { canAccessUserEditPage } from "../access"; import { DeleteUserButton } from "./_components/_delete-user-button"; import { UserProfileAvatarForm } from "./_components/_profile-avatar-form"; @@ -35,10 +36,9 @@ export async function generateMetadata({ params }: Props) { } const t = await getScopedI18n("management.page.user.edit"); - const metaTitle = `${t("metaTitle", { username: user?.name })} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle", { username: user?.name })), }; } diff --git a/apps/nextjs/src/app/[locale]/manage/users/create/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/create/page.tsx index 5f5d28c07..b44c17b6c 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/create/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/create/page.tsx @@ -1,13 +1,13 @@ import { getScopedI18n } from "@homarr/translation/server"; +import { createMetaTitle } from "~/metadata"; import { UserCreateStepperComponent } from "./_components/create-user-stepper"; export async function generateMetadata() { const t = await getScopedI18n("management.page.user.create"); - const metaTitle = `${t("metaTitle")} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle")), }; } diff --git a/apps/nextjs/src/app/[locale]/manage/users/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/page.tsx index 6d5f4f3d5..61a143f5a 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/page.tsx @@ -1,14 +1,14 @@ import { api } from "@homarr/api/server"; import { getScopedI18n } from "@homarr/translation/server"; +import { createMetaTitle } from "~/metadata"; import { UserListComponent } from "./_components/user-list.component"; export async function generateMetadata() { const t = await getScopedI18n("management.page.user.list"); - const metaTitle = `${t("metaTitle")} • Homarr`; return { - title: metaTitle, + title: createMetaTitle(t("metaTitle")), }; } diff --git a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx index 47387f5ef..d77d76fd0 100644 --- a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx +++ b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx @@ -24,7 +24,7 @@ export const BoardRenameModal = createModal( void utils.board.getBoardByName.invalidate({ name: innerProps.previousName, }); - void utils.board.getDefaultBoard.invalidate(); + void utils.board.getHomeBoard.invalidate(); }, }); const form = useZodForm(validation.board.rename.omit({ id: true }), { diff --git a/apps/nextjs/src/components/user-avatar-menu.tsx b/apps/nextjs/src/components/user-avatar-menu.tsx index 04a6b1480..06033c9a0 100644 --- a/apps/nextjs/src/components/user-avatar-menu.tsx +++ b/apps/nextjs/src/components/user-avatar-menu.tsx @@ -14,7 +14,7 @@ import { import { useTimeout } from "@mantine/hooks"; import { IconCheck, - IconDashboard, + IconHome, IconLogin, IconLogout, IconMoon, @@ -72,9 +72,9 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => { } + leftSection={} > - {t("navigateDefaultBoard")} + {t("homeBoard")} diff --git a/apps/nextjs/src/metadata.ts b/apps/nextjs/src/metadata.ts new file mode 100644 index 000000000..856ecc9d3 --- /dev/null +++ b/apps/nextjs/src/metadata.ts @@ -0,0 +1 @@ +export const createMetaTitle = (name: string) => `${name} • Homarr`; diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index b0b9e90eb..e9287b821 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -12,6 +12,7 @@ import { integrationItems, items, sections, + users, } from "@homarr/db/schema/sqlite"; import type { WidgetKind } from "@homarr/definitions"; import { getPermissionsWithParents, widgetKinds } from "@homarr/definitions"; @@ -33,14 +34,15 @@ import { throwIfActionForbiddenAsync } from "./board/board-access"; export const boardRouter = createTRPCRouter({ getAllBoards: publicProcedure.query(async ({ ctx }) => { + const userId = ctx.session?.user.id; const permissionsOfCurrentUserWhenPresent = await ctx.db.query.boardUserPermissions.findMany({ - where: eq(boardUserPermissions.userId, ctx.session?.user.id ?? ""), + where: eq(boardUserPermissions.userId, userId ?? ""), }); const permissionsOfCurrentUserGroupsWhenPresent = await ctx.db.query.groupMembers.findMany({ - where: eq(groupMembers.userId, ctx.session?.user.id ?? ""), + where: eq(groupMembers.userId, userId ?? ""), with: { group: { with: { @@ -60,6 +62,11 @@ export const boardRouter = createTRPCRouter({ ) .flat(), ); + + const currentUserWhenPresent = await ctx.db.query.users.findFirst({ + where: eq(users.id, userId ?? ""), + }); + const dbBoards = await ctx.db.query.boards.findMany({ columns: { id: true, @@ -98,7 +105,10 @@ export const boardRouter = createTRPCRouter({ boardIds.length > 0 ? inArray(boards.id, boardIds) : undefined, ), }); - return dbBoards; + return dbBoards.map((board) => ({ + ...board, + isHome: currentUserWhenPresent?.homeBoardId === board.id, + })); }), createBoard: permissionRequiredProcedure .requiresPermission("board-create") @@ -160,8 +170,31 @@ export const boardRouter = createTRPCRouter({ await ctx.db.delete(boards).where(eq(boards.id, input.id)); }), - getDefaultBoard: publicProcedure.query(async ({ ctx }) => { - const boardWhere = eq(boards.name, "default"); + setHomeBoard: protectedProcedure + .input(z.object({ id: z.string() })) + .mutation(async ({ ctx, input }) => { + await throwIfActionForbiddenAsync( + ctx, + eq(boards.id, input.id), + "board-view", + ); + + await ctx.db + .update(users) + .set({ homeBoardId: input.id }) + .where(eq(users.id, ctx.session.user.id)); + }), + getHomeBoard: publicProcedure.query(async ({ ctx }) => { + const userId = ctx.session?.user.id; + const user = userId + ? await ctx.db.query.users.findFirst({ + where: eq(users.id, userId), + }) + : null; + + const boardWhere = user?.homeBoardId + ? eq(boards.id, user.homeBoardId) + : eq(boards.name, "home"); await throwIfActionForbiddenAsync(ctx, boardWhere, "board-view"); return await getFullBoardWithWhereAsync( diff --git a/packages/api/src/router/test/board.spec.ts b/packages/api/src/router/test/board.spec.ts index 7ae949058..4b62008a9 100644 --- a/packages/api/src/router/test/board.spec.ts +++ b/packages/api/src/router/test/board.spec.ts @@ -41,6 +41,7 @@ const createRandomUserAsync = async (db: Database) => { const userId = createId(); await db.insert(users).values({ id: userId, + homeBoardId: null, }); return userId; }; @@ -493,21 +494,21 @@ describe("deleteBoard should delete board", () => { }); }); -describe("getDefaultBoard should return default board", () => { - it("should return default board", async () => { +describe("getHomeBoard should return home board", () => { + it("should return home board", async () => { // Arrange const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync"); const db = createDb(); const caller = boardRouter.createCaller({ db, session: defaultSession }); - const fullBoardProps = await createFullBoardAsync(db, "default"); + const fullBoardProps = await createFullBoardAsync(db, "home"); // Act - const result = await caller.getDefaultBoard(); + const result = await caller.getHomeBoard(); // Assert expectInputToBeFullBoardWithName(result, { - name: "default", + name: "home", ...fullBoardProps, }); expect(spy).toHaveBeenCalledWith( @@ -1339,7 +1340,7 @@ describe("saveGroupBoardPermissions should save group board permissions", () => }); const expectInputToBeFullBoardWithName = ( - input: RouterOutputs["board"]["getDefaultBoard"], + input: RouterOutputs["board"]["getHomeBoard"], props: { name: string } & Awaited>, ) => { expect(input.id).toBe(props.boardId); diff --git a/packages/api/src/router/test/user.spec.ts b/packages/api/src/router/test/user.spec.ts index 95901ac82..9dfee320c 100644 --- a/packages/api/src/router/test/user.spec.ts +++ b/packages/api/src/router/test/user.spec.ts @@ -232,6 +232,7 @@ describe("editProfile shoud update user", () => { salt: null, password: null, image: null, + homeBoardId: null, }); }); @@ -274,6 +275,7 @@ describe("editProfile shoud update user", () => { salt: null, password: null, image: null, + homeBoardId: null, }); }); }); @@ -297,6 +299,7 @@ describe("delete should delete user", () => { image: null, password: null, salt: null, + homeBoardId: null, }, { id: userToDelete, @@ -306,6 +309,7 @@ describe("delete should delete user", () => { image: null, password: null, salt: null, + homeBoardId: null, }, { id: createId(), @@ -315,6 +319,7 @@ describe("delete should delete user", () => { image: null, password: null, salt: null, + homeBoardId: null, }, ]; diff --git a/packages/db/configs/mysql.config.ts.ts b/packages/db/configs/mysql.config.ts similarity index 100% rename from packages/db/configs/mysql.config.ts.ts rename to packages/db/configs/mysql.config.ts diff --git a/packages/db/migrations/mysql/0001_wild_alex_wilder.sql b/packages/db/migrations/mysql/0001_wild_alex_wilder.sql new file mode 100644 index 000000000..9c3c23f31 --- /dev/null +++ b/packages/db/migrations/mysql/0001_wild_alex_wilder.sql @@ -0,0 +1,2 @@ +ALTER TABLE `user` ADD `homeBoardId` varchar(64);--> statement-breakpoint +ALTER TABLE `user` ADD CONSTRAINT `user_homeBoardId_board_id_fk` FOREIGN KEY (`homeBoardId`) REFERENCES `board`(`id`) ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/migrations/mysql/meta/0001_snapshot.json b/packages/db/migrations/mysql/meta/0001_snapshot.json new file mode 100644 index 000000000..1ba3799f4 --- /dev/null +++ b/packages/db/migrations/mysql/meta/0001_snapshot.json @@ -0,0 +1,1166 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "ba2dd885-4e7f-4a45-99a0-7b45cbd0a5c2", + "prevId": "fdeaf6eb-cd62-4fa5-9b38-d7f80a60db9f", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "app_id": { + "name": "app_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "name": "boardGroupPermission_board_id_group_id_permission_pk", + "columns": ["board_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "name": "boardUserPermission_board_id_user_id_permission_pk", + "columns": ["board_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('fixed')" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('no-repeat')" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('cover')" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fa5252')" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fd7e14')" + }, + "opacity": { + "name": "opacity", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": {}, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "board_id": { + "name": "board_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"] + } + } + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "name": "groupMember_groupId_userId_pk", + "columns": ["groupId", "userId"] + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_id": { + "name": "group_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "iconRepository_iconRepository_id": { + "name": "iconRepository_iconRepository_id", + "columns": ["iconRepository_id"] + } + }, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "varchar(250)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "icon_icon_id": { + "name": "icon_icon_id", + "columns": ["icon_id"] + } + }, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "name": "integration_item_item_id_integration_id_pk", + "columns": ["item_id", "integration_id"] + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "name": "integrationSecret_integration_id_kind_pk", + "columns": ["integration_id", "kind"] + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "integration_id": { + "name": "integration_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "invite_id": { + "name": "invite_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"] + } + } + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_id": { + "name": "item_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_id": { + "name": "section_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_sessionToken": { + "name": "session_sessionToken", + "columns": ["sessionToken"] + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {} + } + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json index 72ae1f1b7..e8f56548b 100644 --- a/packages/db/migrations/mysql/meta/_journal.json +++ b/packages/db/migrations/mysql/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1715334452118, "tag": "0000_harsh_photon", "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1715885855801, + "tag": "0001_wild_alex_wilder", + "breakpoints": true } ] } diff --git a/packages/db/migrations/sqlite/0001_mixed_titanium_man.sql b/packages/db/migrations/sqlite/0001_mixed_titanium_man.sql new file mode 100644 index 000000000..66b30f3e5 --- /dev/null +++ b/packages/db/migrations/sqlite/0001_mixed_titanium_man.sql @@ -0,0 +1,33 @@ +COMMIT TRANSACTION; +--> statement-breakpoint +PRAGMA foreign_keys = OFF; +--> statement-breakpoint +BEGIN TRANSACTION; +--> statement-breakpoint +ALTER TABLE `user` RENAME TO `__user_old`; +--> statement-breakpoint +CREATE TABLE `user` ( + `id` text PRIMARY KEY NOT NULL, + `name` text, + `email` text, + `emailVerified` integer, + `image` text, + `password` text, + `salt` text, + `homeBoardId` text, + FOREIGN KEY (`homeBoardId`) REFERENCES `board`(`id`) ON UPDATE no action ON DELETE set null +); +--> statement-breakpoint +INSERT INTO `user` SELECT `id`, `name`, `email`, `emailVerified`, `image`, `password`, `salt`, null FROM `__user_old`; +--> statement-breakpoint +DROP TABLE `__user_old`; +--> statement-breakpoint +ALTER TABLE `user` RENAME TO `__user_old`; +--> statement-breakpoint +ALTER TABLE `__user_old` RENAME TO `user`; +--> statement-breakpoint +COMMIT TRANSACTION; +--> statement-breakpoint +PRAGMA foreign_keys = ON; +--> statement-breakpoint +BEGIN TRANSACTION; \ No newline at end of file diff --git a/packages/db/migrations/sqlite/meta/0001_snapshot.json b/packages/db/migrations/sqlite/meta/0001_snapshot.json new file mode 100644 index 000000000..e5b1558cf --- /dev/null +++ b/packages/db/migrations/sqlite/meta/0001_snapshot.json @@ -0,0 +1,1114 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2ed0ffc3-8612-42e7-bd8e-f5f8f3338a39", + "prevId": "0575873a-9e10-4480-8d7d-c47198622c22", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "columns": ["board_id", "group_id", "permission"], + "name": "boardGroupPermission_board_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "columns": ["board_id", "permission", "user_id"], + "name": "boardUserPermission_board_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'no-repeat'" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cover'" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fa5252'" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fd7e14'" + }, + "opacity": { + "name": "opacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"], + "isUnique": true + } + }, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "columns": ["groupId", "userId"], + "name": "groupMember_groupId_userId_pk" + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "columns": ["integration_id", "item_id"], + "name": "integration_item_item_id_integration_id_pk" + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "columns": ["integration_id", "kind"], + "name": "integrationSecret_integration_id_kind_pk" + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"], + "isUnique": true + } + }, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json index 8b44adc89..df01d2be3 100644 --- a/packages/db/migrations/sqlite/meta/_journal.json +++ b/packages/db/migrations/sqlite/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1715334238443, "tag": "0000_talented_ben_parker", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1715871797713, + "tag": "0001_mixed_titanium_man", + "breakpoints": true } ] } diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 3af3111fc..32e08e932 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -1,5 +1,6 @@ import type { AdapterAccount } from "@auth/core/adapters"; import { relations } from "drizzle-orm"; +import type { AnyMySqlColumn } from "drizzle-orm/mysql-core"; import { boolean, index, @@ -36,6 +37,12 @@ export const users = mysqlTable("user", { image: text("image"), password: text("password"), salt: text("salt"), + homeBoardId: varchar("homeBoardId", { length: 64 }).references( + (): AnyMySqlColumn => boards.id, + { + onDelete: "set null", + }, + ), }); export const accounts = mysqlTable( diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index a8ed08889..f8af5a4a0 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -1,6 +1,7 @@ import type { AdapterAccount } from "@auth/core/adapters"; import type { InferSelectModel } from "drizzle-orm"; import { relations } from "drizzle-orm"; +import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core"; import { index, int, @@ -35,6 +36,12 @@ export const users = sqliteTable("user", { image: text("image"), password: text("password"), salt: text("salt"), + homeBoardId: text("homeBoardId").references( + (): AnySQLiteColumn => boards.id, + { + onDelete: "set null", + }, + ), }); export const accounts = sqliteTable( diff --git a/packages/db/test/db-mock.ts b/packages/db/test/db-mock.ts index db4c15978..d2befdfb9 100644 --- a/packages/db/test/db-mock.ts +++ b/packages/db/test/db-mock.ts @@ -4,11 +4,16 @@ import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { schema } from ".."; -export const createDb = () => { +export const createDb = (debug?: boolean) => { const sqlite = new Database(":memory:"); - const db = drizzle(sqlite, { schema }); + const db = drizzle(sqlite, { schema, logger: debug }); migrate(db, { migrationsFolder: "./packages/db/migrations/sqlite", }); + + if (debug) { + console.log("Database created"); + } + return db; }; diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts index a8d04b863..caa2be942 100644 --- a/packages/translation/src/lang/en.ts +++ b/packages/translation/src/lang/en.ts @@ -502,7 +502,7 @@ export default { preferences: "Your preferences", logout: "Logout", login: "Login", - navigateDefaultBoard: "Navigate to default board", + homeBoard: "Your home board", loggedOut: "Logged out", }, }, @@ -970,6 +970,9 @@ export default { label: "Name", }, }, + content: { + metaTitle: "{boardName} board", + }, setting: { title: "Settings for {boardName} board", section: { @@ -1152,6 +1155,13 @@ export default { settings: { label: "Settings", }, + setHomeBoard: { + label: "Set as your home board", + badge: { + label: "Home", + tooltip: "This board will show as your home board", + }, + }, delete: { label: "Delete permanently", confirm: { diff --git a/packages/widgets/src/server/runner.tsx b/packages/widgets/src/server/runner.tsx index d3792574b..6c123d05b 100644 --- a/packages/widgets/src/server/runner.tsx +++ b/packages/widgets/src/server/runner.tsx @@ -7,7 +7,7 @@ import { reduceWidgetOptionsWithDefaultValues, widgetImports } from ".."; import { ClientServerDataInitalizer } from "./client"; import { GlobalItemServerDataProvider } from "./provider"; -type Board = RouterOutputs["board"]["getDefaultBoard"]; +type Board = RouterOutputs["board"]["getHomeBoard"]; type Props = PropsWithChildren<{ shouldRun: boolean;