diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b65d5ab4d..67a0a288a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,7 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.58.1 - 1.58.0 - 1.57.1 - 1.57.0 diff --git a/.github/workflows/skip-renovate-stability-days.yml b/.github/workflows/skip-renovate-stability-days.yml index 5e4be4054..e85b2e33e 100644 --- a/.github/workflows/skip-renovate-stability-days.yml +++ b/.github/workflows/skip-renovate-stability-days.yml @@ -7,7 +7,7 @@ on: jobs: skip-stability-days: - if: ${{ !startsWith(github.head_ref, 'renovate/') }} + if: ${{ !startsWith(github.head_ref, 'renovate/') && github.event.pull_request.head.repo.full_name == github.repository }} name: Skip Stability Days runs-on: ubuntu-latest timeout-minutes: 1 diff --git a/.gitignore b/.gitignore index 0aa3ed4c7..43488df7a 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,8 @@ e2e/shared/tmp apps/nextjs/public/images/background.png # next-intl -en.d.json.ts \ No newline at end of file +en.d.json.ts + +# personal agent config +AGENTS.md +.opencode \ No newline at end of file diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 5845f2ac7..968ff2415 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -55,10 +55,10 @@ "@mantine/modals": "^8.3.18", "@mantine/tiptap": "^8.3.18", "@million/lint": "1.0.14", - "@tabler/icons-react": "^3.41.0", - "@tanstack/react-query": "^5.95.2", - "@tanstack/react-query-devtools": "^5.95.2", - "@tanstack/react-query-next-experimental": "^5.95.2", + "@tabler/icons-react": "^3.41.1", + "@tanstack/react-query": "^5.96.2", + "@tanstack/react-query-devtools": "^5.96.2", + "@tanstack/react-query-next-experimental": "^5.96.2", "@trpc/client": "^11.16.0", "@trpc/next": "^11.16.0", "@trpc/react-query": "^11.16.0", @@ -70,20 +70,20 @@ "chroma-js": "^3.2.0", "clsx": "^2.1.1", "dayjs": "^1.11.20", - "dotenv": "^17.3.1", + "dotenv": "^17.4.1", "flag-icons": "^7.5.0", "glob": "^13.0.6", "isomorphic-dompurify": "^3.7.1", "jotai": "^2.19.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.2.1", + "next": "16.2.3", "postcss-preset-mantine": "^1.18.0", "prismjs": "^1.30.0", "react": "19.2.4", "react-dom": "19.2.4", "react-error-boundary": "^6.1.1", "react-simple-code-editor": "^0.14.1", - "sass": "^1.98.0", + "sass": "^1.99.0", "superjson": "2.2.6", "swagger-ui-react": "^5.32.1", "use-deep-compare-effect": "^1.8.1", @@ -94,7 +94,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "3.1.2", - "@types/node": "^24.12.0", + "@types/node": "^24.12.2", "@types/prismjs": "^1.26.6", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", diff --git a/apps/tasks/package.json b/apps/tasks/package.json index 1d58843dc..2b8f849d6 100644 --- a/apps/tasks/package.json +++ b/apps/tasks/package.json @@ -38,18 +38,18 @@ "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", "dayjs": "^1.11.20", - "dotenv": "^17.3.1", + "dotenv": "^17.4.1", "fastify": "^5.8.4", "superjson": "2.2.6", - "undici": "7.24.6" + "undici": "8.0.2" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "@types/node": "^24.12.0", + "@types/node": "^24.12.2", "dotenv-cli": "^11.0.0", - "esbuild": "^0.27.4", + "esbuild": "^0.28.0", "eslint": "^9.39.4", "prettier": "^3.8.1", "tsx": "4.20.4", diff --git a/apps/websocket/package.json b/apps/websocket/package.json index 0d235b01a..dc1ba62f2 100644 --- a/apps/websocket/package.json +++ b/apps/websocket/package.json @@ -25,7 +25,7 @@ "@homarr/definitions": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "dotenv": "^17.3.1", + "dotenv": "^17.4.1", "tsx": "4.20.4", "ws": "^8.20.0" }, @@ -34,7 +34,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/ws": "^8.18.1", - "esbuild": "^0.27.4", + "esbuild": "^0.28.0", "eslint": "^9.39.4", "prettier": "^3.8.1", "typescript": "^5.9.3" diff --git a/docs/README.md b/docs/README.md index f3a85f715..999df9791 100644 --- a/docs/README.md +++ b/docs/README.md @@ -64,6 +64,13 @@ + + Anchor +
+

Anchor

+
+ + Aria2
@@ -97,15 +104,15 @@

Deluge

- - + + Docker Hub

Docker
Hub

- - + + Emby
@@ -146,15 +153,15 @@

Home
Assistant

- - + + iCal

iCal

- - + + Immich
@@ -195,15 +202,15 @@

Nextcloud

- - + + NPM

NPM

- - + + ntfy
@@ -244,15 +251,15 @@

Pi-hole

- - + + Plex

Plex

- - + + Prowlarr
@@ -293,15 +300,15 @@

Readarr

- - + + SABnzbd

SABnzbd

- - + + Search.ch
@@ -330,13 +337,20 @@
+ + Speedtest Tracker +
+

Speedtest
Tracker

+
+ + Tdarr

Tdarr

- - + + Tracearr
@@ -349,8 +363,8 @@

Transmission

