Files
zerobyte/app/server/db/schema.ts
Nico d9b150b34e feat: add 2FA (TOTP) support (#326)
* feat: setup better-auth with 2fa

* feat(totp): frontend

* refactor: split dialogs into components

* feat: disable 2fa cli

* chore: fix liniting issues

* chore(deps): bump the minor-patch group across 1 directory with 19 updates (#327)

* chore(deps): bump the minor-patch group across 1 directory with 19 updates

Bumps the minor-patch group with 19 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node) | `7.11.0` | `7.12.0` |
| [@react-router/serve](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-serve) | `7.11.0` | `7.12.0` |
| [@scalar/hono-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/hono) | `0.9.30` | `0.9.32` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.90.12` | `5.90.16` |
| [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.44.7` | `0.45.1` |
| [hono](https://github.com/honojs/hono) | `4.10.5` | `4.11.3` |
| [hono-rate-limiter](https://github.com/rhinobase/hono-rate-limiter) | `0.5.1` | `0.5.3` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.555.0` | `0.562.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.69.0` | `7.70.0` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.11.0` | `7.12.0` |
| [recharts](https://github.com/recharts/recharts) | `3.5.1` | `3.6.0` |
| [@faker-js/faker](https://github.com/faker-js/faker) | `10.1.0` | `10.2.0` |
| [@happy-dom/global-registrator](https://github.com/capricorn86/happy-dom) | `20.0.11` | `20.1.0` |
| [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) | `0.88.2` | `0.90.2` |
| [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) | `7.11.0` | `7.12.0` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.91.1` | `5.91.2` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.22.0` | `0.23.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.36.0` | `1.38.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.0` | `7.3.1` |

Updates `@react-router/node` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-node/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/node@7.12.0/packages/react-router-node)

Updates `@react-router/serve` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-serve/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/serve@7.12.0/packages/react-router-serve)

Updates `@scalar/hono-api-reference` from 0.9.30 to 0.9.32
- [Release notes](https://github.com/scalar/scalar/releases)
- [Changelog](https://github.com/scalar/scalar/blob/main/integrations/hono/CHANGELOG.md)
- [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/hono)

Updates `@tanstack/react-query` from 5.90.12 to 5.90.16
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.16/packages/react-query)

Updates `drizzle-orm` from 0.44.7 to 0.45.1
- [Release notes](https://github.com/drizzle-team/drizzle-orm/releases)
- [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.44.7...0.45.1)

Updates `hono` from 4.10.5 to 4.11.3
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.10.5...v4.11.3)

Updates `hono-rate-limiter` from 0.5.1 to 0.5.3
- [Release notes](https://github.com/rhinobase/hono-rate-limiter/releases)
- [Commits](https://github.com/rhinobase/hono-rate-limiter/compare/v0.5.1...v0.5.3)

Updates `lucide-react` from 0.555.0 to 0.562.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.562.0/packages/lucide-react)

Updates `react-hook-form` from 7.69.0 to 7.70.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.69.0...v7.70.0)

Updates `react-router` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.12.0/packages/react-router)

Updates `recharts` from 3.5.1 to 3.6.0
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.5.1...v3.6.0)

Updates `@faker-js/faker` from 10.1.0 to 10.2.0
- [Release notes](https://github.com/faker-js/faker/releases)
- [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md)
- [Commits](https://github.com/faker-js/faker/compare/v10.1.0...v10.2.0)

Updates `@happy-dom/global-registrator` from 20.0.11 to 20.1.0
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v20.0.11...v20.1.0)

Updates `@hey-api/openapi-ts` from 0.88.2 to 0.90.2
- [Release notes](https://github.com/hey-api/openapi-ts/releases)
- [Changelog](https://github.com/hey-api/openapi-ts/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/hey-api/openapi-ts/compare/@hey-api/openapi-ts@0.88.2...@hey-api/openapi-ts@0.90.2)

Updates `@react-router/dev` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/@react-router/dev@7.12.0/packages/react-router-dev)

Updates `@tanstack/react-query-devtools` from 5.91.1 to 5.91.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.91.2/packages/react-query-devtools)

Updates `oxfmt` from 0.22.0 to 0.23.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.23.0/npm/oxfmt)

Updates `oxlint` from 1.36.0 to 1.38.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.38.0/npm/oxlint)

Updates `vite` from 7.3.0 to 7.3.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.1/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.1/packages/vite)

---
updated-dependencies:
- dependency-name: "@react-router/node"
  dependency-version: 7.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@react-router/serve"
  dependency-version: 7.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@scalar/hono-api-reference"
  dependency-version: 0.9.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.90.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: drizzle-orm
  dependency-version: 0.45.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: hono
  dependency-version: 4.11.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: hono-rate-limiter
  dependency-version: 0.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.562.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: react-router
  dependency-version: 7.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: recharts
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@faker-js/faker"
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@happy-dom/global-registrator"
  dependency-version: 20.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@hey-api/openapi-ts"
  dependency-version: 0.90.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@react-router/dev"
  dependency-version: 7.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.91.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: oxfmt
  dependency-version: 0.23.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: oxlint
  dependency-version: 1.38.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: vite
  dependency-version: 7.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: downgrade hono

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nicolas Meienberger <github@thisprops.com>

* chore: force hono version in transitive deps

* refactor: remove copy to clipboard everywhere as it's not possible on http

* chore: pr feedbacks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-09 21:12:05 +01:00

348 lines
13 KiB
TypeScript

import { relations, sql } from "drizzle-orm";
import { index, int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core";
import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus } from "~/schemas/restic";
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
/**
* Volumes Table
*/
export const volumesTable = sqliteTable("volumes_table", {
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
name: text().notNull().unique(),
type: text().$type<BackendType>().notNull(),
status: text().$type<BackendStatus>().notNull().default("unmounted"),
lastError: text("last_error"),
lastHealthCheck: integer("last_health_check", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
createdAt: integer("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "number" })
.notNull()
.$onUpdate(() => Date.now())
.default(sql`(unixepoch() * 1000)`),
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true),
});
export type Volume = typeof volumesTable.$inferSelect;
export type VolumeInsert = typeof volumesTable.$inferInsert;
/**
* Users Table
*/
export const usersTable = sqliteTable("users_table", {
id: text("id").primaryKey(),
username: text().notNull().unique(),
passwordHash: text("password_hash"),
hasDownloadedResticPassword: int("has_downloaded_restic_password", { mode: "boolean" }).notNull().default(false),
createdAt: int("created_at", { mode: "timestamp_ms" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: int("updated_at", { mode: "timestamp_ms" })
.notNull()
.$onUpdate(() => new Date())
.default(sql`(unixepoch() * 1000)`),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" }).default(false).notNull(),
image: text("image"),
displayUsername: text("display_username"),
twoFactorEnabled: integer("two_factor_enabled", { mode: "boolean" }).notNull().default(false),
});
export type User = typeof usersTable.$inferSelect;
export const sessionsTable = sqliteTable(
"sessions_table",
{
id: text().primaryKey(),
userId: text("user_id")
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
token: text("token").notNull().unique(),
expiresAt: int("expires_at", { mode: "timestamp_ms" }).notNull(),
createdAt: int("created_at", { mode: "timestamp_ms" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.notNull()
.$onUpdate(() => new Date())
.default(sql`(unixepoch() * 1000)`),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
},
(table) => [index("sessionsTable_userId_idx").on(table.userId)],
);
export type Session = typeof sessionsTable.$inferSelect;
export const account = sqliteTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: integer("access_token_expires_at", {
mode: "timestamp_ms",
}),
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
mode: "timestamp_ms",
}),
scope: text("scope"),
password: text("password"),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => new Date())
.notNull()
.default(sql`(unixepoch() * 1000)`),
},
(table) => [index("account_userId_idx").on(table.userId)],
);
export const verification = sqliteTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => new Date())
.notNull()
.default(sql`(unixepoch() * 1000)`),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
);
export const userRelations = relations(usersTable, ({ many }) => ({
sessions: many(sessionsTable),
accounts: many(account),
twoFactors: many(twoFactor),
}));
export const sessionRelations = relations(sessionsTable, ({ one }) => ({
user: one(usersTable, {
fields: [sessionsTable.userId],
references: [usersTable.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(usersTable, {
fields: [account.userId],
references: [usersTable.id],
}),
}));
/**
* Repositories Table
*/
export const repositoriesTable = sqliteTable("repositories_table", {
id: text().primaryKey(),
shortId: text("short_id").notNull().unique(),
name: text().notNull(),
type: text().$type<RepositoryBackend>().notNull(),
config: text("config", { mode: "json" }).$type<typeof repositoryConfigSchema.inferOut>().notNull(),
compressionMode: text("compression_mode").$type<CompressionMode>().default("auto"),
status: text().$type<RepositoryStatus>().default("unknown"),
lastChecked: int("last_checked", { mode: "number" }),
lastError: text("last_error"),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
});
export type Repository = typeof repositoriesTable.$inferSelect;
export type RepositoryInsert = typeof repositoriesTable.$inferInsert;
/**
* Backup Schedules Table
*/
export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
name: text().notNull().unique(),
volumeId: int("volume_id")
.notNull()
.references(() => volumesTable.id, { onDelete: "cascade" }),
repositoryId: text("repository_id")
.notNull()
.references(() => repositoriesTable.id, { onDelete: "cascade" }),
enabled: int("enabled", { mode: "boolean" }).notNull().default(true),
cronExpression: text("cron_expression").notNull(),
retentionPolicy: text("retention_policy", { mode: "json" }).$type<{
keepLast?: number;
keepHourly?: number;
keepDaily?: number;
keepWeekly?: number;
keepMonthly?: number;
keepYearly?: number;
keepWithinDuration?: string;
}>(),
excludePatterns: text("exclude_patterns", { mode: "json" }).$type<string[]>().default([]),
excludeIfPresent: text("exclude_if_present", { mode: "json" }).$type<string[]>().default([]),
includePatterns: text("include_patterns", { mode: "json" }).$type<string[]>().default([]),
lastBackupAt: int("last_backup_at", { mode: "number" }),
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress" | "warning">(),
lastBackupError: text("last_backup_error"),
nextBackupAt: int("next_backup_at", { mode: "number" }),
oneFileSystem: int("one_file_system", { mode: "boolean" }).notNull().default(false),
sortOrder: int("sort_order", { mode: "number" }).notNull().default(0),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
});
export type BackupScheduleInsert = typeof backupSchedulesTable.$inferInsert;
export const backupScheduleRelations = relations(backupSchedulesTable, ({ one, many }) => ({
volume: one(volumesTable, {
fields: [backupSchedulesTable.volumeId],
references: [volumesTable.id],
}),
repository: one(repositoriesTable, {
fields: [backupSchedulesTable.repositoryId],
references: [repositoriesTable.id],
}),
notifications: many(backupScheduleNotificationsTable),
mirrors: many(backupScheduleMirrorsTable),
}));
export type BackupSchedule = typeof backupSchedulesTable.$inferSelect;
/**
* Notification Destinations Table
*/
export const notificationDestinationsTable = sqliteTable("notification_destinations_table", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull().unique(),
enabled: int("enabled", { mode: "boolean" }).notNull().default(true),
type: text().$type<NotificationType>().notNull(),
config: text("config", { mode: "json" }).$type<typeof notificationConfigSchema.inferOut>().notNull(),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
});
export const notificationDestinationRelations = relations(notificationDestinationsTable, ({ many }) => ({
schedules: many(backupScheduleNotificationsTable),
}));
export type NotificationDestination = typeof notificationDestinationsTable.$inferSelect;
/**
* Backup Schedule Notifications Junction Table (Many-to-Many)
*/
export const backupScheduleNotificationsTable = sqliteTable(
"backup_schedule_notifications_table",
{
scheduleId: int("schedule_id")
.notNull()
.references(() => backupSchedulesTable.id, { onDelete: "cascade" }),
destinationId: int("destination_id")
.notNull()
.references(() => notificationDestinationsTable.id, { onDelete: "cascade" }),
notifyOnStart: int("notify_on_start", { mode: "boolean" }).notNull().default(false),
notifyOnSuccess: int("notify_on_success", { mode: "boolean" }).notNull().default(false),
notifyOnWarning: int("notify_on_warning", { mode: "boolean" }).notNull().default(true),
notifyOnFailure: int("notify_on_failure", { mode: "boolean" }).notNull().default(true),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
},
(table) => [primaryKey({ columns: [table.scheduleId, table.destinationId] })],
);
export const backupScheduleNotificationRelations = relations(backupScheduleNotificationsTable, ({ one }) => ({
schedule: one(backupSchedulesTable, {
fields: [backupScheduleNotificationsTable.scheduleId],
references: [backupSchedulesTable.id],
}),
destination: one(notificationDestinationsTable, {
fields: [backupScheduleNotificationsTable.destinationId],
references: [notificationDestinationsTable.id],
}),
}));
export type BackupScheduleNotification = typeof backupScheduleNotificationsTable.$inferSelect;
/**
* Backup Schedule Mirrors Junction Table (Many-to-Many)
* Allows copying snapshots to secondary repositories after backup completes
*/
export const backupScheduleMirrorsTable = sqliteTable(
"backup_schedule_mirrors_table",
{
id: int().primaryKey({ autoIncrement: true }),
scheduleId: int("schedule_id")
.notNull()
.references(() => backupSchedulesTable.id, { onDelete: "cascade" }),
repositoryId: text("repository_id")
.notNull()
.references(() => repositoriesTable.id, { onDelete: "cascade" }),
enabled: int("enabled", { mode: "boolean" }).notNull().default(true),
lastCopyAt: int("last_copy_at", { mode: "number" }),
lastCopyStatus: text("last_copy_status").$type<"success" | "error">(),
lastCopyError: text("last_copy_error"),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
},
(table) => [unique().on(table.scheduleId, table.repositoryId)],
);
export const backupScheduleMirrorRelations = relations(backupScheduleMirrorsTable, ({ one }) => ({
schedule: one(backupSchedulesTable, {
fields: [backupScheduleMirrorsTable.scheduleId],
references: [backupSchedulesTable.id],
}),
repository: one(repositoriesTable, {
fields: [backupScheduleMirrorsTable.repositoryId],
references: [repositoriesTable.id],
}),
}));
export type BackupScheduleMirror = typeof backupScheduleMirrorsTable.$inferSelect;
/**
* App Metadata Table
* Used for storing key-value pairs like migration checkpoints
*/
export const appMetadataTable = sqliteTable("app_metadata", {
key: text().primaryKey(),
value: text().notNull(),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
updatedAt: int("updated_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
});
export type AppMetadata = typeof appMetadataTable.$inferSelect;
export const twoFactor = sqliteTable(
"two_factor",
{
id: text("id").primaryKey(),
secret: text("secret").notNull(),
backupCodes: text("backup_codes").notNull(),
userId: text("user_id")
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
},
(table) => [index("twoFactor_secret_idx").on(table.secret), index("twoFactor_userId_idx").on(table.userId)],
);