From 26b1c4a3199c59e1b5eb3529da74180a455fa09f Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sun, 19 May 2024 23:01:26 +0200 Subject: [PATCH] feat: add custom css for board and custom classes in advanced options for items (#512) * feat: add custom css for board and custom classes in advanced options for items * chore: add mysql migration * fix: test not working * fix: format issues * fix: typecheck issue * fix: build issue * chore: add missing translations * fix: merge issues related to migrations * fix: format issues * fix: merge issue with migration * fix: format issue --- .../[locale]/boards/(content)/_custom-css.tsx | 9 + .../boards/[name]/settings/_customCss.tsx | 90 +- .../[name]/settings/customcss.module.css | 22 + .../[locale]/boards/[name]/settings/page.tsx | 2 +- .../app/[locale]/boards/_layout-creator.tsx | 2 + .../app/[locale]/widgets/[kind]/_content.tsx | 6 +- .../components/board/items/item-actions.tsx | 40 +- .../src/components/board/sections/content.tsx | 15 +- apps/nextjs/src/styles/prismjs.scss | 225 +++ packages/api/src/router/board.ts | 3 + packages/api/src/router/test/board.spec.ts | 5 + .../mysql/0002_flimsy_deathbird.sql | 1 + ...er.sql => 0003_freezing_black_panther.sql} | 0 .../migrations/mysql/meta/0002_snapshot.json | 46 +- .../migrations/mysql/meta/0003_snapshot.json | 1208 +++++++++++++++++ .../db/migrations/mysql/meta/_journal.json | 9 +- .../db/migrations/sqlite/0002_cooing_sumo.sql | 1 + ...le_raider.sql => 0003_adorable_raider.sql} | 0 .../migrations/sqlite/meta/0002_snapshot.json | 42 +- .../migrations/sqlite/meta/0003_snapshot.json | 1152 ++++++++++++++++ .../db/migrations/sqlite/meta/_journal.json | 9 +- packages/db/schema/mysql.ts | 1 + packages/db/schema/sqlite.ts | 1 + packages/modals/src/index.tsx | 4 +- packages/translation/src/lang/en.ts | 19 +- packages/ui/package.json | 3 +- packages/ui/src/components/index.tsx | 1 + .../ui/src/components/text-multi-select.tsx | 98 ++ packages/validation/src/index.ts | 8 +- packages/validation/src/shared.ts | 7 + packages/widgets/package.json | 11 +- packages/widgets/src/_inputs/form.ts | 3 +- .../modals/widget-advanced-options-modal.tsx | 52 + .../widgets/src/modals/widget-edit-modal.tsx | 48 +- pnpm-lock.yaml | 34 + 35 files changed, 3080 insertions(+), 97 deletions(-) create mode 100644 apps/nextjs/src/app/[locale]/boards/(content)/_custom-css.tsx create mode 100644 apps/nextjs/src/app/[locale]/boards/[name]/settings/customcss.module.css create mode 100644 apps/nextjs/src/styles/prismjs.scss create mode 100644 packages/db/migrations/mysql/0002_flimsy_deathbird.sql rename packages/db/migrations/mysql/{0002_freezing_black_panther.sql => 0003_freezing_black_panther.sql} (100%) create mode 100644 packages/db/migrations/mysql/meta/0003_snapshot.json create mode 100644 packages/db/migrations/sqlite/0002_cooing_sumo.sql rename packages/db/migrations/sqlite/{0002_adorable_raider.sql => 0003_adorable_raider.sql} (100%) create mode 100644 packages/db/migrations/sqlite/meta/0003_snapshot.json create mode 100644 packages/ui/src/components/text-multi-select.tsx create mode 100644 packages/widgets/src/modals/widget-advanced-options-modal.tsx diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_custom-css.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_custom-css.tsx new file mode 100644 index 000000000..ab01aff39 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_custom-css.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { useRequiredBoard } from "./_context"; + +export const CustomCss = () => { + const board = useRequiredBoard(); + + return ; +}; diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_customCss.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_customCss.tsx index 4d9bc7f75..4e007388f 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_customCss.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_customCss.tsx @@ -1,7 +1,91 @@ "use client"; -// TODO: add some sort of store (maybe directory on GitHub) +import { Alert, Button, Group, Input, Stack } from "@mantine/core"; +import { highlight, languages } from "prismjs"; +import Editor from "react-simple-code-editor"; -export const CustomCssSettingsContent = () => { - return null; +import "~/styles/prismjs.scss"; + +import { IconInfoCircle } from "@tabler/icons-react"; + +import { useForm } from "@homarr/form"; +import { useI18n, useScopedI18n } from "@homarr/translation/client"; + +import type { Board } from "../../_types"; +import { useSavePartialSettingsMutation } from "./_shared"; +import classes from "./customcss.module.css"; + +interface Props { + board: Board; +} + +export const CustomCssSettingsContent = ({ board }: Props) => { + const t = useI18n(); + const customCssT = useScopedI18n("board.field.customCss"); + const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board); + const form = useForm({ + initialValues: { + customCss: board.customCss ?? "", + }, + }); + + return ( +
{ + savePartialSettings({ + id: board.id, + ...values, + }); + })} + > + + + + }> + {customCssT("customClassesAlert.description")} + + + + + + +
+ ); +}; + +interface CustomCssInputProps { + value?: string; + onChange: (value: string) => void; +} + +const CustomCssInput = ({ value, onChange }: CustomCssInputProps) => { + const customCssT = useScopedI18n("board.field.customCss"); + + return ( + +
+ highlight(code, languages.extend("css", {}), "css")} + padding={10} + style={{ + fontFamily: '"Fira code", "Fira Mono", monospace', + fontSize: 12, + minHeight: 250, + }} + /> +
+
+ ); }; diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/customcss.module.css b/apps/nextjs/src/app/[locale]/boards/[name]/settings/customcss.module.css new file mode 100644 index 000000000..b01c21383 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/customcss.module.css @@ -0,0 +1,22 @@ +.codeEditorFooter { + border-bottom-left-radius: var(--mantine-radius-sm); + border-bottom-right-radius: var(--mantine-radius-sm); + background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7)); +} + +.codeEditorRoot { + margin-top: 4px; + border-color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4)); + border-width: 1px; + border-style: solid; + border-radius: var(--mantine-radius-sm); +} + +.codeEditor { + background-color: light-dark(white, var(--mantine-color-dark-6)); + font-size: var(--mantine-font-size-xs); +} + +.codeEditor ::placeholder { + color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); +} diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx index cfafacf85..1748ee36b 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx @@ -84,7 +84,7 @@ export default async function BoardSettingsPage({ params, searchParams }: Props) - + {hasFullAccess && ( <> diff --git a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx index 9dd31a049..56dabdd88 100644 --- a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx @@ -12,6 +12,7 @@ import { ClientShell } from "~/components/layout/shell"; import type { Board } from "./_types"; import { BoardProvider } from "./(content)/_context"; import type { Params } from "./(content)/_creator"; +import { CustomCss } from "./(content)/_custom-css"; import { BoardMantineProvider } from "./(content)/_theme"; interface CreateBoardLayoutProps { @@ -44,6 +45,7 @@ export const createBoardLayout = ({ + } diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx index 3c1ad6ba7..9a2a1f100 100644 --- a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx +++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx @@ -8,7 +8,7 @@ import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; import { useModalAction } from "@homarr/modals"; import { showSuccessNotification } from "@homarr/notifications"; import { useScopedI18n } from "@homarr/translation/client"; -import type { BoardItemIntegration } from "@homarr/validation"; +import type { BoardItemAdvancedOptions, BoardItemIntegration } from "@homarr/validation"; import { loadWidgetDynamic, reduceWidgetOptionsWithDefaultValues, @@ -42,9 +42,13 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie const [state, setState] = useState<{ options: Record; integrations: BoardItemIntegration[]; + advancedOptions: BoardItemAdvancedOptions; }>({ options: reduceWidgetOptionsWithDefaultValues(kind, {}), integrations: [], + advancedOptions: { + customCssClasses: [], + }, }); const handleOpenEditWidgetModal = useCallback(() => { diff --git a/apps/nextjs/src/components/board/items/item-actions.tsx b/apps/nextjs/src/components/board/items/item-actions.tsx index 1f3650d9c..309dab8bd 100644 --- a/apps/nextjs/src/components/board/items/item-actions.tsx +++ b/apps/nextjs/src/components/board/items/item-actions.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { createId } from "@homarr/db/client"; import type { WidgetKind } from "@homarr/definitions"; -import type { BoardItemIntegration } from "@homarr/validation"; +import type { BoardItemAdvancedOptions, BoardItemIntegration } from "@homarr/validation"; import type { EmptySection, Item } from "~/app/[locale]/boards/_types"; import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client"; @@ -31,6 +31,11 @@ interface UpdateItemOptions { newOptions: Record; } +interface UpdateItemAdvancedOptions { + itemId: string; + newAdvancedOptions: BoardItemAdvancedOptions; +} + interface UpdateItemIntegrations { itemId: string; newIntegrations: BoardItemIntegration[]; @@ -59,6 +64,9 @@ export const useItemActions = () => { width: 1, height: 1, integrations: [], + advancedOptions: { + customCssClasses: [], + }, } satisfies Omit & { kind: WidgetKind; }; @@ -91,7 +99,7 @@ export const useItemActions = () => { return { ...section, items: section.items.map((item) => { - // Return same item if item is not the one we're moving + // Return same item if item is not the one we're changing if (item.id !== itemId) return item; return { ...item, @@ -106,6 +114,33 @@ export const useItemActions = () => { [updateBoard], ); + const updateItemAdvancedOptions = useCallback( + ({ itemId, newAdvancedOptions }: UpdateItemAdvancedOptions) => { + updateBoard((previous) => { + if (!previous) return previous; + return { + ...previous, + sections: previous.sections.map((section) => { + // Return same section if item is not in it + if (!section.items.some((item) => item.id === itemId)) return section; + return { + ...section, + items: section.items.map((item) => { + // Return same item if item is not the one we're changing + if (item.id !== itemId) return item; + return { + ...item, + advancedOptions: newAdvancedOptions, + }; + }), + }; + }), + }; + }); + }, + [updateBoard], + ); + const updateItemIntegrations = useCallback( ({ itemId, newIntegrations }: UpdateItemIntegrations) => { updateBoard((previous) => { @@ -224,6 +259,7 @@ export const useItemActions = () => { moveItemToSection, removeItem, updateItemOptions, + updateItemAdvancedOptions, updateItemIntegrations, createItem, }; diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx index 85c9d35d8..f978ced0f 100644 --- a/apps/nextjs/src/components/board/sections/content.tsx +++ b/apps/nextjs/src/components/board/sections/content.tsx @@ -70,7 +70,11 @@ const BoardItem = ({ refs, item, opacity }: ItemProps) => { > { const { openModal } = useModalAction(WidgetEditModal); const { openConfirmModal } = useConfirmModal(); const isEditMode = useAtomValue(editModeAtom); - const { updateItemOptions, updateItemIntegrations, removeItem } = useItemActions(); + const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, removeItem } = useItemActions(); const { data: integrationData, isPending } = clientApi.integration.all.useQuery(); const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]); @@ -133,14 +137,19 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => { openModal({ kind: item.kind, value: { + advancedOptions: item.advancedOptions, options: item.options, integrations: item.integrations, }, - onSuccessfulEdit: ({ options, integrations }) => { + onSuccessfulEdit: ({ options, integrations, advancedOptions }) => { updateItemOptions({ itemId: item.id, newOptions: options, }); + updateItemAdvancedOptions({ + itemId: item.id, + newAdvancedOptions: advancedOptions, + }); updateItemIntegrations({ itemId: item.id, newIntegrations: integrations, diff --git a/apps/nextjs/src/styles/prismjs.scss b/apps/nextjs/src/styles/prismjs.scss new file mode 100644 index 000000000..d9f1d6327 --- /dev/null +++ b/apps/nextjs/src/styles/prismjs.scss @@ -0,0 +1,225 @@ +[data-mantine-color-scheme="light"] { + code[class*="language-"], + pre[class*="language-"] { + color: #000; + background: 0 0; + text-shadow: 0 1px #fff; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + code[class*="language-"] ::-moz-selection, + code[class*="language-"]::-moz-selection, + pre[class*="language-"] ::-moz-selection, + pre[class*="language-"]::-moz-selection { + text-shadow: none; + background: #b3d4fc; + } + code[class*="language-"] ::selection, + code[class*="language-"]::selection, + pre[class*="language-"] ::selection, + pre[class*="language-"]::selection { + text-shadow: none; + background: #b3d4fc; + } + @media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } + } + pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + } + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: #f5f2f0; + } + :not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; + } + .token.cdata, + .token.comment, + .token.doctype, + .token.prolog { + color: #708090; + } + .token.punctuation { + color: #999; + } + .token.namespace { + opacity: 0.7; + } + .token.boolean, + .token.constant, + .token.deleted, + .token.number, + .token.property, + .token.symbol, + .token.tag { + color: #905; + } + .token.attr-name, + .token.builtin, + .token.char, + .token.inserted, + .token.selector, + .token.string { + color: #690; + } + .language-css .token.string, + .style .token.string, + .token.entity, + .token.operator, + .token.url { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); + } + .token.atrule, + .token.attr-value, + .token.keyword { + color: #07a; + } + .token.class-name, + .token.function { + color: #dd4a68; + } + .token.important, + .token.regex, + .token.variable { + color: #e90; + } + .token.bold, + .token.important { + font-weight: 700; + } + .token.italic { + font-style: italic; + } + .token.entity { + cursor: help; + } +} + +[data-mantine-color-scheme="dark"] { + code[class*="language-"], + pre[class*="language-"] { + color: #fff; + background: 0 0; + text-shadow: 0 -0.1em 0.2em #000; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + @media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } + } + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: #4c3f33; + } + pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + border: 0.3em solid #7a6651; + border-radius: 0.5em; + box-shadow: 1px 1px 0.5em #000 inset; + } + :not(pre) > code[class*="language-"] { + padding: 0.15em 0.2em 0.05em; + border-radius: 0.3em; + border: 0.13em solid #7a6651; + box-shadow: 1px 1px 0.3em -0.1em #000 inset; + white-space: normal; + } + .token.cdata, + .token.comment, + .token.doctype, + .token.prolog { + color: #997f66; + } + .token.punctuation { + opacity: 0.7; + } + .token.namespace { + opacity: 0.7; + } + .token.boolean, + .token.constant, + .token.number, + .token.property, + .token.symbol, + .token.tag { + color: #d1939e; + } + .token.attr-name, + .token.builtin, + .token.char, + .token.inserted, + .token.selector, + .token.string { + color: #bce051; + } + .language-css .token.string, + .style .token.string, + .token.entity, + .token.operator, + .token.url, + .token.variable { + color: #f4b73d; + } + .token.atrule, + .token.attr-value, + .token.keyword { + color: #d1939e; + } + .token.important, + .token.regex { + color: #e90; + } + .token.bold, + .token.important { + font-weight: 700; + } + .token.italic { + font-style: italic; + } + .token.entity { + cursor: help; + } + .token.deleted { + color: red; + } +} diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index 1e5067a78..c37e92c57 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -16,6 +16,7 @@ import { } from "@homarr/db/schema/sqlite"; import type { WidgetKind } from "@homarr/definitions"; import { getPermissionsWithParents, widgetKinds } from "@homarr/definitions"; +import type { BoardItemAdvancedOptions } from "@homarr/validation"; import { createSectionSchema, sharedItemSchema, validation, z } from "@homarr/validation"; import { zodUnionFromArray } from "../../../validation/src/enums"; @@ -229,6 +230,7 @@ export const boardRouter = createTRPCRouter({ xOffset: item.xOffset, yOffset: item.yOffset, options: superjson.stringify(item.options), + advancedOptions: superjson.stringify(item.advancedOptions), sectionId: item.sectionId, })), ); @@ -515,6 +517,7 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL, use items: section.items.map((item) => ({ ...item, integrations: item.integrations.map((item) => item.integration), + advancedOptions: superjson.parse(item.advancedOptions), options: superjson.parse>(item.options), })), }), diff --git a/packages/api/src/router/test/board.spec.ts b/packages/api/src/router/test/board.spec.ts index 092aca3cb..11ac8c913 100644 --- a/packages/api/src/router/test/board.spec.ts +++ b/packages/api/src/router/test/board.spec.ts @@ -664,6 +664,7 @@ describe("saveBoard should save full board", () => { width: 1, xOffset: 0, yOffset: 0, + advancedOptions: {}, }, ], }, @@ -724,6 +725,7 @@ describe("saveBoard should save full board", () => { width: 1, xOffset: 0, yOffset: 0, + advancedOptions: {}, }, ], }, @@ -837,6 +839,7 @@ describe("saveBoard should save full board", () => { width: 1, xOffset: 3, yOffset: 2, + advancedOptions: {}, }, ], }, @@ -905,6 +908,7 @@ describe("saveBoard should save full board", () => { width: 1, xOffset: 0, yOffset: 0, + advancedOptions: {}, }, ], }, @@ -1018,6 +1022,7 @@ describe("saveBoard should save full board", () => { width: 2, xOffset: 7, yOffset: 5, + advancedOptions: {}, }, ], }, diff --git a/packages/db/migrations/mysql/0002_flimsy_deathbird.sql b/packages/db/migrations/mysql/0002_flimsy_deathbird.sql new file mode 100644 index 000000000..57e15e767 --- /dev/null +++ b/packages/db/migrations/mysql/0002_flimsy_deathbird.sql @@ -0,0 +1 @@ +ALTER TABLE `item` ADD `advanced_options` text DEFAULT ('{"json": {}}') NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/mysql/0002_freezing_black_panther.sql b/packages/db/migrations/mysql/0003_freezing_black_panther.sql similarity index 100% rename from packages/db/migrations/mysql/0002_freezing_black_panther.sql rename to packages/db/migrations/mysql/0003_freezing_black_panther.sql diff --git a/packages/db/migrations/mysql/meta/0002_snapshot.json b/packages/db/migrations/mysql/meta/0002_snapshot.json index 398aab9a7..f098f20c9 100644 --- a/packages/db/migrations/mysql/meta/0002_snapshot.json +++ b/packages/db/migrations/mysql/meta/0002_snapshot.json @@ -1,8 +1,8 @@ { "version": "5", "dialect": "mysql", - "id": "e7a373e1-9f36-4910-9f2b-ac6fd5e79145", - "prevId": "ba2dd885-4e7f-4a45-99a0-7b45cbd0a5c2", + "id": "4e382d0d-a432-4953-bd5e-04f3f33e26a4", + "prevId": "fdeaf6eb-cd62-4fa5-9b38-d7f80a60db9f", "tables": { "account": { "name": "account", @@ -910,6 +910,14 @@ "notNull": true, "autoincrement": false, "default": "('{\"json\": {}}')" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" } }, "indexes": {}, @@ -991,40 +999,6 @@ }, "uniqueConstraints": {} }, - "serverSetting": { - "name": "serverSetting", - "columns": { - "key": { - "name": "key", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "('{\"json\": {}}')" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "serverSetting_key": { - "name": "serverSetting_key", - "columns": ["key"] - } - }, - "uniqueConstraints": { - "serverSetting_key_unique": { - "name": "serverSetting_key_unique", - "columns": ["key"] - } - } - }, "session": { "name": "session", "columns": { diff --git a/packages/db/migrations/mysql/meta/0003_snapshot.json b/packages/db/migrations/mysql/meta/0003_snapshot.json new file mode 100644 index 000000000..19b512ac9 --- /dev/null +++ b/packages/db/migrations/mysql/meta/0003_snapshot.json @@ -0,0 +1,1208 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "4e382d0d-a432-4953-bd5e-04f3f33e26a4", + "prevId": "e7a373e1-9f36-4910-9f2b-ac6fd5e79145", + "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\": {}}')" + }, + "advanced_options": { + "name": "advanced_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": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "serverSetting_key": { + "name": "serverSetting_key", + "columns": ["key"] + } + }, + "uniqueConstraints": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"] + } + } + }, + "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 4c20d0621..bb349fd94 100644 --- a/packages/db/migrations/mysql/meta/_journal.json +++ b/packages/db/migrations/mysql/meta/_journal.json @@ -19,8 +19,15 @@ { "idx": 2, "version": "5", + "when": 1715980459023, + "tag": "0002_flimsy_deathbird", + "breakpoints": true + }, + { + "idx": 3, + "version": "5", "when": 1716148439439, - "tag": "0002_freezing_black_panther", + "tag": "0003_freezing_black_panther", "breakpoints": true } ] diff --git a/packages/db/migrations/sqlite/0002_cooing_sumo.sql b/packages/db/migrations/sqlite/0002_cooing_sumo.sql new file mode 100644 index 000000000..a12cdb821 --- /dev/null +++ b/packages/db/migrations/sqlite/0002_cooing_sumo.sql @@ -0,0 +1 @@ +ALTER TABLE `item` ADD `advanced_options` text DEFAULT '{"json": {}}' NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/sqlite/0002_adorable_raider.sql b/packages/db/migrations/sqlite/0003_adorable_raider.sql similarity index 100% rename from packages/db/migrations/sqlite/0002_adorable_raider.sql rename to packages/db/migrations/sqlite/0003_adorable_raider.sql diff --git a/packages/db/migrations/sqlite/meta/0002_snapshot.json b/packages/db/migrations/sqlite/meta/0002_snapshot.json index 66649ac0e..885a5df37 100644 --- a/packages/db/migrations/sqlite/meta/0002_snapshot.json +++ b/packages/db/migrations/sqlite/meta/0002_snapshot.json @@ -1,8 +1,8 @@ { "version": "6", "dialect": "sqlite", - "id": "b72fe407-31bc-4dd0-8c36-dbb8e42ef708", - "prevId": "2ed0ffc3-8612-42e7-bd8e-f5f8f3338a39", + "id": "5ad60251-8450-437d-9081-a456884120d2", + "prevId": "0575873a-9e10-4480-8d7d-c47198622c22", "tables": { "account": { "name": "account", @@ -877,6 +877,14 @@ "notNull": true, "autoincrement": false, "default": "'{\"json\": {}}'" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" } }, "indexes": {}, @@ -948,36 +956,6 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "serverSetting": { - "name": "serverSetting", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'{\"json\": {}}'" - } - }, - "indexes": { - "serverSetting_key_unique": { - "name": "serverSetting_key_unique", - "columns": ["key"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, "session": { "name": "session", "columns": { diff --git a/packages/db/migrations/sqlite/meta/0003_snapshot.json b/packages/db/migrations/sqlite/meta/0003_snapshot.json new file mode 100644 index 000000000..7324558d3 --- /dev/null +++ b/packages/db/migrations/sqlite/meta/0003_snapshot.json @@ -0,0 +1,1152 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "b72fe407-31bc-4dd0-8c36-dbb8e42ef708", + "prevId": "5ad60251-8450-437d-9081-a456884120d2", + "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\": {}}'" + }, + "advanced_options": { + "name": "advanced_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": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "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 43c0f2a94..5d9bc2d12 100644 --- a/packages/db/migrations/sqlite/meta/_journal.json +++ b/packages/db/migrations/sqlite/meta/_journal.json @@ -19,8 +19,15 @@ { "idx": 2, "version": "6", + "when": 1715973963014, + "tag": "0002_cooing_sumo", + "breakpoints": true + }, + { + "idx": 3, + "version": "6", "when": 1716148434186, - "tag": "0002_adorable_raider", + "tag": "0003_adorable_raider", "breakpoints": true } ] diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 8b9d70bf9..d53a9ec30 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -243,6 +243,7 @@ export const items = mysqlTable("item", { width: int("width").notNull(), height: int("height").notNull(), options: text("options").default('{"json": {}}').notNull(), // empty superjson object + advancedOptions: text("advanced_options").default('{"json": {}}').notNull(), // empty superjson object }); export const apps = mysqlTable("app", { diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index 905225ad2..f7c3c87e5 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -246,6 +246,7 @@ export const items = sqliteTable("item", { width: int("width").notNull(), height: int("height").notNull(), options: text("options").default('{"json": {}}').notNull(), // empty superjson object + advancedOptions: text("advanced_options").default('{"json": {}}').notNull(), // empty superjson object }); export const apps = sqliteTable("app", { diff --git a/packages/modals/src/index.tsx b/packages/modals/src/index.tsx index 26265c7bb..68876a6b6 100644 --- a/packages/modals/src/index.tsx +++ b/packages/modals/src/index.tsx @@ -103,7 +103,6 @@ const ActiveModal = ({ modal, state, handleCloseModal }: ActiveModalProps) => { { fontSize: "1.25rem", fontWeight: 500, }, + inner: { + display: modal.id === state.current?.id ? undefined : "none", + }, }} trapFocus={modal.id === state.current?.id} {...otherModalProps} diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts index fc382d453..757502ffb 100644 --- a/packages/translation/src/lang/en.ts +++ b/packages/translation/src/lang/en.ts @@ -469,6 +469,10 @@ export default { multiSelect: { placeholder: "Pick one or more values", }, + multiText: { + placeholder: "Add more values", + addLabel: `Add {value}`, + }, select: { placeholder: "Pick value", badge: { @@ -594,10 +598,17 @@ export default { }, edit: { title: "Edit item", + advancedOptions: { + label: "Advanced options", + title: "Advanced item options", + }, field: { integrations: { label: "Integrations", }, + customCssClasses: { + label: "Custom css classes", + }, }, }, remove: { @@ -944,7 +955,13 @@ export default { label: "Opacity", }, customCss: { - label: "Custom CSS", + label: "Custom css for this board", + description: "Further, customize your dashboard using CSS, only recommended for experienced users", + customClassesAlert: { + title: "Custom classes", + description: + "You can add custom classes to your board items in the advanced options of each item and use them in the custom CSS above.", + }, }, columnCount: { label: "Column count", diff --git a/packages/ui/package.json b/packages/ui/package.json index 111b352f0..783154b52 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -29,7 +29,8 @@ "typescript": "^5.4.5" }, "dependencies": { - "@homarr/log": "workspace:^0.1.0" + "@homarr/log": "workspace:^0.1.0", + "@homarr/translation": "workspace:^0.1.0" }, "eslintConfig": { "extends": [ diff --git a/packages/ui/src/components/index.tsx b/packages/ui/src/components/index.tsx index 5155f07de..425545c50 100644 --- a/packages/ui/src/components/index.tsx +++ b/packages/ui/src/components/index.tsx @@ -5,3 +5,4 @@ export { UserAvatar } from "./user-avatar"; export { UserAvatarGroup } from "./user-avatar-group"; export { TablePagination } from "./table-pagination"; export { SearchInput } from "./search-input"; +export { TextMultiSelect } from "./text-multi-select"; diff --git a/packages/ui/src/components/text-multi-select.tsx b/packages/ui/src/components/text-multi-select.tsx new file mode 100644 index 000000000..e63d570dc --- /dev/null +++ b/packages/ui/src/components/text-multi-select.tsx @@ -0,0 +1,98 @@ +"use client"; + +import type { FocusEventHandler } from "react"; +import { useState } from "react"; +import { Combobox, Group, Pill, PillsInput, Text, useCombobox } from "@mantine/core"; +import { IconPlus } from "@tabler/icons-react"; + +import { useI18n } from "@homarr/translation/client"; + +interface TextMultiSelectProps { + label: string; + value?: string[]; + onChange: (value: string[]) => void; + onFocus?: FocusEventHandler; + onBlur?: FocusEventHandler; + error?: string; +} + +export const TextMultiSelect = ({ label, value = [], onChange, onBlur, onFocus, error }: TextMultiSelectProps) => { + const t = useI18n(); + const combobox = useCombobox({ + onDropdownClose: () => combobox.resetSelectedOption(), + onDropdownOpen: () => combobox.updateSelectedOptionIndex("active"), + }); + + const [search, setSearch] = useState(""); + + const exactOptionMatch = value.some((item) => item === search); + + const handleValueSelect = (selectedValue: string) => { + setSearch(""); + + if (selectedValue === "$create") { + onChange([...value, search]); + } else { + onChange(value.filter((filterValue) => filterValue !== selectedValue)); + } + }; + + const handleValueRemove = (removedValue: string) => + onChange(value.filter((filterValue) => filterValue !== removedValue)); + + const values = value.map((item) => ( + handleValueRemove(item)}> + {item} + + )); + + return ( + + + combobox.openDropdown()}> + + {values} + + + { + onFocus?.(event); + combobox.openDropdown(); + }} + onBlur={(event) => { + onBlur?.(event); + combobox.closeDropdown(); + }} + value={search} + placeholder={t("common.multiText.placeholder")} + onChange={(event) => { + combobox.updateSelectedOptionIndex(); + setSearch(event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Backspace" && search.length === 0) { + event.preventDefault(); + handleValueRemove(value.at(-1)!); + } + }} + /> + + + + + + {!exactOptionMatch && search.trim().length > 0 && ( + + + + + + {t("common.multiText.addLabel", { value: search })} + + + + + )} + + ); +}; diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts index 2d7f3701d..34c4211c5 100644 --- a/packages/validation/src/index.ts +++ b/packages/validation/src/index.ts @@ -18,4 +18,10 @@ export const validation = { icons: iconsSchemas, }; -export { createSectionSchema, sharedItemSchema, type BoardItemIntegration } from "./shared"; +export { + createSectionSchema, + sharedItemSchema, + itemAdvancedOptionsSchema, + type BoardItemIntegration, + type BoardItemAdvancedOptions, +} from "./shared"; diff --git a/packages/validation/src/shared.ts b/packages/validation/src/shared.ts index 0aa6feaf1..46b2de2c1 100644 --- a/packages/validation/src/shared.ts +++ b/packages/validation/src/shared.ts @@ -13,6 +13,12 @@ export const integrationSchema = z.object({ export type BoardItemIntegration = z.infer; +export const itemAdvancedOptionsSchema = z.object({ + customCssClasses: z.array(z.string()).default([]), +}); + +export type BoardItemAdvancedOptions = z.infer; + export const sharedItemSchema = z.object({ id: z.string(), xOffset: z.number(), @@ -20,6 +26,7 @@ export const sharedItemSchema = z.object({ height: z.number(), width: z.number(), integrations: z.array(integrationSchema), + advancedOptions: itemAdvancedOptionsSchema, }); export const commonItemSchema = z diff --git a/packages/widgets/package.json b/packages/widgets/package.json index dde1e0d8a..a31e97954 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -23,6 +23,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", + "@types/prismjs": "^1.26.4", "@types/video.js": "^7.3.58", "eslint": "^8.57.0", "typescript": "^5.4.5" @@ -40,17 +41,15 @@ "@homarr/form": "workspace:^0.1.0", "@homarr/modals": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0", - "@homarr/spotlight": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", + "@homarr/spotlight": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "@tiptap/extension-link": "^2.4.0", - "@tiptap/react": "^2.4.0", - "@tiptap/starter-kit": "^2.4.0", "@tiptap/extension-color": "2.4.0", "@tiptap/extension-highlight": "2.4.0", "@tiptap/extension-image": "2.4.0", + "@tiptap/extension-link": "^2.4.0", "@tiptap/extension-table": "2.4.0", "@tiptap/extension-table-cell": "2.4.0", "@tiptap/extension-table-header": "2.4.0", @@ -60,6 +59,10 @@ "@tiptap/extension-text-align": "2.4.0", "@tiptap/extension-text-style": "2.4.0", "@tiptap/extension-underline": "2.4.0", + "@tiptap/react": "^2.4.0", + "@tiptap/starter-kit": "^2.4.0", + "prismjs": "^1.29.0", + "react-simple-code-editor": "^0.13.1", "video.js": "^8.12.0" } } diff --git a/packages/widgets/src/_inputs/form.ts b/packages/widgets/src/_inputs/form.ts index a0199b55f..207eee02d 100644 --- a/packages/widgets/src/_inputs/form.ts +++ b/packages/widgets/src/_inputs/form.ts @@ -4,4 +4,5 @@ import { createFormContext } from "@homarr/form"; import type { WidgetEditModalState } from "../modals/widget-edit-modal"; -export const [FormProvider, useFormContext, useForm] = createFormContext(); +export const [FormProvider, useFormContext, useForm] = + createFormContext>(); diff --git a/packages/widgets/src/modals/widget-advanced-options-modal.tsx b/packages/widgets/src/modals/widget-advanced-options-modal.tsx new file mode 100644 index 000000000..674bc8f9c --- /dev/null +++ b/packages/widgets/src/modals/widget-advanced-options-modal.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { Button, Group, Stack } from "@mantine/core"; + +import { useForm } from "@homarr/form"; +import { createModal } from "@homarr/modals"; +import { useI18n } from "@homarr/translation/client"; +import { TextMultiSelect } from "@homarr/ui"; +import type { BoardItemAdvancedOptions } from "@homarr/validation"; + +interface InnerProps { + advancedOptions: BoardItemAdvancedOptions; + onSuccess: (options: BoardItemAdvancedOptions) => void; +} + +export const WidgetAdvancedOptionsModal = createModal(({ actions, innerProps }) => { + const t = useI18n(); + const form = useForm({ + initialValues: innerProps.advancedOptions, + }); + const handleSubmit = (values: BoardItemAdvancedOptions) => { + innerProps.onSuccess(values); + actions.closeModal(); + }; + + return ( +
+ + + + + + + +
+ ); +}).withOptions({ + defaultTitle(t) { + return t("item.edit.advancedOptions.title"); + }, + size: "lg", + transitionProps: { + duration: 0, + }, +}); diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx index 51531ccbd..cad7cf18f 100644 --- a/packages/widgets/src/modals/widget-edit-modal.tsx +++ b/packages/widgets/src/modals/widget-edit-modal.tsx @@ -1,21 +1,25 @@ "use client"; +import { useState } from "react"; import { Button, Group, Stack } from "@mantine/core"; import type { WidgetKind } from "@homarr/definitions"; -import { createModal } from "@homarr/modals"; +import { createModal, useModalAction } from "@homarr/modals"; import { useI18n } from "@homarr/translation/client"; import type { BoardItemIntegration } from "@homarr/validation"; import { widgetImports } from ".."; import { getInputForType } from "../_inputs"; import { FormProvider, useForm } from "../_inputs/form"; +import type { BoardItemAdvancedOptions } from "../../../validation/src/shared"; import type { OptionsBuilderResult } from "../options"; import type { IntegrationSelectOption } from "../widget-integration-select"; import { WidgetIntegrationSelect } from "../widget-integration-select"; +import { WidgetAdvancedOptionsModal } from "./widget-advanced-options-modal"; export interface WidgetEditModalState { options: Record; + advancedOptions: BoardItemAdvancedOptions; integrations: BoardItemIntegration[]; } @@ -29,16 +33,21 @@ interface ModalProps { export const WidgetEditModal = createModal>(({ actions, innerProps }) => { const t = useI18n(); + const [advancedOptions, setAdvancedOptions] = useState(innerProps.value.advancedOptions); const form = useForm({ initialValues: innerProps.value, }); + const { openModal } = useModalAction(WidgetAdvancedOptionsModal); const { definition } = widgetImports[innerProps.kind]; return (
{ - innerProps.onSuccessfulEdit(values); + innerProps.onSuccessfulEdit({ + ...values, + advancedOptions, + }); actions.closeModal(); })} > @@ -60,13 +69,32 @@ export const WidgetEditModal = createModal>(({ actions, i return ; })} - - - + + + + @@ -74,4 +102,8 @@ export const WidgetEditModal = createModal>(({ actions, i ); }).withOptions({ keepMounted: true, + defaultTitle(t) { + return t("item.edit.title"); + }, + size: "lg", }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52312eadc..f3612757e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -798,6 +798,9 @@ importers: '@homarr/log': specifier: workspace:^0.1.0 version: link:../log + '@homarr/translation': + specifier: workspace:^0.1.0 + version: link:../translation devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -926,6 +929,12 @@ importers: '@tiptap/starter-kit': specifier: ^2.4.0 version: 2.4.0(@tiptap/pm@2.2.4) + prismjs: + specifier: ^1.29.0 + version: 1.29.0 + react-simple-code-editor: + specifier: ^0.13.1 + version: 0.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) video.js: specifier: ^8.12.0 version: 8.12.0 @@ -939,6 +948,9 @@ importers: '@homarr/tsconfig': specifier: workspace:^0.1.0 version: link:../../tooling/typescript + '@types/prismjs': + specifier: ^1.26.4 + version: 1.26.4 '@types/video.js': specifier: ^7.3.58 version: 7.3.58 @@ -2367,6 +2379,9 @@ packages: '@types/object.pick@1.3.4': resolution: {integrity: sha512-5PjwB0uP2XDp3nt5u5NJAG2DORHIRClPzWT/TTZhJ2Ekwe8M5bA9tvPdi9NO/n2uvu2/ictat8kgqvLfcIE1SA==} + '@types/prismjs@1.26.4': + resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} + '@types/prop-types@15.7.11': resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} @@ -4647,6 +4662,10 @@ packages: pretty-format@3.8.0: resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -4784,6 +4803,12 @@ packages: '@types/react': optional: true + react-simple-code-editor@0.13.1: + resolution: {integrity: sha512-XYeVwRZwgyKtjNIYcAEgg2FaQcCZwhbarnkJIV20U2wkCU9q/CPFBo8nRXrK4GXUz3AvbqZFsZRrpUTkqqEYyQ==} + peerDependencies: + react: '*' + react-dom: '*' + react-style-singleton@2.2.1: resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -6968,6 +6993,8 @@ snapshots: '@types/object.pick@1.3.4': {} + '@types/prismjs@1.26.4': {} + '@types/prop-types@15.7.11': {} '@types/qs@6.9.11': {} @@ -9614,6 +9641,8 @@ snapshots: pretty-format@3.8.0: {} + prismjs@1.29.0: {} + process@0.11.10: {} prop-types@15.8.1: @@ -9799,6 +9828,11 @@ snapshots: optionalDependencies: '@types/react': 18.3.2 + react-simple-code-editor@0.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-style-singleton@2.2.1(@types/react@18.3.2)(react@18.3.1): dependencies: get-nonce: 1.0.1