mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 08:20:56 +01:00
feat(db): support postgresql database (#3643)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -34,6 +34,10 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
# The following is an example on how to use the node-postgres driver:
|
||||
# DB_DRIVER='node-postgres'
|
||||
# DB_URL='postgres://user:password@host:port/database'
|
||||
|
||||
# The below path can be used to store trusted certificates, it is not required and can be left empty.
|
||||
# If it is empty, it will default to `/appdata/trusted-certificates` in production.
|
||||
# If it is used, please use the full path to the directory where the certificates are stored.
|
||||
|
||||
13
.run/db_migration_postgresql_generate.run copy.xml
Normal file
13
.run/db_migration_postgresql_generate.run copy.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="db:migration:postgresql:generate" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="db:migration:postgresql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -26,5 +26,29 @@ services:
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
|
||||
postgresql:
|
||||
image: postgres
|
||||
restart: always
|
||||
# set shared memory limit when using docker compose
|
||||
shm_size: 128mb
|
||||
# or set shared memory limit when deploy via swarm stack
|
||||
#volumes:
|
||||
# - type: tmpfs
|
||||
# target: /dev/shm
|
||||
# tmpfs:
|
||||
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||
environment:
|
||||
POSTGRES_PASSWORD: homarr
|
||||
POSTGRES_USER: homarr
|
||||
POSTGRES_DB: homarrdb
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- postgresql_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
# if already run PostgreSQL, change port number to use container's service
|
||||
# - 2345:5432
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
mysql_data:
|
||||
postgresql_data:
|
||||
@@ -9,6 +9,8 @@
|
||||
"cli": "pnpm with-env tsx packages/cli/index.ts",
|
||||
"db:migration:mysql:generate": "pnpm -F db migration:mysql:generate",
|
||||
"db:migration:mysql:run": "pnpm -F db migration:mysql:run",
|
||||
"db:migration:postgresql:generate": "pnpm -F db migration:postgresql:generate",
|
||||
"db:migration:postgresql:run": "pnpm -F db migration:postgresql:run",
|
||||
"db:migration:sqlite:generate": "pnpm -F db migration:sqlite:generate",
|
||||
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
||||
"db:push": "pnpm -F db push:sqlite",
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { InferInsertModel } from "drizzle-orm";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
|
||||
import { env } from "./env";
|
||||
import * as schema from "./schema";
|
||||
|
||||
@@ -10,6 +10,14 @@ type TableKey = {
|
||||
[K in keyof typeof schema]: (typeof schema)[K] extends { _: { brand: "Table" } } ? K : never;
|
||||
}[keyof typeof schema];
|
||||
|
||||
export function isMysql(): boolean {
|
||||
return env.DB_DRIVER === "mysql2";
|
||||
}
|
||||
|
||||
export function isPostgresql(): boolean {
|
||||
return env.DB_DRIVER === "node-postgres";
|
||||
}
|
||||
|
||||
export const createDbInsertCollectionForTransaction = <TTableKey extends TableKey>(
|
||||
tablesInInsertOrder: TTableKey[],
|
||||
) => {
|
||||
@@ -35,8 +43,10 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
|
||||
}
|
||||
});
|
||||
},
|
||||
insertAllAsync: async (db: HomarrDatabaseMysql) => {
|
||||
await db.transaction(async (transaction) => {
|
||||
// We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors
|
||||
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => {
|
||||
const innerDb = db as HomarrDatabaseMysql;
|
||||
await innerDb.transaction(async (transaction) => {
|
||||
for (const [key, values] of objectEntries(context)) {
|
||||
if (values.length >= 1) {
|
||||
// Below is actually the mysqlSchema when the driver is mysql
|
||||
@@ -56,12 +66,18 @@ export const createDbInsertCollectionWithoutTransaction = <TTableKey extends Tab
|
||||
return {
|
||||
...collection,
|
||||
insertAllAsync: async (db: HomarrDatabase) => {
|
||||
if (env.DB_DRIVER !== "mysql2") {
|
||||
insertAll(db);
|
||||
return;
|
||||
switch (env.DB_DRIVER) {
|
||||
case "mysql2":
|
||||
case "node-postgres":
|
||||
// For mysql2 and node-postgres, we can use the async insertAllAsync method
|
||||
await insertAllAsync(db as unknown as HomarrDatabaseMysql | HomarrDatabasePostgresql);
|
||||
return;
|
||||
default:
|
||||
// For better-sqlite3, we need to use the synchronous insertAll method
|
||||
// default assumes better-sqlite3. It's original implementation.
|
||||
insertAll(db);
|
||||
break;
|
||||
}
|
||||
|
||||
await insertAllAsync(db as unknown as HomarrDatabaseMysql);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
20
packages/db/configs/postgresql.config.ts
Normal file
20
packages/db/configs/postgresql.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Config } from "drizzle-kit";
|
||||
|
||||
import { env } from "../env";
|
||||
|
||||
export default {
|
||||
dialect: "postgresql",
|
||||
schema: "./schema",
|
||||
casing: "snake_case",
|
||||
|
||||
dbCredentials: env.DB_URL
|
||||
? { url: env.DB_URL }
|
||||
: {
|
||||
host: env.DB_HOST,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
database: env.DB_NAME,
|
||||
},
|
||||
out: "./migrations/postgresql",
|
||||
} satisfies Config;
|
||||
@@ -5,17 +5,22 @@ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
|
||||
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
|
||||
import type { MySql2Database } from "drizzle-orm/mysql2";
|
||||
import { drizzle as drizzleMysql } from "drizzle-orm/mysql2";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
|
||||
import type { Pool as MysqlConnectionPool } from "mysql2";
|
||||
import mysql from "mysql2";
|
||||
import { Pool as PostgresPool } from "pg";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { env } from "./env";
|
||||
import * as mysqlSchema from "./schema/mysql";
|
||||
import * as pgSchema from "./schema/postgresql";
|
||||
import * as sqliteSchema from "./schema/sqlite";
|
||||
|
||||
export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
|
||||
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
|
||||
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
|
||||
|
||||
const init = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
@@ -24,6 +29,9 @@ const init = () => {
|
||||
case "mysql2":
|
||||
initMySQL2();
|
||||
break;
|
||||
case "node-postgres":
|
||||
initNodePostgres();
|
||||
break;
|
||||
default:
|
||||
initBetterSqlite();
|
||||
break;
|
||||
@@ -31,7 +39,7 @@ const init = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export let connection: BetterSqlite3Connection | MysqlConnectionPool;
|
||||
export let connection: BetterSqlite3Connection | MysqlConnectionPool | PostgresPool;
|
||||
export let database: HomarrDatabase;
|
||||
|
||||
class WinstonDrizzleLogger implements Logger {
|
||||
@@ -73,4 +81,33 @@ const initMySQL2 = () => {
|
||||
}) as unknown as HomarrDatabase;
|
||||
};
|
||||
|
||||
const initNodePostgres = () => {
|
||||
if (!env.DB_HOST) {
|
||||
connection = new PostgresPool({
|
||||
connectionString: env.DB_URL,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
} else {
|
||||
connection = new PostgresPool({
|
||||
host: env.DB_HOST,
|
||||
database: env.DB_NAME,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
}
|
||||
|
||||
database = drizzlePg({
|
||||
logger: new WinstonDrizzleLogger(),
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: connection,
|
||||
}) as unknown as HomarrDatabase;
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createEnv } from "@homarr/core/infrastructure/env";
|
||||
const drivers = {
|
||||
betterSqlite3: "better-sqlite3",
|
||||
mysql2: "mysql2",
|
||||
nodePostgres: "node-postgres",
|
||||
} as const;
|
||||
|
||||
const isDriver = (driver: (typeof drivers)[keyof typeof drivers]) => process.env.DB_DRIVER === driver;
|
||||
@@ -21,7 +22,7 @@ export const env = createEnv({
|
||||
*/
|
||||
server: {
|
||||
DB_DRIVER: z
|
||||
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2)], {
|
||||
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], {
|
||||
message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`,
|
||||
})
|
||||
.default(drivers.betterSqlite3),
|
||||
@@ -42,7 +43,7 @@ export const env = createEnv({
|
||||
.regex(/\d+/)
|
||||
.transform(Number)
|
||||
.refine((number) => number >= 1)
|
||||
.default(3306),
|
||||
.default(isDriver(drivers.mysql2) ? 3306 : 5432),
|
||||
DB_USER: z.string(),
|
||||
DB_PASSWORD: z.string(),
|
||||
DB_NAME: z.string(),
|
||||
|
||||
@@ -7,6 +7,6 @@ export * from "drizzle-orm";
|
||||
export const db = database;
|
||||
|
||||
export type Database = typeof db;
|
||||
export type { HomarrDatabaseMysql } from "./driver";
|
||||
export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
|
||||
|
||||
export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions";
|
||||
|
||||
322
packages/db/migrations/postgresql/0000_initial.sql
Normal file
322
packages/db/migrations/postgresql/0000_initial.sql
Normal file
@@ -0,0 +1,322 @@
|
||||
CREATE TABLE "account" (
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"provider" varchar(64) NOT NULL,
|
||||
"provider_account_id" varchar(64) NOT NULL,
|
||||
"refresh_token" text,
|
||||
"access_token" text,
|
||||
"expires_at" integer,
|
||||
"token_type" text,
|
||||
"scope" text,
|
||||
"id_token" text,
|
||||
"session_state" text,
|
||||
CONSTRAINT "account_provider_provider_account_id_pk" PRIMARY KEY("provider","provider_account_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "apiKey" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"api_key" text NOT NULL,
|
||||
"salt" text NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "app" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"icon_url" text NOT NULL,
|
||||
"href" text,
|
||||
"ping_url" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "boardGroupPermission" (
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "boardGroupPermission_board_id_group_id_permission_pk" PRIMARY KEY("board_id","group_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "boardUserPermission" (
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "boardUserPermission_board_id_user_id_permission_pk" PRIMARY KEY("board_id","user_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "board" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(256) NOT NULL,
|
||||
"is_public" boolean DEFAULT false NOT NULL,
|
||||
"creator_id" varchar(64),
|
||||
"page_title" text,
|
||||
"meta_title" text,
|
||||
"logo_image_url" text,
|
||||
"favicon_image_url" text,
|
||||
"background_image_url" text,
|
||||
"background_image_attachment" text DEFAULT 'fixed' NOT NULL,
|
||||
"background_image_repeat" text DEFAULT 'no-repeat' NOT NULL,
|
||||
"background_image_size" text DEFAULT 'cover' NOT NULL,
|
||||
"primary_color" text DEFAULT '#fa5252' NOT NULL,
|
||||
"secondary_color" text DEFAULT '#fd7e14' NOT NULL,
|
||||
"opacity" integer DEFAULT 100 NOT NULL,
|
||||
"custom_css" text,
|
||||
"icon_color" text,
|
||||
"item_radius" text DEFAULT 'lg' NOT NULL,
|
||||
"disable_status" boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT "board_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cron_job_configuration" (
|
||||
"name" varchar(256) PRIMARY KEY NOT NULL,
|
||||
"cron_expression" varchar(32) NOT NULL,
|
||||
"is_enabled" boolean DEFAULT true NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "groupMember" (
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "groupMember_group_id_user_id_pk" PRIMARY KEY("group_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "groupPermission" (
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "group" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(64) NOT NULL,
|
||||
"owner_id" varchar(64),
|
||||
"home_board_id" varchar(64),
|
||||
"mobile_home_board_id" varchar(64),
|
||||
"position" smallint NOT NULL,
|
||||
CONSTRAINT "group_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "iconRepository" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"slug" varchar(150) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "icon" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(250) NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"checksum" text NOT NULL,
|
||||
"icon_repository_id" varchar(64) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationGroupPermissions" (
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
"group_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "integration_group_permission__pk" PRIMARY KEY("integration_id","group_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integration_item" (
|
||||
"item_id" varchar(64) NOT NULL,
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "integration_item_item_id_integration_id_pk" PRIMARY KEY("item_id","integration_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationSecret" (
|
||||
"kind" varchar(16) NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"updated_at" timestamp NOT NULL,
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "integrationSecret_integration_id_kind_pk" PRIMARY KEY("integration_id","kind")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integrationUserPermission" (
|
||||
"integration_id" varchar(64) NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"permission" varchar(128) NOT NULL,
|
||||
CONSTRAINT "integrationUserPermission_integration_id_user_id_permission_pk" PRIMARY KEY("integration_id","user_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "integration" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"kind" varchar(128) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "invite" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"token" varchar(512) NOT NULL,
|
||||
"expiration_date" timestamp NOT NULL,
|
||||
"creator_id" varchar(64) NOT NULL,
|
||||
CONSTRAINT "invite_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "item_layout" (
|
||||
"item_id" varchar(64) NOT NULL,
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"layout_id" varchar(64) NOT NULL,
|
||||
"x_offset" integer NOT NULL,
|
||||
"y_offset" integer NOT NULL,
|
||||
"width" integer NOT NULL,
|
||||
"height" integer NOT NULL,
|
||||
CONSTRAINT "item_layout_item_id_section_id_layout_id_pk" PRIMARY KEY("item_id","section_id","layout_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "item" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"options" text DEFAULT '{"json": {}}' NOT NULL,
|
||||
"advanced_options" text DEFAULT '{"json": {}}' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "layout" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(32) NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"column_count" smallint NOT NULL,
|
||||
"breakpoint" smallint DEFAULT 0 NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "media" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" varchar(512) NOT NULL,
|
||||
"content" "bytea" NOT NULL,
|
||||
"content_type" text NOT NULL,
|
||||
"size" integer NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"creator_id" varchar(64)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "onboarding" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"step" varchar(64) NOT NULL,
|
||||
"previous_step" varchar(64)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "search_engine" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"icon_url" text NOT NULL,
|
||||
"name" varchar(64) NOT NULL,
|
||||
"short" varchar(8) NOT NULL,
|
||||
"description" text,
|
||||
"url_template" text,
|
||||
"type" varchar(64) DEFAULT 'generic' NOT NULL,
|
||||
"integration_id" varchar(64),
|
||||
CONSTRAINT "search_engine_short_unique" UNIQUE("short")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section_collapse_state" (
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"collapsed" boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT "section_collapse_state_user_id_section_id_pk" PRIMARY KEY("user_id","section_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section_layout" (
|
||||
"section_id" varchar(64) NOT NULL,
|
||||
"layout_id" varchar(64) NOT NULL,
|
||||
"parent_section_id" varchar(64),
|
||||
"x_offset" integer NOT NULL,
|
||||
"y_offset" integer NOT NULL,
|
||||
"width" integer NOT NULL,
|
||||
"height" integer NOT NULL,
|
||||
CONSTRAINT "section_layout_section_id_layout_id_pk" PRIMARY KEY("section_id","layout_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "section" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"board_id" varchar(64) NOT NULL,
|
||||
"kind" text NOT NULL,
|
||||
"x_offset" integer,
|
||||
"y_offset" integer,
|
||||
"name" text,
|
||||
"options" text DEFAULT '{"json": {}}'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "serverSetting" (
|
||||
"setting_key" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"value" text DEFAULT '{"json": {}}' NOT NULL,
|
||||
CONSTRAINT "serverSetting_settingKey_unique" UNIQUE("setting_key")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "session" (
|
||||
"session_token" varchar(512) PRIMARY KEY NOT NULL,
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"expires" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "trusted_certificate_hostname" (
|
||||
"hostname" varchar(256) NOT NULL,
|
||||
"thumbprint" varchar(128) NOT NULL,
|
||||
"certificate" text NOT NULL,
|
||||
CONSTRAINT "trusted_certificate_hostname_hostname_thumbprint_pk" PRIMARY KEY("hostname","thumbprint")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
"id" varchar(64) PRIMARY KEY NOT NULL,
|
||||
"name" text,
|
||||
"email" text,
|
||||
"email_verified" timestamp,
|
||||
"image" text,
|
||||
"password" text,
|
||||
"salt" text,
|
||||
"provider" varchar(64) DEFAULT 'credentials' NOT NULL,
|
||||
"home_board_id" varchar(64),
|
||||
"mobile_home_board_id" varchar(64),
|
||||
"default_search_engine_id" varchar(64),
|
||||
"open_search_in_new_tab" boolean DEFAULT false NOT NULL,
|
||||
"color_scheme" varchar(5) DEFAULT 'dark' NOT NULL,
|
||||
"first_day_of_week" smallint DEFAULT 1 NOT NULL,
|
||||
"ping_icons_enabled" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "verificationToken" (
|
||||
"identifier" varchar(64) NOT NULL,
|
||||
"token" varchar(512) NOT NULL,
|
||||
"expires" timestamp NOT NULL,
|
||||
CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "apiKey" ADD CONSTRAINT "apiKey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardGroupPermission" ADD CONSTRAINT "boardGroupPermission_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardGroupPermission" ADD CONSTRAINT "boardGroupPermission_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardUserPermission" ADD CONSTRAINT "boardUserPermission_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "boardUserPermission" ADD CONSTRAINT "boardUserPermission_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "board" ADD CONSTRAINT "board_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupMember" ADD CONSTRAINT "groupMember_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupMember" ADD CONSTRAINT "groupMember_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "groupPermission" ADD CONSTRAINT "groupPermission_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_home_board_id_board_id_fk" FOREIGN KEY ("home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "group" ADD CONSTRAINT "group_mobile_home_board_id_board_id_fk" FOREIGN KEY ("mobile_home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "icon" ADD CONSTRAINT "icon_icon_repository_id_iconRepository_id_fk" FOREIGN KEY ("icon_repository_id") REFERENCES "public"."iconRepository"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationGroupPermissions" ADD CONSTRAINT "integrationGroupPermissions_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationGroupPermissions" ADD CONSTRAINT "integrationGroupPermissions_group_id_group_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."group"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integration_item" ADD CONSTRAINT "integration_item_item_id_item_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."item"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integration_item" ADD CONSTRAINT "integration_item_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationSecret" ADD CONSTRAINT "integrationSecret_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationUserPermission" ADD CONSTRAINT "integrationUserPermission_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "integrationUserPermission" ADD CONSTRAINT "integrationUserPermission_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "invite" ADD CONSTRAINT "invite_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_item_id_item_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."item"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item_layout" ADD CONSTRAINT "item_layout_layout_id_layout_id_fk" FOREIGN KEY ("layout_id") REFERENCES "public"."layout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "item" ADD CONSTRAINT "item_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "layout" ADD CONSTRAINT "layout_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "media" ADD CONSTRAINT "media_creator_id_user_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "search_engine" ADD CONSTRAINT "search_engine_integration_id_integration_id_fk" FOREIGN KEY ("integration_id") REFERENCES "public"."integration"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_collapse_state" ADD CONSTRAINT "section_collapse_state_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_collapse_state" ADD CONSTRAINT "section_collapse_state_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_section_id_section_id_fk" FOREIGN KEY ("section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_layout_id_layout_id_fk" FOREIGN KEY ("layout_id") REFERENCES "public"."layout"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section_layout" ADD CONSTRAINT "section_layout_parent_section_id_section_id_fk" FOREIGN KEY ("parent_section_id") REFERENCES "public"."section"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "section" ADD CONSTRAINT "section_board_id_board_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."board"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_home_board_id_board_id_fk" FOREIGN KEY ("home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_mobile_home_board_id_board_id_fk" FOREIGN KEY ("mobile_home_board_id") REFERENCES "public"."board"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD CONSTRAINT "user_default_search_engine_id_search_engine_id_fk" FOREIGN KEY ("default_search_engine_id") REFERENCES "public"."search_engine"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "integration_secret__kind_idx" ON "integrationSecret" USING btree ("kind");--> statement-breakpoint
|
||||
CREATE INDEX "integration_secret__updated_at_idx" ON "integrationSecret" USING btree ("updated_at");--> statement-breakpoint
|
||||
CREATE INDEX "integration__kind_idx" ON "integration" USING btree ("kind");--> statement-breakpoint
|
||||
CREATE INDEX "user_id_idx" ON "session" USING btree ("user_id");
|
||||
1991
packages/db/migrations/postgresql/meta/0000_snapshot.json
Normal file
1991
packages/db/migrations/postgresql/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
packages/db/migrations/postgresql/meta/_journal.json
Normal file
13
packages/db/migrations/postgresql/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1754853510707,
|
||||
"tag": "0000_initial",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
45
packages/db/migrations/postgresql/migrate.ts
Normal file
45
packages/db/migrations/postgresql/migrate.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
|
||||
import type { Database } from "../..";
|
||||
import { env } from "../../env";
|
||||
import * as pgSchema from "../../schema/postgresql";
|
||||
import { applyCustomMigrationsAsync } from "../custom";
|
||||
import { seedDataAsync } from "../seed";
|
||||
|
||||
const migrationsFolder = process.argv[2] ?? ".";
|
||||
|
||||
const migrateAsync = async () => {
|
||||
const pool = new Pool(
|
||||
env.DB_URL
|
||||
? { connectionString: env.DB_URL }
|
||||
: {
|
||||
host: env.DB_HOST,
|
||||
database: env.DB_NAME,
|
||||
port: env.DB_PORT,
|
||||
user: env.DB_USER,
|
||||
password: env.DB_PASSWORD,
|
||||
},
|
||||
);
|
||||
|
||||
const db = drizzle({
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: pool,
|
||||
});
|
||||
|
||||
await migrate(db, { migrationsFolder });
|
||||
await seedDataAsync(db as unknown as Database);
|
||||
await applyCustomMigrationsAsync(db as unknown as Database);
|
||||
};
|
||||
|
||||
migrateAsync()
|
||||
.then(() => {
|
||||
console.log("Migration complete");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Migration failed", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -15,9 +15,8 @@ import {
|
||||
insertServerSettingByKeyAsync,
|
||||
updateServerSettingByKeyAsync,
|
||||
} from "../queries/server-setting";
|
||||
import { integrations, onboarding, searchEngines } from "../schema";
|
||||
import { groups, integrations, onboarding, searchEngines } from "../schema";
|
||||
import type { Integration } from "../schema";
|
||||
import { groups } from "../schema/mysql";
|
||||
|
||||
export const seedDataAsync = async (db: Database) => {
|
||||
await seedEveryoneGroupAsync(db);
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run build:sqlite && pnpm run build:mysql",
|
||||
"build": "pnpm run build:sqlite && pnpm run build:mysql && pnpm run build:postgresql",
|
||||
"build:mysql": "esbuild migrations/mysql/migrate.ts --bundle --platform=node --outfile=migrations/mysql/migrate.cjs",
|
||||
"build:postgresql": "esbuild migrations/postgresql/migrate.ts --bundle --platform=node --outfile=migrations/postgresql/migrate.cjs",
|
||||
"build:sqlite": "esbuild migrations/sqlite/migrate.ts --bundle --platform=node --outfile=migrations/sqlite/migrate.cjs",
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
@@ -26,10 +27,14 @@
|
||||
"migration:mysql:drop": "pnpm with-env drizzle-kit drop --config ./configs/mysql.config.ts",
|
||||
"migration:mysql:generate": "pnpm with-env drizzle-kit generate --config ./configs/mysql.config.ts",
|
||||
"migration:mysql:run": "pnpm with-env drizzle-kit migrate --config ./configs/mysql.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"migration:postgresql:drop": "pnpm with-env drizzle-kit drop --config ./configs/postgresql.config.ts",
|
||||
"migration:postgresql:generate": "pnpm with-env drizzle-kit generate --config ./configs/postgresql.config.ts",
|
||||
"migration:postgresql:run": "pnpm with-env drizzle-kit migrate --config ./configs/postgresql.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"migration:sqlite:drop": "pnpm with-env drizzle-kit drop --config ./configs/sqlite.config.ts",
|
||||
"migration:sqlite:generate": "pnpm with-env drizzle-kit generate --config ./configs/sqlite.config.ts",
|
||||
"migration:sqlite:run": "pnpm with-env drizzle-kit migrate --config ./configs/sqlite.config.ts && pnpm run seed && pnpm run migration:custom",
|
||||
"push:mysql": "pnpm with-env drizzle-kit push --config ./configs/mysql.config.ts",
|
||||
"push:postgresql": "pnpm with-env drizzle-kit push --config ./configs/postgresql.config.ts",
|
||||
"push:sqlite": "pnpm with-env drizzle-kit push --config ./configs/sqlite.config.ts",
|
||||
"seed": "pnpm with-env tsx ./migrations/run-seed.ts",
|
||||
"studio": "pnpm with-env drizzle-kit studio --config ./configs/sqlite.config.ts",
|
||||
@@ -47,12 +52,14 @@
|
||||
"@mantine/core": "^8.2.5",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^11.5.1",
|
||||
"@testcontainers/postgresql": "^11.4.0",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.4",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.14.3",
|
||||
"pg": "^8.16.3",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -60,6 +67,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.15.4",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "^9.33.0",
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
import { env } from "../env";
|
||||
import * as mysqlSchema from "./mysql";
|
||||
import * as pgSchema from "./postgresql";
|
||||
import * as sqliteSchema from "./sqlite";
|
||||
|
||||
export type PostgreSqlSchema = typeof pgSchema;
|
||||
export type MySqlSchema = typeof mysqlSchema;
|
||||
type Schema = typeof sqliteSchema;
|
||||
|
||||
const schema = process.env.DB_DRIVER === "mysql2" ? (mysqlSchema as unknown as Schema) : sqliteSchema;
|
||||
const schema =
|
||||
env.DB_DRIVER === "mysql2"
|
||||
? (mysqlSchema as unknown as Schema)
|
||||
: env.DB_DRIVER === "node-postgres"
|
||||
? (pgSchema as unknown as Schema)
|
||||
: sqliteSchema;
|
||||
|
||||
// Sadly we can't use export * from here as we have multiple possible exports
|
||||
export const {
|
||||
|
||||
@@ -528,6 +528,7 @@ export const userRelations = relations(users, ({ one, many }) => ({
|
||||
groups: many(groupMembers),
|
||||
ownedGroups: many(groups),
|
||||
invites: many(invites),
|
||||
medias: many(medias),
|
||||
defaultSearchEngine: one(searchEngines, {
|
||||
fields: [users.defaultSearchEngineId],
|
||||
references: [searchEngines.id],
|
||||
|
||||
775
packages/db/schema/postgresql.ts
Normal file
775
packages/db/schema/postgresql.ts
Normal file
@@ -0,0 +1,775 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import type { MantineSize } from "@mantine/core";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { relations } from "drizzle-orm";
|
||||
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
boolean,
|
||||
customType,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
smallint,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import {
|
||||
backgroundImageAttachments,
|
||||
backgroundImageRepeats,
|
||||
backgroundImageSizes,
|
||||
emptySuperJSON,
|
||||
} from "@homarr/definitions";
|
||||
import type {
|
||||
BackgroundImageAttachment,
|
||||
BackgroundImageRepeat,
|
||||
BackgroundImageSize,
|
||||
BoardPermission,
|
||||
ColorScheme,
|
||||
GroupPermissionKey,
|
||||
IntegrationKind,
|
||||
IntegrationPermission,
|
||||
IntegrationSecretKind,
|
||||
OnboardingStep,
|
||||
SearchEngineType,
|
||||
SectionKind,
|
||||
SupportedAuthProvider,
|
||||
WidgetKind,
|
||||
} from "@homarr/definitions";
|
||||
|
||||
const customBlob = customType<{ data: Buffer }>({
|
||||
dataType() {
|
||||
return "bytea";
|
||||
},
|
||||
});
|
||||
|
||||
export const apiKeys = pgTable("apiKey", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
apiKey: text().notNull(),
|
||||
salt: text().notNull(),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references((): AnyPgColumn => users.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const users = pgTable("user", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text(),
|
||||
email: text(),
|
||||
emailVerified: timestamp(),
|
||||
image: text(),
|
||||
password: text(),
|
||||
salt: text(),
|
||||
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
||||
homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
mobileHomeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
defaultSearchEngineId: varchar({ length: 64 }).references(() => searchEngines.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
openSearchInNewTab: boolean().default(false).notNull(),
|
||||
colorScheme: varchar({ length: 5 }).$type<ColorScheme>().default("dark").notNull(),
|
||||
firstDayOfWeek: smallint().$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||
pingIconsEnabled: boolean().default(false).notNull(),
|
||||
});
|
||||
|
||||
export const accounts = pgTable(
|
||||
"account",
|
||||
{
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text().$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: varchar({ length: 64 }).notNull(),
|
||||
providerAccountId: varchar({ length: 64 }).notNull(),
|
||||
refresh_token: text(),
|
||||
access_token: text(),
|
||||
expires_at: integer(),
|
||||
token_type: text(),
|
||||
scope: text(),
|
||||
id_token: text(),
|
||||
session_state: text(),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [account.provider, account.providerAccountId],
|
||||
}),
|
||||
userIdIdx: index("userId_idx").on(account.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sessions = pgTable(
|
||||
"session",
|
||||
{
|
||||
sessionToken: varchar({ length: 512 }).notNull().primaryKey(),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp().notNull(),
|
||||
},
|
||||
(session) => ({
|
||||
userIdIdx: index("user_id_idx").on(session.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: varchar({ length: 64 }).notNull(),
|
||||
token: varchar({ length: 512 }).notNull(),
|
||||
expires: timestamp().notNull(),
|
||||
},
|
||||
(verificationToken) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [verificationToken.identifier, verificationToken.token],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const groupMembers = pgTable(
|
||||
"groupMember",
|
||||
{
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(groupMember) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [groupMember.groupId, groupMember.userId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const groups = pgTable("group", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 64 }).unique().notNull(),
|
||||
ownerId: varchar({ length: 64 }).references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
homeBoardId: varchar({ length: 64 }).references(() => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
mobileHomeBoardId: varchar({ length: 64 }).references(() => boards.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
position: smallint().notNull(),
|
||||
});
|
||||
|
||||
export const groupPermissions = pgTable("groupPermission", {
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: text().$type<GroupPermissionKey>().notNull(),
|
||||
});
|
||||
|
||||
export const invites = pgTable("invite", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
token: varchar({ length: 512 }).notNull().unique(),
|
||||
expirationDate: timestamp().notNull(),
|
||||
creatorId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const medias = pgTable("media", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 512 }).notNull(),
|
||||
content: customBlob().notNull(),
|
||||
contentType: text().notNull(),
|
||||
size: integer().notNull(),
|
||||
createdAt: timestamp({ mode: "date" }).notNull().defaultNow(),
|
||||
creatorId: varchar({ length: 64 }).references(() => users.id, { onDelete: "set null" }),
|
||||
});
|
||||
|
||||
export const integrations = pgTable(
|
||||
"integration",
|
||||
{
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text().notNull(),
|
||||
url: text().notNull(),
|
||||
kind: varchar({ length: 128 }).$type<IntegrationKind>().notNull(),
|
||||
},
|
||||
(integrations) => ({
|
||||
kindIdx: index("integration__kind_idx").on(integrations.kind),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationSecrets = pgTable(
|
||||
"integrationSecret",
|
||||
{
|
||||
kind: varchar({ length: 16 }).$type<IntegrationSecretKind>().notNull(),
|
||||
value: text().$type<`${string}.${string}`>().notNull(),
|
||||
updatedAt: timestamp()
|
||||
.$onUpdateFn(() => new Date())
|
||||
.notNull(),
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(integrationSecret) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [integrationSecret.integrationId, integrationSecret.kind],
|
||||
}),
|
||||
kindIdx: index("integration_secret__kind_idx").on(integrationSecret.kind),
|
||||
updatedAtIdx: index("integration_secret__updated_at_idx").on(integrationSecret.updatedAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationUserPermissions = pgTable(
|
||||
"integrationUserPermission",
|
||||
{
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<IntegrationPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.integrationId, table.userId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const integrationGroupPermissions = pgTable(
|
||||
"integrationGroupPermissions",
|
||||
{
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<IntegrationPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.integrationId, table.groupId, table.permission],
|
||||
name: "integration_group_permission__pk",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const boards = pgTable("board", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 256 }).unique().notNull(),
|
||||
isPublic: boolean().default(false).notNull(),
|
||||
creatorId: varchar({ length: 64 }).references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
pageTitle: text(),
|
||||
metaTitle: text(),
|
||||
logoImageUrl: text(),
|
||||
faviconImageUrl: text(),
|
||||
backgroundImageUrl: text(),
|
||||
backgroundImageAttachment: text()
|
||||
.$type<BackgroundImageAttachment>()
|
||||
.default(backgroundImageAttachments.defaultValue)
|
||||
.notNull(),
|
||||
backgroundImageRepeat: text().$type<BackgroundImageRepeat>().default(backgroundImageRepeats.defaultValue).notNull(),
|
||||
backgroundImageSize: text().$type<BackgroundImageSize>().default(backgroundImageSizes.defaultValue).notNull(),
|
||||
primaryColor: text().default("#fa5252").notNull(),
|
||||
secondaryColor: text().default("#fd7e14").notNull(),
|
||||
opacity: integer().default(100).notNull(),
|
||||
customCss: text(),
|
||||
iconColor: text(),
|
||||
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
||||
disableStatus: boolean().default(false).notNull(),
|
||||
});
|
||||
|
||||
export const boardUserPermissions = pgTable(
|
||||
"boardUserPermission",
|
||||
{
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<BoardPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.boardId, table.userId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const boardGroupPermissions = pgTable(
|
||||
"boardGroupPermission",
|
||||
{
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
groupId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => groups.id, { onDelete: "cascade" }),
|
||||
permission: varchar({ length: 128 }).$type<BoardPermission>().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.boardId, table.groupId, table.permission],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const layouts = pgTable("layout", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
columnCount: smallint().notNull(),
|
||||
breakpoint: smallint().notNull().default(0),
|
||||
});
|
||||
|
||||
export const itemLayouts = pgTable(
|
||||
"item_layout",
|
||||
{
|
||||
itemId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => items.id, { onDelete: "cascade" }),
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
layoutId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => layouts.id, { onDelete: "cascade" }),
|
||||
xOffset: integer().notNull(),
|
||||
yOffset: integer().notNull(),
|
||||
width: integer().notNull(),
|
||||
height: integer().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.itemId, table.sectionId, table.layoutId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sectionLayouts = pgTable(
|
||||
"section_layout",
|
||||
{
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
layoutId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => layouts.id, { onDelete: "cascade" }),
|
||||
parentSectionId: varchar({ length: 64 }).references((): AnyPgColumn => sections.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
xOffset: integer().notNull(),
|
||||
yOffset: integer().notNull(),
|
||||
width: integer().notNull(),
|
||||
height: integer().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.sectionId, table.layoutId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sections = pgTable("section", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
kind: text().$type<SectionKind>().notNull(),
|
||||
xOffset: integer(),
|
||||
yOffset: integer(),
|
||||
name: text(),
|
||||
options: text().default(emptySuperJSON),
|
||||
});
|
||||
|
||||
export const sectionCollapseStates = pgTable(
|
||||
"section_collapse_state",
|
||||
{
|
||||
userId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
sectionId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => sections.id, { onDelete: "cascade" }),
|
||||
collapsed: boolean().default(false).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.userId, table.sectionId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const items = pgTable("item", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
boardId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => boards.id, { onDelete: "cascade" }),
|
||||
kind: text().$type<WidgetKind>().notNull(),
|
||||
options: text().default(emptySuperJSON).notNull(),
|
||||
advancedOptions: text().default(emptySuperJSON).notNull(),
|
||||
});
|
||||
|
||||
export const apps = pgTable("app", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: text().notNull(),
|
||||
description: text(),
|
||||
iconUrl: text().notNull(),
|
||||
href: text(),
|
||||
pingUrl: text(),
|
||||
});
|
||||
|
||||
export const integrationItems = pgTable(
|
||||
"integration_item",
|
||||
{
|
||||
itemId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => items.id, { onDelete: "cascade" }),
|
||||
integrationId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => integrations.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.itemId, table.integrationId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const icons = pgTable("icon", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
name: varchar({ length: 250 }).notNull(),
|
||||
url: text().notNull(),
|
||||
checksum: text().notNull(),
|
||||
iconRepositoryId: varchar({ length: 64 })
|
||||
.notNull()
|
||||
.references(() => iconRepositories.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const iconRepositories = pgTable("iconRepository", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
slug: varchar({ length: 150 }).notNull(),
|
||||
});
|
||||
|
||||
export const serverSettings = pgTable("serverSetting", {
|
||||
settingKey: varchar({ length: 64 }).notNull().unique().primaryKey(),
|
||||
value: text().default(emptySuperJSON).notNull(),
|
||||
});
|
||||
|
||||
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [apiKeys.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const searchEngines = pgTable("search_engine", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
iconUrl: text().notNull(),
|
||||
name: varchar({ length: 64 }).notNull(),
|
||||
short: varchar({ length: 8 }).unique().notNull(),
|
||||
description: text(),
|
||||
urlTemplate: text(),
|
||||
type: varchar({ length: 64 }).$type<SearchEngineType>().notNull().default("generic"),
|
||||
integrationId: varchar({ length: 64 }).references(() => integrations.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const onboarding = pgTable("onboarding", {
|
||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||
step: varchar({ length: 64 }).$type<OnboardingStep>().notNull(),
|
||||
previousStep: varchar({ length: 64 }).$type<OnboardingStep>(),
|
||||
});
|
||||
|
||||
export const trustedCertificateHostnames = pgTable(
|
||||
"trusted_certificate_hostname",
|
||||
{
|
||||
hostname: varchar({ length: 256 }).notNull(),
|
||||
thumbprint: varchar({ length: 128 }).notNull(),
|
||||
certificate: text().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
compoundKey: primaryKey({
|
||||
columns: [table.hostname, table.thumbprint],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const cronJobConfigurations = pgTable("cron_job_configuration", {
|
||||
name: varchar({ length: 256 }).notNull().primaryKey(),
|
||||
cronExpression: varchar({ length: 32 }).notNull(),
|
||||
isEnabled: boolean().default(true).notNull(),
|
||||
});
|
||||
|
||||
export const accountRelations = relations(accounts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [accounts.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const userRelations = relations(users, ({ one, many }) => ({
|
||||
accounts: many(accounts),
|
||||
boards: many(boards),
|
||||
boardPermissions: many(boardUserPermissions),
|
||||
groups: many(groupMembers),
|
||||
ownedGroups: many(groups),
|
||||
invites: many(invites),
|
||||
medias: many(medias),
|
||||
defaultSearchEngine: one(searchEngines, {
|
||||
fields: [users.defaultSearchEngineId],
|
||||
references: [searchEngines.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const mediaRelations = relations(medias, ({ one }) => ({
|
||||
creator: one(users, {
|
||||
fields: [medias.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const iconRelations = relations(icons, ({ one }) => ({
|
||||
repository: one(iconRepositories, {
|
||||
fields: [icons.iconRepositoryId],
|
||||
references: [iconRepositories.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const iconRepositoryRelations = relations(iconRepositories, ({ many }) => ({
|
||||
icons: many(icons),
|
||||
}));
|
||||
|
||||
export const inviteRelations = relations(invites, ({ one }) => ({
|
||||
creator: one(users, {
|
||||
fields: [invites.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sessionRelations = relations(sessions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [sessions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupMemberRelations = relations(groupMembers, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [groupMembers.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [groupMembers.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupRelations = relations(groups, ({ one, many }) => ({
|
||||
permissions: many(groupPermissions),
|
||||
boardPermissions: many(boardGroupPermissions),
|
||||
members: many(groupMembers),
|
||||
owner: one(users, {
|
||||
fields: [groups.ownerId],
|
||||
references: [users.id],
|
||||
}),
|
||||
homeBoard: one(boards, {
|
||||
fields: [groups.homeBoardId],
|
||||
references: [boards.id],
|
||||
relationName: "groupRelations__board__homeBoardId",
|
||||
}),
|
||||
mobileHomeBoard: one(boards, {
|
||||
fields: [groups.mobileHomeBoardId],
|
||||
references: [boards.id],
|
||||
relationName: "groupRelations__board__mobileHomeBoardId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const groupPermissionRelations = relations(groupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [groupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardUserPermissionRelations = relations(boardUserPermissions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [boardUserPermissions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [boardUserPermissions.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardGroupPermissionRelations = relations(boardGroupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [boardGroupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
board: one(boards, {
|
||||
fields: [boardGroupPermissions.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationRelations = relations(integrations, ({ many }) => ({
|
||||
secrets: many(integrationSecrets),
|
||||
items: many(integrationItems),
|
||||
userPermissions: many(integrationUserPermissions),
|
||||
groupPermissions: many(integrationGroupPermissions),
|
||||
}));
|
||||
|
||||
export const integrationUserPermissionRelations = relations(integrationUserPermissions, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [integrationUserPermissions.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
integration: one(integrations, {
|
||||
fields: [integrationUserPermissions.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationGroupPermissionRelations = relations(integrationGroupPermissions, ({ one }) => ({
|
||||
group: one(groups, {
|
||||
fields: [integrationGroupPermissions.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
integration: one(integrations, {
|
||||
fields: [integrationGroupPermissions.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationSecretRelations = relations(integrationSecrets, ({ one }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [integrationSecrets.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const boardRelations = relations(boards, ({ many, one }) => ({
|
||||
sections: many(sections),
|
||||
items: many(items),
|
||||
creator: one(users, {
|
||||
fields: [boards.creatorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
userPermissions: many(boardUserPermissions),
|
||||
groupPermissions: many(boardGroupPermissions),
|
||||
layouts: many(layouts),
|
||||
groupHomes: many(groups, {
|
||||
relationName: "groupRelations__board__homeBoardId",
|
||||
}),
|
||||
mobileHomeBoard: many(groups, {
|
||||
relationName: "groupRelations__board__mobileHomeBoardId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionRelations = relations(sections, ({ many, one }) => ({
|
||||
board: one(boards, {
|
||||
fields: [sections.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
collapseStates: many(sectionCollapseStates),
|
||||
layouts: many(sectionLayouts, {
|
||||
relationName: "sectionLayoutRelations__section__sectionId",
|
||||
}),
|
||||
children: many(sectionLayouts, {
|
||||
relationName: "sectionLayoutRelations__section__parentSectionId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionCollapseStateRelations = relations(sectionCollapseStates, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [sectionCollapseStates.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
section: one(sections, {
|
||||
fields: [sectionCollapseStates.sectionId],
|
||||
references: [sections.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const itemRelations = relations(items, ({ one, many }) => ({
|
||||
integrations: many(integrationItems),
|
||||
layouts: many(itemLayouts),
|
||||
board: one(boards, {
|
||||
fields: [items.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const integrationItemRelations = relations(integrationItems, ({ one }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [integrationItems.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
item: one(items, {
|
||||
fields: [integrationItems.itemId],
|
||||
references: [items.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const searchEngineRelations = relations(searchEngines, ({ one, many }) => ({
|
||||
integration: one(integrations, {
|
||||
fields: [searchEngines.integrationId],
|
||||
references: [integrations.id],
|
||||
}),
|
||||
usersWithDefault: many(users),
|
||||
}));
|
||||
|
||||
export const itemLayoutRelations = relations(itemLayouts, ({ one }) => ({
|
||||
item: one(items, {
|
||||
fields: [itemLayouts.itemId],
|
||||
references: [items.id],
|
||||
}),
|
||||
section: one(sections, {
|
||||
fields: [itemLayouts.sectionId],
|
||||
references: [sections.id],
|
||||
}),
|
||||
layout: one(layouts, {
|
||||
fields: [itemLayouts.layoutId],
|
||||
references: [layouts.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const sectionLayoutRelations = relations(sectionLayouts, ({ one }) => ({
|
||||
section: one(sections, {
|
||||
fields: [sectionLayouts.sectionId],
|
||||
references: [sections.id],
|
||||
relationName: "sectionLayoutRelations__section__sectionId",
|
||||
}),
|
||||
layout: one(layouts, {
|
||||
fields: [sectionLayouts.layoutId],
|
||||
references: [layouts.id],
|
||||
}),
|
||||
parentSection: one(sections, {
|
||||
fields: [sectionLayouts.parentSectionId],
|
||||
references: [sections.id],
|
||||
relationName: "sectionLayoutRelations__section__parentSectionId",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const layoutRelations = relations(layouts, ({ one, many }) => ({
|
||||
items: many(itemLayouts),
|
||||
sections: many(sectionLayouts),
|
||||
board: one(boards, {
|
||||
fields: [layouts.boardId],
|
||||
references: [boards.id],
|
||||
}),
|
||||
}));
|
||||
46
packages/db/test/postgresql-migration.spec.ts
Normal file
46
packages/db/test/postgresql-migration.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import path from "path";
|
||||
import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||
import { Pool } from "pg";
|
||||
import { describe, test } from "vitest";
|
||||
|
||||
import * as pgSchema from "../schema/postgresql";
|
||||
|
||||
describe("PostgreSql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
const container = new PostgreSqlContainer("postgres:latest");
|
||||
const postgreSqlContainer = await container.start();
|
||||
|
||||
const pool = new Pool({
|
||||
user: postgreSqlContainer.getUsername(),
|
||||
database: postgreSqlContainer.getDatabase(),
|
||||
password: postgreSqlContainer.getPassword(),
|
||||
port: postgreSqlContainer.getPort(),
|
||||
host: postgreSqlContainer.getHost(),
|
||||
keepAlive: true,
|
||||
max: 0,
|
||||
idleTimeoutMillis: 60000,
|
||||
allowExitOnIdle: false,
|
||||
});
|
||||
|
||||
const database = drizzle({
|
||||
schema: pgSchema,
|
||||
casing: "snake_case",
|
||||
client: pool,
|
||||
});
|
||||
|
||||
// Run migrations and check if it works
|
||||
await migrate(database, {
|
||||
migrationsFolder: path.join(__dirname, "..", "migrations", "postgresql"),
|
||||
});
|
||||
|
||||
// Check if users table exists
|
||||
await database.query.users.findMany();
|
||||
|
||||
// Close the pool to release resources
|
||||
await pool.end();
|
||||
// Stop the container
|
||||
await postgreSqlContainer.stop();
|
||||
}, 40_000);
|
||||
});
|
||||
@@ -1,15 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { Column, InferSelectModel } from "drizzle-orm";
|
||||
import type { ForeignKey as MysqlForeignKey, MySqlTableWithColumns } from "drizzle-orm/mysql-core";
|
||||
import type { PgTableWithColumns, ForeignKey as PostgresqlForeignKey } from "drizzle-orm/pg-core";
|
||||
import type { ForeignKey as SqliteForeignKey, SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
|
||||
import { expect, expectTypeOf, test } from "vitest";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import * as mysqlSchema from "../schema/mysql";
|
||||
import * as postgresqlSchema from "../schema/postgresql";
|
||||
import * as sqliteSchema from "../schema/sqlite";
|
||||
|
||||
// We need the following two types as there is currently no support for Buffer in mysql and
|
||||
// We need the following three types as there is currently no support for Buffer in mysql & pg and
|
||||
// so we use a custom type which results in the config beeing different
|
||||
type FixedMysqlConfig = {
|
||||
[key in keyof MysqlConfig]: {
|
||||
@@ -22,6 +24,22 @@ type FixedMysqlConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
type FixedPostgresqlConfig = {
|
||||
[key in keyof PostgreisqlConfig]: {
|
||||
[column in keyof PostgreisqlConfig[key]]: {
|
||||
[property in Exclude<
|
||||
keyof PostgreisqlConfig[key][column],
|
||||
"dataType" | "data"
|
||||
>]: PostgreisqlConfig[key][column][property];
|
||||
} & {
|
||||
dataType: PostgreisqlConfig[key][column]["data"] extends Buffer
|
||||
? "buffer"
|
||||
: PostgreisqlConfig[key][column]["dataType"];
|
||||
data: PostgreisqlConfig[key][column]["data"] extends Buffer ? Buffer : PostgreisqlConfig[key][column]["data"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type FixedSqliteConfig = {
|
||||
[key in keyof SqliteConfig]: {
|
||||
[column in keyof SqliteConfig[key]]: {
|
||||
@@ -117,6 +135,91 @@ test("schemas should match", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("schemas should match for postgresql", () => {
|
||||
expectTypeOf<SqliteTables>().toEqualTypeOf<PostgresqlTables>();
|
||||
expectTypeOf<PostgresqlTables>().toEqualTypeOf<SqliteTables>();
|
||||
expectTypeOf<FixedSqliteConfig>().toEqualTypeOf<FixedPostgresqlConfig>();
|
||||
expectTypeOf<FixedPostgresqlConfig>().toEqualTypeOf<FixedSqliteConfig>();
|
||||
|
||||
objectEntries(sqliteSchema).forEach(([tableName, sqliteTable]) => {
|
||||
// keys of sqliteSchema and postgresqlSchema are the same, so we can safely use tableName as key
|
||||
// skipcq: JS-E1007
|
||||
const postgresqlTable = postgresqlSchema[tableName];
|
||||
Object.entries(sqliteTable).forEach(([columnName, sqliteColumn]: [string, object]) => {
|
||||
if (!("isUnique" in sqliteColumn)) return;
|
||||
if (!("uniqueName" in sqliteColumn)) return;
|
||||
if (!("primary" in sqliteColumn)) return;
|
||||
|
||||
const postgresqlColumn = postgresqlTable[columnName as keyof typeof postgresqlTable] as object;
|
||||
if (!("isUnique" in postgresqlColumn)) return;
|
||||
if (!("uniqueName" in postgresqlColumn)) return;
|
||||
if (!("primary" in postgresqlColumn)) return;
|
||||
|
||||
expect(
|
||||
sqliteColumn.isUnique,
|
||||
`expect unique of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.isUnique);
|
||||
expect(
|
||||
sqliteColumn.uniqueName,
|
||||
`expect uniqueName of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.uniqueName);
|
||||
expect(
|
||||
sqliteColumn.primary,
|
||||
`expect primary of column ${columnName} in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlColumn.primary);
|
||||
});
|
||||
|
||||
const sqliteForeignKeys = sqliteTable[Symbol.for("drizzle:SQLiteInlineForeignKeys") as keyof typeof sqliteTable] as
|
||||
| SqliteForeignKey[]
|
||||
| undefined;
|
||||
const postgresqlForeignKeys = postgresqlTable[
|
||||
Symbol.for("drizzle:PgInlineForeignKeys") as keyof typeof postgresqlTable
|
||||
] as PostgresqlForeignKey[] | undefined;
|
||||
if (!sqliteForeignKeys && !postgresqlForeignKeys) return;
|
||||
|
||||
expect(postgresqlForeignKeys, `postgresql foreign key for ${tableName} to be defined`).toBeDefined();
|
||||
expect(sqliteForeignKeys, `sqlite foreign key for ${tableName} to be defined`).toBeDefined();
|
||||
|
||||
expect(
|
||||
sqliteForeignKeys!.length,
|
||||
`expect number of foreign keys in table ${tableName} to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKeys?.length);
|
||||
|
||||
sqliteForeignKeys?.forEach((sqliteForeignKey) => {
|
||||
sqliteForeignKey.getName();
|
||||
const postgresqlForeignKey = postgresqlForeignKeys!.find((key) => key.getName() === sqliteForeignKey.getName());
|
||||
expect(
|
||||
postgresqlForeignKey,
|
||||
`expect foreign key ${sqliteForeignKey.getName()} to be defined in postgresql schema`,
|
||||
).toBeDefined();
|
||||
|
||||
// In PostgreSql, onDelete is "no action" by default, so it is treated as undefined to match Sqlite.
|
||||
expect(
|
||||
sqliteForeignKey.onDelete,
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) onDelete to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKey!.onDelete === "no action" ? undefined : postgresqlForeignKey!.onDelete);
|
||||
|
||||
// In PostgreSql, onUpdate is "no action" by default, so it is treated as undefined to match Sqlite.
|
||||
expect(
|
||||
sqliteForeignKey.onUpdate,
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) onUpdate to be the same for both schemas`,
|
||||
).toEqual(postgresqlForeignKey!.onUpdate === "no action" ? undefined : postgresqlForeignKey!.onUpdate);
|
||||
|
||||
sqliteForeignKey.reference().foreignColumns.forEach((column) => {
|
||||
expect(
|
||||
postgresqlForeignKey!.reference().foreignColumns.map((column) => column.name),
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) columns to be the same for both schemas`,
|
||||
).toContainEqual(column.name);
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.keys(sqliteForeignKey.reference().foreignTable),
|
||||
`expect foreign key (${sqliteForeignKey.getName()}) table to be the same for both schemas`,
|
||||
).toEqual(Object.keys(postgresqlForeignKey!.reference().foreignTable).filter((key) => key !== "enableRLS"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type SqliteTables = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof sqliteSchema]: (typeof sqliteSchema)[K] extends SQLiteTableWithColumns<any>
|
||||
@@ -130,6 +233,13 @@ type MysqlTables = {
|
||||
: never;
|
||||
};
|
||||
|
||||
type PostgresqlTables = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof postgresqlSchema]: (typeof postgresqlSchema)[K] extends PgTableWithColumns<any>
|
||||
? InferSelectModel<(typeof postgresqlSchema)[K]>
|
||||
: never;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type InferColumnConfig<T extends Column<any, object>> =
|
||||
T extends Column<infer C, object> ? Omit<C, "columnType" | "enumValues" | "driverParam"> : never;
|
||||
@@ -155,3 +265,14 @@ type MysqlConfig = {
|
||||
}
|
||||
: never;
|
||||
};
|
||||
|
||||
type PostgreisqlConfig = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[K in keyof typeof postgresqlSchema]: (typeof postgresqlSchema)[K] extends PgTableWithColumns<any>
|
||||
? {
|
||||
[C in keyof (typeof postgresqlSchema)[K]["_"]["config"]["columns"]]: InferColumnConfig<
|
||||
(typeof postgresqlSchema)[K]["_"]["config"]["columns"][C]
|
||||
>;
|
||||
}
|
||||
: never;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { isMysql, isPostgresql } from "./collection";
|
||||
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
|
||||
import { env } from "./env";
|
||||
import * as mysqlSchema from "./schema/mysql";
|
||||
|
||||
type MysqlSchema = typeof mysqlSchema;
|
||||
import type { MySqlSchema } from "./schema";
|
||||
import * as schema from "./schema";
|
||||
|
||||
interface HandleTransactionInput {
|
||||
handleAsync: (db: HomarrDatabaseMysql, schema: MysqlSchema) => Promise<void>;
|
||||
handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>;
|
||||
handleSync: (db: HomarrDatabase) => void;
|
||||
}
|
||||
|
||||
@@ -15,10 +14,10 @@ interface HandleTransactionInput {
|
||||
* But it can also generally be used when dealing with different database drivers.
|
||||
*/
|
||||
export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => {
|
||||
if (env.DB_DRIVER !== "mysql2") {
|
||||
if (isMysql() || isPostgresql()) {
|
||||
// Schema type is always the correct one based on env variables
|
||||
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
|
||||
} else {
|
||||
input.handleSync(db);
|
||||
return;
|
||||
}
|
||||
|
||||
await input.handleAsync(db as unknown as HomarrDatabaseMysql, mysqlSchema);
|
||||
};
|
||||
|
||||
135
pnpm-lock.yaml
generated
135
pnpm-lock.yaml
generated
@@ -1140,6 +1140,9 @@ importers:
|
||||
'@testcontainers/mysql':
|
||||
specifier: ^11.5.1
|
||||
version: 11.5.1
|
||||
'@testcontainers/postgresql':
|
||||
specifier: ^11.4.0
|
||||
version: 11.4.0
|
||||
better-sqlite3:
|
||||
specifier: ^12.2.0
|
||||
version: 12.2.0
|
||||
@@ -1151,13 +1154,16 @@ importers:
|
||||
version: 0.31.4
|
||||
drizzle-orm:
|
||||
specifier: ^0.44.4
|
||||
version: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)
|
||||
version: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.4)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)(pg@8.16.3)
|
||||
drizzle-zod:
|
||||
specifier: ^0.8.3
|
||||
version: 0.8.3(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@4.0.17)
|
||||
version: 0.8.3(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.4)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)(pg@8.16.3))(zod@4.0.17)
|
||||
mysql2:
|
||||
specifier: 3.14.3
|
||||
version: 3.14.3
|
||||
pg:
|
||||
specifier: ^8.16.3
|
||||
version: 8.16.3
|
||||
superjson:
|
||||
specifier: 2.2.2
|
||||
version: 2.2.2
|
||||
@@ -1174,6 +1180,9 @@ importers:
|
||||
'@types/better-sqlite3':
|
||||
specifier: 7.6.13
|
||||
version: 7.6.13
|
||||
'@types/pg':
|
||||
specifier: ^8.15.4
|
||||
version: 8.15.4
|
||||
dotenv-cli:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
@@ -4111,6 +4120,9 @@ packages:
|
||||
'@testcontainers/mysql@11.5.1':
|
||||
resolution: {integrity: sha512-znonzVMlcCLQ6t7zDH0TOEcbGkz6iwIk5x5ZP5GqEdN0z3GLUu/jEF7yQUtfCY+PbVnCkigRg4WYS4bt6+zhyA==}
|
||||
|
||||
'@testcontainers/postgresql@11.4.0':
|
||||
resolution: {integrity: sha512-WiKsz3Np5twNZGp2kgatqGaE/KqNR271CPwvIgAvFyN7E581P34glQljM4iLfxdv1XpzVYGWRO6PbQAVDbehBQ==}
|
||||
|
||||
'@testcontainers/redis@11.5.1':
|
||||
resolution: {integrity: sha512-ThGaUPUCFW4Vwmx6kfPYhhTQjq/3UXJQrU/xxiYLqgvFJNtvtYlWmzXrwORLhPkkqnoFUnfFaX3u9u1GnrlDkw==}
|
||||
|
||||
@@ -4571,6 +4583,9 @@ packages:
|
||||
'@types/normalize-package-data@2.4.4':
|
||||
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
|
||||
|
||||
'@types/pg@8.15.4':
|
||||
resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==}
|
||||
|
||||
'@types/prismjs@1.26.5':
|
||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||
|
||||
@@ -8469,6 +8484,40 @@ packages:
|
||||
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
pg-cloudflare@1.2.7:
|
||||
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
|
||||
|
||||
pg-connection-string@2.9.1:
|
||||
resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==}
|
||||
|
||||
pg-int8@1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
pg-pool@3.10.1:
|
||||
resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==}
|
||||
peerDependencies:
|
||||
pg: '>=8.0'
|
||||
|
||||
pg-protocol@1.10.3:
|
||||
resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==}
|
||||
|
||||
pg-types@2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
pg@8.16.3:
|
||||
resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
|
||||
pgpass@1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||
|
||||
picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
@@ -8575,6 +8624,22 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
postgres-bytea@1.0.0:
|
||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
postgres-date@1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
postgres-interval@1.2.0:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
preact-render-to-string@6.5.11:
|
||||
resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==}
|
||||
peerDependencies:
|
||||
@@ -12652,6 +12717,13 @@ snapshots:
|
||||
- bare-buffer
|
||||
- supports-color
|
||||
|
||||
'@testcontainers/postgresql@11.4.0':
|
||||
dependencies:
|
||||
testcontainers: 11.5.1
|
||||
transitivePeerDependencies:
|
||||
- bare-buffer
|
||||
- supports-color
|
||||
|
||||
'@testcontainers/redis@11.5.1':
|
||||
dependencies:
|
||||
testcontainers: 11.5.1
|
||||
@@ -13166,6 +13238,12 @@ snapshots:
|
||||
|
||||
'@types/normalize-package-data@2.4.4': {}
|
||||
|
||||
'@types/pg@8.15.4':
|
||||
dependencies:
|
||||
'@types/node': 22.17.2
|
||||
pg-protocol: 1.10.3
|
||||
pg-types: 2.2.0
|
||||
|
||||
'@types/prismjs@1.26.5': {}
|
||||
|
||||
'@types/qs@6.9.16': {}
|
||||
@@ -14683,17 +14761,19 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3):
|
||||
drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.4)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)(pg@8.16.3):
|
||||
optionalDependencies:
|
||||
'@libsql/client-wasm': 0.14.0
|
||||
'@types/better-sqlite3': 7.6.13
|
||||
'@types/pg': 8.15.4
|
||||
better-sqlite3: 12.2.0
|
||||
gel: 2.0.0
|
||||
mysql2: 3.14.3
|
||||
pg: 8.16.3
|
||||
|
||||
drizzle-zod@0.8.3(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@4.0.17):
|
||||
drizzle-zod@0.8.3(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.4)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)(pg@8.16.3))(zod@4.0.17):
|
||||
dependencies:
|
||||
drizzle-orm: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)
|
||||
drizzle-orm: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.4)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3)(pg@8.16.3)
|
||||
zod: 4.0.17
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
@@ -17576,6 +17656,41 @@ snapshots:
|
||||
|
||||
pathval@2.0.0: {}
|
||||
|
||||
pg-cloudflare@1.2.7:
|
||||
optional: true
|
||||
|
||||
pg-connection-string@2.9.1: {}
|
||||
|
||||
pg-int8@1.0.1: {}
|
||||
|
||||
pg-pool@3.10.1(pg@8.16.3):
|
||||
dependencies:
|
||||
pg: 8.16.3
|
||||
|
||||
pg-protocol@1.10.3: {}
|
||||
|
||||
pg-types@2.2.0:
|
||||
dependencies:
|
||||
pg-int8: 1.0.1
|
||||
postgres-array: 2.0.0
|
||||
postgres-bytea: 1.0.0
|
||||
postgres-date: 1.0.7
|
||||
postgres-interval: 1.2.0
|
||||
|
||||
pg@8.16.3:
|
||||
dependencies:
|
||||
pg-connection-string: 2.9.1
|
||||
pg-pool: 3.10.1(pg@8.16.3)
|
||||
pg-protocol: 1.10.3
|
||||
pg-types: 2.2.0
|
||||
pgpass: 1.0.5
|
||||
optionalDependencies:
|
||||
pg-cloudflare: 1.2.7
|
||||
|
||||
pgpass@1.0.5:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
||||
picocolors@1.0.1: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -17684,6 +17799,16 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postgres-array@2.0.0: {}
|
||||
|
||||
postgres-bytea@1.0.0: {}
|
||||
|
||||
postgres-date@1.0.7: {}
|
||||
|
||||
postgres-interval@1.2.0:
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
|
||||
preact-render-to-string@6.5.11(preact@10.24.3):
|
||||
dependencies:
|
||||
preact: 10.24.3
|
||||
|
||||
Reference in New Issue
Block a user