IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

schema.ts

Buka raw
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()),
  ],
);
byte
10.812
baris
278
Jenis
ts
Commit terakhir
16 Oktober 2026