feat: add better auth sqlite runtime and server-side session flow

This commit is contained in:
Poyraz
2026-07-10 22:07:37 +03:00
parent 3504a02229
commit 57aab2932e
41 changed files with 4907 additions and 143 deletions
+64
View File
@@ -0,0 +1,64 @@
import "server-only";
import Database from "better-sqlite3";
import { drizzle, type BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import { ensureDataDirectories, getServerConfig } from "@/server/config";
import * as schema from "@/server/db/schema";
export type SqliteConnection = {
sqlite: Database.Database;
db: BetterSQLite3Database<typeof schema>;
};
const globalForSqlite = globalThis as typeof globalThis & {
__netaSqliteConnection?: SqliteConnection;
__netaSqliteCloseHandlersRegistered?: boolean;
};
export function getSqliteConnection(): SqliteConnection {
if (globalForSqlite.__netaSqliteConnection) {
return globalForSqlite.__netaSqliteConnection;
}
const config = getServerConfig();
ensureDataDirectories(config);
const sqlite = new Database(config.databasePath);
applyPragmas(sqlite);
const connection = {
sqlite,
db: drizzle({ client: sqlite, schema }),
};
globalForSqlite.__netaSqliteConnection = connection;
registerCloseHandlers();
return connection;
}
export function closeSqliteConnection(): void {
const connection = globalForSqlite.__netaSqliteConnection;
if (!connection) {
return;
}
connection.sqlite.close();
globalForSqlite.__netaSqliteConnection = undefined;
}
export function applyPragmas(sqlite: Database.Database): void {
sqlite.pragma("foreign_keys = ON");
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("synchronous = NORMAL");
sqlite.pragma("busy_timeout = 5000");
}
function registerCloseHandlers(): void {
if (globalForSqlite.__netaSqliteCloseHandlersRegistered || process.env.NODE_ENV !== "production") {
return;
}
process.once("beforeExit", closeSqliteConnection);
globalForSqlite.__netaSqliteCloseHandlersRegistered = true;
}
+59
View File
@@ -0,0 +1,59 @@
import "server-only";
import fs from "node:fs";
import path from "node:path";
import { ensureDataDirectories, getServerConfig } from "@/server/config";
import { getSqliteConnection } from "@/server/db/client";
export type ReadinessStatus = {
ok: boolean;
checks: {
dataDirWritable: boolean;
databaseReachable: boolean;
migrationsApplied: boolean;
};
error?: string;
};
export function checkReadiness(): ReadinessStatus {
const config = getServerConfig();
const checks = {
dataDirWritable: false,
databaseReachable: false,
migrationsApplied: false,
};
try {
ensureDataDirectories(config);
assertWritableDirectory(config.dataDir);
checks.dataDirWritable = true;
const { sqlite } = getSqliteConnection();
sqlite.prepare("select 1 as ok").get();
checks.databaseReachable = true;
const migrationRow = sqlite
.prepare("select name from sqlite_master where type = 'table' and name = 'runtime_checks'")
.get();
checks.migrationsApplied = Boolean(migrationRow);
return {
ok: Boolean(migrationRow),
checks,
error: migrationRow ? undefined : "Migrations have not been applied.",
};
} catch (error) {
return {
ok: false,
checks,
error: error instanceof Error ? error.message : "Unknown readiness error.",
};
}
}
function assertWritableDirectory(dir: string): void {
const probePath = path.join(dir, `.neta-write-${process.pid}-${Date.now()}`);
fs.writeFileSync(probePath, "ok", { encoding: "utf8", flag: "wx" });
fs.unlinkSync(probePath);
}
+13
View File
@@ -0,0 +1,13 @@
CREATE TABLE `runtime_checks` (
`key` text PRIMARY KEY NOT NULL,
`value` text NOT NULL,
`created_at` integer NOT NULL,
`updated_at` integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE `runtime_events` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`type` text NOT NULL,
`message` text NOT NULL,
`created_at` integer NOT NULL
);
@@ -0,0 +1,104 @@
CREATE TABLE `account` (
`id` text PRIMARY KEY NOT NULL,
`account_id` text NOT NULL,
`provider_id` text NOT NULL,
`user_id` text NOT NULL,
`access_token` text,
`refresh_token` text,
`id_token` text,
`access_token_expires_at` integer,
`refresh_token_expires_at` integer,
`scope` text,
`password` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `account_user_id_idx` ON `account` (`user_id`);--> statement-breakpoint
CREATE TABLE `app_profiles` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`auth_user_id` text NOT NULL,
`email` text NOT NULL,
`display_name` text NOT NULL,
`role` text NOT NULL,
`disabled` integer DEFAULT false NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`auth_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `app_profiles_auth_user_id_unique` ON `app_profiles` (`auth_user_id`);--> statement-breakpoint
CREATE INDEX `app_profiles_role_idx` ON `app_profiles` (`role`);--> statement-breakpoint
CREATE TABLE `app_setup_state` (
`key` text PRIMARY KEY NOT NULL,
`status` text NOT NULL,
`locked_by` text,
`locked_at` integer,
`completed_at` integer,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE TABLE `auth_audit_events` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`type` text NOT NULL,
`auth_user_id` text,
`email` text,
`metadata` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`auth_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE INDEX `auth_audit_events_type_idx` ON `auth_audit_events` (`type`);--> statement-breakpoint
CREATE INDEX `auth_audit_events_auth_user_id_idx` ON `auth_audit_events` (`auth_user_id`);--> statement-breakpoint
CREATE TABLE `portal_invitations` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`token_hash` text NOT NULL,
`client_id` text NOT NULL,
`email` text NOT NULL,
`status` text DEFAULT 'pending' NOT NULL,
`expires_at` integer NOT NULL,
`accepted_at` integer,
`created_by_user_id` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`created_by_user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE UNIQUE INDEX `portal_invitations_token_hash_unique` ON `portal_invitations` (`token_hash`);--> statement-breakpoint
CREATE INDEX `portal_invitations_client_id_idx` ON `portal_invitations` (`client_id`);--> statement-breakpoint
CREATE INDEX `portal_invitations_email_idx` ON `portal_invitations` (`email`);--> statement-breakpoint
CREATE TABLE `session` (
`id` text PRIMARY KEY NOT NULL,
`expires_at` integer NOT NULL,
`token` text NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer NOT NULL,
`ip_address` text,
`user_agent` text,
`user_id` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
CREATE INDEX `session_user_id_idx` ON `session` (`user_id`);--> statement-breakpoint
CREATE TABLE `user` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`email` text NOT NULL,
`email_verified` integer DEFAULT false NOT NULL,
`image` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
CREATE TABLE `verification` (
`id` text PRIMARY KEY NOT NULL,
`identifier` text NOT NULL,
`value` text NOT NULL,
`expires_at` integer NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);
@@ -0,0 +1,94 @@
{
"version": "6",
"dialect": "sqlite",
"id": "edc570f7-a411-4b4b-901e-94d53532fd37",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"runtime_checks": {
"name": "runtime_checks",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"runtime_events": {
"name": "runtime_events",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}
@@ -0,0 +1,790 @@
{
"version": "6",
"dialect": "sqlite",
"id": "cb8d4285-951f-4bbf-b848-953bdf769836",
"prevId": "edc570f7-a411-4b4b-901e-94d53532fd37",
"tables": {
"account": {
"name": "account",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"account_user_id_idx": {
"name": "account_user_id_idx",
"columns": [
"user_id"
],
"isUnique": false
}
},
"foreignKeys": {
"account_user_id_user_id_fk": {
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"app_profiles": {
"name": "app_profiles",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"auth_user_id": {
"name": "auth_user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"disabled": {
"name": "disabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {
"app_profiles_auth_user_id_unique": {
"name": "app_profiles_auth_user_id_unique",
"columns": [
"auth_user_id"
],
"isUnique": true
},
"app_profiles_role_idx": {
"name": "app_profiles_role_idx",
"columns": [
"role"
],
"isUnique": false
}
},
"foreignKeys": {
"app_profiles_auth_user_id_user_id_fk": {
"name": "app_profiles_auth_user_id_user_id_fk",
"tableFrom": "app_profiles",
"tableTo": "user",
"columnsFrom": [
"auth_user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"app_setup_state": {
"name": "app_setup_state",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"locked_by": {
"name": "locked_by",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"locked_at": {
"name": "locked_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"completed_at": {
"name": "completed_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"auth_audit_events": {
"name": "auth_audit_events",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"auth_user_id": {
"name": "auth_user_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"metadata": {
"name": "metadata",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {
"auth_audit_events_type_idx": {
"name": "auth_audit_events_type_idx",
"columns": [
"type"
],
"isUnique": false
},
"auth_audit_events_auth_user_id_idx": {
"name": "auth_audit_events_auth_user_id_idx",
"columns": [
"auth_user_id"
],
"isUnique": false
}
},
"foreignKeys": {
"auth_audit_events_auth_user_id_user_id_fk": {
"name": "auth_audit_events_auth_user_id_user_id_fk",
"tableFrom": "auth_audit_events",
"tableTo": "user",
"columnsFrom": [
"auth_user_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"portal_invitations": {
"name": "portal_invitations",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"token_hash": {
"name": "token_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pending'"
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"accepted_at": {
"name": "accepted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_by_user_id": {
"name": "created_by_user_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {
"portal_invitations_token_hash_unique": {
"name": "portal_invitations_token_hash_unique",
"columns": [
"token_hash"
],
"isUnique": true
},
"portal_invitations_client_id_idx": {
"name": "portal_invitations_client_id_idx",
"columns": [
"client_id"
],
"isUnique": false
},
"portal_invitations_email_idx": {
"name": "portal_invitations_email_idx",
"columns": [
"email"
],
"isUnique": false
}
},
"foreignKeys": {
"portal_invitations_created_by_user_id_user_id_fk": {
"name": "portal_invitations_created_by_user_id_user_id_fk",
"tableFrom": "portal_invitations",
"tableTo": "user",
"columnsFrom": [
"created_by_user_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"session": {
"name": "session",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"session_token_unique": {
"name": "session_token_unique",
"columns": [
"token"
],
"isUnique": true
},
"session_user_id_idx": {
"name": "session_user_id_idx",
"columns": [
"user_id"
],
"isUnique": false
}
},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email_verified": {
"name": "email_verified",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"verification": {
"name": "verification",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(cast(unixepoch('subsecond') * 1000 as integer))"
}
},
"indexes": {
"verification_identifier_idx": {
"name": "verification_identifier_idx",
"columns": [
"identifier"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"runtime_checks": {
"name": "runtime_checks",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"runtime_events": {
"name": "runtime_events",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1783708523046,
"tag": "0000_wise_reaper",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1783709956320,
"tag": "0001_silky_jetstream",
"breakpoints": true
}
]
}
+151
View File
@@ -0,0 +1,151 @@
import { sql } from "drizzle-orm";
import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
import type { AuthAuditEventType, SetupStatus, UserRole } from "@/server/auth/types";
const nowMs = sql`(cast(unixepoch('subsecond') * 1000 as integer))`;
export const user = sqliteTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" }).default(false).notNull(),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(nowMs)
.$onUpdate(() => new Date())
.notNull(),
});
export const session = sqliteTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
token: text("token").notNull().unique(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_user_id_idx").on(table.userId)],
);
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(() => user.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" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.$onUpdate(() => new Date())
.notNull(),
},
(table) => [index("account_user_id_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" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(nowMs)
.$onUpdate(() => new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)],
);
export const appProfiles = sqliteTable(
"app_profiles",
{
id: integer("id").primaryKey({ autoIncrement: true }),
authUserId: text("auth_user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
email: text("email").notNull(),
displayName: text("display_name").notNull(),
role: text("role").$type<UserRole>().notNull(),
disabled: integer("disabled", { mode: "boolean" }).default(false).notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(nowMs)
.$onUpdate(() => new Date())
.notNull(),
},
(table) => [
uniqueIndex("app_profiles_auth_user_id_unique").on(table.authUserId),
index("app_profiles_role_idx").on(table.role),
],
);
export const appSetupState = sqliteTable("app_setup_state", {
key: text("key").primaryKey(),
status: text("status").$type<SetupStatus>().notNull(),
lockedBy: text("locked_by"),
lockedAt: integer("locked_at", { mode: "timestamp_ms" }),
completedAt: integer("completed_at", { mode: "timestamp_ms" }),
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
.default(nowMs)
.$onUpdate(() => new Date())
.notNull(),
});
export const portalInvitations = sqliteTable(
"portal_invitations",
{
id: integer("id").primaryKey({ autoIncrement: true }),
tokenHash: text("token_hash").notNull(),
clientId: text("client_id").notNull(),
email: text("email").notNull(),
status: text("status", { enum: ["pending", "accepted", "revoked", "expired"] })
.default("pending")
.notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
acceptedAt: integer("accepted_at", { mode: "timestamp_ms" }),
createdByUserId: text("created_by_user_id").references(() => user.id, { onDelete: "set null" }),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
},
(table) => [
uniqueIndex("portal_invitations_token_hash_unique").on(table.tokenHash),
index("portal_invitations_client_id_idx").on(table.clientId),
index("portal_invitations_email_idx").on(table.email),
],
);
export const authAuditEvents = sqliteTable(
"auth_audit_events",
{
id: integer("id").primaryKey({ autoIncrement: true }),
type: text("type").$type<AuthAuditEventType>().notNull(),
authUserId: text("auth_user_id").references(() => user.id, { onDelete: "set null" }),
email: text("email"),
metadata: text("metadata", { mode: "json" }).$type<Record<string, unknown> | null>(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).default(nowMs).notNull(),
},
(table) => [
index("auth_audit_events_type_idx").on(table.type),
index("auth_audit_events_auth_user_id_idx").on(table.authUserId),
],
);
+2
View File
@@ -0,0 +1,2 @@
export * from "./auth";
export * from "./runtime";
+15
View File
@@ -0,0 +1,15 @@
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const runtimeChecks = sqliteTable("runtime_checks", {
key: text("key").primaryKey(),
value: text("value").notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
});
export const runtimeEvents = sqliteTable("runtime_events", {
id: integer("id").primaryKey({ autoIncrement: true }),
type: text("type").notNull(),
message: text("message").notNull(),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
});
+25
View File
@@ -0,0 +1,25 @@
import "server-only";
import { getSqliteConnection, type SqliteConnection } from "@/server/db/client";
let transactionDepth = 0;
export function runInTransaction<T>(operation: (connection: SqliteConnection) => T): T {
const connection = getSqliteConnection();
if (transactionDepth > 0) {
return operation(connection);
}
const execute = connection.sqlite.transaction(() => {
transactionDepth += 1;
try {
return operation(connection);
} finally {
transactionDepth -= 1;
}
});
return execute();
}