diff --git a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx
index 7ddf2816a..11aa2e8eb 100644
--- a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx
@@ -105,7 +105,7 @@ const AppNoResults = async () => {
{t("app.page.list.noResults.title")}
- {t("app.page.list.noResults.description")}
+ {t("app.page.list.noResults.action")}
);
diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx
index 58314d65a..b4d1564c4 100644
--- a/apps/nextjs/src/app/[locale]/manage/layout.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx
@@ -15,6 +15,7 @@ import {
IconPlug,
IconQuestionMark,
IconReport,
+ IconSearch,
IconSettings,
IconTool,
IconUser,
@@ -53,6 +54,11 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
href: "/manage/integrations",
label: t("items.integrations"),
},
+ {
+ icon: IconSearch,
+ href: "/manage/search-engines",
+ label: t("items.searchEngies"),
+ },
{
icon: IconUser,
label: t("items.users.label"),
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx
new file mode 100644
index 000000000..e937cf503
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import Link from "next/link";
+import { Button, Grid, Group, Stack, Textarea, TextInput } from "@mantine/core";
+
+import { useZodForm } from "@homarr/form";
+import type { TranslationFunction } from "@homarr/translation";
+import { useI18n } from "@homarr/translation/client";
+import type { z } from "@homarr/validation";
+import { validation } from "@homarr/validation";
+
+import { IconPicker } from "~/components/icons/picker/icon-picker";
+
+type FormType = z.infer;
+
+interface SearchEngineFormProps {
+ submitButtonTranslation: (t: TranslationFunction) => string;
+ initialValues?: FormType;
+ handleSubmit: (values: FormType) => void;
+ isPending: boolean;
+ disableShort?: boolean;
+}
+
+export const SearchEngineForm = (props: SearchEngineFormProps) => {
+ const { submitButtonTranslation, handleSubmit, initialValues, isPending, disableShort } = props;
+ const t = useI18n();
+
+ const form = useZodForm(validation.searchEngine.manage, {
+ initialValues: initialValues ?? {
+ name: "",
+ short: "",
+ iconUrl: "",
+ urlTemplate: "",
+ description: "",
+ },
+ });
+
+ return (
+
+ );
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/_search-engine-delete-button.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/_search-engine-delete-button.tsx
new file mode 100644
index 000000000..b8df69619
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/_search-engine-delete-button.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import { useCallback } from "react";
+import { ActionIcon } from "@mantine/core";
+import { IconTrash } from "@tabler/icons-react";
+
+import type { RouterOutputs } from "@homarr/api";
+import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
+import { useConfirmModal } from "@homarr/modals";
+import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
+import { useScopedI18n } from "@homarr/translation/client";
+
+interface SearchEngineDeleteButtonProps {
+ searchEngine: RouterOutputs["searchEngine"]["getPaginated"]["items"][number];
+}
+
+export const SearchEngineDeleteButton = ({ searchEngine }: SearchEngineDeleteButtonProps) => {
+ const t = useScopedI18n("search.engine.page.delete");
+ const { openConfirmModal } = useConfirmModal();
+ const { mutate, isPending } = clientApi.searchEngine.delete.useMutation();
+
+ const onClick = useCallback(() => {
+ openConfirmModal({
+ title: t("title"),
+ children: t("message", searchEngine),
+ onConfirm: () => {
+ mutate(
+ { id: searchEngine.id },
+ {
+ onSuccess: () => {
+ showSuccessNotification({
+ title: t("notification.success.title"),
+ message: t("notification.success.message"),
+ });
+ void revalidatePathActionAsync("/manage/search-engines");
+ },
+ onError: () => {
+ showErrorNotification({
+ title: t("notification.error.title"),
+ message: t("notification.error.message"),
+ });
+ },
+ },
+ );
+ },
+ });
+ }, [searchEngine, mutate, t, openConfirmModal]);
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx
new file mode 100644
index 000000000..93782e597
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import { useCallback } from "react";
+import { useRouter } from "next/navigation";
+
+import type { RouterOutputs } from "@homarr/api";
+import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
+import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
+import type { TranslationFunction } from "@homarr/translation";
+import { useScopedI18n } from "@homarr/translation/client";
+import type { validation, z } from "@homarr/validation";
+
+import { SearchEngineForm } from "../../_form";
+
+interface SearchEngineEditFormProps {
+ searchEngine: RouterOutputs["searchEngine"]["byId"];
+}
+
+export const SearchEngineEditForm = ({ searchEngine }: SearchEngineEditFormProps) => {
+ const t = useScopedI18n("search.engine.page.edit.notification");
+ const router = useRouter();
+
+ const { mutate, isPending } = clientApi.searchEngine.update.useMutation({
+ onSuccess: () => {
+ showSuccessNotification({
+ title: t("success.title"),
+ message: t("success.message"),
+ });
+ void revalidatePathActionAsync("/manage/search-engines").then(() => {
+ router.push("/manage/search-engines");
+ });
+ },
+ onError: () => {
+ showErrorNotification({
+ title: t("error.title"),
+ message: t("error.message"),
+ });
+ },
+ });
+
+ const handleSubmit = useCallback(
+ (values: z.infer) => {
+ mutate({
+ id: searchEngine.id,
+ ...values,
+ });
+ },
+ [mutate, searchEngine.id],
+ );
+
+ const submitButtonTranslation = useCallback((t: TranslationFunction) => t("common.action.save"), []);
+
+ return (
+
+ );
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/page.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/page.tsx
new file mode 100644
index 000000000..a130fb4fa
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/page.tsx
@@ -0,0 +1,27 @@
+import { Stack, Title } from "@mantine/core";
+
+import { api } from "@homarr/api/server";
+import { getI18n } from "@homarr/translation/server";
+
+import { ManageContainer } from "~/components/manage/manage-container";
+import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
+import { SearchEngineEditForm } from "./_search-engine-edit-form";
+
+interface SearchEngineEditPageProps {
+ params: { id: string };
+}
+
+export default async function SearchEngineEditPage({ params }: SearchEngineEditPageProps) {
+ const searchEngine = await api.searchEngine.byId({ id: params.id });
+ const t = await getI18n();
+
+ return (
+
+
+
+ {t("search.engine.page.edit.title")}
+
+
+
+ );
+}
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx
new file mode 100644
index 000000000..59fe95ccd
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { useCallback } from "react";
+import { useRouter } from "next/navigation";
+
+import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
+import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
+import type { TranslationFunction } from "@homarr/translation";
+import { useScopedI18n } from "@homarr/translation/client";
+import type { validation, z } from "@homarr/validation";
+
+import { SearchEngineForm } from "../_form";
+
+export const SearchEngineNewForm = () => {
+ const t = useScopedI18n("search.engine.page.create.notification");
+ const router = useRouter();
+
+ const { mutate, isPending } = clientApi.searchEngine.create.useMutation({
+ onSuccess: async () => {
+ showSuccessNotification({
+ title: t("success.title"),
+ message: t("success.message"),
+ });
+ await revalidatePathActionAsync("/manage/search-engines");
+ router.push("/manage/search-engines");
+ },
+ onError: () => {
+ showErrorNotification({
+ title: t("error.title"),
+ message: t("error.message"),
+ });
+ },
+ });
+
+ const handleSubmit = useCallback(
+ (values: z.infer) => {
+ mutate(values);
+ },
+ [mutate],
+ );
+
+ const submitButtonTranslation = useCallback((t: TranslationFunction) => t("common.action.create"), []);
+
+ return (
+
+ );
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/new/page.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/new/page.tsx
new file mode 100644
index 000000000..fb1c51340
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/new/page.tsx
@@ -0,0 +1,21 @@
+import { Stack, Title } from "@mantine/core";
+
+import { getI18n } from "@homarr/translation/server";
+
+import { ManageContainer } from "~/components/manage/manage-container";
+import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
+import { SearchEngineNewForm } from "./_search-engine-new-form";
+
+export default async function SearchEngineNewPage() {
+ const t = await getI18n();
+
+ return (
+
+
+
+ {t("search.engine.page.create.title")}
+
+
+
+ );
+}
diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx
new file mode 100644
index 000000000..2951e31a9
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx
@@ -0,0 +1,139 @@
+import Link from "next/link";
+import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
+import { IconPencil, IconSearch } from "@tabler/icons-react";
+
+import type { RouterOutputs } from "@homarr/api";
+import { api } from "@homarr/api/server";
+import { getI18n, getScopedI18n } from "@homarr/translation/server";
+import { SearchInput, TablePagination } from "@homarr/ui";
+import { z } from "@homarr/validation";
+
+import { ManageContainer } from "~/components/manage/manage-container";
+import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
+import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
+import { SearchEngineDeleteButton } from "./_search-engine-delete-button";
+
+const searchParamsSchema = z.object({
+ search: z.string().optional(),
+ pageSize: z.string().regex(/\d+/).transform(Number).catch(10),
+ page: z.string().regex(/\d+/).transform(Number).catch(1),
+});
+
+type SearchParamsSchemaInputFromSchema> = Partial<{
+ [K in keyof TSchema]: Exclude extends unknown[] ? string[] : string;
+}>;
+
+interface SearchEnginesPageProps {
+ searchParams: SearchParamsSchemaInputFromSchema>;
+}
+
+export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
+ const searchParams = searchParamsSchema.parse(props.searchParams);
+ const { items: searchEngines, totalCount } = await api.searchEngine.getPaginated(searchParams);
+
+ const t = await getI18n();
+ const tEngine = await getScopedI18n("search.engine");
+
+ return (
+
+
+
+ {tEngine("page.list.title")}
+
+
+
+ {tEngine("page.create.title")}
+
+
+ {searchEngines.length === 0 && }
+ {searchEngines.length > 0 && (
+
+ {searchEngines.map((searchEngine) => (
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+interface SearchEngineCardProps {
+ searchEngine: RouterOutputs["searchEngine"]["getPaginated"]["items"][number];
+}
+
+const SearchEngineCard = async ({ searchEngine }: SearchEngineCardProps) => {
+ const t = await getScopedI18n("search.engine");
+
+ return (
+
+
+
+
+
+
+ {searchEngine.name}
+
+ {searchEngine.description && (
+
+ {searchEngine.description}
+
+ )}
+
+ {searchEngine.urlTemplate}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const SearchEngineNoResults = async () => {
+ const t = await getI18n();
+
+ return (
+
+
+
+
+ {t("search.engine.page.list.noResults.title")}
+
+ {t("search.engine.page.list.noResults.action")}
+
+
+ );
+};
diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts
index 33eaf7780..a54648739 100644
--- a/packages/api/src/root.ts
+++ b/packages/api/src/root.ts
@@ -9,6 +9,7 @@ import { integrationRouter } from "./router/integration/integration-router";
import { inviteRouter } from "./router/invite";
import { locationRouter } from "./router/location";
import { logRouter } from "./router/log";
+import { searchEngineRouter } from "./router/search-engine/search-engine-router";
import { serverSettingsRouter } from "./router/serverSettings";
import { userRouter } from "./router/user";
import { widgetRouter } from "./router/widgets";
@@ -21,6 +22,7 @@ export const appRouter = createTRPCRouter({
integration: integrationRouter,
board: boardRouter,
app: innerAppRouter,
+ searchEngine: searchEngineRouter,
widget: widgetRouter,
location: locationRouter,
log: logRouter,
diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts
index 714828838..b6319625c 100644
--- a/packages/api/src/router/app.ts
+++ b/packages/api/src/router/app.ts
@@ -31,7 +31,7 @@ export const appRouter = createTRPCRouter({
limit: input.limit,
});
}),
- byId: publicProcedure.input(validation.app.byId).query(async ({ ctx, input }) => {
+ byId: publicProcedure.input(validation.common.byId).query(async ({ ctx, input }) => {
const app = await ctx.db.query.apps.findFirst({
where: eq(apps.id, input.id),
});
@@ -76,7 +76,7 @@ export const appRouter = createTRPCRouter({
})
.where(eq(apps.id, input.id));
}),
- delete: publicProcedure.input(validation.app.byId).mutation(async ({ ctx, input }) => {
+ delete: publicProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => {
await ctx.db.delete(apps).where(eq(apps.id, input.id));
}),
});
diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts
index 639a5bf15..1397abdd0 100644
--- a/packages/api/src/router/group.ts
+++ b/packages/api/src/router/group.ts
@@ -8,7 +8,7 @@ import { validation, z } from "@homarr/validation";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
export const groupRouter = createTRPCRouter({
- getPaginated: protectedProcedure.input(validation.group.paginated).query(async ({ input, ctx }) => {
+ getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => {
const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined;
const groupCount = await ctx.db
.select({
@@ -45,7 +45,7 @@ export const groupRouter = createTRPCRouter({
totalCount: groupCount[0]?.count ?? 0,
};
}),
- getById: protectedProcedure.input(validation.group.byId).query(async ({ input, ctx }) => {
+ getById: protectedProcedure.input(validation.common.byId).query(async ({ input, ctx }) => {
const group = await ctx.db.query.groups.findFirst({
where: eq(groups.id, input.id),
with: {
@@ -156,7 +156,7 @@ export const groupRouter = createTRPCRouter({
})
.where(eq(groups.id, input.groupId));
}),
- deleteGroup: protectedProcedure.input(validation.group.byId).mutation(async ({ input, ctx }) => {
+ deleteGroup: protectedProcedure.input(validation.common.byId).mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.id);
await ctx.db.delete(groups).where(eq(groups.id, input.id));
diff --git a/packages/api/src/router/search-engine/search-engine-router.ts b/packages/api/src/router/search-engine/search-engine-router.ts
new file mode 100644
index 000000000..0452857d5
--- /dev/null
+++ b/packages/api/src/router/search-engine/search-engine-router.ts
@@ -0,0 +1,85 @@
+import { TRPCError } from "@trpc/server";
+
+import { createId, eq, like, sql } from "@homarr/db";
+import { searchEngines } from "@homarr/db/schema/sqlite";
+import { validation } from "@homarr/validation";
+
+import { createTRPCRouter, protectedProcedure } from "../../trpc";
+
+export const searchEngineRouter = createTRPCRouter({
+ getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => {
+ const whereQuery = input.search ? like(searchEngines.name, `%${input.search.trim()}%`) : undefined;
+ const searchEngineCount = await ctx.db
+ .select({
+ count: sql`count(*)`,
+ })
+ .from(searchEngines)
+ .where(whereQuery);
+
+ const dbSearachEngines = await ctx.db.query.searchEngines.findMany({
+ limit: input.pageSize,
+ offset: (input.page - 1) * input.pageSize,
+ where: whereQuery,
+ });
+
+ return {
+ items: dbSearachEngines,
+ totalCount: searchEngineCount[0]?.count ?? 0,
+ };
+ }),
+ byId: protectedProcedure.input(validation.common.byId).query(async ({ ctx, input }) => {
+ const searchEngine = await ctx.db.query.searchEngines.findFirst({
+ where: eq(searchEngines.id, input.id),
+ });
+
+ if (!searchEngine) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Search engine not found",
+ });
+ }
+
+ return searchEngine;
+ }),
+ search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
+ return await ctx.db.query.searchEngines.findMany({
+ where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),
+ limit: input.limit,
+ });
+ }),
+ create: protectedProcedure.input(validation.searchEngine.manage).mutation(async ({ ctx, input }) => {
+ await ctx.db.insert(searchEngines).values({
+ id: createId(),
+ name: input.name,
+ short: input.short.toLowerCase(),
+ iconUrl: input.iconUrl,
+ urlTemplate: input.urlTemplate,
+ description: input.description,
+ });
+ }),
+ update: protectedProcedure.input(validation.searchEngine.edit).mutation(async ({ ctx, input }) => {
+ const searchEngine = await ctx.db.query.searchEngines.findFirst({
+ where: eq(searchEngines.id, input.id),
+ });
+
+ if (!searchEngine) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Search engine not found",
+ });
+ }
+
+ await ctx.db
+ .update(searchEngines)
+ .set({
+ name: input.name,
+ iconUrl: input.iconUrl,
+ urlTemplate: input.urlTemplate,
+ description: input.description,
+ })
+ .where(eq(searchEngines.id, input.id));
+ }),
+ delete: protectedProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => {
+ await ctx.db.delete(searchEngines).where(eq(searchEngines.id, input.id));
+ }),
+});
diff --git a/packages/db/migrations/mysql/0008_far_lifeguard.sql b/packages/db/migrations/mysql/0008_far_lifeguard.sql
new file mode 100644
index 000000000..9864d714f
--- /dev/null
+++ b/packages/db/migrations/mysql/0008_far_lifeguard.sql
@@ -0,0 +1,9 @@
+CREATE TABLE `search_engine` (
+ `id` varchar(64) NOT NULL,
+ `icon_url` text NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `short` varchar(8) NOT NULL,
+ `description` text,
+ `url_template` text NOT NULL,
+ CONSTRAINT `search_engine_id` PRIMARY KEY(`id`)
+);
diff --git a/packages/db/migrations/mysql/meta/0008_snapshot.json b/packages/db/migrations/mysql/meta/0008_snapshot.json
new file mode 100644
index 000000000..f75aa2f21
--- /dev/null
+++ b/packages/db/migrations/mysql/meta/0008_snapshot.json
@@ -0,0 +1,1429 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "7063029b-fdf4-405f-9808-85c61747185b",
+ "prevId": "b7264307-fc31-48c0-973f-0e0dd91f5f90",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "userId_idx": {
+ "name": "userId_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": ["provider", "providerAccountId"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "app": {
+ "name": "app",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "href": {
+ "name": "href",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "app_id": {
+ "name": "app_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "boardGroupPermission": {
+ "name": "boardGroupPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardGroupPermission_board_id_board_id_fk": {
+ "name": "boardGroupPermission_board_id_board_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardGroupPermission_group_id_group_id_fk": {
+ "name": "boardGroupPermission_group_id_group_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardGroupPermission_board_id_group_id_permission_pk": {
+ "name": "boardGroupPermission_board_id_group_id_permission_pk",
+ "columns": ["board_id", "group_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "boardUserPermission": {
+ "name": "boardUserPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardUserPermission_board_id_board_id_fk": {
+ "name": "boardUserPermission_board_id_board_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardUserPermission_user_id_user_id_fk": {
+ "name": "boardUserPermission_user_id_user_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardUserPermission_board_id_user_id_permission_pk": {
+ "name": "boardUserPermission_board_id_user_id_permission_pk",
+ "columns": ["board_id", "user_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "board": {
+ "name": "board",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "page_title": {
+ "name": "page_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta_title": {
+ "name": "meta_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logo_image_url": {
+ "name": "logo_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon_image_url": {
+ "name": "favicon_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_url": {
+ "name": "background_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_attachment": {
+ "name": "background_image_attachment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('fixed')"
+ },
+ "background_image_repeat": {
+ "name": "background_image_repeat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('no-repeat')"
+ },
+ "background_image_size": {
+ "name": "background_image_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('cover')"
+ },
+ "primary_color": {
+ "name": "primary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('#fa5252')"
+ },
+ "secondary_color": {
+ "name": "secondary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('#fd7e14')"
+ },
+ "opacity": {
+ "name": "opacity",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 100
+ },
+ "custom_css": {
+ "name": "custom_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "column_count": {
+ "name": "column_count",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 10
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "board_creator_id_user_id_fk": {
+ "name": "board_creator_id_user_id_fk",
+ "tableFrom": "board",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "board_id": {
+ "name": "board_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "board_name_unique": {
+ "name": "board_name_unique",
+ "columns": ["name"]
+ }
+ }
+ },
+ "groupMember": {
+ "name": "groupMember",
+ "columns": {
+ "groupId": {
+ "name": "groupId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "groupMember_groupId_group_id_fk": {
+ "name": "groupMember_groupId_group_id_fk",
+ "tableFrom": "groupMember",
+ "tableTo": "group",
+ "columnsFrom": ["groupId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "groupMember_userId_user_id_fk": {
+ "name": "groupMember_userId_user_id_fk",
+ "tableFrom": "groupMember",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "groupMember_groupId_userId_pk": {
+ "name": "groupMember_groupId_userId_pk",
+ "columns": ["groupId", "userId"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "groupPermission": {
+ "name": "groupPermission",
+ "columns": {
+ "groupId": {
+ "name": "groupId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "groupPermission_groupId_group_id_fk": {
+ "name": "groupPermission_groupId_group_id_fk",
+ "tableFrom": "groupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["groupId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "group": {
+ "name": "group",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "group_owner_id_user_id_fk": {
+ "name": "group_owner_id_user_id_fk",
+ "tableFrom": "group",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_id": {
+ "name": "group_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "iconRepository": {
+ "name": "iconRepository",
+ "columns": {
+ "iconRepository_id": {
+ "name": "iconRepository_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "iconRepository_slug": {
+ "name": "iconRepository_slug",
+ "type": "varchar(150)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "iconRepository_iconRepository_id": {
+ "name": "iconRepository_iconRepository_id",
+ "columns": ["iconRepository_id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "icon": {
+ "name": "icon",
+ "columns": {
+ "icon_id": {
+ "name": "icon_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_name": {
+ "name": "icon_name",
+ "type": "varchar(250)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_checksum": {
+ "name": "icon_checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "iconRepository_id": {
+ "name": "iconRepository_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "icon_iconRepository_id_iconRepository_iconRepository_id_fk": {
+ "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk",
+ "tableFrom": "icon",
+ "tableTo": "iconRepository",
+ "columnsFrom": ["iconRepository_id"],
+ "columnsTo": ["iconRepository_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "icon_icon_id": {
+ "name": "icon_icon_id",
+ "columns": ["icon_id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "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": {}
+ },
+ "integration_item": {
+ "name": "integration_item",
+ "columns": {
+ "item_id": {
+ "name": "item_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integration_id": {
+ "name": "integration_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integration_item_item_id_item_id_fk": {
+ "name": "integration_item_item_id_item_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "item",
+ "columnsFrom": ["item_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integration_item_integration_id_integration_id_fk": {
+ "name": "integration_item_integration_id_integration_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integration_item_item_id_integration_id_pk": {
+ "name": "integration_item_item_id_integration_id_pk",
+ "columns": ["item_id", "integration_id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "integrationSecret": {
+ "name": "integrationSecret",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "varchar(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integration_id": {
+ "name": "integration_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration_secret__kind_idx": {
+ "name": "integration_secret__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ },
+ "integration_secret__updated_at_idx": {
+ "name": "integration_secret__updated_at_idx",
+ "columns": ["updated_at"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "integrationSecret_integration_id_integration_id_fk": {
+ "name": "integrationSecret_integration_id_integration_id_fk",
+ "tableFrom": "integrationSecret",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integrationSecret_integration_id_kind_pk": {
+ "name": "integrationSecret_integration_id_kind_pk",
+ "columns": ["integration_id", "kind"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "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": {}
+ },
+ "integration": {
+ "name": "integration",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration__kind_idx": {
+ "name": "integration__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "integration_id": {
+ "name": "integration_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "invite": {
+ "name": "invite",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expiration_date": {
+ "name": "expiration_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invite_creator_id_user_id_fk": {
+ "name": "invite_creator_id_user_id_fk",
+ "tableFrom": "invite",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "invite_id": {
+ "name": "invite_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "invite_token_unique": {
+ "name": "invite_token_unique",
+ "columns": ["token"]
+ }
+ }
+ },
+ "item": {
+ "name": "item",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "x_offset": {
+ "name": "x_offset",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "y_offset": {
+ "name": "y_offset",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "width": {
+ "name": "width",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "height": {
+ "name": "height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "options": {
+ "name": "options",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('{\"json\": {}}')"
+ },
+ "advanced_options": {
+ "name": "advanced_options",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('{\"json\": {}}')"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "item_section_id_section_id_fk": {
+ "name": "item_section_id_section_id_fk",
+ "tableFrom": "item",
+ "tableTo": "section",
+ "columnsFrom": ["section_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "item_id": {
+ "name": "item_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "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": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "search_engine_id": {
+ "name": "search_engine_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "section": {
+ "name": "section",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "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": false,
+ "autoincrement": false
+ },
+ "height": {
+ "name": "height",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parent_section_id": {
+ "name": "parent_section_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "section_board_id_board_id_fk": {
+ "name": "section_board_id_board_id_fk",
+ "tableFrom": "section",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "section_parent_section_id_section_id_fk": {
+ "name": "section_parent_section_id_section_id_fk",
+ "tableFrom": "section",
+ "tableTo": "section",
+ "columnsFrom": ["parent_section_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "section_id": {
+ "name": "section_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "serverSetting": {
+ "name": "serverSetting",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('{\"json\": {}}')"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "serverSetting_key": {
+ "name": "serverSetting_key",
+ "columns": ["key"]
+ }
+ },
+ "uniqueConstraints": {
+ "serverSetting_key_unique": {
+ "name": "serverSetting_key_unique",
+ "columns": ["key"]
+ }
+ }
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_id_idx": {
+ "name": "user_id_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "session_sessionToken": {
+ "name": "session_sessionToken",
+ "columns": ["sessionToken"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'credentials'"
+ },
+ "homeBoardId": {
+ "name": "homeBoardId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colorScheme": {
+ "name": "colorScheme",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'auto'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_homeBoardId_board_id_fk": {
+ "name": "user_homeBoardId_board_id_fk",
+ "tableFrom": "user",
+ "tableTo": "board",
+ "columnsFrom": ["homeBoardId"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_id": {
+ "name": "user_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": ["identifier", "token"]
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json
index 8917665c6..59bfad2ae 100644
--- a/packages/db/migrations/mysql/meta/_journal.json
+++ b/packages/db/migrations/mysql/meta/_journal.json
@@ -57,6 +57,13 @@
"when": 1723749320706,
"tag": "0007_boring_nocturne",
"breakpoints": true
+ },
+ {
+ "idx": 8,
+ "version": "5",
+ "when": 1727532165317,
+ "tag": "0008_far_lifeguard",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/migrations/sqlite/0008_third_thor.sql b/packages/db/migrations/sqlite/0008_third_thor.sql
new file mode 100644
index 000000000..451a49004
--- /dev/null
+++ b/packages/db/migrations/sqlite/0008_third_thor.sql
@@ -0,0 +1,8 @@
+CREATE TABLE `search_engine` (
+ `id` text PRIMARY KEY NOT NULL,
+ `icon_url` text NOT NULL,
+ `name` text NOT NULL,
+ `short` text NOT NULL,
+ `description` text,
+ `url_template` text NOT NULL
+);
diff --git a/packages/db/migrations/sqlite/meta/0008_snapshot.json b/packages/db/migrations/sqlite/meta/0008_snapshot.json
new file mode 100644
index 000000000..b7b04d999
--- /dev/null
+++ b/packages/db/migrations/sqlite/meta/0008_snapshot.json
@@ -0,0 +1,1367 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "752c40b4-2326-4bb0-a714-42dc54a2b3e0",
+ "prevId": "11d3d936-3e3a-43b7-84b8-f0a1020b3b82",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "userId_idx": {
+ "name": "userId_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": ["provider", "providerAccountId"],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "app": {
+ "name": "app",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "href": {
+ "name": "href",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "boardGroupPermission": {
+ "name": "boardGroupPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardGroupPermission_board_id_board_id_fk": {
+ "name": "boardGroupPermission_board_id_board_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardGroupPermission_group_id_group_id_fk": {
+ "name": "boardGroupPermission_group_id_group_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardGroupPermission_board_id_group_id_permission_pk": {
+ "columns": ["board_id", "group_id", "permission"],
+ "name": "boardGroupPermission_board_id_group_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "boardUserPermission": {
+ "name": "boardUserPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardUserPermission_board_id_board_id_fk": {
+ "name": "boardUserPermission_board_id_board_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardUserPermission_user_id_user_id_fk": {
+ "name": "boardUserPermission_user_id_user_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardUserPermission_board_id_user_id_permission_pk": {
+ "columns": ["board_id", "user_id", "permission"],
+ "name": "boardUserPermission_board_id_user_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "board": {
+ "name": "board",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "page_title": {
+ "name": "page_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta_title": {
+ "name": "meta_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logo_image_url": {
+ "name": "logo_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon_image_url": {
+ "name": "favicon_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_url": {
+ "name": "background_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_attachment": {
+ "name": "background_image_attachment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'fixed'"
+ },
+ "background_image_repeat": {
+ "name": "background_image_repeat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'no-repeat'"
+ },
+ "background_image_size": {
+ "name": "background_image_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'cover'"
+ },
+ "primary_color": {
+ "name": "primary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#fa5252'"
+ },
+ "secondary_color": {
+ "name": "secondary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#fd7e14'"
+ },
+ "opacity": {
+ "name": "opacity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 100
+ },
+ "custom_css": {
+ "name": "custom_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "column_count": {
+ "name": "column_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 10
+ }
+ },
+ "indexes": {
+ "board_name_unique": {
+ "name": "board_name_unique",
+ "columns": ["name"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "board_creator_id_user_id_fk": {
+ "name": "board_creator_id_user_id_fk",
+ "tableFrom": "board",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "groupMember": {
+ "name": "groupMember",
+ "columns": {
+ "groupId": {
+ "name": "groupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "groupMember_groupId_group_id_fk": {
+ "name": "groupMember_groupId_group_id_fk",
+ "tableFrom": "groupMember",
+ "tableTo": "group",
+ "columnsFrom": ["groupId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "groupMember_userId_user_id_fk": {
+ "name": "groupMember_userId_user_id_fk",
+ "tableFrom": "groupMember",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "groupMember_groupId_userId_pk": {
+ "columns": ["groupId", "userId"],
+ "name": "groupMember_groupId_userId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "groupPermission": {
+ "name": "groupPermission",
+ "columns": {
+ "groupId": {
+ "name": "groupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "groupPermission_groupId_group_id_fk": {
+ "name": "groupPermission_groupId_group_id_fk",
+ "tableFrom": "groupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["groupId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "group": {
+ "name": "group",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "group_owner_id_user_id_fk": {
+ "name": "group_owner_id_user_id_fk",
+ "tableFrom": "group",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "iconRepository": {
+ "name": "iconRepository",
+ "columns": {
+ "iconRepository_id": {
+ "name": "iconRepository_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "iconRepository_slug": {
+ "name": "iconRepository_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "icon": {
+ "name": "icon",
+ "columns": {
+ "icon_id": {
+ "name": "icon_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_name": {
+ "name": "icon_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_checksum": {
+ "name": "icon_checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "iconRepository_id": {
+ "name": "iconRepository_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "icon_iconRepository_id_iconRepository_iconRepository_id_fk": {
+ "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk",
+ "tableFrom": "icon",
+ "tableTo": "iconRepository",
+ "columnsFrom": ["iconRepository_id"],
+ "columnsTo": ["iconRepository_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "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": {}
+ },
+ "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": {}
+ },
+ "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": {}
+ },
+ "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": {}
+ },
+ "integration": {
+ "name": "integration",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration__kind_idx": {
+ "name": "integration__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "invite": {
+ "name": "invite",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expiration_date": {
+ "name": "expiration_date",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "invite_token_unique": {
+ "name": "invite_token_unique",
+ "columns": ["token"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "invite_creator_id_user_id_fk": {
+ "name": "invite_creator_id_user_id_fk",
+ "tableFrom": "invite",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "item": {
+ "name": "item",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "x_offset": {
+ "name": "x_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "y_offset": {
+ "name": "y_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "options": {
+ "name": "options",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{\"json\": {}}'"
+ },
+ "advanced_options": {
+ "name": "advanced_options",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{\"json\": {}}'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "item_section_id_section_id_fk": {
+ "name": "item_section_id_section_id_fk",
+ "tableFrom": "item",
+ "tableTo": "section",
+ "columnsFrom": ["section_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "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": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "section": {
+ "name": "section",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "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": false,
+ "autoincrement": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "parent_section_id": {
+ "name": "parent_section_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "section_board_id_board_id_fk": {
+ "name": "section_board_id_board_id_fk",
+ "tableFrom": "section",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "section_parent_section_id_section_id_fk": {
+ "name": "section_parent_section_id_section_id_fk",
+ "tableFrom": "section",
+ "tableTo": "section",
+ "columnsFrom": ["parent_section_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "serverSetting": {
+ "name": "serverSetting",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{\"json\": {}}'"
+ }
+ },
+ "indexes": {
+ "serverSetting_key_unique": {
+ "name": "serverSetting_key_unique",
+ "columns": ["key"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_id_idx": {
+ "name": "user_id_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'credentials'"
+ },
+ "homeBoardId": {
+ "name": "homeBoardId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "colorScheme": {
+ "name": "colorScheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'auto'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_homeBoardId_board_id_fk": {
+ "name": "user_homeBoardId_board_id_fk",
+ "tableFrom": "user",
+ "tableTo": "board",
+ "columnsFrom": ["homeBoardId"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": ["identifier", "token"],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json
index e2e4864d9..edfca232c 100644
--- a/packages/db/migrations/sqlite/meta/_journal.json
+++ b/packages/db/migrations/sqlite/meta/_journal.json
@@ -57,6 +57,13 @@
"when": 1723746828385,
"tag": "0007_known_ultragirl",
"breakpoints": true
+ },
+ {
+ "idx": 8,
+ "version": "6",
+ "when": 1727526190343,
+ "tag": "0008_third_thor",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts
index e89942d43..64fe30cc0 100644
--- a/packages/db/schema/mysql.ts
+++ b/packages/db/schema/mysql.ts
@@ -341,6 +341,15 @@ export const serverSettings = mysqlTable("serverSetting", {
value: text("value").default('{"json": {}}').notNull(), // empty superjson object
});
+export const searchEngines = mysqlTable("search_engine", {
+ id: varchar("id", { length: 64 }).notNull().primaryKey(),
+ iconUrl: text("icon_url").notNull(),
+ name: varchar("name", { length: 64 }).notNull(),
+ short: varchar("short", { length: 8 }).notNull(),
+ description: text("description"),
+ urlTemplate: text("url_template").notNull(),
+});
+
export const accountRelations = relations(accounts, ({ one }) => ({
user: one(users, {
fields: [accounts.userId],
diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts
index 268c19c75..eebd75114 100644
--- a/packages/db/schema/sqlite.ts
+++ b/packages/db/schema/sqlite.ts
@@ -343,6 +343,15 @@ export const serverSettings = sqliteTable("serverSetting", {
value: text("value").default('{"json": {}}').notNull(), // empty superjson object
});
+export const searchEngines = sqliteTable("search_engine", {
+ id: text("id").notNull().primaryKey(),
+ iconUrl: text("icon_url").notNull(),
+ name: text("name").notNull(),
+ short: text("short").notNull(),
+ description: text("description"),
+ urlTemplate: text("url_template").notNull(),
+});
+
export const accountRelations = relations(accounts, ({ one }) => ({
user: one(users, {
fields: [accounts.userId],
diff --git a/packages/spotlight/src/components/actions/group-actions.tsx b/packages/spotlight/src/components/actions/group-actions.tsx
index ce2d8f370..1c208c491 100644
--- a/packages/spotlight/src/components/actions/group-actions.tsx
+++ b/packages/spotlight/src/components/actions/group-actions.tsx
@@ -1,4 +1,5 @@
import { Center, Loader } from "@mantine/core";
+import { useWindowEvent } from "@mantine/hooks";
import type { TranslationObject } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
@@ -27,6 +28,11 @@ export const SpotlightGroupActions = >({
const options = useOptions(query);
const t = useI18n();
+ useWindowEvent("keydown", (event) => {
+ const optionsArray = Array.isArray(options) ? options : (options.data ?? []);
+ group.onKeyDown?.(event, optionsArray, query, { setChildrenOptions });
+ });
+
if (Array.isArray(options)) {
const filteredOptions = options
.filter((option) => ("filter" in group ? group.filter(query, option) : false))
diff --git a/packages/spotlight/src/components/actions/groups/action-group.tsx b/packages/spotlight/src/components/actions/groups/action-group.tsx
index c35fa2d35..300e0f10a 100644
--- a/packages/spotlight/src/components/actions/groups/action-group.tsx
+++ b/packages/spotlight/src/components/actions/groups/action-group.tsx
@@ -15,18 +15,13 @@ interface SpotlightActionGroupsProps {
setChildrenOptions: (options: inferSearchInteractionOptions<"children">) => void;
}
-export const SpotlightActionGroups = ({ groups, query, setMode, setChildrenOptions }: SpotlightActionGroupsProps) => {
+export const SpotlightActionGroups = ({ groups, ...others }: SpotlightActionGroupsProps) => {
const t = useI18n();
return groups.map((group) => (
{/*eslint-disable-next-line @typescript-eslint/no-explicit-any */}
-
- group={group}
- query={query}
- setMode={setMode}
- setChildrenOptions={setChildrenOptions}
- />
+ group={group} {...others} />
));
};
diff --git a/packages/spotlight/src/components/spotlight.tsx b/packages/spotlight/src/components/spotlight.tsx
index c9c9c2904..5f1904926 100644
--- a/packages/spotlight/src/components/spotlight.tsx
+++ b/packages/spotlight/src/components/spotlight.tsx
@@ -105,8 +105,11 @@ export const Spotlight = () => {
}}
setChildrenOptions={(options) => {
setChildrenOptions(options);
- setQuery("");
- setTimeout(() => selectAction(0, spotlightStore));
+
+ setTimeout(() => {
+ setQuery("");
+ selectAction(0, spotlightStore);
+ });
}}
query={query}
groups={activeMode.groups}
diff --git a/packages/spotlight/src/lib/group.ts b/packages/spotlight/src/lib/group.ts
index d1cfc4a44..dda1fa7a8 100644
--- a/packages/spotlight/src/lib/group.ts
+++ b/packages/spotlight/src/lib/group.ts
@@ -2,7 +2,7 @@ import type { UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { stringOrTranslation } from "@homarr/translation";
-import type { inferSearchInteractionDefinition, SearchInteraction } from "./interaction";
+import type { inferSearchInteractionDefinition, inferSearchInteractionOptions, SearchInteraction } from "./interaction";
type CommonSearchGroup, TOptionProps extends Record> = {
// key path is used to define the path to a unique key in the option object
@@ -10,6 +10,14 @@ type CommonSearchGroup, TOptionProps ext
title: stringOrTranslation;
component: (option: TOption) => JSX.Element;
useInteraction: (option: TOption, query: string) => inferSearchInteractionDefinition;
+ onKeyDown?: (
+ event: KeyboardEvent,
+ options: TOption[],
+ query: string,
+ actions: {
+ setChildrenOptions: (options: inferSearchInteractionOptions<"children">) => void;
+ },
+ ) => void;
} & TOptionProps;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/packages/spotlight/src/modes/external/search-engines-search-group.tsx b/packages/spotlight/src/modes/external/search-engines-search-group.tsx
index 45669ba52..4c76532e9 100644
--- a/packages/spotlight/src/modes/external/search-engines-search-group.tsx
+++ b/packages/spotlight/src/modes/external/search-engines-search-group.tsx
@@ -1,82 +1,85 @@
-import { Group, Stack, Text } from "@mantine/core";
-import type { TablerIcon } from "@tabler/icons-react";
-import { IconDownload } from "@tabler/icons-react";
+import { Group, Kbd, Stack, Text } from "@mantine/core";
+import { IconSearch } from "@tabler/icons-react";
+import type { RouterOutputs } from "@homarr/api";
+import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
+import { createChildrenOptions } from "../../lib/children";
import { createGroup } from "../../lib/group";
import { interaction } from "../../lib/interaction";
-// This has to be type so it can be interpreted as Record.
-// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
-type SearchEngine = {
- short: string;
- image: string | TablerIcon;
- name: string;
- description: string;
- urlTemplate: string;
-};
+type SearchEngine = RouterOutputs["searchEngine"]["search"][number];
+
+export const searchEnginesChildrenOptions = createChildrenOptions({
+ useActions: () => [
+ {
+ key: "search",
+ component: ({ name }) => {
+ const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
+
+ return (
+
+
+ {tChildren("action.search.label", { name })}
+
+ );
+ },
+ useInteraction: interaction.link(({ urlTemplate }, query) => ({
+ href: urlTemplate.replace("%s", query),
+ })),
+ },
+ ],
+ detailComponent({ options }) {
+ const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
+ return (
+
+ {tChildren("detail.title")}
+
+
+ {options.name}
+
+
+ );
+ },
+});
export const searchEnginesSearchGroups = createGroup({
keyPath: "short",
title: (t) => t("search.mode.external.group.searchEngine.title"),
- component: ({ image: Image, name, description }) => (
-
-
- {typeof Image === "string" ? : }
-
- {name}
-
- {description}
-
-
+ component: ({ iconUrl, name, short, description }) => {
+ return (
+
+
+
+
+ {name}
+
+ {description}
+
+
+
+
+ {short}
-
- ),
- filter: () => true,
+ );
+ },
+ onKeyDown(event, options, query, { setChildrenOptions }) {
+ if (event.code !== "Space") return;
+
+ const engine = options.find((option) => option.short === query);
+ if (!engine) return;
+
+ setChildrenOptions(searchEnginesChildrenOptions(engine));
+ },
useInteraction: interaction.link(({ urlTemplate }, query) => ({
href: urlTemplate.replace("%s", query),
newTab: true,
})),
- useOptions() {
- const tOption = useScopedI18n("search.mode.external.group.searchEngine.option");
-
- return [
- {
- short: "g",
- name: tOption("google.name"),
- image: "https://www.google.com/favicon.ico",
- description: tOption("google.description"),
- urlTemplate: "https://www.google.com/search?q=%s",
- },
- {
- short: "b",
- name: tOption("bing.name"),
- image: "https://www.bing.com/favicon.ico",
- description: tOption("bing.description"),
- urlTemplate: "https://www.bing.com/search?q=%s",
- },
- {
- short: "d",
- name: tOption("duckduckgo.name"),
- image: "https://duckduckgo.com/favicon.ico",
- description: tOption("duckduckgo.description"),
- urlTemplate: "https://duckduckgo.com/?q=%s",
- },
- {
- short: "t",
- name: tOption("torrent.name"),
- image: IconDownload,
- description: tOption("torrent.description"),
- urlTemplate: "https://www.torrentdownloads.pro/search/?search=%s",
- },
- {
- short: "y",
- name: tOption("youTube.name"),
- image: "https://www.youtube.com/favicon.ico",
- description: tOption("youTube.description"),
- urlTemplate: "https://www.youtube.com/results?search_query=%s",
- },
- ];
+ useQueryOptions(query) {
+ return clientApi.searchEngine.search.useQuery({
+ query: query.trim(),
+ limit: 5,
+ });
},
});
diff --git a/packages/spotlight/src/modes/page/pages-search-group.tsx b/packages/spotlight/src/modes/page/pages-search-group.tsx
index d4ad2eb52..f3efe677f 100644
--- a/packages/spotlight/src/modes/page/pages-search-group.tsx
+++ b/packages/spotlight/src/modes/page/pages-search-group.tsx
@@ -9,6 +9,7 @@ import {
IconMailForward,
IconPlug,
IconReport,
+ IconSearch,
IconSettings,
IconUsers,
IconUsersGroup,
@@ -82,6 +83,12 @@ export const pagesSearchGroup = createGroup<{
name: t("manageIntegration.label"),
hidden: !session,
},
+ {
+ icon: IconSearch,
+ path: "/manage/search-engines",
+ name: t("manageSearchEngine.label"),
+ hidden: !session,
+ },
{
icon: IconUsers,
path: "/manage/users",
diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts
index e1248959b..953733613 100644
--- a/packages/translation/src/lang/en.ts
+++ b/packages/translation/src/lang/en.ts
@@ -303,8 +303,8 @@ export default {
list: {
title: "Apps",
noResults: {
- title: "There aren't any apps.",
- description: "Create your first app",
+ title: "There aren't any apps",
+ action: "Create your first app",
},
},
create: {
@@ -1559,6 +1559,7 @@ export default {
boards: "Boards",
apps: "Apps",
integrations: "Integrations",
+ searchEngies: "Search engines",
users: {
label: "Users",
items: {
@@ -2010,13 +2011,22 @@ export default {
label: "New",
},
},
+ "search-engines": {
+ label: "Search engines",
+ new: {
+ label: "New",
+ },
+ edit: {
+ label: "Edit",
+ },
+ },
apps: {
label: "Apps",
new: {
- label: "New App",
+ label: "New",
},
edit: {
- label: "Edit App",
+ label: "Edit",
},
},
users: {
@@ -2154,6 +2164,16 @@ export default {
group: {
searchEngine: {
title: "Search engines",
+ children: {
+ action: {
+ search: {
+ label: "Search with {name}",
+ },
+ },
+ detail: {
+ title: "Select an action for the search engine",
+ },
+ },
option: {
google: {
name: "Google",
@@ -2218,6 +2238,9 @@ export default {
manageIntegration: {
label: "Manage integrations",
},
+ manageSearchEngine: {
+ label: "Manage search engines",
+ },
manageUser: {
label: "Manage users",
},
@@ -2293,5 +2316,71 @@ export default {
},
},
},
+ engine: {
+ search: "Find a search engine",
+ field: {
+ name: {
+ label: "Name",
+ },
+ short: {
+ label: "Short",
+ },
+ urlTemplate: {
+ label: "URL search template",
+ },
+ description: {
+ label: "Description",
+ },
+ },
+ page: {
+ list: {
+ title: "Search engines",
+ noResults: {
+ title: "There aren't any search engines",
+ action: "Create your first search engine",
+ },
+ },
+ create: {
+ title: "New search engine",
+ notification: {
+ success: {
+ title: "Search engine created",
+ message: "The search engine was created successfully",
+ },
+ error: {
+ title: "Search engine not created",
+ message: "The search engine could not be created",
+ },
+ },
+ },
+ edit: {
+ title: "Edit search engine",
+ notification: {
+ success: {
+ title: "Changes applied successfully",
+ message: "The search engine was saved successfully",
+ },
+ error: {
+ title: "Unable to apply changes",
+ message: "The search engine could not be saved",
+ },
+ },
+ },
+ delete: {
+ title: "Delete search engine",
+ message: "Are you sure you want to delete the search engine '{name}'?",
+ notification: {
+ success: {
+ title: "Search engine deleted",
+ message: "The search engine was deleted successfully",
+ },
+ error: {
+ title: "Search engine not deleted",
+ message: "The search engine could not be deleted",
+ },
+ },
+ },
+ },
+ },
},
} as const;
diff --git a/packages/validation/src/app.ts b/packages/validation/src/app.ts
index db3d94d59..6ac45ac35 100644
--- a/packages/validation/src/app.ts
+++ b/packages/validation/src/app.ts
@@ -9,10 +9,7 @@ const manageAppSchema = z.object({
const editAppSchema = manageAppSchema.and(z.object({ id: z.string() }));
-const byIdSchema = z.object({ id: z.string() });
-
export const appSchemas = {
manage: manageAppSchema,
edit: editAppSchema,
- byId: byIdSchema,
};
diff --git a/packages/validation/src/common.ts b/packages/validation/src/common.ts
new file mode 100644
index 000000000..68506df63
--- /dev/null
+++ b/packages/validation/src/common.ts
@@ -0,0 +1,22 @@
+import { z } from "zod";
+
+const paginatedSchema = z.object({
+ search: z.string().optional(),
+ pageSize: z.number().int().positive().default(10),
+ page: z.number().int().positive().default(1),
+});
+
+export const byIdSchema = z.object({
+ id: z.string(),
+});
+
+const searchSchema = z.object({
+ query: z.string(),
+ limit: z.number().int().positive().default(10),
+});
+
+export const commonSchemas = {
+ paginated: paginatedSchema,
+ byId: byIdSchema,
+ search: searchSchema,
+};
diff --git a/packages/validation/src/form/i18n.ts b/packages/validation/src/form/i18n.ts
index c0577248d..c057fad57 100644
--- a/packages/validation/src/form/i18n.ts
+++ b/packages/validation/src/form/i18n.ts
@@ -48,7 +48,7 @@ const handleStringError = (issue: z.ZodInvalidStringIssue) => {
}
return {
- key: "errors.invalid_string.includes",
+ key: "errors.string.includes",
params: {
includes: issue.validation.includes,
},
diff --git a/packages/validation/src/group.ts b/packages/validation/src/group.ts
index da053380f..f30898f95 100644
--- a/packages/validation/src/group.ts
+++ b/packages/validation/src/group.ts
@@ -2,18 +2,9 @@ import { z } from "zod";
import { groupPermissionKeys } from "@homarr/definitions";
+import { byIdSchema } from "./common";
import { zodEnumFromArray } from "./enums";
-const paginatedSchema = z.object({
- search: z.string().optional(),
- pageSize: z.number().int().positive().default(10),
- page: z.number().int().positive().default(1),
-});
-
-const byIdSchema = z.object({
- id: z.string(),
-});
-
const createSchema = z.object({
name: z.string().max(64),
});
@@ -28,8 +19,6 @@ const savePermissionsSchema = z.object({
const groupUserSchema = z.object({ groupId: z.string(), userId: z.string() });
export const groupSchemas = {
- paginated: paginatedSchema,
- byId: byIdSchema,
create: createSchema,
update: updateSchema,
savePermissions: savePermissionsSchema,
diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts
index b03232328..2cd11284d 100644
--- a/packages/validation/src/index.ts
+++ b/packages/validation/src/index.ts
@@ -1,9 +1,11 @@
import { appSchemas } from "./app";
import { boardSchemas } from "./board";
+import { commonSchemas } from "./common";
import { groupSchemas } from "./group";
import { iconsSchemas } from "./icons";
import { integrationSchemas } from "./integration";
import { locationSchemas } from "./location";
+import { searchEngineSchemas } from "./search-engine";
import { userSchemas } from "./user";
import { widgetSchemas } from "./widgets";
@@ -16,15 +18,17 @@ export const validation = {
widget: widgetSchemas,
location: locationSchemas,
icons: iconsSchemas,
+ searchEngine: searchEngineSchemas,
+ common: commonSchemas,
};
+export { oldmarrImportConfigurationSchema, superRefineJsonImportFile } from "./board";
+export type { OldmarrImportConfiguration } from "./board";
export {
createSectionSchema,
- sharedItemSchema,
itemAdvancedOptionsSchema,
- type BoardItemIntegration,
+ sharedItemSchema,
type BoardItemAdvancedOptions,
+ type BoardItemIntegration,
} from "./shared";
export { passwordRequirements } from "./user";
-export { oldmarrImportConfigurationSchema, superRefineJsonImportFile } from "./board";
-export type { OldmarrImportConfiguration } from "./board";
diff --git a/packages/validation/src/search-engine.ts b/packages/validation/src/search-engine.ts
new file mode 100644
index 000000000..ab5f4823e
--- /dev/null
+++ b/packages/validation/src/search-engine.ts
@@ -0,0 +1,20 @@
+import { z } from "zod";
+
+const manageSearchEngineSchema = z.object({
+ name: z.string().min(1).max(64),
+ short: z.string().min(1).max(8),
+ iconUrl: z.string().min(1),
+ urlTemplate: z.string().min(1).startsWith("http").includes("%s"),
+ description: z.string().max(512).nullable(),
+});
+
+const editSearchEngineSchema = manageSearchEngineSchema
+ .extend({
+ id: z.string(),
+ })
+ .omit({ short: true });
+
+export const searchEngineSchemas = {
+ manage: manageSearchEngineSchema,
+ edit: editSearchEngineSchema,
+};