homeio
PublikFork dari doctor-io/homeio
schema.ts
import {
boolean,
index,
integer,
jsonb,
pgTable,
text,
timestamp,
uniqueIndex,
} from "drizzle-orm/pg-core";
export const apps = pgTable(
"apps",
{
id: text("id").primaryKey(),
name: text("name").notNull(),
status: text("status").notNull().default("unknown"),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index("apps_name_idx").on(table.name)],
);
export const users = pgTable(
"users",
{
id: text("id").primaryKey(),
username: text("username").notNull().unique(),
passwordHash: text("password_hash").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
totpSecret: text("totp_secret"),
totpEnabled: boolean("totp_enabled").notNull().default(false),
totpBackupCodes: text("totp_backup_codes"),
totpEnrolledAt: timestamp("totp_enrolled_at", { withTimezone: true }),
},
(table) => [index("users_username_idx").on(table.username)],
);
export const sessions = pgTable(
"sessions",
{
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
index("sessions_user_id_idx").on(table.userId),
index("sessions_expires_at_idx").on(table.expiresAt),
],
);
export const appStacks = pgTable(
"app_stacks",
{
appId: text("app_id").primaryKey(),
templateName: text("template_name").notNull(),
stackName: text("stack_name").notNull(),
composePath: text("compose_path").notNull(),
status: text("status").notNull().default("not_installed"),
webUiPort: integer("web_ui_port"),
webUiUrl: text("web_ui_url"),
/** Subdomain this app is published under when the tunnel is enabled. */
tunnelSubdomain: text("tunnel_subdomain"),
envJson: jsonb("env_json").notNull().default({}),
displayName: text("display_name"),
iconUrl: text("icon_url"),
installedAt: timestamp("installed_at", { withTimezone: true }),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
isUpToDate: boolean("is_up_to_date").default(true),
lastUpdateCheck: timestamp("last_update_check", { withTimezone: true }),
localDigest: text("local_digest"),
remoteDigest: text("remote_digest"),
},
(table) => [
index("app_stacks_status_idx").on(table.status),
index("app_stacks_web_ui_port_idx").on(table.webUiPort),
],
);
export const appOperations = pgTable(
"app_operations",
{
id: text("id").primaryKey(),
appId: text("app_id").notNull(),
action: text("action").notNull(),
status: text("status").notNull(),
progressPercent: integer("progress_percent").notNull().default(0),
currentStep: text("current_step").notNull().default("queued"),
errorMessage: text("error_message"),
startedAt: timestamp("started_at", { withTimezone: true }),
finishedAt: timestamp("finished_at", { withTimezone: true }),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
index("app_operations_app_id_idx").on(table.appId),
index("app_operations_status_idx").on(table.status),
index("app_operations_updated_at_idx").on(table.updatedAt.desc()),
],
);
export const customStoreApps = pgTable(
"custom_store_apps",
{
appId: text("app_id").primaryKey(),
name: text("name").notNull(),
iconUrl: text("icon_url"),
webUiUrl: text("web_ui_url"),
sourceType: text("source_type").notNull(),
sourceText: text("source_text").notNull(),
composeContent: text("compose_content").notNull(),
repositoryUrl: text("repository_url"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index("custom_store_apps_updated_at_idx").on(table.updatedAt.desc())],
);
/**
* Name, icon and link an operator set for a container Homeio did not deploy.
* Keyed by container name: the id changes every time the container is recreated.
*/
export const containerCustomizations = pgTable("container_customizations", {
containerName: text("container_name").primaryKey(),
displayName: text("display_name"),
iconUrl: text("icon_url"),
dashboardUrl: text("dashboard_url"),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
export const filesNetworkShares = pgTable(
"files_network_shares",
{
id: text("id").primaryKey(),
host: text("host").notNull(),
share: text("share").notNull(),
username: text("username").notNull(),
passwordCiphertext: text("password_ciphertext").notNull(),
passwordIv: text("password_iv").notNull(),
passwordTag: text("password_tag").notNull(),
mountPath: text("mount_path").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("files_network_shares_mount_path_idx").on(table.mountPath),
index("files_network_shares_created_at_idx").on(table.createdAt),
],
);
export const filesLocalShares = pgTable(
"files_local_shares",
{
id: text("id").primaryKey(),
shareName: text("share_name").notNull(),
sourcePath: text("source_path").notNull(),
sharedPath: text("shared_path").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("files_local_shares_share_name_idx").on(table.shareName),
uniqueIndex("files_local_shares_source_path_idx").on(table.sourcePath),
uniqueIndex("files_local_shares_shared_path_idx").on(table.sharedPath),
index("files_local_shares_created_at_idx").on(table.createdAt),
],
);
export const notifications = pgTable(
"notifications",
{
id: text("id").primaryKey(),
title: text("title").notNull(),
body: text("body").notNull(),
kind: text("kind").notNull().default("info"),
read: boolean("read").notNull().default(false),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index("notifications_created_at_idx").on(table.createdAt.desc())],
);
export const scheduledTasks = pgTable(
"scheduled_tasks",
{
id: text("id").primaryKey(),
label: text("label").notNull(),
taskType: text("task_type").notNull(),
taskConfig: jsonb("task_config").notNull().default({}),
cronExpression: text("cron_expression").notNull(),
enabled: boolean("enabled").notNull().default(true),
lastRunAt: timestamp("last_run_at", { withTimezone: true }),
lastRunStatus: text("last_run_status"),
lastRunOutput: text("last_run_output"),
nextRunAt: timestamp("next_run_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index("scheduled_tasks_next_run_at_idx").on(table.nextRunAt)],
);
export const scheduledTaskExecutions = pgTable(
"scheduled_task_executions",
{
id: text("id").primaryKey(),
taskId: text("task_id").notNull().references(() => scheduledTasks.id, { onDelete: "cascade" }),
status: text("status").notNull(),
output: text("output"),
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
durationMs: integer("duration_ms"),
},
(table) => [
index("scheduled_task_executions_task_id_idx").on(table.taskId),
index("scheduled_task_executions_started_at_idx").on(table.startedAt.desc()),
],
);
export const settings = pgTable("settings", {
id: text("id").primaryKey().default("singleton"),
appearanceJson: jsonb("appearance_json").notNull().default({}),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
googleClientId: text("google_client_id"),
googleClientSecretCiphertext: text("google_client_secret_ciphertext"),
googleClientSecretIv: text("google_client_secret_iv"),
googleClientSecretTag: text("google_client_secret_tag"),
googleRedirectUri: text("google_redirect_uri"),
tailscaleTailnet: text("tailscale_tailnet"),
tailscaleApiKeyCiphertext: text("tailscale_api_key_ciphertext"),
tailscaleApiKeyIv: text("tailscale_api_key_iv"),
tailscaleApiKeyTag: text("tailscale_api_key_tag"),
cloudflareTunnelEnabled: boolean("cloudflare_tunnel_enabled").notNull().default(false),
cloudflareTunnelDomain: text("cloudflare_tunnel_domain"),
cloudflareTunnelTokenCiphertext: text("cloudflare_tunnel_token_ciphertext"),
cloudflareTunnelTokenIv: text("cloudflare_tunnel_token_iv"),
cloudflareTunnelTokenTag: text("cloudflare_tunnel_token_tag"),
cloudflareApiTokenCiphertext: text("cloudflare_api_token_ciphertext"),
cloudflareApiTokenIv: text("cloudflare_api_token_iv"),
cloudflareApiTokenTag: text("cloudflare_api_token_tag"),
telemetryEnabled: boolean("telemetry_enabled").notNull().default(true),
telemetryInstanceId: text("telemetry_instance_id"),
});
export const filesGoogleDriveTokens = pgTable(
"files_google_drive_tokens",
{
id: text("id").primaryKey(),
email: text("email").notNull(),
displayName: text("display_name"),
accessTokenCiphertext: text("access_token_ciphertext").notNull(),
accessTokenIv: text("access_token_iv").notNull(),
accessTokenTag: text("access_token_tag").notNull(),
refreshTokenCiphertext: text("refresh_token_ciphertext"),
refreshTokenIv: text("refresh_token_iv"),
refreshTokenTag: text("refresh_token_tag"),
expiresAt: timestamp("expires_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("files_google_drive_tokens_email_idx").on(table.email),
index("files_google_drive_tokens_created_at_idx").on(table.createdAt),
],
);
export const filesTrashEntries = pgTable(
"files_trash_entries",
{
id: text("id").primaryKey(),
trashPath: text("trash_path").notNull(),
originalPath: text("original_path").notNull(),
deletedAt: timestamp("deleted_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("files_trash_entries_trash_path_idx").on(table.trashPath),
index("files_trash_entries_deleted_at_idx").on(table.deletedAt.desc()),
],
);