diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 8978459cf..377002605 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -1,31 +1,19 @@
{
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "config:recommended"
- ],
- "packageRules": [
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
+ extends: ["config:recommended"],
+ packageRules: [
{
- "matchPackagePatterns": [
- "^@homarr/",
- "tsx" // Disabled for now as version 0.14.4 did not work with the current version of homarr. It resulted in a ERR_MODULE_NOT_FOUND error
- ],
- "enabled": false
+ matchPackagePatterns: ["^@homarr/"],
+ enabled: false,
},
{
- "matchUpdateTypes": [
- "minor",
- "patch",
- "pin",
- "digest"
- ],
- "automerge": true
- }
+ matchUpdateTypes: ["minor", "patch", "pin", "digest"],
+ automerge: true,
+ },
],
- "updateInternalDeps": true,
- "rangeStrategy": "bump",
- "automerge": false,
- "baseBranches": [
- "dev"
- ],
- "dependencyDashboard": false
-}
\ No newline at end of file
+ updateInternalDeps: true,
+ rangeStrategy: "bump",
+ automerge: false,
+ baseBranches: ["dev"],
+ dependencyDashboard: false,
+}
diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx
index e0bd9fcb5..43e11f3a3 100644
--- a/apps/nextjs/src/app/[locale]/manage/layout.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx
@@ -25,6 +25,7 @@ import {
import { auth } from "@homarr/auth/next";
import { isProviderEnabled } from "@homarr/auth/server";
+import { createDocumentationLink } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { MainHeader } from "~/components/layout/header";
@@ -124,7 +125,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
{
label: t("items.help.items.documentation"),
icon: IconBook2,
- href: "https://homarr.dev/docs/getting-started/",
+ href: createDocumentationLink("/docs/getting-started"),
external: true,
},
{
diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_rename-group-form.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_rename-group-form.tsx
index f5241161f..5f71808d4 100644
--- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_rename-group-form.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_rename-group-form.tsx
@@ -15,9 +15,10 @@ interface RenameGroupFormProps {
id: string;
name: string;
};
+ disabled?: boolean;
}
-export const RenameGroupForm = ({ group }: RenameGroupFormProps) => {
+export const RenameGroupForm = ({ group, disabled }: RenameGroupFormProps) => {
const t = useI18n();
const { mutate, isPending } = clientApi.group.updateGroup.useMutation();
const form = useZodForm(validation.group.update.pick({ name: true }), {
@@ -28,6 +29,9 @@ export const RenameGroupForm = ({ group }: RenameGroupFormProps) => {
const handleSubmit = useCallback(
(values: FormType) => {
+ if (disabled) {
+ return;
+ }
mutate(
{
...values,
@@ -60,13 +64,15 @@ export const RenameGroupForm = ({ group }: RenameGroupFormProps) => {
return (
);
diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_reserved-group-alert.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_reserved-group-alert.tsx
new file mode 100644
index 000000000..bd6920792
--- /dev/null
+++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/_reserved-group-alert.tsx
@@ -0,0 +1,27 @@
+import Link from "next/link";
+import { Alert, Anchor } from "@mantine/core";
+import { IconExclamationCircle } from "@tabler/icons-react";
+
+import { createDocumentationLink } from "@homarr/definitions";
+import { getI18n } from "@homarr/translation/server";
+
+export const ReservedGroupAlert = async () => {
+ const t = await getI18n();
+
+ return (
+ }>
+ {t("group.reservedNotice.message", {
+ checkoutDocs: (
+
+ {t("common.action.checkoutDocs")}
+
+ ),
+ })}
+
+ );
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx
index 972b62d99..d20f285a8 100644
--- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx
@@ -6,9 +6,11 @@ import type { RouterOutputs } from "@homarr/api";
import { api } from "@homarr/api/server";
import { env } from "@homarr/auth/env.mjs";
import { isProviderEnabled } from "@homarr/auth/server";
+import { everyoneGroup } from "@homarr/definitions";
import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { SearchInput, UserAvatar } from "@homarr/ui";
+import { ReservedGroupAlert } from "../_reserved-group-alert";
import { AddGroupMember } from "./_add-group-member";
import { RemoveGroupMember } from "./_remove-group-member";
@@ -25,6 +27,7 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD
const t = await getI18n();
const tMembers = await getScopedI18n("management.page.group.setting.members");
const group = await api.group.getById({ id: params.id });
+ const isReserved = group.name === everyoneGroup;
const filteredMembers = searchParams.search
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -41,15 +44,19 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD
{tMembers("title")}
- {providerTypes !== "credentials" && (
- }>
- {t(`group.memberNotice.${providerTypes}`)}
-
+ {isReserved ? (
+
+ ) : (
+ providerTypes !== "credentials" && (
+ }>
+ {t(`group.memberNotice.${providerTypes}`)}
+
+ )
)}
- {isProviderEnabled("credentials") && (
+ {isProviderEnabled("credentials") && !isReserved && (
member.id)} />
)}
@@ -63,7 +70,7 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD
{filteredMembers.map((member) => (
-
+
))}
@@ -74,9 +81,10 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD
interface RowProps {
member: RouterOutputs["group"]["getById"]["members"][number];
groupId: string;
+ disabled?: boolean;
}
-const Row = ({ member, groupId }: RowProps) => {
+const Row = ({ member, groupId, disabled }: RowProps) => {
return (
@@ -88,7 +96,7 @@ const Row = ({ member, groupId }: RowProps) => {
- {member.provider === "credentials" && }
+ {member.provider === "credentials" && !disabled && }
);
diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx
index cc3496520..455a835e0 100644
--- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx
@@ -1,11 +1,13 @@
import { Stack, Title } from "@mantine/core";
import { api } from "@homarr/api/server";
+import { everyoneGroup } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { DangerZoneItem, DangerZoneRoot } from "~/components/manage/danger-zone";
import { DeleteGroup } from "./_delete-group";
import { RenameGroupForm } from "./_rename-group-form";
+import { ReservedGroupAlert } from "./_reserved-group-alert";
import { TransferGroupOwnership } from "./_transfer-group-ownership";
interface GroupsDetailPageProps {
@@ -18,26 +20,31 @@ export default async function GroupsDetailPage({ params }: GroupsDetailPageProps
const group = await api.group.getById({ id: params.id });
const tGeneral = await getScopedI18n("management.page.group.setting.general");
const tGroupAction = await getScopedI18n("group.action");
+ const isReserved = group.name === everyoneGroup;
return (
{tGeneral("title")}
-
+ {isReserved && }
-
- }
- />
+
- }
- />
-
+ {!isReserved && (
+
+ }
+ />
+
+ }
+ />
+
+ )}
);
}
diff --git a/apps/tasks/package.json b/apps/tasks/package.json
index 6b843a392..3eb638eb7 100644
--- a/apps/tasks/package.json
+++ b/apps/tasks/package.json
@@ -48,7 +48,7 @@
"dotenv-cli": "^7.4.2",
"eslint": "^9.13.0",
"prettier": "^3.3.3",
- "tsx": "4.13.3",
+ "tsx": "4.19.1",
"typescript": "^5.6.3"
}
}
diff --git a/apps/tasks/src/main.ts b/apps/tasks/src/main.ts
index 4ae066828..2cb395ad2 100644
--- a/apps/tasks/src/main.ts
+++ b/apps/tasks/src/main.ts
@@ -4,10 +4,7 @@ import "./undici-log-agent-override";
import { registerCronJobRunner } from "@homarr/cron-job-runner";
import { jobGroup } from "@homarr/cron-jobs";
-import { seedServerSettingsAsync } from "./seed-server-settings";
-
void (async () => {
registerCronJobRunner();
await jobGroup.startAllAsync();
- await seedServerSettingsAsync();
})();
diff --git a/apps/tasks/src/seed-server-settings.ts b/apps/tasks/src/seed-server-settings.ts
deleted file mode 100644
index 885d4e64c..000000000
--- a/apps/tasks/src/seed-server-settings.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import SuperJSON from "superjson";
-
-import { db } from "@homarr/db";
-import { serverSettings } from "@homarr/db/schema/sqlite";
-import { logger } from "@homarr/log";
-
-import { defaultServerSettings, defaultServerSettingsKeys } from "../../../packages/server-settings";
-
-export const seedServerSettingsAsync = async () => {
- const serverSettingsData = await db.query.serverSettings.findMany();
- let insertedSettingsCount = 0;
-
- for (const settingsKey of defaultServerSettingsKeys) {
- if (serverSettingsData.some((setting) => setting.settingKey === settingsKey)) {
- return;
- }
-
- await db.insert(serverSettings).values({
- settingKey: settingsKey,
- value: SuperJSON.stringify(defaultServerSettings[settingsKey]),
- });
- insertedSettingsCount++;
- }
-
- if (insertedSettingsCount > 0) {
- logger.info(`Inserted ${insertedSettingsCount} missing settings`);
- }
-};
diff --git a/apps/websocket/package.json b/apps/websocket/package.json
index 9b28c5191..bf522cfdf 100644
--- a/apps/websocket/package.json
+++ b/apps/websocket/package.json
@@ -26,7 +26,7 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.4.5",
- "tsx": "4.13.3",
+ "tsx": "4.19.1",
"ws": "^8.18.0"
},
"devDependencies": {
diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts
index d14083995..8726d9fd7 100644
--- a/packages/api/src/router/group.ts
+++ b/packages/api/src/router/group.ts
@@ -3,6 +3,7 @@ import { TRPCError } from "@trpc/server";
import type { Database } from "@homarr/db";
import { and, createId, eq, like, not, sql } from "@homarr/db";
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema/sqlite";
+import { everyoneGroup } from "@homarr/definitions";
import { validation, z } from "@homarr/validation";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../trpc";
@@ -121,13 +122,12 @@ export const groupRouter = createTRPCRouter({
.requiresPermission("admin")
.input(validation.group.create)
.mutation(async ({ input, ctx }) => {
- const normalizedName = normalizeName(input.name);
- await checkSimilarNameAndThrowAsync(ctx.db, normalizedName);
+ await checkSimilarNameAndThrowAsync(ctx.db, input.name);
const id = createId();
await ctx.db.insert(groups).values({
id,
- name: normalizedName,
+ name: input.name,
ownerId: ctx.session.user.id,
});
@@ -138,14 +138,14 @@ export const groupRouter = createTRPCRouter({
.input(validation.group.update)
.mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.id);
+ await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
- const normalizedName = normalizeName(input.name);
- await checkSimilarNameAndThrowAsync(ctx.db, normalizedName, input.id);
+ await checkSimilarNameAndThrowAsync(ctx.db, input.name, input.id);
await ctx.db
.update(groups)
.set({
- name: normalizedName,
+ name: input.name,
})
.where(eq(groups.id, input.id));
}),
@@ -169,6 +169,7 @@ export const groupRouter = createTRPCRouter({
.input(validation.group.groupUser)
.mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
+ await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
await ctx.db
.update(groups)
@@ -182,6 +183,7 @@ export const groupRouter = createTRPCRouter({
.input(validation.common.byId)
.mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.id);
+ await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
await ctx.db.delete(groups).where(eq(groups.id, input.id));
}),
@@ -190,6 +192,7 @@ export const groupRouter = createTRPCRouter({
.input(validation.group.groupUser)
.mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
+ await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
throwIfCredentialsDisabled();
const user = await ctx.db.query.users.findFirst({
@@ -213,6 +216,7 @@ export const groupRouter = createTRPCRouter({
.input(validation.group.groupUser)
.mutation(async ({ input, ctx }) => {
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
+ await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
throwIfCredentialsDisabled();
await ctx.db
@@ -221,8 +225,6 @@ export const groupRouter = createTRPCRouter({
}),
});
-const normalizeName = (name: string) => name.trim();
-
const checkSimilarNameAndThrowAsync = async (db: Database, name: string, ignoreId?: string) => {
const similar = await db.query.groups.findFirst({
where: and(like(groups.name, `${name}`), not(eq(groups.id, ignoreId ?? ""))),
@@ -236,6 +238,17 @@ const checkSimilarNameAndThrowAsync = async (db: Database, name: string, ignoreI
}
};
+const throwIfGroupNameIsReservedAsync = async (db: Database, id: string) => {
+ const count = await db.$count(groups, and(eq(groups.id, id), eq(groups.name, everyoneGroup)));
+
+ if (count > 0) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Action is forbidden for reserved group names",
+ });
+ }
+};
+
const throwIfGroupNotFoundAsync = async (db: Database, id: string) => {
const group = await db.query.groups.findFirst({
where: eq(groups.id, id),
diff --git a/packages/auth/events.ts b/packages/auth/events.ts
index f4cd1eeac..292204de0 100644
--- a/packages/auth/events.ts
+++ b/packages/auth/events.ts
@@ -5,6 +5,7 @@ import type { NextAuthConfig } from "next-auth";
import { and, eq, inArray } from "@homarr/db";
import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
+import { everyoneGroup } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { env } from "./env.mjs";
@@ -33,6 +34,7 @@ export const createSignInEventHandler = (db: Database): Exclude {
+ const dbEveryoneGroup = await db.query.groups.findFirst({
+ where: eq(groups.name, everyoneGroup),
+ with: {
+ members: {
+ where: eq(groupMembers.userId, userId),
+ },
+ },
+ });
+
+ if (dbEveryoneGroup?.members.length === 0) {
+ await db.insert(groupMembers).values({
+ userId,
+ groupId: dbEveryoneGroup.id,
+ });
+ logger.info(`Added user to everyone group. user=${userId}`);
+ }
+};
+
const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: string, externalGroups: string[]) => {
+ const ignoredGroups = [everyoneGroup];
const dbGroupMembers = await db.query.groupMembers.findMany({
where: eq(groupMembers.userId, userId),
with: {
@@ -102,11 +124,11 @@ const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: s
}
/**
- * The below groups are those groups the user is part of in Homarr, but not in the external system.
+ * The below groups are those groups the user is part of in Homarr, but not in the external system and not ignored.
* So he has to be removed from those groups.
*/
const groupsUserIsNoLongerMemberOfExternally = dbGroupMembers.filter(
- ({ group }) => !externalGroups.includes(group.name),
+ ({ group }) => !externalGroups.concat(ignoredGroups).includes(group.name),
);
if (groupsUserIsNoLongerMemberOfExternally.length > 0) {
diff --git a/packages/auth/test/events.spec.ts b/packages/auth/test/events.spec.ts
index 02f44abd3..325d1d949 100644
--- a/packages/auth/test/events.spec.ts
+++ b/packages/auth/test/events.spec.ts
@@ -7,6 +7,7 @@ import { eq } from "@homarr/db";
import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
import { createDb } from "@homarr/db/test";
+import { everyoneGroup } from "@homarr/definitions";
import { createSignInEventHandler } from "../events";
@@ -34,6 +35,29 @@ vi.mock("next/headers", async (importOriginal) => {
});
describe("createSignInEventHandler should create signInEventHandler", () => {
+ describe("signInEventHandler should add users to everyone group", () => {
+ test("should add user to everyone group if he isn't already", async () => {
+ // Arrange
+ const db = createDb();
+ await createUserAsync(db);
+ await createGroupAsync(db, everyoneGroup);
+ const eventHandler = createSignInEventHandler(db);
+
+ // Act
+ await eventHandler?.({
+ user: { id: "1", name: "test" },
+ profile: undefined,
+ account: null,
+ });
+
+ // Assert
+ const dbGroupMembers = await db.query.groupMembers.findFirst({
+ where: eq(groupMembers.userId, "1"),
+ });
+ expect(dbGroupMembers?.groupId).toBe("1");
+ });
+ });
+
describe("signInEventHandler should synchronize ldap groups", () => {
test("should add missing group membership", async () => {
// Arrange
@@ -79,6 +103,30 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
});
expect(dbGroupMembers).toBeUndefined();
});
+ test("should not remove group membership for everyone group", async () => {
+ // Arrange
+ const db = createDb();
+ await createUserAsync(db);
+ await createGroupAsync(db, everyoneGroup);
+ await db.insert(groupMembers).values({
+ userId: "1",
+ groupId: "1",
+ });
+ const eventHandler = createSignInEventHandler(db);
+
+ // Act
+ await eventHandler?.({
+ user: { id: "1", name: "test", groups: [] } as never,
+ profile: undefined,
+ account: null,
+ });
+
+ // Assert
+ const dbGroupMembers = await db.query.groupMembers.findFirst({
+ where: eq(groupMembers.userId, "1"),
+ });
+ expect(dbGroupMembers?.groupId).toBe("1");
+ });
});
describe("signInEventHandler should synchronize oidc groups", () => {
test("should add missing group membership", async () => {
@@ -125,6 +173,30 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
});
expect(dbGroupMembers).toBeUndefined();
});
+ test("should not remove group membership for everyone group", async () => {
+ // Arrange
+ const db = createDb();
+ await createUserAsync(db);
+ await createGroupAsync(db, everyoneGroup);
+ await db.insert(groupMembers).values({
+ userId: "1",
+ groupId: "1",
+ });
+ const eventHandler = createSignInEventHandler(db);
+
+ // Act
+ await eventHandler?.({
+ user: { id: "1", name: "test" },
+ profile: { preferred_username: "test", someRandomGroupsKey: [] },
+ account: null,
+ });
+
+ // Assert
+ const dbGroupMembers = await db.query.groupMembers.findFirst({
+ where: eq(groupMembers.userId, "1"),
+ });
+ expect(dbGroupMembers?.groupId).toBe("1");
+ });
});
test.each([
["ldap" as const, { name: "test-new" }, undefined],
@@ -183,8 +255,8 @@ const createUserAsync = async (db: Database) =>
colorScheme: "dark",
});
-const createGroupAsync = async (db: Database) =>
+const createGroupAsync = async (db: Database, name = "test") =>
await db.insert(groups).values({
id: "1",
- name: "test",
+ name,
});
diff --git a/packages/db/migrations/mysql/0013_youthful_vulture.sql b/packages/db/migrations/mysql/0013_youthful_vulture.sql
new file mode 100644
index 000000000..0ca6ea7b9
--- /dev/null
+++ b/packages/db/migrations/mysql/0013_youthful_vulture.sql
@@ -0,0 +1 @@
+ALTER TABLE `group` ADD CONSTRAINT `group_name_unique` UNIQUE(`name`);
\ No newline at end of file
diff --git a/packages/db/migrations/mysql/meta/0013_snapshot.json b/packages/db/migrations/mysql/meta/0013_snapshot.json
new file mode 100644
index 000000000..69a7e253f
--- /dev/null
+++ b/packages/db/migrations/mysql/meta/0013_snapshot.json
@@ -0,0 +1,1527 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "aa507e60-8e16-4546-b7a5-86304be877ab",
+ "prevId": "ae1da11f-d73a-427c-8581-3ea6aabdd6d2",
+ "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": {},
+ "checkConstraint": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "apiKey_id": {
+ "name": "apiKey_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "app": {
+ "name": "app",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "href": {
+ "name": "href",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "app_id": {
+ "name": "app_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "boardGroupPermission": {
+ "name": "boardGroupPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardGroupPermission_board_id_board_id_fk": {
+ "name": "boardGroupPermission_board_id_board_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardGroupPermission_group_id_group_id_fk": {
+ "name": "boardGroupPermission_group_id_group_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardGroupPermission_board_id_group_id_permission_pk": {
+ "name": "boardGroupPermission_board_id_group_id_permission_pk",
+ "columns": ["board_id", "group_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "boardUserPermission": {
+ "name": "boardUserPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardUserPermission_board_id_board_id_fk": {
+ "name": "boardUserPermission_board_id_board_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardUserPermission_user_id_user_id_fk": {
+ "name": "boardUserPermission_user_id_user_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardUserPermission_board_id_user_id_permission_pk": {
+ "name": "boardUserPermission_board_id_user_id_permission_pk",
+ "columns": ["board_id", "user_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "board": {
+ "name": "board",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "page_title": {
+ "name": "page_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta_title": {
+ "name": "meta_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logo_image_url": {
+ "name": "logo_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon_image_url": {
+ "name": "favicon_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_url": {
+ "name": "background_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_attachment": {
+ "name": "background_image_attachment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('fixed')"
+ },
+ "background_image_repeat": {
+ "name": "background_image_repeat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('no-repeat')"
+ },
+ "background_image_size": {
+ "name": "background_image_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('cover')"
+ },
+ "primary_color": {
+ "name": "primary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('#fa5252')"
+ },
+ "secondary_color": {
+ "name": "secondary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "('#fd7e14')"
+ },
+ "opacity": {
+ "name": "opacity",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 100
+ },
+ "custom_css": {
+ "name": "custom_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "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": {},
+ "checkConstraint": {}
+ },
+ "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": {},
+ "checkConstraint": {}
+ },
+ "group": {
+ "name": "group",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "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": {
+ "group_name_unique": {
+ "name": "group_name_unique",
+ "columns": ["name"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "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": {},
+ "checkConstraint": {}
+ },
+ "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": {},
+ "checkConstraint": {}
+ },
+ "integrationGroupPermissions": {
+ "name": "integrationGroupPermissions",
+ "columns": {
+ "integration_id": {
+ "name": "integration_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integrationGroupPermissions_integration_id_integration_id_fk": {
+ "name": "integrationGroupPermissions_integration_id_integration_id_fk",
+ "tableFrom": "integrationGroupPermissions",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integrationGroupPermissions_group_id_group_id_fk": {
+ "name": "integrationGroupPermissions_group_id_group_id_fk",
+ "tableFrom": "integrationGroupPermissions",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integration_group_permission__pk": {
+ "name": "integration_group_permission__pk",
+ "columns": ["integration_id", "group_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "integration_item": {
+ "name": "integration_item",
+ "columns": {
+ "item_id": {
+ "name": "item_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integration_id": {
+ "name": "integration_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integration_item_item_id_item_id_fk": {
+ "name": "integration_item_item_id_item_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "item",
+ "columnsFrom": ["item_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integration_item_integration_id_integration_id_fk": {
+ "name": "integration_item_integration_id_integration_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integration_item_item_id_integration_id_pk": {
+ "name": "integration_item_item_id_integration_id_pk",
+ "columns": ["item_id", "integration_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "integrationSecret": {
+ "name": "integrationSecret",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "varchar(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": {},
+ "checkConstraint": {}
+ },
+ "integrationUserPermission": {
+ "name": "integrationUserPermission",
+ "columns": {
+ "integration_id": {
+ "name": "integration_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integrationUserPermission_integration_id_integration_id_fk": {
+ "name": "integrationUserPermission_integration_id_integration_id_fk",
+ "tableFrom": "integrationUserPermission",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integrationUserPermission_user_id_user_id_fk": {
+ "name": "integrationUserPermission_user_id_user_id_fk",
+ "tableFrom": "integrationUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integrationUserPermission_integration_id_user_id_permission_pk": {
+ "name": "integrationUserPermission_integration_id_user_id_permission_pk",
+ "columns": ["integration_id", "user_id", "permission"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "integration": {
+ "name": "integration",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration__kind_idx": {
+ "name": "integration__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "integration_id": {
+ "name": "integration_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "invite": {
+ "name": "invite",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expiration_date": {
+ "name": "expiration_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invite_creator_id_user_id_fk": {
+ "name": "invite_creator_id_user_id_fk",
+ "tableFrom": "invite",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "invite_id": {
+ "name": "invite_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {
+ "invite_token_unique": {
+ "name": "invite_token_unique",
+ "columns": ["token"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "item": {
+ "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": {},
+ "checkConstraint": {}
+ },
+ "search_engine": {
+ "name": "search_engine",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "short": {
+ "name": "short",
+ "type": "varchar(8)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "url_template": {
+ "name": "url_template",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "search_engine_id": {
+ "name": "search_engine_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "section": {
+ "name": "section",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "board_id": {
+ "name": "board_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "x_offset": {
+ "name": "x_offset",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": 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": {},
+ "checkConstraint": {}
+ },
+ "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"]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "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": {},
+ "checkConstraint": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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": "'dark'"
+ },
+ "firstDayOfWeek": {
+ "name": "firstDayOfWeek",
+ "type": "tinyint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "pingIconsEnabled": {
+ "name": "pingIconsEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_homeBoardId_board_id_fk": {
+ "name": "user_homeBoardId_board_id_fk",
+ "tableFrom": "user",
+ "tableTo": "board",
+ "columnsFrom": ["homeBoardId"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_id": {
+ "name": "user_id",
+ "columns": ["id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": ["identifier", "token"]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json
index ac2bc7f14..7ac6a43d8 100644
--- a/packages/db/migrations/mysql/meta/_journal.json
+++ b/packages/db/migrations/mysql/meta/_journal.json
@@ -92,6 +92,13 @@
"when": 1729348221072,
"tag": "0012_abnormal_wendell_vaughn",
"breakpoints": true
+ },
+ {
+ "idx": 13,
+ "version": "5",
+ "when": 1729369383739,
+ "tag": "0013_youthful_vulture",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/migrations/mysql/migrate.ts b/packages/db/migrations/mysql/migrate.ts
index 50d495d00..7cb343ceb 100644
--- a/packages/db/migrations/mysql/migrate.ts
+++ b/packages/db/migrations/mysql/migrate.ts
@@ -3,25 +3,35 @@ import { drizzle } from "drizzle-orm/mysql2";
import { migrate } from "drizzle-orm/mysql2/migrator";
import mysql from "mysql2";
+import type { Database } from "../..";
+import * as mysqlSchema from "../../schema/mysql";
+import { seedDataAsync } from "../seed";
+
const migrationsFolder = process.argv[2] ?? ".";
-const mysql2 = mysql.createConnection(
- process.env.DB_HOST
- ? {
- host: process.env.DB_HOST,
- database: process.env.DB_NAME!,
- port: Number(process.env.DB_PORT),
- user: process.env.DB_USER,
- password: process.env.DB_PASSWORD,
- }
- : { uri: process.env.DB_URL },
-);
+const migrateAsync = async () => {
+ const mysql2 = mysql.createConnection(
+ process.env.DB_HOST
+ ? {
+ host: process.env.DB_HOST,
+ database: process.env.DB_NAME!,
+ port: Number(process.env.DB_PORT),
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ }
+ : { uri: process.env.DB_URL },
+ );
-const db = drizzle(mysql2, {
- mode: "default",
-});
+ const db = drizzle(mysql2, {
+ mode: "default",
+ schema: mysqlSchema,
+ });
-migrate(db, { migrationsFolder })
+ await migrate(db, { migrationsFolder });
+ await seedDataAsync(db as unknown as Database);
+};
+
+migrateAsync()
.then(() => {
console.log("Migration complete");
process.exit(0);
diff --git a/packages/db/migrations/run-seed.ts b/packages/db/migrations/run-seed.ts
new file mode 100644
index 000000000..7a0e346bc
--- /dev/null
+++ b/packages/db/migrations/run-seed.ts
@@ -0,0 +1,12 @@
+import { database } from "../driver";
+import { seedDataAsync } from "./seed";
+
+seedDataAsync(database)
+ .then(() => {
+ console.log("Seed complete");
+ process.exit(0);
+ })
+ .catch((err) => {
+ console.log("Seed failed\n\t", err);
+ process.exit(1);
+ });
diff --git a/packages/db/migrations/seed.ts b/packages/db/migrations/seed.ts
new file mode 100644
index 000000000..ab4a865e7
--- /dev/null
+++ b/packages/db/migrations/seed.ts
@@ -0,0 +1,52 @@
+import SuperJSON from "superjson";
+
+import { everyoneGroup } from "@homarr/definitions";
+import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
+
+import { createId, eq } from "..";
+import type { Database } from "..";
+import { groups } from "../schema/mysql";
+import { serverSettings } from "../schema/sqlite";
+
+export const seedDataAsync = async (db: Database) => {
+ await seedEveryoneGroupAsync(db);
+ await seedServerSettingsAsync(db);
+};
+
+const seedEveryoneGroupAsync = async (db: Database) => {
+ const group = await db.query.groups.findFirst({
+ where: eq(groups.name, everyoneGroup),
+ });
+
+ if (group) {
+ console.log("Skipping seeding of group 'everyone' as it already exists");
+ return;
+ }
+
+ await db.insert(groups).values({
+ id: createId(),
+ name: everyoneGroup,
+ });
+ console.log("Created group 'everyone' through seed");
+};
+
+const seedServerSettingsAsync = async (db: Database) => {
+ const serverSettingsData = await db.query.serverSettings.findMany();
+ let insertedSettingsCount = 0;
+
+ for (const settingsKey of defaultServerSettingsKeys) {
+ if (serverSettingsData.some((setting) => setting.settingKey === settingsKey)) {
+ return;
+ }
+
+ await db.insert(serverSettings).values({
+ settingKey: settingsKey,
+ value: SuperJSON.stringify(defaultServerSettings[settingsKey]),
+ });
+ insertedSettingsCount++;
+ }
+
+ if (insertedSettingsCount > 0) {
+ console.info(`Inserted ${insertedSettingsCount} missing settings`);
+ }
+};
diff --git a/packages/db/migrations/sqlite/0013_faithful_hex.sql b/packages/db/migrations/sqlite/0013_faithful_hex.sql
new file mode 100644
index 000000000..d2632a0dd
--- /dev/null
+++ b/packages/db/migrations/sqlite/0013_faithful_hex.sql
@@ -0,0 +1 @@
+CREATE UNIQUE INDEX `group_name_unique` ON `group` (`name`);
\ No newline at end of file
diff --git a/packages/db/migrations/sqlite/meta/0013_snapshot.json b/packages/db/migrations/sqlite/meta/0013_snapshot.json
new file mode 100644
index 000000000..1dd77999c
--- /dev/null
+++ b/packages/db/migrations/sqlite/meta/0013_snapshot.json
@@ -0,0 +1,1461 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "767f5db4-59ab-46c7-a154-4fcd9474c358",
+ "prevId": "20b3ebeb-a111-4576-b530-a2634d154132",
+ "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": {},
+ "checkConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "salt": {
+ "name": "salt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "app": {
+ "name": "app",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "href": {
+ "name": "href",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "boardGroupPermission": {
+ "name": "boardGroupPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardGroupPermission_board_id_board_id_fk": {
+ "name": "boardGroupPermission_board_id_board_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardGroupPermission_group_id_group_id_fk": {
+ "name": "boardGroupPermission_group_id_group_id_fk",
+ "tableFrom": "boardGroupPermission",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardGroupPermission_board_id_group_id_permission_pk": {
+ "columns": ["board_id", "group_id", "permission"],
+ "name": "boardGroupPermission_board_id_group_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "boardUserPermission": {
+ "name": "boardUserPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardUserPermission_board_id_board_id_fk": {
+ "name": "boardUserPermission_board_id_board_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardUserPermission_user_id_user_id_fk": {
+ "name": "boardUserPermission_user_id_user_id_fk",
+ "tableFrom": "boardUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardUserPermission_board_id_user_id_permission_pk": {
+ "columns": ["board_id", "user_id", "permission"],
+ "name": "boardUserPermission_board_id_user_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "board": {
+ "name": "board",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "page_title": {
+ "name": "page_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "meta_title": {
+ "name": "meta_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logo_image_url": {
+ "name": "logo_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon_image_url": {
+ "name": "favicon_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_url": {
+ "name": "background_image_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "background_image_attachment": {
+ "name": "background_image_attachment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'fixed'"
+ },
+ "background_image_repeat": {
+ "name": "background_image_repeat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'no-repeat'"
+ },
+ "background_image_size": {
+ "name": "background_image_size",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'cover'"
+ },
+ "primary_color": {
+ "name": "primary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#fa5252'"
+ },
+ "secondary_color": {
+ "name": "secondary_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#fd7e14'"
+ },
+ "opacity": {
+ "name": "opacity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 100
+ },
+ "custom_css": {
+ "name": "custom_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "group": {
+ "name": "group",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "group_name_unique": {
+ "name": "group_name_unique",
+ "columns": ["name"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "group_owner_id_user_id_fk": {
+ "name": "group_owner_id_user_id_fk",
+ "tableFrom": "group",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "integrationGroupPermissions": {
+ "name": "integrationGroupPermissions",
+ "columns": {
+ "integration_id": {
+ "name": "integration_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integrationGroupPermissions_integration_id_integration_id_fk": {
+ "name": "integrationGroupPermissions_integration_id_integration_id_fk",
+ "tableFrom": "integrationGroupPermissions",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integrationGroupPermissions_group_id_group_id_fk": {
+ "name": "integrationGroupPermissions_group_id_group_id_fk",
+ "tableFrom": "integrationGroupPermissions",
+ "tableTo": "group",
+ "columnsFrom": ["group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integrationGroupPermissions_integration_id_group_id_permission_pk": {
+ "columns": ["integration_id", "group_id", "permission"],
+ "name": "integrationGroupPermissions_integration_id_group_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "integration_item": {
+ "name": "integration_item",
+ "columns": {
+ "item_id": {
+ "name": "item_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integration_id": {
+ "name": "integration_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integration_item_item_id_item_id_fk": {
+ "name": "integration_item_item_id_item_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "item",
+ "columnsFrom": ["item_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integration_item_integration_id_integration_id_fk": {
+ "name": "integration_item_integration_id_integration_id_fk",
+ "tableFrom": "integration_item",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integration_item_item_id_integration_id_pk": {
+ "columns": ["item_id", "integration_id"],
+ "name": "integration_item_item_id_integration_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "integrationSecret": {
+ "name": "integrationSecret",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integration_id": {
+ "name": "integration_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration_secret__kind_idx": {
+ "name": "integration_secret__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ },
+ "integration_secret__updated_at_idx": {
+ "name": "integration_secret__updated_at_idx",
+ "columns": ["updated_at"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "integrationSecret_integration_id_integration_id_fk": {
+ "name": "integrationSecret_integration_id_integration_id_fk",
+ "tableFrom": "integrationSecret",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integrationSecret_integration_id_kind_pk": {
+ "columns": ["integration_id", "kind"],
+ "name": "integrationSecret_integration_id_kind_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "integrationUserPermission": {
+ "name": "integrationUserPermission",
+ "columns": {
+ "integration_id": {
+ "name": "integration_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integrationUserPermission_integration_id_integration_id_fk": {
+ "name": "integrationUserPermission_integration_id_integration_id_fk",
+ "tableFrom": "integrationUserPermission",
+ "tableTo": "integration",
+ "columnsFrom": ["integration_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "integrationUserPermission_user_id_user_id_fk": {
+ "name": "integrationUserPermission_user_id_user_id_fk",
+ "tableFrom": "integrationUserPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "integrationUserPermission_integration_id_user_id_permission_pk": {
+ "columns": ["integration_id", "user_id", "permission"],
+ "name": "integrationUserPermission_integration_id_user_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "integration": {
+ "name": "integration",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "integration__kind_idx": {
+ "name": "integration__kind_idx",
+ "columns": ["kind"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "invite": {
+ "name": "invite",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expiration_date": {
+ "name": "expiration_date",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "invite_token_unique": {
+ "name": "invite_token_unique",
+ "columns": ["token"],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "invite_creator_id_user_id_fk": {
+ "name": "invite_creator_id_user_id_fk",
+ "tableFrom": "invite",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "item": {
+ "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": {},
+ "checkConstraints": {}
+ },
+ "search_engine": {
+ "name": "search_engine",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "short": {
+ "name": "short",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "url_template": {
+ "name": "url_template",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "section": {
+ "name": "section",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "x_offset": {
+ "name": "x_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": 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": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "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": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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": "'dark'"
+ },
+ "firstDayOfWeek": {
+ "name": "firstDayOfWeek",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "pingIconsEnabled": {
+ "name": "pingIconsEnabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_homeBoardId_board_id_fk": {
+ "name": "user_homeBoardId_board_id_fk",
+ "tableFrom": "user",
+ "tableTo": "board",
+ "columnsFrom": ["homeBoardId"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": ["identifier", "token"],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json
index 327142451..e88a243de 100644
--- a/packages/db/migrations/sqlite/meta/_journal.json
+++ b/packages/db/migrations/sqlite/meta/_journal.json
@@ -92,6 +92,13 @@
"when": 1729348200091,
"tag": "0012_ambiguous_black_panther",
"breakpoints": true
+ },
+ {
+ "idx": 13,
+ "version": "6",
+ "when": 1729369389386,
+ "tag": "0013_faithful_hex",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/migrations/sqlite/migrate.ts b/packages/db/migrations/sqlite/migrate.ts
index abef5ea9d..47a901745 100644
--- a/packages/db/migrations/sqlite/migrate.ts
+++ b/packages/db/migrations/sqlite/migrate.ts
@@ -2,10 +2,27 @@ import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
+import { schema } from "../..";
+import { seedDataAsync } from "../seed";
+
const migrationsFolder = process.argv[2] ?? ".";
-const sqlite = new Database(process.env.DB_URL?.replace("file:", ""));
+const migrateAsync = async () => {
+ const sqlite = new Database(process.env.DB_URL?.replace("file:", ""));
-const db = drizzle(sqlite);
+ const db = drizzle(sqlite, { schema });
-migrate(db, { migrationsFolder });
+ migrate(db, { migrationsFolder });
+
+ await seedDataAsync(db);
+};
+
+migrateAsync()
+ .then(() => {
+ console.log("Migration complete");
+ process.exit(0);
+ })
+ .catch((err) => {
+ console.log("Migration failed", err);
+ process.exit(1);
+ });
diff --git a/packages/db/package.json b/packages/db/package.json
index db156f545..9ef951981 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -21,15 +21,17 @@
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"migration:mysql:generate": "drizzle-kit generate --config ./configs/mysql.config.ts",
- "migration:mysql:run": "drizzle-kit migrate --config ./configs/mysql.config.ts",
+ "migration:mysql:run": "drizzle-kit migrate --config ./configs/mysql.config.ts && pnpm run seed",
"migration:mysql:drop": "drizzle-kit drop --config ./configs/mysql.config.ts",
"migration:sqlite:generate": "drizzle-kit generate --config ./configs/sqlite.config.ts",
- "migration:sqlite:run": "drizzle-kit migrate --config ./configs/sqlite.config.ts",
+ "migration:sqlite:run": "drizzle-kit migrate --config ./configs/sqlite.config.ts && pnpm run seed",
"migration:sqlite:drop": "drizzle-kit drop --config ./configs/sqlite.config.ts",
"push:mysql": "drizzle-kit push --config ./configs/mysql.config.ts",
"push:sqlite": "drizzle-kit push --config ./configs/sqlite.config.ts",
+ "seed": "pnpm with-env tsx ./migrations/run-seed.ts",
"studio": "drizzle-kit studio --config ./configs/sqlite.config.ts",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit",
+ "with-env": "dotenv -e ../../.env --"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
@@ -37,6 +39,7 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
+ "@homarr/server-settings": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^2.2.2",
"@testcontainers/mysql": "^10.13.2",
"better-sqlite3": "^11.4.0",
@@ -53,6 +56,7 @@
"dotenv-cli": "^7.4.2",
"eslint": "^9.13.0",
"prettier": "^3.3.3",
+ "tsx": "4.19.1",
"typescript": "^5.6.3"
}
}
diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts
index 510ddaf89..e5b2b050f 100644
--- a/packages/db/schema/mysql.ts
+++ b/packages/db/schema/mysql.ts
@@ -120,7 +120,7 @@ export const groupMembers = mysqlTable(
export const groups = mysqlTable("group", {
id: varchar("id", { length: 64 }).notNull().primaryKey(),
- name: varchar("name", { length: 64 }).notNull(),
+ name: varchar("name", { length: 64 }).unique().notNull(),
ownerId: varchar("owner_id", { length: 64 }).references(() => users.id, {
onDelete: "set null",
}),
diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts
index f0d1a52e4..08a8b8c44 100644
--- a/packages/db/schema/sqlite.ts
+++ b/packages/db/schema/sqlite.ts
@@ -121,7 +121,7 @@ export const groupMembers = sqliteTable(
export const groups = sqliteTable("group", {
id: text("id").notNull().primaryKey(),
- name: text("name").notNull(),
+ name: text("name").unique().notNull(),
ownerId: text("owner_id").references(() => users.id, {
onDelete: "set null",
}),
diff --git a/packages/definitions/package.json b/packages/definitions/package.json
index 4beccf3e0..e58e30125 100644
--- a/packages/definitions/package.json
+++ b/packages/definitions/package.json
@@ -18,7 +18,8 @@
"clean": "rm -rf .turbo node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit",
+ "postinstall": "tsx ./src/docs/codegen.ts"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
diff --git a/packages/definitions/src/docs/codegen.ts b/packages/definitions/src/docs/codegen.ts
new file mode 100644
index 000000000..9f35a3af3
--- /dev/null
+++ b/packages/definitions/src/docs/codegen.ts
@@ -0,0 +1,75 @@
+import fs from "fs/promises";
+import path, { dirname } from "path";
+import { fileURLToPath } from "url";
+import { XMLParser } from "fast-xml-parser";
+import { z } from "zod";
+
+import { createDocumentationLink } from "./index";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const removeCommonUrl = (url: string) => {
+ return url.replace("https://homarr.dev", "");
+};
+
+const sitemapSchema = z.object({
+ urlset: z.object({
+ url: z.array(
+ z.object({
+ loc: z.string(),
+ }),
+ ),
+ }),
+});
+
+const fetchSitemapAsync = async () => {
+ const response = await fetch(createDocumentationLink("/sitemap.xml"));
+ return await response.text();
+};
+
+const parseXml = (sitemapXml: string) => {
+ const parser = new XMLParser();
+ const data: unknown = parser.parse(sitemapXml);
+ const result = sitemapSchema.safeParse(data);
+ if (!result.success) {
+ throw new Error("Invalid sitemap schema");
+ }
+
+ return result.data;
+};
+
+const mapSitemapXmlToPaths = (sitemapData: z.infer) => {
+ return sitemapData.urlset.url.map((url) => removeCommonUrl(url.loc));
+};
+
+const createSitemapPathType = (paths: string[]) => {
+ return "export type HomarrDocumentationPath =\n" + paths.map((path) => ` | "${path.replace(/\/$/, "")}"`).join("\n");
+};
+
+const updateSitemapTypeFileAsync = async (sitemapPathType: string) => {
+ const content =
+ "// This file is auto-generated by the codegen script\n" +
+ "// it uses the sitemap.xml to generate the HomarrDocumentationPath type\n" +
+ sitemapPathType +
+ ";\n";
+
+ await fs.writeFile(path.join(__dirname, "homarr-docs-sitemap.ts"), content);
+};
+
+/**
+ * This script fetches the sitemap.xml and generates the HomarrDocumentationPath type
+ * which is used for typesafe documentation links
+ */
+// eslint-disable-next-line no-restricted-syntax
+const main = async () => {
+ const sitemapXml = await fetchSitemapAsync();
+ const sitemapData = parseXml(sitemapXml);
+ const paths = mapSitemapXmlToPaths(sitemapData);
+ // Adding sitemap as it's not in the sitemap.xml and we need it for this file
+ paths.push("/sitemap.xml");
+ const sitemapPathType = createSitemapPathType(paths);
+ await updateSitemapTypeFileAsync(sitemapPathType);
+};
+
+void main();
diff --git a/packages/definitions/src/docs/homarr-docs-sitemap.ts b/packages/definitions/src/docs/homarr-docs-sitemap.ts
new file mode 100644
index 000000000..0e4ffbd96
--- /dev/null
+++ b/packages/definitions/src/docs/homarr-docs-sitemap.ts
@@ -0,0 +1,191 @@
+// This file is auto-generated by the codegen script
+// it uses the sitemap.xml to generate the HomarrDocumentationPath type
+export type HomarrDocumentationPath =
+ | "/about-us"
+ | "/blog"
+ | "/blog/2023/01/11/version0.11"
+ | "/blog/2023/04/16/version0.12-more-widgets"
+ | "/blog/2023/11/10/authentication"
+ | "/blog/2023/12/22/updated-documentation"
+ | "/blog/2024/09/23/version-1.0"
+ | "/blog/archive"
+ | "/blog/authors"
+ | "/blog/authors/ajnart"
+ | "/blog/authors/manuel-rw"
+ | "/blog/authors/meierschlumpf"
+ | "/blog/authors/tagashi"
+ | "/blog/authors/walkx"
+ | "/blog/documentation-migration"
+ | "/blog/tags"
+ | "/blog/tags/authentication"
+ | "/blog/tags/breaking-changes"
+ | "/blog/tags/contributions"
+ | "/blog/tags/design"
+ | "/blog/tags/dnd"
+ | "/blog/tags/docs"
+ | "/blog/tags/documentation"
+ | "/blog/tags/gridstack"
+ | "/blog/tags/homarr"
+ | "/blog/tags/migration"
+ | "/blog/tags/notepad"
+ | "/blog/tags/security"
+ | "/blog/tags/translations"
+ | "/blog/tags/update"
+ | "/blog/tags/version"
+ | "/blog/translations"
+ | "/docs/tags"
+ | "/docs/tags/active-directory"
+ | "/docs/tags/ad-guard"
+ | "/docs/tags/ad-guard-home"
+ | "/docs/tags/administration"
+ | "/docs/tags/advanced"
+ | "/docs/tags/analytics"
+ | "/docs/tags/api"
+ | "/docs/tags/banner"
+ | "/docs/tags/blocking"
+ | "/docs/tags/board"
+ | "/docs/tags/boards"
+ | "/docs/tags/bookmark"
+ | "/docs/tags/caddy"
+ | "/docs/tags/checklist"
+ | "/docs/tags/code"
+ | "/docs/tags/community"
+ | "/docs/tags/configuration"
+ | "/docs/tags/connections"
+ | "/docs/tags/customization"
+ | "/docs/tags/data-sources"
+ | "/docs/tags/developer"
+ | "/docs/tags/development"
+ | "/docs/tags/dns"
+ | "/docs/tags/docker"
+ | "/docs/tags/edit-mode"
+ | "/docs/tags/env"
+ | "/docs/tags/environment-variables"
+ | "/docs/tags/feeds"
+ | "/docs/tags/getting-started"
+ | "/docs/tags/google"
+ | "/docs/tags/grafana"
+ | "/docs/tags/groups"
+ | "/docs/tags/hardware"
+ | "/docs/tags/health"
+ | "/docs/tags/help"
+ | "/docs/tags/icons"
+ | "/docs/tags/iframe"
+ | "/docs/tags/images"
+ | "/docs/tags/installation"
+ | "/docs/tags/integrade"
+ | "/docs/tags/integration"
+ | "/docs/tags/integrations"
+ | "/docs/tags/interface"
+ | "/docs/tags/jellyserr"
+ | "/docs/tags/ldap"
+ | "/docs/tags/links"
+ | "/docs/tags/lists"
+ | "/docs/tags/management"
+ | "/docs/tags/monitoring"
+ | "/docs/tags/news"
+ | "/docs/tags/notebook"
+ | "/docs/tags/notes"
+ | "/docs/tags/oidc"
+ | "/docs/tags/open-media-vault"
+ | "/docs/tags/overseerr"
+ | "/docs/tags/permissions"
+ | "/docs/tags/pi-hole"
+ | "/docs/tags/preferences"
+ | "/docs/tags/programming"
+ | "/docs/tags/proxmox"
+ | "/docs/tags/proxy"
+ | "/docs/tags/roles"
+ | "/docs/tags/rss"
+ | "/docs/tags/search"
+ | "/docs/tags/search-engines"
+ | "/docs/tags/security"
+ | "/docs/tags/seo"
+ | "/docs/tags/server"
+ | "/docs/tags/settings"
+ | "/docs/tags/sinkhole"
+ | "/docs/tags/sso"
+ | "/docs/tags/system"
+ | "/docs/tags/table"
+ | "/docs/tags/technical-documentation"
+ | "/docs/tags/text"
+ | "/docs/tags/theming"
+ | "/docs/tags/traefik"
+ | "/docs/tags/translations"
+ | "/docs/tags/unraid"
+ | "/docs/tags/user"
+ | "/docs/tags/users"
+ | "/docs/tags/variables"
+ | "/docs/tags/widgets"
+ | "/docs/advanced/command-line"
+ | "/docs/advanced/command-line/password-recovery"
+ | "/docs/advanced/configuration/environment-variables"
+ | "/docs/advanced/configuration/keyboard-shortcuts"
+ | "/docs/advanced/configuration/proxies-and-certificates"
+ | "/docs/advanced/customizations/board-customization"
+ | "/docs/advanced/customizations/dark-mode"
+ | "/docs/advanced/customizations/icons"
+ | "/docs/advanced/customizations/user-preferences"
+ | "/docs/advanced/sso"
+ | "/docs/category/advanced"
+ | "/docs/category/getting-started"
+ | "/docs/category/installation"
+ | "/docs/category/installation-1"
+ | "/docs/category/integrations"
+ | "/docs/category/management"
+ | "/docs/category/more"
+ | "/docs/category/widgets"
+ | "/docs/community/developer-guides"
+ | "/docs/community/donate"
+ | "/docs/community/faq"
+ | "/docs/community/get-in-touch"
+ | "/docs/community/license"
+ | "/docs/community/translations"
+ | "/docs/getting-started"
+ | "/docs/getting-started/after-the-installation"
+ | "/docs/getting-started/glossary"
+ | "/docs/getting-started/installation/docker"
+ | "/docs/getting-started/installation/easy-panel"
+ | "/docs/getting-started/installation/home-assistant"
+ | "/docs/getting-started/installation/kubernetes"
+ | "/docs/getting-started/installation/portainer"
+ | "/docs/getting-started/installation/qnap"
+ | "/docs/getting-started/installation/saltbox"
+ | "/docs/getting-started/installation/source"
+ | "/docs/getting-started/installation/synology"
+ | "/docs/getting-started/installation/truenas"
+ | "/docs/getting-started/installation/unraid"
+ | "/docs/integrations/containers"
+ | "/docs/integrations/dns"
+ | "/docs/integrations/hardware"
+ | "/docs/integrations/media-requester"
+ | "/docs/integrations/media-server"
+ | "/docs/integrations/servarr"
+ | "/docs/integrations/torrent"
+ | "/docs/integrations/usenet"
+ | "/docs/management/api"
+ | "/docs/management/boards"
+ | "/docs/management/integrations"
+ | "/docs/management/search-engines"
+ | "/docs/management/settings"
+ | "/docs/management/users"
+ | "/docs/widgets/bookmarks"
+ | "/docs/widgets/calendar-widget"
+ | "/docs/widgets/clock-widget"
+ | "/docs/widgets/dashdot-widget"
+ | "/docs/widgets/dns-hole"
+ | "/docs/widgets/download-speed-widget"
+ | "/docs/widgets/health-monitoring"
+ | "/docs/widgets/home-assistant"
+ | "/docs/widgets/iframe"
+ | "/docs/widgets/indexer-manager"
+ | "/docs/widgets/media-requests"
+ | "/docs/widgets/media-server"
+ | "/docs/widgets/notebook"
+ | "/docs/widgets/rss-widget"
+ | "/docs/widgets/torrent-widget"
+ | "/docs/widgets/usenet-widget"
+ | "/docs/widgets/video"
+ | "/docs/widgets/weather-widget"
+ | ""
+ | "/sitemap.xml";
diff --git a/packages/definitions/src/docs/index.ts b/packages/definitions/src/docs/index.ts
new file mode 100644
index 000000000..cc6c0f515
--- /dev/null
+++ b/packages/definitions/src/docs/index.ts
@@ -0,0 +1,7 @@
+import type { HomarrDocumentationPath } from "./homarr-docs-sitemap";
+
+const documentationBaseUrl = "https://deploy-preview-113--homarr-docs.netlify.app";
+
+// Please use the method so the path can be checked!
+export const createDocumentationLink = (path: HomarrDocumentationPath, hashTag?: `#${string}`) =>
+ `${documentationBaseUrl}${path}${hashTag ?? ""}`;
diff --git a/packages/definitions/src/group.ts b/packages/definitions/src/group.ts
new file mode 100644
index 000000000..ebac0daa1
--- /dev/null
+++ b/packages/definitions/src/group.ts
@@ -0,0 +1 @@
+export const everyoneGroup = "everyone";
diff --git a/packages/definitions/src/index.ts b/packages/definitions/src/index.ts
index d2dcd8e0a..20919b208 100644
--- a/packages/definitions/src/index.ts
+++ b/packages/definitions/src/index.ts
@@ -6,3 +6,5 @@ export * from "./permissions";
export * from "./docker";
export * from "./auth";
export * from "./user";
+export * from "./group";
+export * from "./docs";
diff --git a/packages/spotlight/src/modes/index.tsx b/packages/spotlight/src/modes/index.tsx
index afb613e74..eb286a1cd 100644
--- a/packages/spotlight/src/modes/index.tsx
+++ b/packages/spotlight/src/modes/index.tsx
@@ -1,6 +1,7 @@
import { Group, Kbd, Text } from "@mantine/core";
import { IconBook2, IconBrandDiscord, IconBrandGithub } from "@tabler/icons-react";
+import { createDocumentationLink } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { createGroup } from "../lib/group";
@@ -45,7 +46,7 @@ const helpMode = {
{
label: t("documentation.label"),
icon: IconBook2,
- href: "https://homarr.dev/docs/getting-started/",
+ href: createDocumentationLink("/docs/getting-started"),
},
{
label: t("submitIssue.label"),
diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts
index 834326730..670be6712 100644
--- a/packages/translation/src/lang/en.ts
+++ b/packages/translation/src/lang/en.ts
@@ -249,6 +249,9 @@ export default {
mixed: "Some members are from external providers and cannot be managed here",
external: "All members are from external providers and cannot be managed here",
},
+ reservedNotice: {
+ message: "This group is reserved for system use and restricts some actions. {checkoutDocs}",
+ },
action: {
create: {
label: "New group",
diff --git a/packages/validation/src/group.ts b/packages/validation/src/group.ts
index cd476c5df..a48642caf 100644
--- a/packages/validation/src/group.ts
+++ b/packages/validation/src/group.ts
@@ -1,12 +1,19 @@
import { z } from "zod";
-import { groupPermissionKeys } from "@homarr/definitions";
+import { everyoneGroup, groupPermissionKeys } from "@homarr/definitions";
import { byIdSchema } from "./common";
import { zodEnumFromArray } from "./enums";
const createSchema = z.object({
- name: z.string().trim().min(1).max(64),
+ name: z
+ .string()
+ .trim()
+ .min(1)
+ .max(64)
+ .refine((value) => value !== everyoneGroup, {
+ message: "'everyone' is a reserved group name",
+ }),
});
const updateSchema = createSchema.merge(byIdSchema);
diff --git a/packages/widgets/src/video/component.tsx b/packages/widgets/src/video/component.tsx
index 0893e6d8d..3527246db 100644
--- a/packages/widgets/src/video/component.tsx
+++ b/packages/widgets/src/video/component.tsx
@@ -13,6 +13,8 @@ import classes from "./component.module.css";
import "video.js/dist/video-js.css";
+import { createDocumentationLink } from "@homarr/definitions";
+
export default function VideoWidget({ options }: WidgetComponentProps<"video">) {
if (options.feedUrl.trim() === "") {
return ;
@@ -46,7 +48,7 @@ const ForYoutubeUseIframe = () => {
{t("widget.video.error.forYoutubeUseIframe")}
- {t("common.action.checkoutDocs")}
+ {t("common.action.checkoutDocs")}
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9081ca2bb..db7fe9746 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,7 +24,7 @@ importers:
version: 4.3.3(vite@5.4.5(@types/node@20.16.13)(sass@1.80.3)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/coverage-v8':
specifier: ^2.1.3
- version: 2.1.3(vitest@2.1.3)
+ version: 2.1.3(vitest@2.1.3(@types/node@20.16.13)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.80.3)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/ui':
specifier: ^2.1.3
version: 2.1.3(vitest@2.1.3)
@@ -358,8 +358,8 @@ importers:
specifier: ^3.3.3
version: 3.3.3
tsx:
- specifier: 4.13.3
- version: 4.13.3
+ specifier: 4.19.1
+ version: 4.19.1
typescript:
specifier: ^5.6.3
version: 5.6.3
@@ -394,8 +394,8 @@ importers:
specifier: ^16.4.5
version: 16.4.5
tsx:
- specifier: 4.13.3
- version: 4.13.3
+ specifier: 4.19.1
+ version: 4.19.1
ws:
specifier: ^8.18.0
version: 8.18.0
@@ -844,6 +844,9 @@ importers:
'@homarr/log':
specifier: workspace:^0.1.0
version: link:../log
+ '@homarr/server-settings':
+ specifier: workspace:^0.1.0
+ version: link:../server-settings
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
@@ -887,6 +890,9 @@ importers:
prettier:
specifier: ^3.3.3
version: 3.3.3
+ tsx:
+ specifier: 4.19.1
+ version: 4.19.1
typescript:
specifier: ^5.6.3
version: 5.6.3
@@ -1907,6 +1913,12 @@ packages:
cpu: [ppc64]
os: [aix]
+ '@esbuild/aix-ppc64@0.23.1':
+ resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/android-arm64@0.18.20':
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
@@ -1931,6 +1943,12 @@ packages:
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm64@0.23.1':
+ resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm@0.18.20':
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
engines: {node: '>=12'}
@@ -1955,6 +1973,12 @@ packages:
cpu: [arm]
os: [android]
+ '@esbuild/android-arm@0.23.1':
+ resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-x64@0.18.20':
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
engines: {node: '>=12'}
@@ -1979,6 +2003,12 @@ packages:
cpu: [x64]
os: [android]
+ '@esbuild/android-x64@0.23.1':
+ resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/darwin-arm64@0.18.20':
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
engines: {node: '>=12'}
@@ -2003,6 +2033,12 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-arm64@0.23.1':
+ resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.18.20':
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
engines: {node: '>=12'}
@@ -2027,6 +2063,12 @@ packages:
cpu: [x64]
os: [darwin]
+ '@esbuild/darwin-x64@0.23.1':
+ resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/freebsd-arm64@0.18.20':
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
engines: {node: '>=12'}
@@ -2051,6 +2093,12 @@ packages:
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-arm64@0.23.1':
+ resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.18.20':
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
engines: {node: '>=12'}
@@ -2075,6 +2123,12 @@ packages:
cpu: [x64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.23.1':
+ resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/linux-arm64@0.18.20':
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
engines: {node: '>=12'}
@@ -2099,6 +2153,12 @@ packages:
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm64@0.23.1':
+ resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm@0.18.20':
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
engines: {node: '>=12'}
@@ -2123,6 +2183,12 @@ packages:
cpu: [arm]
os: [linux]
+ '@esbuild/linux-arm@0.23.1':
+ resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-ia32@0.18.20':
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
engines: {node: '>=12'}
@@ -2147,6 +2213,12 @@ packages:
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-ia32@0.23.1':
+ resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-loong64@0.18.20':
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
engines: {node: '>=12'}
@@ -2171,6 +2243,12 @@ packages:
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-loong64@0.23.1':
+ resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.18.20':
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
engines: {node: '>=12'}
@@ -2195,6 +2273,12 @@ packages:
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-mips64el@0.23.1':
+ resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.18.20':
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
engines: {node: '>=12'}
@@ -2219,6 +2303,12 @@ packages:
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-ppc64@0.23.1':
+ resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.18.20':
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
engines: {node: '>=12'}
@@ -2243,6 +2333,12 @@ packages:
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-riscv64@0.23.1':
+ resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-s390x@0.18.20':
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
engines: {node: '>=12'}
@@ -2267,6 +2363,12 @@ packages:
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-s390x@0.23.1':
+ resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-x64@0.18.20':
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
engines: {node: '>=12'}
@@ -2291,6 +2393,12 @@ packages:
cpu: [x64]
os: [linux]
+ '@esbuild/linux-x64@0.23.1':
+ resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/netbsd-x64@0.18.20':
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
engines: {node: '>=12'}
@@ -2315,6 +2423,18 @@ packages:
cpu: [x64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.18.20':
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
engines: {node: '>=12'}
@@ -2339,6 +2459,12 @@ packages:
cpu: [x64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/sunos-x64@0.18.20':
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
engines: {node: '>=12'}
@@ -2363,6 +2489,12 @@ packages:
cpu: [x64]
os: [sunos]
+ '@esbuild/sunos-x64@0.23.1':
+ resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/win32-arm64@0.18.20':
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
engines: {node: '>=12'}
@@ -2387,6 +2519,12 @@ packages:
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-arm64@0.23.1':
+ resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-ia32@0.18.20':
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
engines: {node: '>=12'}
@@ -2411,6 +2549,12 @@ packages:
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-ia32@0.23.1':
+ resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-x64@0.18.20':
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
engines: {node: '>=12'}
@@ -2435,6 +2579,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@esbuild/win32-x64@0.23.1':
+ resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -4707,6 +4857,11 @@ packages:
engines: {node: '>=12'}
hasBin: true
+ esbuild@0.23.1:
+ resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -7339,8 +7494,8 @@ packages:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'}
- tsx@4.13.3:
- resolution: {integrity: sha512-FTAJJLQCMiIbt78kD5qhLjHIR5NOQDKC63wcdelWRDBE+d1xSrXYhXq4DzejnC2tGhFZHpDy2Ika0Ugf7sK8gA==}
+ tsx@4.19.1:
+ resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==}
engines: {node: '>=18.0.0'}
hasBin: true
@@ -8180,6 +8335,9 @@ snapshots:
'@esbuild/aix-ppc64@0.21.5':
optional: true
+ '@esbuild/aix-ppc64@0.23.1':
+ optional: true
+
'@esbuild/android-arm64@0.18.20':
optional: true
@@ -8192,6 +8350,9 @@ snapshots:
'@esbuild/android-arm64@0.21.5':
optional: true
+ '@esbuild/android-arm64@0.23.1':
+ optional: true
+
'@esbuild/android-arm@0.18.20':
optional: true
@@ -8204,6 +8365,9 @@ snapshots:
'@esbuild/android-arm@0.21.5':
optional: true
+ '@esbuild/android-arm@0.23.1':
+ optional: true
+
'@esbuild/android-x64@0.18.20':
optional: true
@@ -8216,6 +8380,9 @@ snapshots:
'@esbuild/android-x64@0.21.5':
optional: true
+ '@esbuild/android-x64@0.23.1':
+ optional: true
+
'@esbuild/darwin-arm64@0.18.20':
optional: true
@@ -8228,6 +8395,9 @@ snapshots:
'@esbuild/darwin-arm64@0.21.5':
optional: true
+ '@esbuild/darwin-arm64@0.23.1':
+ optional: true
+
'@esbuild/darwin-x64@0.18.20':
optional: true
@@ -8240,6 +8410,9 @@ snapshots:
'@esbuild/darwin-x64@0.21.5':
optional: true
+ '@esbuild/darwin-x64@0.23.1':
+ optional: true
+
'@esbuild/freebsd-arm64@0.18.20':
optional: true
@@ -8252,6 +8425,9 @@ snapshots:
'@esbuild/freebsd-arm64@0.21.5':
optional: true
+ '@esbuild/freebsd-arm64@0.23.1':
+ optional: true
+
'@esbuild/freebsd-x64@0.18.20':
optional: true
@@ -8264,6 +8440,9 @@ snapshots:
'@esbuild/freebsd-x64@0.21.5':
optional: true
+ '@esbuild/freebsd-x64@0.23.1':
+ optional: true
+
'@esbuild/linux-arm64@0.18.20':
optional: true
@@ -8276,6 +8455,9 @@ snapshots:
'@esbuild/linux-arm64@0.21.5':
optional: true
+ '@esbuild/linux-arm64@0.23.1':
+ optional: true
+
'@esbuild/linux-arm@0.18.20':
optional: true
@@ -8288,6 +8470,9 @@ snapshots:
'@esbuild/linux-arm@0.21.5':
optional: true
+ '@esbuild/linux-arm@0.23.1':
+ optional: true
+
'@esbuild/linux-ia32@0.18.20':
optional: true
@@ -8300,6 +8485,9 @@ snapshots:
'@esbuild/linux-ia32@0.21.5':
optional: true
+ '@esbuild/linux-ia32@0.23.1':
+ optional: true
+
'@esbuild/linux-loong64@0.18.20':
optional: true
@@ -8312,6 +8500,9 @@ snapshots:
'@esbuild/linux-loong64@0.21.5':
optional: true
+ '@esbuild/linux-loong64@0.23.1':
+ optional: true
+
'@esbuild/linux-mips64el@0.18.20':
optional: true
@@ -8324,6 +8515,9 @@ snapshots:
'@esbuild/linux-mips64el@0.21.5':
optional: true
+ '@esbuild/linux-mips64el@0.23.1':
+ optional: true
+
'@esbuild/linux-ppc64@0.18.20':
optional: true
@@ -8336,6 +8530,9 @@ snapshots:
'@esbuild/linux-ppc64@0.21.5':
optional: true
+ '@esbuild/linux-ppc64@0.23.1':
+ optional: true
+
'@esbuild/linux-riscv64@0.18.20':
optional: true
@@ -8348,6 +8545,9 @@ snapshots:
'@esbuild/linux-riscv64@0.21.5':
optional: true
+ '@esbuild/linux-riscv64@0.23.1':
+ optional: true
+
'@esbuild/linux-s390x@0.18.20':
optional: true
@@ -8360,6 +8560,9 @@ snapshots:
'@esbuild/linux-s390x@0.21.5':
optional: true
+ '@esbuild/linux-s390x@0.23.1':
+ optional: true
+
'@esbuild/linux-x64@0.18.20':
optional: true
@@ -8372,6 +8575,9 @@ snapshots:
'@esbuild/linux-x64@0.21.5':
optional: true
+ '@esbuild/linux-x64@0.23.1':
+ optional: true
+
'@esbuild/netbsd-x64@0.18.20':
optional: true
@@ -8384,6 +8590,12 @@ snapshots:
'@esbuild/netbsd-x64@0.21.5':
optional: true
+ '@esbuild/netbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ optional: true
+
'@esbuild/openbsd-x64@0.18.20':
optional: true
@@ -8396,6 +8608,9 @@ snapshots:
'@esbuild/openbsd-x64@0.21.5':
optional: true
+ '@esbuild/openbsd-x64@0.23.1':
+ optional: true
+
'@esbuild/sunos-x64@0.18.20':
optional: true
@@ -8408,6 +8623,9 @@ snapshots:
'@esbuild/sunos-x64@0.21.5':
optional: true
+ '@esbuild/sunos-x64@0.23.1':
+ optional: true
+
'@esbuild/win32-arm64@0.18.20':
optional: true
@@ -8420,6 +8638,9 @@ snapshots:
'@esbuild/win32-arm64@0.21.5':
optional: true
+ '@esbuild/win32-arm64@0.23.1':
+ optional: true
+
'@esbuild/win32-ia32@0.18.20':
optional: true
@@ -8432,6 +8653,9 @@ snapshots:
'@esbuild/win32-ia32@0.21.5':
optional: true
+ '@esbuild/win32-ia32@0.23.1':
+ optional: true
+
'@esbuild/win32-x64@0.18.20':
optional: true
@@ -8444,6 +8668,9 @@ snapshots:
'@esbuild/win32-x64@0.21.5':
optional: true
+ '@esbuild/win32-x64@0.23.1':
+ optional: true
+
'@eslint-community/eslint-utils@4.4.0(eslint@9.13.0)':
dependencies:
eslint: 9.13.0
@@ -9930,7 +10157,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vitest/coverage-v8@2.1.3(vitest@2.1.3)':
+ '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@20.16.13)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.80.3)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3
@@ -11214,6 +11441,33 @@ snapshots:
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
+ esbuild@0.23.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.23.1
+ '@esbuild/android-arm': 0.23.1
+ '@esbuild/android-arm64': 0.23.1
+ '@esbuild/android-x64': 0.23.1
+ '@esbuild/darwin-arm64': 0.23.1
+ '@esbuild/darwin-x64': 0.23.1
+ '@esbuild/freebsd-arm64': 0.23.1
+ '@esbuild/freebsd-x64': 0.23.1
+ '@esbuild/linux-arm': 0.23.1
+ '@esbuild/linux-arm64': 0.23.1
+ '@esbuild/linux-ia32': 0.23.1
+ '@esbuild/linux-loong64': 0.23.1
+ '@esbuild/linux-mips64el': 0.23.1
+ '@esbuild/linux-ppc64': 0.23.1
+ '@esbuild/linux-riscv64': 0.23.1
+ '@esbuild/linux-s390x': 0.23.1
+ '@esbuild/linux-x64': 0.23.1
+ '@esbuild/netbsd-x64': 0.23.1
+ '@esbuild/openbsd-arm64': 0.23.1
+ '@esbuild/openbsd-x64': 0.23.1
+ '@esbuild/sunos-x64': 0.23.1
+ '@esbuild/win32-arm64': 0.23.1
+ '@esbuild/win32-ia32': 0.23.1
+ '@esbuild/win32-x64': 0.23.1
+
escalade@3.2.0: {}
escape-goat@2.1.1: {}
@@ -14149,9 +14403,9 @@ snapshots:
tsscmp@1.0.6: {}
- tsx@4.13.3:
+ tsx@4.19.1:
dependencies:
- esbuild: 0.20.2
+ esbuild: 0.23.1
get-tsconfig: 4.8.1
optionalDependencies:
fsevents: 2.3.3
diff --git a/tooling/eslint/base.js b/tooling/eslint/base.js
index 118762731..47632dbc2 100644
--- a/tooling/eslint/base.js
+++ b/tooling/eslint/base.js
@@ -39,7 +39,7 @@ export default tseslint.config(
"warn",
{
min: 3,
- exceptions: ["_", "i", "z", "t", "id", "db"], // _ for unused variables, i for index, z for zod, t for translation
+ exceptions: ["_", "i", "z", "t", "id", "db", "fs"], // _ for unused variables, i for index, z for zod, t for translation
properties: "never", // This allows for example the use of as sm and md would be too short
},
],
@@ -80,6 +80,11 @@ export default tseslint.config(
"VariableDeclarator[init.type=/FunctionExpression$/][init.async=true][id.name=/^[a-z].*$/][id.name!=/Async$/]",
message: "Async function name must end in 'Async' (variable declarator)",
},
+ {
+ // \\u002F is the unicode escape for / and is used because of https://github.com/estools/esquery/issues/68
+ selector: "Literal[value=/^https:\\u002F\\u002Fhomarr\\.dev\\u002F.*$/]",
+ message: "Links to 'https://homarr.dev/' should be used with createDocumentationLink method",
+ },
],
},
},