import { sqliteTable, text, integer, customType as sqliteCustomType } from 'drizzle-orm/sqlite-core'; import { pgTable, uuid, varchar, text as pgText, timestamp, boolean as pgBoolean, bigint, customType as pgCustomType, } from 'drizzle-orm/pg-core'; // Better Auth table definitions for both dialects. // // The `user` model maps onto the EXISTING `users` table so user IDs (and every // foreign key that references them) survive the auth migration untouched. Only // the columns Better Auth reads/writes are declared here; legacy columns // (password, google_id, token_version) stay physically present but invisible // to Better Auth. The full application-facing definition lives in schema.ts — // two Drizzle table objects can safely describe the same SQL table. const dbType = process.env.DB_TYPE || 'sqlite'; // Better Auth hands the adapter JS Date objects, but the legacy sqlite `users` // timestamps are ISO-8601 TEXT columns. Bridge the two representations. const isoText = sqliteCustomType<{ data: Date; driverData: string }>({ dataType() { return 'text'; }, toDriver(value: Date): string { return (value instanceof Date ? value : new Date(value)).toISOString(); }, fromDriver(value: string): Date { return new Date(value); }, }); // Legacy pg `users.is_claimed` is an INTEGER 0/1 column; expose it as boolean. const pgIntBool = pgCustomType<{ data: boolean; driverData: number }>({ dataType() { return 'integer'; }, toDriver(value: boolean): number { return value ? 1 : 0; }, fromDriver(value: number | boolean): boolean { return Boolean(value); }, }); // ==================== SQLite ==================== export const sqliteAuthUsers = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), emailVerified: integer('email_verified', { mode: 'boolean' }).notNull().default(false), image: text('image'), createdAt: isoText('created_at').notNull(), updatedAt: isoText('updated_at').notNull(), // admin plugin fields role: text('role').notNull().default('user'), banned: integer('banned', { mode: 'boolean' }).notNull().default(false), banReason: text('ban_reason'), banExpires: integer('ban_expires', { mode: 'timestamp_ms' }), // application additionalFields phone: text('phone'), languagePreference: text('language_preference'), rucNumber: text('ruc_number'), isClaimed: integer('is_claimed', { mode: 'boolean' }).notNull().default(true), accountStatus: text('account_status').notNull().default('active'), }); export const sqliteAuthSessions = sqliteTable('auth_sessions', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => sqliteAuthUsers.id, { onDelete: 'cascade' }), token: text('token').notNull().unique(), expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), ipAddress: text('ip_address'), userAgent: text('user_agent'), // admin plugin (impersonation) impersonatedBy: text('impersonated_by'), createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), }); export const sqliteAuthAccounts = sqliteTable('auth_accounts', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => sqliteAuthUsers.id, { onDelete: 'cascade' }), accountId: text('account_id').notNull(), providerId: text('provider_id').notNull(), 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(), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), }); export const sqliteAuthVerifications = sqliteTable('auth_verifications', { 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(), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), }); export const sqliteAuthRateLimits = sqliteTable('auth_rate_limits', { id: text('id').primaryKey(), key: text('key'), count: integer('count'), lastRequest: integer('last_request'), }); // ==================== PostgreSQL ==================== export const pgAuthUsers = pgTable('users', { id: uuid('id').primaryKey(), name: varchar('name', { length: 255 }).notNull(), email: varchar('email', { length: 255 }).notNull().unique(), emailVerified: pgBoolean('email_verified').notNull().default(false), image: pgText('image'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), // admin plugin fields role: varchar('role', { length: 20 }).notNull().default('user'), banned: pgBoolean('banned').notNull().default(false), banReason: pgText('ban_reason'), banExpires: timestamp('ban_expires'), // application additionalFields phone: varchar('phone', { length: 50 }), languagePreference: varchar('language_preference', { length: 10 }), rucNumber: varchar('ruc_number', { length: 15 }), isClaimed: pgIntBool('is_claimed').notNull(), accountStatus: varchar('account_status', { length: 20 }).notNull().default('active'), }); export const pgAuthSessions = pgTable('auth_sessions', { id: uuid('id').primaryKey(), userId: uuid('user_id') .notNull() .references(() => pgAuthUsers.id, { onDelete: 'cascade' }), token: varchar('token', { length: 255 }).notNull().unique(), expiresAt: timestamp('expires_at').notNull(), ipAddress: varchar('ip_address', { length: 45 }), userAgent: pgText('user_agent'), // admin plugin (impersonation) impersonatedBy: uuid('impersonated_by'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgAuthAccounts = pgTable('auth_accounts', { id: uuid('id').primaryKey(), userId: uuid('user_id') .notNull() .references(() => pgAuthUsers.id, { onDelete: 'cascade' }), accountId: varchar('account_id', { length: 255 }).notNull(), providerId: varchar('provider_id', { length: 100 }).notNull(), accessToken: pgText('access_token'), refreshToken: pgText('refresh_token'), idToken: pgText('id_token'), accessTokenExpiresAt: timestamp('access_token_expires_at'), refreshTokenExpiresAt: timestamp('refresh_token_expires_at'), scope: pgText('scope'), password: pgText('password'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgAuthVerifications = pgTable('auth_verifications', { id: uuid('id').primaryKey(), identifier: varchar('identifier', { length: 255 }).notNull(), value: pgText('value').notNull(), expiresAt: timestamp('expires_at').notNull(), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgAuthRateLimits = pgTable('auth_rate_limits', { id: varchar('id', { length: 64 }).primaryKey(), key: varchar('key', { length: 255 }), count: bigint('count', { mode: 'number' }), lastRequest: bigint('last_request', { mode: 'number' }), }); // ==================== Runtime-switched exports ==================== export const authUsers = dbType === 'postgres' ? pgAuthUsers : sqliteAuthUsers; export const authSessions = dbType === 'postgres' ? pgAuthSessions : sqliteAuthSessions; export const authAccounts = dbType === 'postgres' ? pgAuthAccounts : sqliteAuthAccounts; export const authVerifications = dbType === 'postgres' ? pgAuthVerifications : sqliteAuthVerifications; export const authRateLimits = dbType === 'postgres' ? pgAuthRateLimits : sqliteAuthRateLimits;