- - + + TrueNAS
diff --git a/package.json b/package.json index b84cfe167..14e98626f 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@semantic-release/npm": "^13.1.5", "@semantic-release/release-notes-generator": "^14.1.0", "@testcontainers/redis": "^11.13.0", - "@turbo/gen": "^2.8.21", + "@turbo/gen": "^2.9.3", "@vitejs/plugin-react": "^5.2.0", "@vitest/coverage-v8": "^4.1.2", "@vitest/ui": "^4.1.2", @@ -54,7 +54,7 @@ "prettier": "^3.8.1", "semantic-release": "^25.0.3", "testcontainers": "^11.13.0", - "turbo": "^2.8.21", + "turbo": "^2.9.3", "typescript": "^5.9.3", "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.2" @@ -79,18 +79,18 @@ "overrides": { "@babel/helpers@<7.26.10": ">=7.29.2", "@babel/runtime@<7.26.10": ">=7.29.2", - "axios@>=1.0.0 <1.8.2": ">=1.14.0", + "axios@>=1.0.0 <1.8.2": ">=1.15.0", "brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.5", "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.5", - "esbuild@<=0.24.2": ">=0.27.4", + "esbuild@<=0.24.2": ">=0.28.0", "form-data@>=4.0.0 <4.0.4": ">=4.0.5", - "hono@<4.6.5": ">=4.12.9", + "hono@<4.6.5": ">=4.12.10", "linkifyjs@<4.3.2": ">=4.3.2", "nanoid@>=4.0.0 <5.0.9": ">=5.1.7", "prismjs@<1.30.0": ">=1.30.0", - "proxmox-api>undici": "7.24.6", + "proxmox-api>undici": "8.0.2", "react-is": "^19.2.4", - "rollup@>=4.0.0 <4.22.4": ">=4.60.0", + "rollup@>=4.0.0 <4.22.4": ">=4.60.1", "sha.js@<=2.4.11": ">=2.4.12", "tar-fs@>=3.0.0 <3.0.9": ">=3.1.2", "tar-fs@>=2.0.0 <2.1.3": ">=3.1.2", diff --git a/packages/api/package.json b/packages/api/package.json index 0de085d2c..a20071b31 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -41,13 +41,13 @@ "@homarr/translation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@kubernetes/client-node": "^1.4.0", - "@tanstack/react-query": "^5.95.2", + "@tanstack/react-query": "^5.96.2", "@trpc/client": "^11.16.0", "@trpc/react-query": "^11.16.0", "@trpc/server": "^11.16.0", "@trpc/tanstack-react-query": "^11.16.0", "lodash.clonedeep": "^4.5.0", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "superjson": "2.2.6", diff --git a/packages/api/src/router/apiKeys.ts b/packages/api/src/router/apiKeys.ts index 9c0117085..71efd98ca 100644 --- a/packages/api/src/router/apiKeys.ts +++ b/packages/api/src/router/apiKeys.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; -import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; +import { hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; import { generateSecureRandomToken } from "@homarr/common/server"; import { db, eq } from "@homarr/db"; @@ -14,7 +14,6 @@ export const apiKeysRouter = createTRPCRouter({ columns: { id: true, apiKey: false, - salt: false, }, with: { user: { @@ -29,14 +28,12 @@ export const apiKeysRouter = createTRPCRouter({ }); }), create: permissionRequiredProcedure.requiresPermission("admin").mutation(async ({ ctx }) => { - const salt = await createSaltAsync(); const randomToken = generateSecureRandomToken(64); - const hashedRandomToken = await hashPasswordAsync(randomToken, salt); + const hashedRandomToken = await hashPasswordAsync(randomToken); const id = createId(); await db.insert(apiKeys).values({ id, apiKey: hashedRandomToken, - salt, userId: ctx.session.user.id, }); return { diff --git a/packages/api/src/router/test/widgets/rssFeed.spec.ts b/packages/api/src/router/test/widgets/rssFeed.spec.ts index acd7f5c17..4773fa149 100644 --- a/packages/api/src/router/test/widgets/rssFeed.spec.ts +++ b/packages/api/src/router/test/widgets/rssFeed.spec.ts @@ -208,6 +208,7 @@ async function setupUrlRestrictionAsync(dbUrls: string[]) { hideDescription: false, textLinesClamp: 3, maximumAmountPosts: 5, + showPosterImage: true, } satisfies WidgetComponentProps<"rssFeed">["options"]), }) satisfies InferInsertModel, ), diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index 954a79815..ad11f4fce 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -3,7 +3,7 @@ import type { MySqlRawQueryResult } from "drizzle-orm/mysql2"; import type { QueryResult } from "pg"; import { z } from "zod/v4"; -import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; +import { comparePasswordsAsync, hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; import { createLogger } from "@homarr/core/infrastructure/logs"; import type { Database } from "@homarr/db"; @@ -11,8 +11,8 @@ import { and, eq, handleTransactionsAsync, like } from "@homarr/db"; import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema"; import { selectUserSchema } from "@homarr/db/validationSchemas"; -import { credentialsAdminGroup } from "@homarr/definitions"; import type { SupportedAuthProvider } from "@homarr/definitions"; +import { credentialsAdminGroup } from "@homarr/definitions"; import { byIdSchema } from "@homarr/validation/common"; import type { userBaseCreateSchema } from "@homarr/validation/user"; import { @@ -92,15 +92,13 @@ export const userRouter = createTRPCRouter({ await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.username); - const salt = await createSaltAsync(); - const hashedPassword = await hashPasswordAsync(input.password, salt); + const hashedPassword = await hashPasswordAsync(input.password); const userId = createId(); const user = { id: userId, name: input.username, password: hashedPassword, - salt, }; await handleTransactionsAsync(ctx.db, { @@ -393,7 +391,6 @@ export const userRouter = createTRPCRouter({ columns: { id: true, password: true, - salt: true, provider: true, }, where: eq(users.id, input.userId), @@ -423,8 +420,7 @@ export const userRouter = createTRPCRouter({ }); if (isPreviousPasswordRequired) { - const previousPasswordHash = await hashPasswordAsync(input.previousPassword, dbUser.salt ?? ""); - const isValid = previousPasswordHash === dbUser.password; + const isValid = await comparePasswordsAsync(input.previousPassword, dbUser.password ?? ""); if (!isValid) { throw new TRPCError({ @@ -434,8 +430,7 @@ export const userRouter = createTRPCRouter({ } } - const salt = await createSaltAsync(); - const hashedPassword = await hashPasswordAsync(input.password, salt); + const hashedPassword = await hashPasswordAsync(input.password); await ctx.db .update(users) .set({ @@ -583,8 +578,7 @@ export const userRouter = createTRPCRouter({ }); const createUserAsync = async (db: Database, input: Omit, "groupIds">) => { - const salt = await createSaltAsync(); - const hashedPassword = await hashPasswordAsync(input.password, salt); + const hashedPassword = await hashPasswordAsync(input.password); const userId = createId(); await db.insert(users).values({ @@ -592,7 +586,6 @@ const createUserAsync = async (db: Database, input: Omit { + try { + const parsed: unknown = JSON.parse(value); + return typeof parsed === "object" && parsed !== null && Array.isArray((parsed as { ops?: unknown }).ops); + } catch { + return false; + } +}; + +const normalizeAnchorContent = (content: string | undefined) => { + if (content === undefined) return undefined; + + const trimmed = content.trim(); + if (!trimmed) { + return JSON.stringify({ ops: [{ insert: "\n" }] }); + } + + if (isJsonDeltaString(content)) return content; + + const normalizedText = content.endsWith("\n") ? content : `${content}\n`; + return JSON.stringify({ ops: [{ insert: normalizedText }] }); +}; + +export const anchorNotesRouter = createTRPCRouter({ + listNotes: publicProcedure + .concat(createOneIntegrationMiddleware("query", "anchor")) + .input(anchorNotesListInputSchema) + .query(async ({ ctx, input }) => { + const handler = anchorNotesListRequestHandler.handler(ctx.integration, { ...input, limit: input.limit ?? 50 }); + + const { data } = await handler.getCachedOrUpdatedDataAsync({ + forceUpdate: false, + }); + + return data; + }), + getNote: publicProcedure + .concat(createOneIntegrationMiddleware("query", "anchor")) + .input(noteIdInput) + .query(async ({ ctx, input }) => { + const handler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId }); + + const { data } = await handler.getCachedOrUpdatedDataAsync({ + forceUpdate: false, + }); + + return data; + }), + updateNote: protectedProcedure + .concat(createOneIntegrationMiddleware("interact", "anchor")) + .input(anchorNoteUpdateInputSchema) + .mutation(async ({ ctx, input }) => { + const integrationInstance = await createIntegrationAsync(ctx.integration); + const normalizedContent = normalizeAnchorContent(input.content); + const isContentNormalized = + input.content !== undefined && normalizedContent !== undefined && input.content !== normalizedContent; + + try { + const updatedNote = await integrationInstance.updateNoteAsync({ + noteId: input.noteId, + ...(input.title !== undefined ? { title: input.title } : {}), + ...(normalizedContent !== undefined ? { content: normalizedContent } : {}), + }); + + const noteHandler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId }); + await noteHandler.invalidateAsync(); + + return updatedNote; + } catch (error) { + if (error instanceof ResponseError && error.statusCode === 400) { + logger.warn("Anchor update note failed validation", { + noteId: input.noteId, + integrationId: ctx.integration.id, + contentLength: input.content?.length ?? 0, + contentIsJsonDelta: input.content ? isJsonDeltaString(input.content) : false, + contentNormalized: isContentNormalized, + }); + } + + if (error instanceof ResponseError && error.statusCode === 403) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You do not have permission to edit this note", + }); + } + + if (error instanceof ResponseError && error.statusCode === 404) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Note not found", + }); + } + + throw error; + } + }), +}); diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index e58c62e6a..5e29dfa72 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -3,6 +3,7 @@ import { lazy } from "@trpc/server"; import { createTRPCRouter } from "../../trpc"; export const widgetRouter = createTRPCRouter({ + anchorNotes: lazy(() => import("./anchor-notes").then((mod) => mod.anchorNotesRouter)), coolify: lazy(() => import("./coolify").then((mod) => mod.coolifyRouter)), immich: lazy(() => import("./immich").then((mod) => mod.immichRouter)), notebook: lazy(() => import("./notebook").then((mod) => mod.notebookRouter)), @@ -28,4 +29,5 @@ export const widgetRouter = createTRPCRouter({ notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)), timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)), tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)), + speedtestTracker: lazy(() => import("./speedtest-tracker").then((mod) => mod.speedtestTrackerRouter)), }); diff --git a/packages/api/src/router/widgets/rssFeed.ts b/packages/api/src/router/widgets/rssFeed.ts index 079d84a32..ec9c223e0 100644 --- a/packages/api/src/router/widgets/rssFeed.ts +++ b/packages/api/src/router/widgets/rssFeed.ts @@ -1,3 +1,4 @@ +import { observable } from "@trpc/server/observable"; import SuperJSON from "superjson"; import { z } from "zod/v4"; @@ -5,45 +6,59 @@ import type { Session } from "@homarr/auth"; import type { Database } from "@homarr/db"; import { eq } from "@homarr/db"; import { items, users } from "@homarr/db/schema"; +import type { ExtendedFeedEntry } from "@homarr/request-handler/rss-feeds"; import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; import type { WidgetComponentProps } from "../../../../widgets/src"; import { createTRPCRouter, publicProcedure } from "../../trpc"; +const feedsInput = z.object({ + urls: z.array(z.string()).max(100), + maximumAmountPosts: z.number(), +}); + export const rssFeedRouter = createTRPCRouter({ - getFeeds: publicProcedure - .input( - z.object({ - urls: z.array(z.string()).max(100), - maximumAmountPosts: z.number(), - }), - ) - .query(async ({ ctx, input }) => { - const urls = (await canAccessAllFeedsAsync(ctx.db, ctx.session)) - ? input.urls - : await restrictUrlsAsync(ctx.db, input.urls); + getFeeds: publicProcedure.input(feedsInput).query(async ({ ctx, input }) => { + const urls = (await canAccessAllFeedsAsync(ctx.db, ctx.session)) + ? input.urls + : await restrictUrlsAsync(ctx.db, input.urls); - const rssFeeds = await Promise.all( - urls.map(async (url) => { - const innerHandler = rssFeedsRequestHandler.handler({ - url, - count: input.maximumAmountPosts, - }); - return await innerHandler.getCachedOrUpdatedDataAsync({ - forceUpdate: false, - }); - }), - ); - - return rssFeeds - .flatMap((rssFeed) => rssFeed.data.entries) - .slice(0, input.maximumAmountPosts) - .sort((entryA, entryB) => { - return entryA.published && entryB.published - ? new Date(entryB.published).getTime() - new Date(entryA.published).getTime() - : 0; + const rssFeeds = await Promise.all( + urls.map(async (url) => { + const innerHandler = rssFeedsRequestHandler.handler({ + url, + count: input.maximumAmountPosts, }); - }), + return await innerHandler.getCachedOrUpdatedDataAsync({ + forceUpdate: false, + }); + }), + ); + + return rssFeeds + .flatMap((rssFeed) => rssFeed.data.entries) + .slice(0, input.maximumAmountPosts) + .sort((entryA, entryB) => { + return entryA.published && entryB.published + ? new Date(entryB.published).getTime() - new Date(entryA.published).getTime() + : 0; + }); + }), + subscribeFeeds: publicProcedure.input(feedsInput).subscription(({ input }) => { + // It is not necessary to check access to all feeds here, as the client will only subscribe to feeds which already exist + return observable<{ url: string; entries: ExtendedFeedEntry[] }>((emit) => { + const unsubscribes = input.urls.map((url) => { + const innerHandler = rssFeedsRequestHandler.handler({ url, count: input.maximumAmountPosts }); + return innerHandler.subscribe((data) => { + emit.next({ url, entries: data.entries }); + }); + }); + + return () => { + unsubscribes.forEach((unsubscribe) => unsubscribe()); + }; + }); + }), }); export async function canAccessAllFeedsAsync(db: Database, session: Session | null) { diff --git a/packages/api/src/router/widgets/speedtest-tracker.ts b/packages/api/src/router/widgets/speedtest-tracker.ts new file mode 100644 index 000000000..be4245921 --- /dev/null +++ b/packages/api/src/router/widgets/speedtest-tracker.ts @@ -0,0 +1,55 @@ +import { observable } from "@trpc/server/observable"; + +import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types"; +import { speedtestTrackerRequestHandler } from "@homarr/request-handler/speedtest-tracker"; + +import { createManyIntegrationMiddleware } from "../../middlewares/integration"; +import { createTRPCRouter, publicProcedure } from "../../trpc"; + +export const speedtestTrackerRouter = createTRPCRouter({ + getDashboard: publicProcedure + .concat(createManyIntegrationMiddleware("query", "speedtestTracker")) + .query(async ({ ctx }) => { + const results = await Promise.all( + ctx.integrations.map(async (integration) => { + const innerHandler = speedtestTrackerRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + dashboard: data, + updatedAt: timestamp, + }; + }), + ); + + return results; + }), + + subscribeToDashboard: publicProcedure + .concat(createManyIntegrationMiddleware("query", "speedtestTracker")) + .subscription(({ ctx }) => { + return observable<{ + integrationId: string; + dashboard: SpeedtestTrackerDashboardData; + timestamp: Date; + }>((emit) => { + const unsubscribes = ctx.integrations.map((integration) => { + const innerHandler = speedtestTrackerRequestHandler.handler(integration, {}); + return innerHandler.subscribe((dashboard) => { + emit.next({ + integrationId: integration.id, + dashboard, + timestamp: new Date(), + }); + }); + }); + + return () => { + unsubscribes.forEach((unsubscribe) => unsubscribe()); + }; + }); + }), +}); diff --git a/packages/auth/api-key/get-api-key-session.ts b/packages/auth/api-key/get-api-key-session.ts index 6fbaec972..632271498 100644 --- a/packages/auth/api-key/get-api-key-session.ts +++ b/packages/auth/api-key/get-api-key-session.ts @@ -5,7 +5,7 @@ import type { Database } from "@homarr/db"; import { eq } from "@homarr/db"; import { apiKeys } from "@homarr/db/schema"; -import { hashPasswordAsync } from "../security"; +import { comparePasswordsAsync } from "../security"; import { createSessionAsync } from "../server"; const logger = createLogger({ module: "apiKeyAuth" }); @@ -41,7 +41,6 @@ export const getSessionFromApiKeyAsync = async ( columns: { id: true, apiKey: true, - salt: true, }, with: { user: { @@ -60,9 +59,9 @@ export const getSessionFromApiKeyAsync = async ( return null; } - const hashedApiKey = await hashPasswordAsync(apiKey, apiKeyFromDb.salt); + const isValid = await comparePasswordsAsync(apiKey, apiKeyFromDb.apiKey); - if (apiKeyFromDb.apiKey !== hashedApiKey) { + if (!isValid) { logger.warn("Failed to authenticate with api-key", { ipAddress, userAgent, reason: "API_KEY_MISMATCH" }); return null; } diff --git a/packages/auth/api-key/test/get-api-key-session.spec.ts b/packages/auth/api-key/test/get-api-key-session.spec.ts index 5040ea441..dac713bd8 100644 --- a/packages/auth/api-key/test/get-api-key-session.spec.ts +++ b/packages/auth/api-key/test/get-api-key-session.spec.ts @@ -5,7 +5,7 @@ import { createId } from "@homarr/common"; import { apiKeys, users } from "@homarr/db/schema"; import { createDb } from "@homarr/db/test"; -import { createSaltAsync, hashPasswordAsync } from "../../security"; +import { hashPasswordAsync } from "../../security"; import { getSessionFromApiKeyAsync } from "../get-api-key-session"; // Mock the logger to avoid console output during tests @@ -118,11 +118,9 @@ const setupAsync = async (options?: SetupOptions) => { }); if (options?.token) { - const salt = await createSaltAsync(); await db.insert(apiKeys).values({ id: defaultApiKeyId, - apiKey: await hashPasswordAsync(options.token, salt), - salt, + apiKey: await hashPasswordAsync(options.token), userId: defaultUserId, }); } diff --git a/packages/auth/package.json b/packages/auth/package.json index 46f903e75..896275c8d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -34,7 +34,7 @@ "bcrypt": "^6.0.0", "cookies": "^0.9.1", "ldapts": "8.1.7", - "next": "16.2.1", + "next": "16.2.3", "next-auth": "5.0.0-beta.30", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/packages/auth/providers/credentials/authorization/basic-authorization.ts b/packages/auth/providers/credentials/authorization/basic-authorization.ts index 04253d837..2a6b96a31 100644 --- a/packages/auth/providers/credentials/authorization/basic-authorization.ts +++ b/packages/auth/providers/credentials/authorization/basic-authorization.ts @@ -1,4 +1,3 @@ -import bcrypt from "bcrypt"; import type { z } from "zod/v4"; import { createLogger } from "@homarr/core/infrastructure/logs"; @@ -7,6 +6,8 @@ import { and, eq } from "@homarr/db"; import { users } from "@homarr/db/schema"; import type { userSignInSchema } from "@homarr/validation/user"; +import { comparePasswordsAsync } from "../../../security"; + const logger = createLogger({ module: "basicAuthorization" }); export const authorizeWithBasicCredentialsAsync = async ( @@ -23,7 +24,7 @@ export const authorizeWithBasicCredentialsAsync = async ( } logger.info("User is trying to log in. Checking password...", { userName: user.name }); - const isValidPassword = await bcrypt.compare(credentials.password, user.password); + const isValidPassword = await comparePasswordsAsync(credentials.password, user.password); if (!isValidPassword) { logger.warn("Password for user was incorrect", { userName: user.name }); diff --git a/packages/auth/providers/test/basic-authorization.spec.ts b/packages/auth/providers/test/basic-authorization.spec.ts index 17d1d00e1..6188729c7 100644 --- a/packages/auth/providers/test/basic-authorization.spec.ts +++ b/packages/auth/providers/test/basic-authorization.spec.ts @@ -4,7 +4,7 @@ import { createId } from "@homarr/common"; import { users } from "@homarr/db/schema"; import { createDb } from "@homarr/db/test"; -import { createSaltAsync, hashPasswordAsync } from "../../security"; +import { hashPasswordAsync } from "../../security"; import { authorizeWithBasicCredentialsAsync } from "../credentials/authorization/basic-authorization"; const defaultUserId = createId(); @@ -13,12 +13,10 @@ describe("authorizeWithBasicCredentials", () => { test("should authorize user with correct credentials", async () => { // Arrange const db = createDb(); - const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, name: "test", - salt, - password: await hashPasswordAsync("test", salt), + password: await hashPasswordAsync("test"), }); // Act @@ -34,12 +32,10 @@ describe("authorizeWithBasicCredentials", () => { test("should not authorize user with incorrect credentials", async () => { // Arrange const db = createDb(); - const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, name: "test", - salt, - password: await hashPasswordAsync("test", salt), + password: await hashPasswordAsync("test"), }); // Act @@ -55,12 +51,10 @@ describe("authorizeWithBasicCredentials", () => { test("should not authorize user with incorrect username", async () => { // Arrange const db = createDb(); - const salt = await createSaltAsync(); await db.insert(users).values({ id: defaultUserId, name: "test", - salt, - password: await hashPasswordAsync("test", salt), + password: await hashPasswordAsync("test"), }); // Act diff --git a/packages/auth/security.ts b/packages/auth/security.ts index cc0c16c6b..de6c4523b 100644 --- a/packages/auth/security.ts +++ b/packages/auth/security.ts @@ -1,9 +1,15 @@ import bcrypt from "bcrypt"; -export const createSaltAsync = async () => { - return bcrypt.genSalt(10); +export const hashPasswordAsync = async (password: string) => { + return bcrypt.hash(password, 10); }; -export const hashPasswordAsync = async (password: string, salt: string) => { - return bcrypt.hash(password, salt); +/** + * Compares a plaintext password with a hashed password and returns whether they match. + * @param password plaintext password + * @param hash hashed password + * @returns wheter it is the same + */ +export const comparePasswordsAsync = async (password: string, hash: string) => { + return await bcrypt.compare(password, hash); }; diff --git a/packages/auth/test/security.spec.ts b/packages/auth/test/security.spec.ts index fed59513b..fee34ec7f 100644 --- a/packages/auth/test/security.spec.ts +++ b/packages/auth/test/security.spec.ts @@ -1,25 +1,11 @@ import { describe, expect, it } from "vitest"; -import { createSaltAsync, hashPasswordAsync } from "../security"; - -describe("createSalt should return a salt", () => { - it("should return a salt", async () => { - const result = await createSaltAsync(); - expect(result).toBeDefined(); - expect(result.length).toBeGreaterThan(25); - }); - it("should return a different salt each time", async () => { - const result1 = await createSaltAsync(); - const result2 = await createSaltAsync(); - expect(result1).not.toEqual(result2); - }); -}); +import { comparePasswordsAsync, hashPasswordAsync } from "../security"; describe("hashPassword should return a hash", () => { it("should return a hash", async () => { const password = "password"; - const salt = await createSaltAsync(); - const result = await hashPasswordAsync(password, salt); + const result = await hashPasswordAsync(password); expect(result).toBeDefined(); expect(result.length).toBeGreaterThan(55); expect(result).not.toEqual(password); @@ -27,21 +13,44 @@ describe("hashPassword should return a hash", () => { it("should return a different hash each time", async () => { const password = "password"; const password2 = "another password"; - const salt = await createSaltAsync(); - const result1 = await hashPasswordAsync(password, salt); - const result2 = await hashPasswordAsync(password2, salt); + const result1 = await hashPasswordAsync(password); + const result2 = await hashPasswordAsync(password2); expect(result1).not.toEqual(result2); }); it("should return a different hash for the same password with different salts", async () => { const password = "password"; - const salt1 = await createSaltAsync(); - const salt2 = await createSaltAsync(); - const result1 = await hashPasswordAsync(password, salt1); - const result2 = await hashPasswordAsync(password, salt2); + const result1 = await hashPasswordAsync(password); + const result2 = await hashPasswordAsync(password); expect(result1).not.toEqual(result2); }); }); + +describe("comparePasswordsAsync should compare passwords correctly", () => { + it("should return true for matching passwords", async () => { + // Arrange + const password = "password"; + const hash = await hashPasswordAsync(password); + + // Act + const result = await comparePasswordsAsync(password, hash); + + // Assert + expect(result).toBe(true); + }); + it("should return false for non-matching passwords", async () => { + // Arrange + const password = "password"; + const wrongPassword = "wrongpassword"; + const hash = await hashPasswordAsync(password); + + // Act + const result = await comparePasswordsAsync(wrongPassword, hash); + + // Assert + expect(result).toBe(false); + }); +}); diff --git a/packages/cli/package.json b/packages/cli/package.json index e73f61b65..0615fc8a8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,13 +30,13 @@ "@homarr/common": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", - "dotenv": "^17.3.1" + "dotenv": "^17.4.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "esbuild": "^0.27.4", + "esbuild": "^0.28.0", "eslint": "^9.39.4", "typescript": "^5.9.3" } diff --git a/packages/cli/src/commands/recreate-admin.ts b/packages/cli/src/commands/recreate-admin.ts index 8c2b9fd8d..5734d9c9d 100644 --- a/packages/cli/src/commands/recreate-admin.ts +++ b/packages/cli/src/commands/recreate-admin.ts @@ -1,6 +1,6 @@ import { command, string } from "@drizzle-team/brocli"; -import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; +import { hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; import { generateSecureRandomToken } from "@homarr/common/server"; import { and, count, db, eq } from "@homarr/db"; @@ -67,9 +67,8 @@ export const recreateAdmin = command({ permission: "admin", }); - const salt = await createSaltAsync(); const password = generateSecureRandomToken(24); - const hashedPassword = await hashPasswordAsync(password, salt); + const hashedPassword = await hashPasswordAsync(password); const userId = createId(); await db.insert(users).values({ @@ -77,7 +76,6 @@ export const recreateAdmin = command({ name: result.data, provider: "credentials", password: hashedPassword, - salt, }); await db.insert(groupMembers).values({ diff --git a/packages/cli/src/commands/reset-password.ts b/packages/cli/src/commands/reset-password.ts index ee4383205..1c10734d7 100644 --- a/packages/cli/src/commands/reset-password.ts +++ b/packages/cli/src/commands/reset-password.ts @@ -22,7 +22,7 @@ export const resetPassword = command({ where: and(eq(users.name, options.username), eq(users.provider, "credentials")), }); - if (!user?.salt) { + if (!user?.password) { console.error(`User ${options.username} not found`); return; } @@ -33,7 +33,7 @@ export const resetPassword = command({ await db .update(users) .set({ - password: await hashPasswordAsync(newPassword, user.salt), + password: await hashPasswordAsync(newPassword), }) .where(eq(users.id, user.id)); diff --git a/packages/cli/src/commands/users-update-password.ts b/packages/cli/src/commands/users-update-password.ts index c579f0d24..2db3342b1 100644 --- a/packages/cli/src/commands/users-update-password.ts +++ b/packages/cli/src/commands/users-update-password.ts @@ -29,7 +29,7 @@ export const usersUpdatePassword = command({ where: options.id ? eq(users.id, options.id) : eq(users.name, options.username!), }); - if (!user?.salt) { + if (!user?.password) { console.error("User not found or has no credentials record"); return; } @@ -37,7 +37,7 @@ export const usersUpdatePassword = command({ await db .update(users) .set({ - password: await hashPasswordAsync(options.password, user.salt), + password: await hashPasswordAsync(options.password), }) .where(eq(users.id, user.id)); diff --git a/packages/common/package.json b/packages/common/package.json index 5e014c671..bbc00b5bf 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -30,11 +30,11 @@ "@paralleldrive/cuid2": "^3.3.0", "cookie": "1.1.1", "dayjs": "^1.11.20", - "next": "16.2.1", + "next": "16.2.3", "octokit": "^5.0.5", "react": "19.2.4", "react-dom": "19.2.4", - "undici": "7.24.6", + "undici": "8.0.2", "zod": "^4.3.6", "zod-validation-error": "^5.0.0" }, diff --git a/packages/core/src/infrastructure/logs/format/metadata.ts b/packages/core/src/infrastructure/logs/format/metadata.ts index 057aa157d..515bea272 100644 --- a/packages/core/src/infrastructure/logs/format/metadata.ts +++ b/packages/core/src/infrastructure/logs/format/metadata.ts @@ -1,7 +1,10 @@ import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; export const formatMetadata = (metadata: Record | Error, ignoreKeys?: string[]) => { - const metadataObject = metadata instanceof ErrorWithMetadata ? metadata.metadata : metadata; + const metadataObject = + metadata instanceof ErrorWithMetadata || ("metadata" in metadata && typeof metadata.metadata === "object") + ? (metadata.metadata as Record) + : metadata; const filteredMetadata = Object.keys(metadataObject) .filter((key) => !ignoreKeys?.includes(key)) diff --git a/packages/cron-job-api/package.json b/packages/cron-job-api/package.json index 7a715adb7..4d3e229ea 100644 --- a/packages/cron-job-api/package.json +++ b/packages/cron-job-api/package.json @@ -28,7 +28,7 @@ "@homarr/common": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0", - "@tanstack/react-query": "^5.95.2", + "@tanstack/react-query": "^5.96.2", "@trpc/client": "^11.16.0", "@trpc/server": "^11.16.0", "@trpc/tanstack-react-query": "^11.16.0", diff --git a/packages/cron-jobs/src/jobs/rss-feeds.ts b/packages/cron-jobs/src/jobs/rss-feeds.ts index 2dda83499..6a497a043 100644 --- a/packages/cron-jobs/src/jobs/rss-feeds.ts +++ b/packages/cron-jobs/src/jobs/rss-feeds.ts @@ -5,9 +5,9 @@ import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { db, eq } from "@homarr/db"; import { items } from "@homarr/db/schema"; -// This import is done that way to avoid circular dependencies. import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; +// This import is done that way to avoid circular dependencies. import type { WidgetComponentProps } from "../../../widgets"; import { createCronJob } from "../lib"; diff --git a/packages/db/migrations/mysql/0037_remove_salt.sql b/packages/db/migrations/mysql/0037_remove_salt.sql new file mode 100644 index 000000000..20f6ed492 --- /dev/null +++ b/packages/db/migrations/mysql/0037_remove_salt.sql @@ -0,0 +1,2 @@ +ALTER TABLE `apiKey` DROP COLUMN `salt`;--> statement-breakpoint +ALTER TABLE `user` DROP COLUMN `salt`; \ No newline at end of file diff --git a/packages/db/migrations/mysql/meta/0037_snapshot.json b/packages/db/migrations/mysql/meta/0037_snapshot.json new file mode 100644 index 000000000..1ccd5e954 --- /dev/null +++ b/packages/db/migrations/mysql/meta/0037_snapshot.json @@ -0,0 +1,2096 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "730ee1c1-a35e-426f-a828-7ecde882f920", + "prevId": "90ea3b87-abde-433b-9cd4-0aaff72311a9", + "tables": { + "account": { + "name": "account", + "columns": { + "user_id": { + "name": "user_id", + "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 + }, + "provider_account_id": { + "name": "provider_account_id", + "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": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_provider_account_id_pk": { + "name": "account_provider_provider_account_id_pk", + "columns": ["provider", "provider_account_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_user_id_user_id_fk": { + "name": "apiKey_user_id_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "apiKey_id": { + "name": "apiKey_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "ping_url": { + "name": "ping_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "app_id": { + "name": "app_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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": {}, + "checkConstraint": {} + }, + "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": {}, + "checkConstraint": {} + }, + "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 + }, + "icon_color": { + "name": "icon_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_radius": { + "name": "item_radius", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('lg')" + }, + "disable_status": { + "name": "disable_status", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "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"] + } + }, + "checkConstraint": {} + }, + "cron_job_configuration": { + "name": "cron_job_configuration", + "columns": { + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "cron_job_configuration_name": { + "name": "cron_job_configuration_name", + "columns": ["name"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "groupMember": { + "name": "groupMember", + "columns": { + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_group_id_group_id_fk": { + "name": "groupMember_group_id_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_user_id_user_id_fk": { + "name": "groupMember_user_id_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_group_id_user_id_pk": { + "name": "groupMember_group_id_user_id_pk", + "columns": ["group_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_group_id_group_id_fk": { + "name": "groupPermission_group_id_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "home_board_id": { + "name": "home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "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" + }, + "group_home_board_id_board_id_fk": { + "name": "group_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "group_mobile_home_board_id_board_id_fk": { + "name": "group_mobile_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_id": { + "name": "group_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "group_name_unique": { + "name": "group_name_unique", + "columns": ["name"] + } + }, + "checkConstraint": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "iconRepository_id": { + "name": "iconRepository_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "icon": { + "name": "icon", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(250)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_repository_id": { + "name": "icon_repository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_icon_repository_id_iconRepository_id_fk": { + "name": "icon_icon_repository_id_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["icon_repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "icon_id": { + "name": "icon_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_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": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_group_permission__pk": { + "name": "integration_group_permission__pk", + "columns": ["integration_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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": {}, + "checkConstraint": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "varchar(64)", + "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": {}, + "checkConstraint": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_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": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "name": "integrationUserPermission_integration_id_user_id_permission_pk", + "columns": ["integration_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "app_id": { + "name": "app_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": { + "integration_app_id_app_id_fk": { + "name": "integration_app_id_app_id_fk", + "tableFrom": "integration", + "tableTo": "app", + "columnsFrom": ["app_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_id": { + "name": "integration_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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"] + } + }, + "checkConstraint": {} + }, + "item_layout": { + "name": "item_layout", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout_id": { + "name": "layout_id", + "type": "varchar(64)", + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "item_layout_item_id_item_id_fk": { + "name": "item_layout_item_id_item_id_fk", + "tableFrom": "item_layout", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_section_id_section_id_fk": { + "name": "item_layout_section_id_section_id_fk", + "tableFrom": "item_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_layout_id_layout_id_fk": { + "name": "item_layout_layout_id_layout_id_fk", + "tableFrom": "item_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_layout_item_id_section_id_layout_id_pk": { + "name": "item_layout_item_id_section_id_layout_id_pk", + "columns": ["item_id", "section_id", "layout_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "item": { + "name": "item", + "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 + }, + "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_board_id_board_id_fk": { + "name": "item_board_id_board_id_fk", + "tableFrom": "item", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_id": { + "name": "item_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "layout": { + "name": "layout", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "tinyint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breakpoint": { + "name": "breakpoint", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "layout_board_id_board_id_fk": { + "name": "layout_board_id_board_id_fk", + "tableFrom": "layout", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "layout_id": { + "name": "layout_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "media": { + "name": "media", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "LONGBLOB", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "media_creator_id_user_id_fk": { + "name": "media_creator_id_user_id_fk", + "tableFrom": "media", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_id": { + "name": "media_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "onboarding": { + "name": "onboarding", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step": { + "name": "step", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "previous_step": { + "name": "previous_step", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "onboarding_id": { + "name": "onboarding_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'generic'" + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "search_engine_integration_id_integration_id_fk": { + "name": "search_engine_integration_id_integration_id_fk", + "tableFrom": "search_engine", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "search_engine_id": { + "name": "search_engine_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "search_engine_short_unique": { + "name": "search_engine_short_unique", + "columns": ["short"] + } + }, + "checkConstraint": {} + }, + "section_collapse_state": { + "name": "section_collapse_state", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "collapsed": { + "name": "collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_collapse_state_user_id_user_id_fk": { + "name": "section_collapse_state_user_id_user_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_collapse_state_section_id_section_id_fk": { + "name": "section_collapse_state_section_id_section_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_collapse_state_user_id_section_id_pk": { + "name": "section_collapse_state_user_id_section_id_pk", + "columns": ["user_id", "section_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "section_layout": { + "name": "section_layout", + "columns": { + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout_id": { + "name": "layout_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "section_layout_section_id_section_id_fk": { + "name": "section_layout_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_layout_id_layout_id_fk": { + "name": "section_layout_layout_id_layout_id_fk", + "tableFrom": "section_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_parent_section_id_section_id_fk": { + "name": "section_layout_parent_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_layout_section_id_layout_id_pk": { + "name": "section_layout_section_id_layout_id_pk", + "columns": ["section_id", "layout_id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "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": {}, + "checkConstraint": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "setting_key": { + "name": "setting_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_setting_key": { + "name": "serverSetting_setting_key", + "columns": ["setting_key"] + } + }, + "uniqueConstraints": { + "serverSetting_settingKey_unique": { + "name": "serverSetting_settingKey_unique", + "columns": ["setting_key"] + } + }, + "checkConstraint": {} + }, + "session": { + "name": "session", + "columns": { + "session_token": { + "name": "session_token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "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": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_session_token": { + "name": "session_session_token", + "columns": ["session_token"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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 + }, + "email_verified": { + "name": "email_verified", + "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 + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "home_board_id": { + "name": "home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_search_engine_id": { + "name": "default_search_engine_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_search_in_new_tab": { + "name": "open_search_in_new_tab", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "color_scheme": { + "name": "color_scheme", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'dark'" + }, + "first_day_of_week": { + "name": "first_day_of_week", + "type": "tinyint", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "ping_icons_enabled": { + "name": "ping_icons_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_home_board_id_board_id_fk": { + "name": "user_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_mobile_home_board_id_board_id_fk": { + "name": "user_mobile_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_default_search_engine_id_search_engine_id_fk": { + "name": "user_default_search_engine_id_search_engine_id_fk", + "tableFrom": "user", + "tableTo": "search_engine", + "columnsFrom": ["default_search_engine_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "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": {}, + "checkConstraint": {} + }, + "trusted_certificate_hostname": { + "name": "trusted_certificate_hostname", + "columns": { + "hostname": { + "name": "hostname", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thumbprint": { + "name": "thumbprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "certificate": { + "name": "certificate", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "trusted_certificate_hostname_hostname_thumbprint_pk": { + "name": "trusted_certificate_hostname_hostname_thumbprint_pk", + "columns": ["hostname", "thumbprint"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json index 284af5f2a..aa12294e3 100644 --- a/packages/db/migrations/mysql/meta/_journal.json +++ b/packages/db/migrations/mysql/meta/_journal.json @@ -260,6 +260,13 @@ "when": 1760968518445, "tag": "0036_add_app_reference_to_integration", "breakpoints": true + }, + { + "idx": 37, + "version": "5", + "when": 1775399941430, + "tag": "0037_remove_salt", + "breakpoints": true } ] } diff --git a/packages/db/migrations/postgresql/0003_remove_salt.sql b/packages/db/migrations/postgresql/0003_remove_salt.sql new file mode 100644 index 000000000..413cdb18e --- /dev/null +++ b/packages/db/migrations/postgresql/0003_remove_salt.sql @@ -0,0 +1,2 @@ +ALTER TABLE "apiKey" DROP COLUMN "salt";--> statement-breakpoint +ALTER TABLE "user" DROP COLUMN "salt"; \ No newline at end of file diff --git a/packages/db/migrations/postgresql/meta/0003_snapshot.json b/packages/db/migrations/postgresql/meta/0003_snapshot.json new file mode 100644 index 000000000..8c2b80518 --- /dev/null +++ b/packages/db/migrations/postgresql/meta/0003_snapshot.json @@ -0,0 +1,1995 @@ +{ + "id": "259f3d62-93b1-4e4c-b201-38ee6d5a5db1", + "prevId": "66a8193e-c82f-4b41-99da-8d94f5737c62", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_provider_account_id_pk": { + "name": "account_provider_provider_account_id_pk", + "columns": ["provider", "provider_account_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apiKey": { + "name": "apiKey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_user_id_user_id_fk": { + "name": "apiKey_user_id_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app": { + "name": "app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ping_url": { + "name": "ping_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.boardGroupPermission": { + "name": "boardGroupPermission", + "schema": "", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + } + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.boardUserPermission": { + "name": "boardUserPermission", + "schema": "", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + } + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board": { + "name": "board", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'no-repeat'" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cover'" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#fa5252'" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#fd7e14'" + }, + "opacity": { + "name": "opacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_color": { + "name": "icon_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "item_radius": { + "name": "item_radius", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'lg'" + }, + "disable_status": { + "name": "disable_status", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "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": {}, + "uniqueConstraints": { + "board_name_unique": { + "name": "board_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cron_job_configuration": { + "name": "cron_job_configuration", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.groupMember": { + "name": "groupMember", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_group_id_group_id_fk": { + "name": "groupMember_group_id_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_user_id_user_id_fk": { + "name": "groupMember_user_id_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_group_id_user_id_pk": { + "name": "groupMember_group_id_user_id_pk", + "columns": ["group_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.groupPermission": { + "name": "groupPermission", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_group_id_group_id_fk": { + "name": "groupPermission_group_id_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group": { + "name": "group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "home_board_id": { + "name": "home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "smallint", + "primaryKey": false, + "notNull": true + } + }, + "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" + }, + "group_home_board_id_board_id_fk": { + "name": "group_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "group_mobile_home_board_id_board_id_fk": { + "name": "group_mobile_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "group_name_unique": { + "name": "group_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.iconRepository": { + "name": "iconRepository", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.icon": { + "name": "icon", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(250)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon_repository_id": { + "name": "icon_repository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "icon_icon_repository_id_iconRepository_id_fk": { + "name": "icon_icon_repository_id_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["icon_repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "schema": "", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_group_permission__pk": { + "name": "integration_group_permission__pk", + "columns": ["integration_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_item": { + "name": "integration_item", + "schema": "", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrationSecret": { + "name": "integrationSecret", + "schema": "", + "columns": { + "kind": { + "name": "kind", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrationUserPermission": { + "name": "integrationUserPermission", + "schema": "", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "name": "integrationUserPermission_integration_id_user_id_permission_pk", + "columns": ["integration_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration": { + "name": "integration", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_app_id_app_id_fk": { + "name": "integration_app_id_app_id_fk", + "tableFrom": "integration", + "tableTo": "app", + "columnsFrom": ["app_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invite": { + "name": "invite", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "expiration_date": { + "name": "expiration_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "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": {}, + "uniqueConstraints": { + "invite_token_unique": { + "name": "invite_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.item_layout": { + "name": "item_layout", + "schema": "", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "layout_id": { + "name": "layout_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "item_layout_item_id_item_id_fk": { + "name": "item_layout_item_id_item_id_fk", + "tableFrom": "item_layout", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_section_id_section_id_fk": { + "name": "item_layout_section_id_section_id_fk", + "tableFrom": "item_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_layout_id_layout_id_fk": { + "name": "item_layout_layout_id_layout_id_fk", + "tableFrom": "item_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_layout_item_id_section_id_layout_id_pk": { + "name": "item_layout_item_id_section_id_layout_id_pk", + "columns": ["item_id", "section_id", "layout_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.item": { + "name": "item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{\"json\": {}}'" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{\"json\": {}}'" + } + }, + "indexes": {}, + "foreignKeys": { + "item_board_id_board_id_fk": { + "name": "item_board_id_board_id_fk", + "tableFrom": "item", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.layout": { + "name": "layout", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "column_count": { + "name": "column_count", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "breakpoint": { + "name": "breakpoint", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "layout_board_id_board_id_fk": { + "name": "layout_board_id_board_id_fk", + "tableFrom": "layout", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "media_creator_id_user_id_fk": { + "name": "media_creator_id_user_id_fk", + "tableFrom": "media", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.onboarding": { + "name": "onboarding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "step": { + "name": "step", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "previous_step": { + "name": "previous_step", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.search_engine": { + "name": "search_engine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "short": { + "name": "short", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "search_engine_integration_id_integration_id_fk": { + "name": "search_engine_integration_id_integration_id_fk", + "tableFrom": "search_engine", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "search_engine_short_unique": { + "name": "search_engine_short_unique", + "nullsNotDistinct": false, + "columns": ["short"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.section_collapse_state": { + "name": "section_collapse_state", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "collapsed": { + "name": "collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_collapse_state_user_id_user_id_fk": { + "name": "section_collapse_state_user_id_user_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_collapse_state_section_id_section_id_fk": { + "name": "section_collapse_state_section_id_section_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_collapse_state_user_id_section_id_pk": { + "name": "section_collapse_state_user_id_section_id_pk", + "columns": ["user_id", "section_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.section_layout": { + "name": "section_layout", + "schema": "", + "columns": { + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "layout_id": { + "name": "layout_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "section_layout_section_id_section_id_fk": { + "name": "section_layout_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_layout_id_layout_id_fk": { + "name": "section_layout_layout_id_layout_id_fk", + "tableFrom": "section_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_parent_section_id_section_id_fk": { + "name": "section_layout_parent_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_layout_section_id_layout_id_pk": { + "name": "section_layout_section_id_layout_id_pk", + "columns": ["section_id", "layout_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.section": { + "name": "section", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'{\"json\": {}}'" + } + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.serverSetting": { + "name": "serverSetting", + "schema": "", + "columns": { + "setting_key": { + "name": "setting_key", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{\"json\": {}}'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "serverSetting_settingKey_unique": { + "name": "serverSetting_settingKey_unique", + "nullsNotDistinct": false, + "columns": ["setting_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "session_token": { + "name": "session_token", + "type": "varchar(512)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "default": "'credentials'" + }, + "home_board_id": { + "name": "home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "default_search_engine_id": { + "name": "default_search_engine_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "open_search_in_new_tab": { + "name": "open_search_in_new_tab", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color_scheme": { + "name": "color_scheme", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'dark'" + }, + "first_day_of_week": { + "name": "first_day_of_week", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "ping_icons_enabled": { + "name": "ping_icons_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_home_board_id_board_id_fk": { + "name": "user_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_mobile_home_board_id_board_id_fk": { + "name": "user_mobile_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_default_search_engine_id_search_engine_id_fk": { + "name": "user_default_search_engine_id_search_engine_id_fk", + "tableFrom": "user", + "tableTo": "search_engine", + "columnsFrom": ["default_search_engine_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_certificate_hostname": { + "name": "trusted_certificate_hostname", + "schema": "", + "columns": { + "hostname": { + "name": "hostname", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "thumbprint": { + "name": "thumbprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "certificate": { + "name": "certificate", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "trusted_certificate_hostname_hostname_thumbprint_pk": { + "name": "trusted_certificate_hostname_hostname_thumbprint_pk", + "columns": ["hostname", "thumbprint"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/postgresql/meta/_journal.json b/packages/db/migrations/postgresql/meta/_journal.json index 01f3a047c..e84263a17 100644 --- a/packages/db/migrations/postgresql/meta/_journal.json +++ b/packages/db/migrations/postgresql/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1760968530084, "tag": "0002_add_app_reference_to_integration", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1775399963041, + "tag": "0003_remove_salt", + "breakpoints": true } ] } diff --git a/packages/db/migrations/sqlite/0035_remove_salt.sql b/packages/db/migrations/sqlite/0035_remove_salt.sql new file mode 100644 index 000000000..20f6ed492 --- /dev/null +++ b/packages/db/migrations/sqlite/0035_remove_salt.sql @@ -0,0 +1,2 @@ +ALTER TABLE `apiKey` DROP COLUMN `salt`;--> statement-breakpoint +ALTER TABLE `user` DROP COLUMN `salt`; \ No newline at end of file diff --git a/packages/db/migrations/sqlite/meta/0035_snapshot.json b/packages/db/migrations/sqlite/meta/0035_snapshot.json new file mode 100644 index 000000000..6e987190d --- /dev/null +++ b/packages/db/migrations/sqlite/meta/0035_snapshot.json @@ -0,0 +1,2011 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "fc94243d-373c-4834-890e-560bbbd53f12", + "prevId": "26cc52b8-505b-44af-9cbd-6f70f6f3bb6b", + "tables": { + "account": { + "name": "account", + "columns": { + "user_id": { + "name": "user_id", + "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 + }, + "provider_account_id": { + "name": "provider_account_id", + "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": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_provider_account_id_pk": { + "columns": ["provider", "provider_account_id"], + "name": "account_provider_provider_account_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_user_id_user_id_fk": { + "name": "apiKey_user_id_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "ping_url": { + "name": "ping_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {}, + "checkConstraints": {} + }, + "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", "user_id", "permission"], + "name": "boardUserPermission_board_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "icon_color": { + "name": "icon_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_radius": { + "name": "item_radius", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lg'" + }, + "disable_status": { + "name": "disable_status", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "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": {}, + "checkConstraints": {} + }, + "cron_job_configuration": { + "name": "cron_job_configuration", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groupMember": { + "name": "groupMember", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_group_id_group_id_fk": { + "name": "groupMember_group_id_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_user_id_user_id_fk": { + "name": "groupMember_user_id_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_group_id_user_id_pk": { + "columns": ["group_id", "user_id"], + "name": "groupMember_group_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "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": { + "groupPermission_group_id_group_id_fk": { + "name": "groupPermission_group_id_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "home_board_id": { + "name": "home_board_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "group_name_unique": { + "name": "group_name_unique", + "columns": ["name"], + "isUnique": true + } + }, + "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" + }, + "group_home_board_id_board_id_fk": { + "name": "group_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "group_mobile_home_board_id_board_id_fk": { + "name": "group_mobile_home_board_id_board_id_fk", + "tableFrom": "group", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "icon": { + "name": "icon", + "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 + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_repository_id": { + "name": "icon_repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_icon_repository_id_iconRepository_id_fk": { + "name": "icon_icon_repository_id_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["icon_repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_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": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationGroupPermissions_integration_id_group_id_permission_pk": { + "columns": ["integration_id", "group_id", "permission"], + "name": "integrationGroupPermissions_integration_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": ["item_id", "integration_id"], + "name": "integration_item_item_id_integration_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {}, + "checkConstraints": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_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": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "columns": ["integration_id", "user_id", "permission"], + "name": "integrationUserPermission_integration_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": { + "integration_app_id_app_id_fk": { + "name": "integration_app_id_app_id_fk", + "tableFrom": "integration", + "tableTo": "app", + "columnsFrom": ["app_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {}, + "checkConstraints": {} + }, + "item_layout": { + "name": "item_layout", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout_id": { + "name": "layout_id", + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "item_layout_item_id_item_id_fk": { + "name": "item_layout_item_id_item_id_fk", + "tableFrom": "item_layout", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_section_id_section_id_fk": { + "name": "item_layout_section_id_section_id_fk", + "tableFrom": "item_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "item_layout_layout_id_layout_id_fk": { + "name": "item_layout_layout_id_layout_id_fk", + "tableFrom": "item_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_layout_item_id_section_id_layout_id_pk": { + "columns": ["item_id", "section_id", "layout_id"], + "name": "item_layout_item_id_section_id_layout_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "item": { + "name": "item", + "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 + }, + "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_board_id_board_id_fk": { + "name": "item_board_id_board_id_fk", + "tableFrom": "item", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "layout": { + "name": "layout", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "breakpoint": { + "name": "breakpoint", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "layout_board_id_board_id_fk": { + "name": "layout_board_id_board_id_fk", + "tableFrom": "layout", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media": { + "name": "media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "media_creator_id_user_id_fk": { + "name": "media_creator_id_user_id_fk", + "tableFrom": "media", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "onboarding": { + "name": "onboarding", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "step": { + "name": "step", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "previous_step": { + "name": "previous_step", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'generic'" + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "search_engine_short_unique": { + "name": "search_engine_short_unique", + "columns": ["short"], + "isUnique": true + } + }, + "foreignKeys": { + "search_engine_integration_id_integration_id_fk": { + "name": "search_engine_integration_id_integration_id_fk", + "tableFrom": "search_engine", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "section_collapse_state": { + "name": "section_collapse_state", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "collapsed": { + "name": "collapsed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_collapse_state_user_id_user_id_fk": { + "name": "section_collapse_state_user_id_user_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_collapse_state_section_id_section_id_fk": { + "name": "section_collapse_state_section_id_section_id_fk", + "tableFrom": "section_collapse_state", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_collapse_state_user_id_section_id_pk": { + "columns": ["user_id", "section_id"], + "name": "section_collapse_state_user_id_section_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "section_layout": { + "name": "section_layout", + "columns": { + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout_id": { + "name": "layout_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "section_layout_section_id_section_id_fk": { + "name": "section_layout_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_layout_id_layout_id_fk": { + "name": "section_layout_layout_id_layout_id_fk", + "tableFrom": "section_layout", + "tableTo": "layout", + "columnsFrom": ["layout_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_layout_parent_section_id_section_id_fk": { + "name": "section_layout_parent_section_id_section_id_fk", + "tableFrom": "section_layout", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_layout_section_id_layout_id_pk": { + "columns": ["section_id", "layout_id"], + "name": "section_layout_section_id_layout_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "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": {}, + "checkConstraints": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "setting_key": { + "name": "setting_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": { + "serverSetting_settingKey_unique": { + "name": "serverSetting_settingKey_unique", + "columns": ["setting_key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "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": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "trusted_certificate_hostname": { + "name": "trusted_certificate_hostname", + "columns": { + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thumbprint": { + "name": "thumbprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "certificate": { + "name": "certificate", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "trusted_certificate_hostname_hostname_thumbprint_pk": { + "columns": ["hostname", "thumbprint"], + "name": "trusted_certificate_hostname_hostname_thumbprint_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "email_verified": { + "name": "email_verified", + "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 + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "home_board_id": { + "name": "home_board_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mobile_home_board_id": { + "name": "mobile_home_board_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_search_engine_id": { + "name": "default_search_engine_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "open_search_in_new_tab": { + "name": "open_search_in_new_tab", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "color_scheme": { + "name": "color_scheme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'dark'" + }, + "first_day_of_week": { + "name": "first_day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "ping_icons_enabled": { + "name": "ping_icons_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_home_board_id_board_id_fk": { + "name": "user_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_mobile_home_board_id_board_id_fk": { + "name": "user_mobile_home_board_id_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["mobile_home_board_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_default_search_engine_id_search_engine_id_fk": { + "name": "user_default_search_engine_id_search_engine_id_fk", + "tableFrom": "user", + "tableTo": "search_engine", + "columnsFrom": ["default_search_engine_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json index ef6bdbd49..a5e7c4a39 100644 --- a/packages/db/migrations/sqlite/meta/_journal.json +++ b/packages/db/migrations/sqlite/meta/_journal.json @@ -246,6 +246,13 @@ "when": 1760968503571, "tag": "0034_add_app_reference_to_integration", "breakpoints": true + }, + { + "idx": 35, + "version": "6", + "when": 1775399918831, + "tag": "0035_remove_salt", + "breakpoints": true } ] } diff --git a/packages/db/package.json b/packages/db/package.json index e322c0374..7a5b21387 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -52,7 +52,7 @@ "@testcontainers/mysql": "^11.13.0", "@testcontainers/postgresql": "^11.13.0", "better-sqlite3": "^12.8.0", - "dotenv": "^17.3.1", + "dotenv": "^17.4.1", "drizzle-kit": "^0.31.10", "drizzle-orm": "^0.45.2", "drizzle-zod": "^0.8.3", @@ -67,7 +67,7 @@ "@types/better-sqlite3": "7.6.13", "@types/pg": "^8.20.0", "dotenv-cli": "^11.0.0", - "esbuild": "^0.27.4", + "esbuild": "^0.28.0", "eslint": "^9.39.4", "prettier": "^3.8.1", "tsx": "4.20.4", diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index edbcec281..719d0a74e 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -51,7 +51,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/mysql"; export const apiKeys = mysqlTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), - salt: text().notNull(), userId: varchar({ length: 64 }) .notNull() .references((): AnyMySqlColumn => users.id, { @@ -66,7 +65,6 @@ export const users = mysqlTable("user", { emailVerified: timestamp(), image: text(), password: text(), - salt: text(), provider: varchar({ length: 64 }).$type().default("credentials").notNull(), homeBoardId: varchar({ length: 64 }).references((): AnyMySqlColumn => boards.id, { onDelete: "set null", diff --git a/packages/db/schema/postgresql.ts b/packages/db/schema/postgresql.ts index e41d72f75..7b5a7af54 100644 --- a/packages/db/schema/postgresql.ts +++ b/packages/db/schema/postgresql.ts @@ -50,7 +50,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/postgresql" export const apiKeys = pgTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), - salt: text().notNull(), userId: varchar({ length: 64 }) .notNull() .references((): AnyPgColumn => users.id, { @@ -65,7 +64,6 @@ export const users = pgTable("user", { emailVerified: timestamp(), image: text(), password: text(), - salt: text(), provider: varchar({ length: 64 }).$type().default("credentials").notNull(), homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, { onDelete: "set null", diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index 78ec4ac9c..a2485596c 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -33,7 +33,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/sqlite"; export const apiKeys = sqliteTable("apiKey", { id: text().notNull().primaryKey(), apiKey: text().notNull(), - salt: text().notNull(), userId: text() .notNull() .references((): AnySQLiteColumn => users.id, { @@ -48,7 +47,6 @@ export const users = sqliteTable("user", { emailVerified: int({ mode: "timestamp_ms" }), image: text(), password: text(), - salt: text(), provider: text().$type().default("credentials").notNull(), homeBoardId: text().references((): AnySQLiteColumn => boards.id, { onDelete: "set null", diff --git a/packages/definitions/package.json b/packages/definitions/package.json index 34b0e4b34..0d5cc6e9f 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -24,7 +24,7 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", - "fast-xml-parser": "^5.5.9", + "fast-xml-parser": "^5.5.10", "zod": "^4.3.6" }, "devDependencies": { diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index e9853b4bb..5f7802a73 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -312,6 +312,14 @@ export const integrationDefs = { category: ["calendar"], documentationUrl: createDocumentationLink("/docs/integrations/ical"), }, + anchor: { + name: "Anchor", + secretKinds: [["apiKey"]], + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/anchor.svg", + category: ["notes"], + // @ts-expect-error - docs page will be created when integration is merged + documentationUrl: createDocumentationLink("/docs/integrations/anchor"), + }, truenas: { name: "TrueNAS", secretKinds: [["username", "password"]], @@ -356,6 +364,14 @@ export const integrationDefs = { category: ["mediaMonitoring"], documentationUrl: createDocumentationLink("/docs/integrations/tracearr"), }, + speedtestTracker: { + name: "Speedtest Tracker", + secretKinds: [["apiKey"]], + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png", + category: ["speedtest"], + // @ts-expect-error - docs page will be created when integration is merged + documentationUrl: createDocumentationLink("/docs/integrations/speedtest-tracker"), + }, // This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page) mock: { name: "Mock", @@ -444,7 +460,9 @@ export const integrationCategories = [ "firewall", "timetable", "photoService", + "notes", "mediaMonitoring", + "speedtest", ] as const; export type IntegrationCategory = (typeof integrationCategories)[number]; diff --git a/packages/definitions/src/widget.ts b/packages/definitions/src/widget.ts index 732b3f1bc..b246b32ea 100644 --- a/packages/definitions/src/widget.ts +++ b/packages/definitions/src/widget.ts @@ -5,6 +5,7 @@ export const widgetKinds = [ "iframe", "video", "notebook", + "anchorNote", "dnsHoleSummary", "dnsHoleControls", "smartHome-entityState", @@ -35,5 +36,6 @@ export const widgetKinds = [ "immich-serverStats", "immich-albumCarousel", "tracearr", + "speedtestTracker", ] as const; export type WidgetKind = (typeof widgetKinds)[number]; diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 406fedc1c..a31d27351 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -47,7 +47,7 @@ "proxmox-api": "1.1.1", "timezones-ical-library": "^2.1.3", "tsdav": "^2.1.8", - "undici": "7.24.6", + "undici": "8.0.2", "xml2js": "^0.6.2", "zod": "^4.3.6" }, diff --git a/packages/integrations/src/anchor/anchor-integration.ts b/packages/integrations/src/anchor/anchor-integration.ts new file mode 100644 index 000000000..5db4a024c --- /dev/null +++ b/packages/integrations/src/anchor/anchor-integration.ts @@ -0,0 +1,118 @@ +import type { fetch as undiciFetch } from "undici"; + +import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; +import { createLogger } from "@homarr/core/infrastructure/logs"; + +import type { IntegrationTestingInput } from "../base/integration"; +import { Integration } from "../base/integration"; +import { TestConnectionError } from "../base/test-connection/test-connection-error"; +import type { TestingResult } from "../base/test-connection/test-connection-service"; +import { anchorNoteSchema, anchorNoteSummaryListSchema } from "./anchor-types"; +import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary, AnchorNoteUpdateInput } from "./anchor-types"; + +const logger = createLogger({ module: "anchorIntegration" }); + +export class AnchorIntegration extends Integration { + protected async testingAsync(input: IntegrationTestingInput): Promise { + const response = await input.fetchAsync(this.url("/api/notes", { limit: 1 }), { + headers: { + Authorization: `Bearer ${this.getSecretValue("apiKey")}`, + }, + }); + + if (!response.ok) return TestConnectionError.StatusResult(response); + + anchorNoteSummaryListSchema.parse(await response.json()); + return { success: true }; + } + + public async listNotesAsync(input: AnchorNotesListInput = {}): Promise { + const queryParams = buildListQuery(input); + const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, "/api/notes", { + queryParams, + }); + return anchorNoteSummaryListSchema.parse(response); + } + + public async getNoteAsync(noteId: string): Promise { + const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, `/api/notes/${noteId}`); + return anchorNoteSchema.parse(response); + } + + public async updateNoteAsync(input: AnchorNoteUpdateInput): Promise { + const { noteId, ...updateData } = input; + const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, `/api/notes/${noteId}`, { + method: "PATCH", + body: updateData, + }); + if (response === null) { + return this.getNoteAsync(noteId); + } + return anchorNoteSchema.parse(response); + } + + private async requestAsync( + fetchAsync: typeof undiciFetch, + path: `/${string}`, + options?: { + queryParams?: Record; + method?: string; + body?: unknown; + }, + ): Promise { + const url = this.url(path, options?.queryParams); + const headers: Record = { + Authorization: `Bearer ${this.getSecretValue("apiKey")}`, + }; + + if (options?.body !== undefined) { + headers["Content-Type"] = "application/json"; + } + + const response = await fetchAsync(url, { + method: options?.method ?? "GET", + headers, + body: options?.body !== undefined ? JSON.stringify(options.body) : undefined, + }); + + if (!response.ok) { + let errorBody: string | null = null; + try { + errorBody = await response.text(); + } catch { + errorBody = null; + } + logger.warn("Anchor request failed", { + statusCode: response.status, + url, + errorBody: errorBody ? errorBody.slice(0, 200) : null, + }); + throw new ResponseError(response); + } + + if (response.status === 204) { + return null; + } + + return await response.json(); + } +} + +const buildListQuery = (input: AnchorNotesListInput) => { + const queryParams: Record = {}; + + if (input.search?.trim()) { + queryParams.search = input.search.trim(); + } + + if (input.tagId?.trim()) { + queryParams.tagId = input.tagId.trim(); + } + + if (typeof input.limit === "number") { + queryParams.limit = input.limit.toString(); + } + + return Object.keys(queryParams).length > 0 ? queryParams : undefined; +}; diff --git a/packages/integrations/src/anchor/anchor-types.ts b/packages/integrations/src/anchor/anchor-types.ts new file mode 100644 index 000000000..547969cbb --- /dev/null +++ b/packages/integrations/src/anchor/anchor-types.ts @@ -0,0 +1,43 @@ +import { z } from "zod/v4"; + +export const anchorNotesListInputSchema = z.object({ + search: z.string().optional(), + tagId: z.string().optional(), + limit: z.number().min(1).max(200).optional(), +}); +export type AnchorNotesListInput = z.infer; + +export const anchorNotePermissionSchema = z.enum(["owner", "viewer", "editor"]); +export type AnchorNotePermission = z.infer; + +export const anchorNoteSummarySchema = z.object({ + id: z.string(), + title: z.string(), + updatedAt: z.string(), + isPinned: z.boolean(), + tagIds: z.array(z.string()), + permission: anchorNotePermissionSchema, +}); +export type AnchorNoteSummary = z.infer; + +export const anchorNoteSchema = anchorNoteSummarySchema.extend({ + content: z.string().nullable().optional(), + createdAt: z.string(), + isArchived: z.boolean(), + background: z.string().nullable().optional(), + userId: z.string(), +}); +export type AnchorNote = z.infer; + +export const anchorNoteSummaryListSchema = z.array(anchorNoteSummarySchema); + +export const anchorNoteUpdateInputSchema = z + .object({ + noteId: z.string(), + title: z.string().optional(), + content: z.string().optional(), + }) + .refine((value) => value.title !== undefined || value.content !== undefined, { + message: "At least one field to update must be provided", + }); +export type AnchorNoteUpdateInput = z.infer; diff --git a/packages/integrations/src/base/creator.ts b/packages/integrations/src/base/creator.ts index c0743b5d4..5748bdd96 100644 --- a/packages/integrations/src/base/creator.ts +++ b/packages/integrations/src/base/creator.ts @@ -1,6 +1,7 @@ import type { IntegrationKind } from "@homarr/definitions"; import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration"; +import { AnchorIntegration } from "../anchor/anchor-integration"; import { CodebergIntegration } from "../codeberg/codeberg-integration"; import { CoolifyIntegration } from "../coolify/coolify-integration"; import { DashDotIntegration } from "../dashdot/dashdot-integration"; @@ -42,6 +43,7 @@ import { ProxmoxIntegration } from "../proxmox/proxmox-integration"; import { QuayIntegration } from "../quay/quay-integration"; import { SearchChIntegration } from "../search-ch/search-ch-integration"; import { SeerrIntegration } from "../seerr/seerr-integration"; +import { SpeedtestTrackerIntegration } from "../speedtest-tracker/speedtest-tracker-integration"; import { TracearrIntegration } from "../tracearr/tracearr-integration"; import { TrueNasIntegration } from "../truenas/truenas-integration"; import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration"; @@ -71,6 +73,7 @@ type IntegrationInstance = new (integration: IntegrationInput) => Integration; // factories are an array, to differentiate in js between class constructors and functions export const integrationCreators = { + anchor: AnchorIntegration, piHole: [createPiHoleIntegrationAsync], adGuardHome: AdGuardHomeIntegration, homeAssistant: HomeAssistantIntegration, @@ -117,6 +120,7 @@ export const integrationCreators = { glances: GlancesIntegration, searchCh: SearchChIntegration, immich: ImmichIntegration, + speedtestTracker: SpeedtestTrackerIntegration, } satisfies Record Promise]>; type IntegrationInstanceOfKind = { diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 3514e022b..2ac590bdb 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -1,5 +1,6 @@ // General integrations export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration"; +export { AnchorIntegration } from "./anchor/anchor-integration"; export { Aria2Integration } from "./download-client/aria2/aria2-integration"; export { DelugeIntegration } from "./download-client/deluge/deluge-integration"; export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration"; @@ -31,6 +32,7 @@ export { ICalIntegration } from "./ical/ical-integration"; export { CoolifyIntegration } from "./coolify/coolify-integration"; export { ImmichIntegration } from "./immich/immich-integration"; export { TracearrIntegration } from "./tracearr/tracearr-integration"; +export { SpeedtestTrackerIntegration } from "./speedtest-tracker/speedtest-tracker-integration"; // Types export type { IntegrationInput } from "./base/integration"; @@ -57,9 +59,19 @@ export type { export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types"; export type { Notification } from "./interfaces/notifications/notification-types"; export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration"; +export type { + AnchorNote, + AnchorNotePermission, + AnchorNoteUpdateInput, + AnchorNotesListInput, + AnchorNoteSummary, +} from "./anchor/anchor-types"; export type { TracearrDashboardData } from "./tracearr/tracearr-types"; +export type { SpeedtestTrackerDashboardData } from "./speedtest-tracker/speedtest-tracker-types"; // Schemas +export { anchorNotesListInputSchema } from "./anchor/anchor-types"; +export { anchorNoteUpdateInputSchema } from "./anchor/anchor-types"; export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items"; // Helpers diff --git a/packages/integrations/src/speedtest-tracker/speedtest-tracker-integration.ts b/packages/integrations/src/speedtest-tracker/speedtest-tracker-integration.ts new file mode 100644 index 000000000..80517c69e --- /dev/null +++ b/packages/integrations/src/speedtest-tracker/speedtest-tracker-integration.ts @@ -0,0 +1,98 @@ +import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; + +import type { IntegrationTestingInput } from "../base/integration"; +import { Integration } from "../base/integration"; +import { TestConnectionError } from "../base/test-connection/test-connection-error"; +import type { TestingResult } from "../base/test-connection/test-connection-service"; +import { + speedtestTrackerLatestResultEnvelopeSchema, + speedtestTrackerResultsCollectionSchema, + speedtestTrackerStatsEnvelopeSchema, +} from "./speedtest-tracker-types"; +import type { + SpeedtestTrackerDashboardData, + SpeedtestTrackerResult, + SpeedtestTrackerResultsCollection, + SpeedtestTrackerStats, +} from "./speedtest-tracker-types"; + +export class SpeedtestTrackerIntegration extends Integration { + protected async testingAsync(input: IntegrationTestingInput): Promise { + const url = this.url("/api/v1/results/latest"); + const response = await input.fetchAsync(url, { + headers: this.getAuthHeaders(), + }); + + if (!response.ok) { + return TestConnectionError.StatusResult(response); + } + + return { success: true }; + } + + public async getLatestResultAsync(): Promise { + const response = await this.getAsync("/api/v1/results/latest"); + + // Speedtest Tracker returns 404 when no tests have been run yet (fresh install) + if (response.status === 404) { + return null; + } + + if (!response.ok) { + throw new ResponseError(response); + } + + return speedtestTrackerLatestResultEnvelopeSchema.parse(await response.json()).data; + } + + public async getStatsAsync(): Promise { + const response = await this.getAsync("/api/v1/stats"); + + if (!response.ok) { + throw new ResponseError(response); + } + + return speedtestTrackerStatsEnvelopeSchema.parse(await response.json()).data; + } + + public async getRecentResultsAsync(perPage = 10): Promise { + const response = await this.getAsync("/api/v1/results", { + result_count: String(perPage), + sort: "-created_at", + }); + + if (!response.ok) { + throw new ResponseError(response); + } + + return speedtestTrackerResultsCollectionSchema.parse(await response.json()); + } + + public async getDashboardDataAsync(): Promise { + const [latestResult, stats, recentCollection] = await Promise.all([ + this.getLatestResultAsync(), + this.getStatsAsync(), + this.getRecentResultsAsync(30), + ]); + + return { + latestResult, + stats, + recentResults: recentCollection.data, + }; + } + + private async getAsync(path: `/api/v1/${string}`, queryParams?: Record) { + return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), { + headers: this.getAuthHeaders(), + }); + } + + private getAuthHeaders(): Record { + return { + Authorization: `Bearer ${this.getSecretValue("apiKey")}`, + Accept: "application/json", + }; + } +} diff --git a/packages/integrations/src/speedtest-tracker/speedtest-tracker-types.ts b/packages/integrations/src/speedtest-tracker/speedtest-tracker-types.ts new file mode 100644 index 000000000..83aa5bb24 --- /dev/null +++ b/packages/integrations/src/speedtest-tracker/speedtest-tracker-types.ts @@ -0,0 +1,58 @@ +import { z } from "zod/v4"; + +const createEnvelopeSchema = (dataSchema: T) => z.object({ data: dataSchema }); + +const parseTimestamp = (timestamp: string): Date => new Date(`${timestamp.replace(" ", "T")}Z`); + +export const speedtestTrackerResultSchema = z.object({ + id: z.number().int(), + ping: z.number().nullable(), + download_bits: z.number().int().nullish(), + upload_bits: z.number().int().nullish(), + healthy: z.boolean().nullable(), + created_at: z.string().transform(parseTimestamp), +}); + +export type SpeedtestTrackerResult = z.infer; + +export const speedtestTrackerLatestResultEnvelopeSchema = createEnvelopeSchema(speedtestTrackerResultSchema); + +export const speedtestTrackerStatsBandwidthSchema = z.object({ + avg: z.number(), + avg_bits: z.number().optional(), + min: z.number(), + max: z.number(), +}); + +export const speedtestTrackerStatsSchema = z.object({ + ping: z.object({ + avg: z.number(), + min: z.number(), + max: z.number(), + }), + download: speedtestTrackerStatsBandwidthSchema, + upload: speedtestTrackerStatsBandwidthSchema, + total_results: z.number().int(), +}); + +export type SpeedtestTrackerStats = z.infer; + +export const speedtestTrackerStatsEnvelopeSchema = createEnvelopeSchema(speedtestTrackerStatsSchema); + +export const speedtestTrackerResultsCollectionSchema = z.object({ + data: z.array(speedtestTrackerResultSchema), + meta: z.object({ + current_page: z.number().int(), + last_page: z.number().int(), + total: z.number().int(), + per_page: z.number().int().optional(), + }), +}); + +export type SpeedtestTrackerResultsCollection = z.infer; + +export interface SpeedtestTrackerDashboardData { + latestResult: SpeedtestTrackerResult | null; + stats: SpeedtestTrackerStats | null; + recentResults: SpeedtestTrackerResult[]; +} diff --git a/packages/integrations/src/types.ts b/packages/integrations/src/types.ts index 7c7bc4780..eb8b9e6ca 100644 --- a/packages/integrations/src/types.ts +++ b/packages/integrations/src/types.ts @@ -12,5 +12,7 @@ export * from "./unifi-controller/unifi-controller-types"; export * from "./opnsense/opnsense-types"; export * from "./interfaces/media-releases"; export * from "./coolify/coolify-types"; +export * from "./anchor/anchor-types"; export * from "./interfaces/timetable/timetable-types"; export * from "./tracearr/tracearr-types"; +export * from "./speedtest-tracker/speedtest-tracker-types"; diff --git a/packages/modals-collection/package.json b/packages/modals-collection/package.json index 4349797fd..e88993557 100644 --- a/packages/modals-collection/package.json +++ b/packages/modals-collection/package.json @@ -34,9 +34,9 @@ "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@mantine/core": "^8.3.18", - "@tabler/icons-react": "^3.41.0", + "@tabler/icons-react": "^3.41.1", "dayjs": "^1.11.20", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "zod": "^4.3.6" diff --git a/packages/notifications/package.json b/packages/notifications/package.json index ba8f2c6d4..096ffaeb1 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -25,7 +25,7 @@ "dependencies": { "@homarr/ui": "workspace:^0.1.0", "@mantine/notifications": "^8.3.18", - "@tabler/icons-react": "^3.41.0" + "@tabler/icons-react": "^3.41.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/old-import/package.json b/packages/old-import/package.json index 21145a5cb..26940cae7 100644 --- a/packages/old-import/package.json +++ b/packages/old-import/package.json @@ -39,8 +39,8 @@ "@homarr/validation": "workspace:^0.1.0", "@mantine/core": "^8.3.18", "@mantine/hooks": "^8.3.18", - "adm-zip": "0.5.16", - "next": "16.2.1", + "adm-zip": "0.5.17", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "superjson": "2.2.6", diff --git a/packages/old-import/src/mappers/map-user.ts b/packages/old-import/src/mappers/map-user.ts index f0b57cebf..48ca701ee 100644 --- a/packages/old-import/src/mappers/map-user.ts +++ b/packages/old-import/src/mappers/map-user.ts @@ -16,7 +16,7 @@ export const mapAndDecryptUsers = (importUsers: OldmarrImportUser[], encryptionT ({ id, password, - salt, + salt: _saltIsNotNeededAsPartOfPassword, settings, ...user }): InferInsertModel & { oldId: string; isAdmin: boolean } => ({ @@ -30,7 +30,6 @@ export const mapAndDecryptUsers = (importUsers: OldmarrImportUser[], encryptionT pingIconsEnabled: settings?.replacePingWithIcons, isAdmin: user.isAdmin || user.isOwner, password: decryptSecretWithKey(password, key), - salt: decryptSecretWithKey(salt, key), }), ); }; diff --git a/packages/old-import/src/widgets/options.ts b/packages/old-import/src/widgets/options.ts index b3e5f0b9f..32b1f25c3 100644 --- a/packages/old-import/src/widgets/options.ts +++ b/packages/old-import/src/widgets/options.ts @@ -122,6 +122,7 @@ const optionMapping: OptionMapping = { maximumAmountPosts: (oldOptions) => oldOptions.maximumAmountOfPosts, textLinesClamp: (oldOptions) => oldOptions.textLinesClamp, hideDescription: () => undefined, + showPosterImage: () => true, // This was the default in oldmarr }, notebook: { allowReadOnlyCheck: (oldOptions) => oldOptions.allowReadOnlyCheck, diff --git a/packages/request-handler/package.json b/packages/request-handler/package.json index f8c3f710a..87db3ce6a 100644 --- a/packages/request-handler/package.json +++ b/packages/request-handler/package.json @@ -33,7 +33,7 @@ "dayjs": "^1.11.20", "octokit": "^5.0.5", "superjson": "2.2.6", - "undici": "7.24.6", + "undici": "8.0.2", "zod": "^4.3.6" }, "devDependencies": { diff --git a/packages/request-handler/src/anchor-notes.ts b/packages/request-handler/src/anchor-notes.ts new file mode 100644 index 000000000..bd6f19e44 --- /dev/null +++ b/packages/request-handler/src/anchor-notes.ts @@ -0,0 +1,30 @@ +import dayjs from "dayjs"; + +import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary } from "@homarr/integrations"; +import { createIntegrationAsync } from "@homarr/integrations"; + +import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; + +export const anchorNotesListRequestHandler = createCachedIntegrationRequestHandler< + AnchorNoteSummary[], + "anchor", + AnchorNotesListInput +>({ + queryKey: "anchorNotesList", + cacheDuration: dayjs.duration(15, "seconds"), + async requestAsync(integration, input) { + const instance = await createIntegrationAsync(integration); + return instance.listNotesAsync(input); + }, +}); + +export const anchorNoteRequestHandler = createCachedIntegrationRequestHandler( + { + queryKey: "anchorNote", + cacheDuration: dayjs.duration(15, "seconds"), + async requestAsync(integration, input) { + const instance = await createIntegrationAsync(integration); + return instance.getNoteAsync(input.noteId); + }, + }, +); diff --git a/packages/request-handler/src/rss-feeds.ts b/packages/request-handler/src/rss-feeds.ts index eb13caa88..9eaf026ea 100644 --- a/packages/request-handler/src/rss-feeds.ts +++ b/packages/request-handler/src/rss-feeds.ts @@ -28,7 +28,7 @@ export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({ return { ...result, - entries: result.entries?.slice(0, input.count) ?? [], + entries: result.entries?.map((entry) => ({ ...entry, feedUrl: input.url })).slice(0, input.count) ?? [], }; }, cacheDuration: dayjs.duration(5, "minutes"), @@ -109,6 +109,7 @@ const getFirstMediaProperty = (feedObject: object) => { * This interface adds properties on top of the default ones. */ export interface ExtendedFeedEntry extends FeedEntry { + feedUrl: string; enclosure?: string; } diff --git a/packages/request-handler/src/speedtest-tracker.ts b/packages/request-handler/src/speedtest-tracker.ts new file mode 100644 index 000000000..799d37f16 --- /dev/null +++ b/packages/request-handler/src/speedtest-tracker.ts @@ -0,0 +1,19 @@ +import dayjs from "dayjs"; + +import { createIntegrationAsync } from "@homarr/integrations"; +import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types"; + +import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; + +export const speedtestTrackerRequestHandler = createCachedIntegrationRequestHandler< + SpeedtestTrackerDashboardData, + "speedtestTracker", + Record +>({ + async requestAsync(integration, _input) { + const integrationInstance = await createIntegrationAsync(integration); + return await integrationInstance.getDashboardDataAsync(); + }, + cacheDuration: dayjs.duration(10, "minutes"), + queryKey: "speedtestTrackerDashboard", +}); diff --git a/packages/settings/package.json b/packages/settings/package.json index 5f02ea5aa..5173ac99f 100644 --- a/packages/settings/package.json +++ b/packages/settings/package.json @@ -26,7 +26,7 @@ "@homarr/db": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", "@mantine/dates": "^8.3.18", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4" }, diff --git a/packages/spotlight/package.json b/packages/spotlight/package.json index 215c68bc6..459670f12 100644 --- a/packages/spotlight/package.json +++ b/packages/spotlight/package.json @@ -36,9 +36,9 @@ "@mantine/core": "^8.3.18", "@mantine/hooks": "^8.3.18", "@mantine/spotlight": "^8.3.18", - "@tabler/icons-react": "^3.41.0", + "@tabler/icons-react": "^3.41.1", "jotai": "^2.19.0", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "use-deep-compare-effect": "^1.8.1" diff --git a/packages/translation/package.json b/packages/translation/package.json index 374780c53..13da7310f 100644 --- a/packages/translation/package.json +++ b/packages/translation/package.json @@ -32,8 +32,8 @@ "dayjs": "^1.11.20", "deepmerge": "4.3.1", "mantine-react-table": "2.0.0-beta.9", - "next": "16.2.1", - "next-intl": "4.8.3", + "next": "16.2.3", + "next-intl": "4.9.0", "react": "19.2.4", "react-dom": "19.2.4" }, diff --git a/packages/translation/src/lang/en-gb.json b/packages/translation/src/lang/en-gb.json index 15f673443..8edd4057b 100644 --- a/packages/translation/src/lang/en-gb.json +++ b/packages/translation/src/lang/en-gb.json @@ -1526,13 +1526,13 @@ } }, "timetable": { - "name": "", - "description": "", - "title": "", - "noStation": "", + "name": "Timetable", + "description": "Displays departure times for a station", + "title": "Departures {station}", + "noStation": "No station selected", "option": { "station": { - "label": "" + "label": "Station" } } }, @@ -1977,177 +1977,177 @@ } }, "error": { - "internalServerError": "" + "internalServerError": "Failed to fetch containers stats" } }, "coolify": { - "name": "", - "description": "", + "name": "Coolify", + "description": "Overview of your Coolify instance with servers and applications", "option": { "showServers": { - "label": "" + "label": "Show servers" }, "showApplications": { - "label": "" + "label": "Show applications" }, "showServices": { - "label": "" + "label": "Show services" } }, "empty": { - "servers": "", - "applications": "", - "services": "" + "servers": "No servers found", + "applications": "No applications found", + "services": "No services found" }, "footer": { - "updated": "" + "updated": "Updated {when}" }, "error": { - "noIntegration": "", - "internalServerError": "" + "noIntegration": "No Coolify integration configured", + "internalServerError": "Failed to fetch Coolify data" }, "server": { - "buildServer": "" + "buildServer": "Build" }, "instances": { - "title": "" + "title": "Instances" } }, "tracearr": { - "name": "", - "description": "", + "name": "Tracearr", + "description": "Enhanced media server monitoring with detailed stream and transcoding data", "option": { "showStreams": { - "label": "" + "label": "Show active streams" }, "showStats": { - "label": "" + "label": "Show stats summary" }, "showRecentActivity": { - "label": "" + "label": "Show recent activity" }, "showViolations": { - "label": "" + "label": "Show violations" } }, "stats": { - "activeStreams": "", - "totalUsers": "", - "transcodes": "", - "bandwidth": "" + "activeStreams": "Active Streams", + "totalUsers": "Total Users", + "transcodes": "Transcodes", + "bandwidth": "Bandwidth" }, "streams": { - "empty": "", - "user": "", - "media": "", - "state": "", - "quality": "", - "device": "", - "progress": "", + "empty": "No active streams", + "user": "User", + "media": "Media", + "state": "State", + "quality": "Quality", + "device": "Device", + "progress": "Progress", "videoDecision": { - "directPlay": "", - "transcode": "", - "directStream": "" + "directPlay": "Direct Play", + "transcode": "Transcode", + "directStream": "Direct Stream" } }, "recentActivity": { - "title": "", - "empty": "", - "watched": "", - "partial": "" + "title": "Recent Activity", + "empty": "No recent activity", + "watched": "Watched", + "partial": "Partial" }, "violations": { - "title": "", - "empty": "", - "rule": "", - "user": "" + "title": "Violations", + "empty": "No recent violations", + "rule": "Rule", + "user": "User" }, - "noSectionsEnabled": "", + "noSectionsEnabled": "No sections enabled", "error": { - "noIntegration": "", - "internalServerError": "" + "noIntegration": "No Tracearr integration configured", + "internalServerError": "Failed to fetch Tracearr data" } }, "common": { "location": { - "query": "", - "latitude": "", - "longitude": "", - "disabledTooltip": "", - "unknownLocation": "", - "search": "", + "query": "City / Postal code", + "latitude": "Latitude", + "longitude": "Longitude", + "disabledTooltip": "Please enter a city or postal code", + "unknownLocation": "Unknown location", + "search": "Search", "table": { "header": { - "city": "", - "country": "", - "coordinates": "", - "population": "" + "city": "City", + "country": "Country", + "coordinates": "Coordinates", + "population": "Population" }, "action": { - "select": "" + "select": "Select {city}, {countryCode}" }, "population": { - "fallback": "" + "fallback": "Unknown" } } }, "integration": { - "noData": "", - "description": "" + "noData": "No integration found", + "description": "Click to create a new integration" }, "app": { - "noData": "", - "description": "", - "quickCreate": "" + "noData": "No app found", + "description": "Click to create a new app", + "quickCreate": "Create app on the fly" }, "error": { - "noIntegration": "", - "noData": "" + "noIntegration": "No integration selected", + "noData": "No integration data available" }, "option": {} }, "video": { - "name": "", - "description": "", + "name": "Video Stream", + "description": "Embed a video stream or video from a camera or a website", "option": { "feedUrl": { - "label": "" + "label": "Feed URL" }, "hasAutoPlay": { - "label": "", - "description": "" + "label": "Autoplay", + "description": "Autoplay only works when muted because of browser restrictions" }, "isMuted": { - "label": "" + "label": "Muted" }, "hasControls": { - "label": "" + "label": "Show controls" } }, "error": { - "noUrl": "", - "forYoutubeUseIframe": "" + "noUrl": "No Video URL provided", + "forYoutubeUseIframe": "For YouTube videos use the iframe option" } }, "mediaServer": { - "name": "", - "description": "", + "name": "Current media server streams", + "description": "Show the current streams on your media servers", "option": { "showOnlyPlaying": { - "label": "", - "description": "" + "label": "Show only currently playing", + "description": "Disabling this will not work for Plex" } }, "items": { - "currentlyPlaying": "", - "user": "", - "name": "", - "id": "", + "currentlyPlaying": "Currently playing", + "user": "User", + "name": "Name", + "id": "Id", "metadata": { - "title": "", + "title": "Stats for nerds", "video": { - "title": "", - "resolution": "" + "title": "Video", + "resolution": "Resolution" }, "audio": { "title": "", @@ -4335,13 +4335,13 @@ } }, "newUser": { - "label": "" + "label": "Create a new user" }, "newInvite": { - "label": "" + "label": "Create a new invite" }, "newGroup": { - "label": "" + "label": "Create a new group" } } } @@ -4384,12 +4384,12 @@ "description": "" }, "torrent": { - "name": "", - "description": "" + "name": "Torrents", + "description": "Search for torrents on torrentdownloads.pro" }, "youTube": { - "name": "", - "description": "" + "name": "YouTube", + "description": "Search for videos on YouTube" } } } @@ -4398,10 +4398,10 @@ "help": { "group": { "mode": { - "title": "" + "title": "Modes" }, "help": { - "title": "", + "title": "Help", "option": { "documentation": { "label": "Documentation" diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index a069f9c4e..aadcb0d96 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1606,6 +1606,34 @@ } } }, + "anchorNote": { + "name": "Anchor Note", + "description": "Displays a selected note from Anchor", + "option": { + "noteId": { + "label": "Note" + }, + "showTitle": { + "label": "Show title" + }, + "showUpdatedAt": { + "label": "Show last updated" + } + }, + "empty": "Select a note to display", + "emptyContent": "This note has no content", + "notFound": "Note not found", + "noNotes": "No notes found", + "integrationRequired": "Select an Anchor integration first", + "updatedAt": "Updated {date}", + "untitled": "Untitled", + "edit": "Edit", + "save": "Save", + "cancel": "Cancel", + "readOnlyViewer": "You have viewer access to this note. Editing is disabled.", + "saveForbidden": "You do not have permission to edit this note.", + "saveFailed": "Failed to save this note. Please try again." + }, "iframe": { "name": "iFrame", "description": "Embed any content from the internet. Some websites may restrict access.", @@ -2516,6 +2544,9 @@ }, "hideDescription": { "label": "Hide description" + }, + "showPosterImage": { + "label": "Show poster image" } } }, @@ -2729,6 +2760,45 @@ "noItems": "No notifications to display.", "option": {} }, + "speedtestTracker": { + "name": "Speedtest Tracker", + "description": "Displays speed test results from your Speedtest Tracker instance.", + "option": { + "showLatestResult": { + "label": "Show latest result" + }, + "showStats": { + "label": "Show averages" + }, + "showRecentResults": { + "label": "Show recent results" + }, + "showPingGraph": { + "label": "Show ping graph" + } + }, + "latestResult": "Latest Result", + "averages": "Averages", + "tests": "{count} tests", + "recentResults": "Recent Results", + "recentResultsSpeed": "Speed", + "recentResultsPing": "Ping", + "download": "Download", + "upload": "Upload", + "ping": "Ping", + "status": "Status", + "avgDownload": "Avg Download", + "avgUpload": "Avg Upload", + "avgPing": "Avg Ping", + "healthy": "Healthy", + "unhealthy": "Unhealthy", + "ok": "OK", + "fail": "Fail", + "noSectionsEnabled": "Enable at least one section in widget options.", + "error": { + "internalServerError": "Failed to fetch speedtest data" + } + }, "systemResources": { "name": "System resources", "description": "CPU, Memory, Disk and other hardware usage of your system", diff --git a/packages/translation/src/lang/it.json b/packages/translation/src/lang/it.json index 5740ac915..350825da2 100644 --- a/packages/translation/src/lang/it.json +++ b/packages/translation/src/lang/it.json @@ -1526,13 +1526,13 @@ } }, "timetable": { - "name": "", - "description": "", - "title": "", - "noStation": "", + "name": "Orario", + "description": "Visualizza gli orari di partenza per una stazione", + "title": "Partenze {station}", + "noStation": "Nessuna stazione selezionata", "option": { "station": { - "label": "" + "label": "Stazione" } } }, @@ -3525,7 +3525,7 @@ "label": "Stato del server Minecraft" }, "timetable": { - "label": "" + "label": "Orario" }, "iconsUpdater": { "label": "Aggiornatore Icone" diff --git a/packages/translation/src/lang/pt.json b/packages/translation/src/lang/pt.json index 82fe26570..4e5ac53d3 100644 --- a/packages/translation/src/lang/pt.json +++ b/packages/translation/src/lang/pt.json @@ -7,15 +7,15 @@ "description": "Para começar, por favor selecione como você deseja configurar sua instância Homarr.", "action": { "scratch": "Começar do zero", - "importOldmarr": "Importe de uma instância Homarr anterior a 1.0" + "importOldmarr": "Importe de uma instância do Homarr anterior a 1.0" } }, "import": { "title": "Importar dados", "subtitle": "Você pode importar dados de uma instância existente do Homarr.", "dropzone": { - "title": "Arraste o arquivo zip aqui ou clique para procurar", - "description": "O arquivo zip enviado será processado e você poderá selecionar o que deseja importar" + "title": "Arraste o arquivo ZIP aqui ou clique para procurar", + "description": "O arquivo ZIP enviado será processado e você poderá selecionar o que deseja importar" }, "fileInfo": { "action": { @@ -27,7 +27,7 @@ "description": "Configure o comportamento da importação" }, "boardSelection": { - "title": "{count} painéis encontrados", + "title": "{count} Painéis encontrados", "description": "Escolha todos os painéis com o tamanho que você deseja importar", "action": { "selectAll": "Selecionar todos", @@ -1526,13 +1526,13 @@ } }, "timetable": { - "name": "", - "description": "", - "title": "", - "noStation": "", + "name": "Calendário", + "description": "Mostra os horários de partida de uma estação", + "title": "Partidas {station}", + "noStation": "Nenhuma estação selecionada", "option": { "station": { - "label": "" + "label": "Estação" } } }, @@ -3525,7 +3525,7 @@ "label": "Status do servidor Minecraft" }, "timetable": { - "label": "" + "label": "Horários" }, "iconsUpdater": { "label": "Atualizador de Ícones" diff --git a/packages/ui/package.json b/packages/ui/package.json index 0556e69c9..a6b3bc6e3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -33,10 +33,10 @@ "@mantine/core": "^8.3.18", "@mantine/dates": "^8.3.18", "@mantine/hooks": "^8.3.18", - "@tabler/icons-react": "^3.41.0", + "@tabler/icons-react": "^3.41.1", "crypto-js": "^4.2.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "svgson": "^5.3.1" diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 46963188d..e5ece6f17 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -51,31 +51,32 @@ "@mantine/charts": "^8.3.18", "@mantine/core": "^8.3.18", "@mantine/hooks": "^8.3.18", - "@tabler/icons-react": "^3.41.0", - "@tiptap/extension-color": "3.21.0", - "@tiptap/extension-highlight": "3.21.0", - "@tiptap/extension-image": "3.21.0", - "@tiptap/extension-link": "^3.21.0", - "@tiptap/extension-placeholder": "^3.21.0", - "@tiptap/extension-table": "3.21.0", - "@tiptap/extension-table-cell": "3.21.0", - "@tiptap/extension-table-header": "3.21.0", - "@tiptap/extension-table-row": "3.21.0", - "@tiptap/extension-task-item": "3.21.0", - "@tiptap/extension-task-list": "3.21.0", - "@tiptap/extension-text-align": "3.21.0", - "@tiptap/extension-text-style": "3.21.0", - "@tiptap/extension-underline": "3.21.0", - "@tiptap/react": "^3.21.0", - "@tiptap/starter-kit": "^3.21.0", + "@tabler/icons-react": "^3.41.1", + "@tiptap/extension-color": "3.22.2", + "@tiptap/extension-highlight": "3.22.2", + "@tiptap/extension-image": "3.22.2", + "@tiptap/extension-link": "^3.22.2", + "@tiptap/extension-placeholder": "^3.22.2", + "@tiptap/extension-table": "3.22.2", + "@tiptap/extension-table-cell": "3.22.2", + "@tiptap/extension-table-header": "3.22.2", + "@tiptap/extension-table-row": "3.22.2", + "@tiptap/extension-task-item": "3.22.2", + "@tiptap/extension-task-list": "3.22.2", + "@tiptap/extension-text-align": "3.22.2", + "@tiptap/extension-text-style": "3.22.2", + "@tiptap/extension-underline": "3.22.2", + "@tiptap/react": "^3.22.2", + "@tiptap/starter-kit": "^3.22.2", "clsx": "^2.1.1", "dayjs": "^1.11.20", "mantine-form-zod-resolver": "^1.3.0", "mantine-react-table": "2.0.0-beta.9", - "next": "16.2.1", + "next": "16.2.3", "react": "19.2.4", "react-dom": "19.2.4", "react-markdown": "^10.1.0", + "react-quill-new": "^3.8.3", "recharts": "^2.15.4", "video.js": "^8.23.7", "zod": "^4.3.6" diff --git a/packages/widgets/src/_inputs/index.ts b/packages/widgets/src/_inputs/index.ts index 7018e8dc2..8917664de 100644 --- a/packages/widgets/src/_inputs/index.ts +++ b/packages/widgets/src/_inputs/index.ts @@ -1,4 +1,5 @@ import type { WidgetOptionType } from "../options"; +import { WidgetAnchorNoteInput } from "./widget-anchor-note-input"; import { WidgetAppInput } from "./widget-app-input"; import { WidgetDynamicSelectInput } from "./widget-dynamic-select-input"; import { WidgetLocationInput } from "./widget-location-input"; @@ -13,6 +14,7 @@ import { WidgetSwitchInput } from "./widget-switch-input"; import { WidgetTextInput } from "./widget-text-input"; const mapping = { + anchorNote: WidgetAnchorNoteInput, text: WidgetTextInput, location: WidgetLocationInput, multiSelect: WidgetMultiSelectInput, diff --git a/packages/widgets/src/_inputs/widget-anchor-note-input.tsx b/packages/widgets/src/_inputs/widget-anchor-note-input.tsx new file mode 100644 index 000000000..e045e7df0 --- /dev/null +++ b/packages/widgets/src/_inputs/widget-anchor-note-input.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { Loader, Select } from "@mantine/core"; +import { useDebouncedValue } from "@mantine/hooks"; + +import { clientApi } from "@homarr/api/client"; +import { useI18n, useScopedI18n } from "@homarr/translation/client"; + +import type { CommonWidgetInputProps } from "./common"; +import { useWidgetInputTranslation } from "./common"; +import { useFormContext } from "./form"; + +export const WidgetAnchorNoteInput = ({ property, kind, options }: CommonWidgetInputProps<"anchorNote">) => { + const t = useI18n(); + const tWidget = useWidgetInputTranslation(kind, property); + const tAnchor = useScopedI18n("widget.anchorNote"); + const form = useFormContext(); + + const integrationId = form.values.integrationIds[0]; + const [search, setSearch] = useState(""); + const [debouncedSearch] = useDebouncedValue(search, 250); + + const { data: notes, isPending } = clientApi.widget.anchorNotes.listNotes.useQuery( + { + integrationId: integrationId ?? "", + search: debouncedSearch.trim() ? debouncedSearch : undefined, + limit: 200, + }, + { + enabled: Boolean(integrationId), + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + retry: false, + }, + ); + + const noteOptions = useMemo( + () => + notes?.map((note) => ({ + value: note.id, + label: note.title || tAnchor("untitled"), + })) ?? [], + [notes, tAnchor], + ); + + return ( +