SPB Git

spb/autoscholar

Public
3.6 KB · 94 lines typescript
1import { boolean, index, inet, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";23export const adminUsers = pgTable("admin_users", {4  id: uuid("id").primaryKey().defaultRandom(),5  username: text("username").notNull().unique(),6  passwordHash: text("password_hash").notNull(),7  mustChangePassword: boolean("must_change_password").notNull().default(true),8  bootstrapped: boolean("bootstrapped").notNull().default(false),9  totpSecret: text("totp_secret"),10  failedAttempts: integer("failed_attempts").notNull().default(0),11  lockedUntil: timestamp("locked_until", { withTimezone: true }),12  lastLoginAt: timestamp("last_login_at", { withTimezone: true }),13  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),14  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),15});1617export const sessions = pgTable(18  "sessions",19  {20    id: uuid("id").primaryKey().defaultRandom(),21    tokenHash: text("token_hash").notNull().unique(),22    userId: uuid("user_id")23      .notNull()24      .references(() => adminUsers.id, { onDelete: "cascade" }),25    csrfSecret: text("csrf_secret").notNull(),26    createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),27    expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),28    lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),29    rotatedFrom: uuid("rotated_from"),30    ip: inet("ip"),31    userAgent: text("user_agent"),32    revokedAt: timestamp("revoked_at", { withTimezone: true }),33  },34  (t) => [index("sessions_user_idx").on(t.userId), index("sessions_expires_idx").on(t.expiresAt)],35);3637export const passwordChanges = pgTable("password_changes", {38  id: uuid("id").primaryKey().defaultRandom(),39  userId: uuid("user_id")40    .notNull()41    .references(() => adminUsers.id, { onDelete: "cascade" }),42  previousHash: text("previous_hash").notNull(),43  reason: text("reason").notNull(),44  ip: inet("ip"),45  changedAt: timestamp("changed_at", { withTimezone: true }).notNull().defaultNow(),46});4748export const securityEvents = pgTable(49  "security_events",50  {51    id: uuid("id").primaryKey().defaultRandom(),52    userId: uuid("user_id"),53    kind: text("kind").notNull(),54    ip: inet("ip"),55    detail: jsonb("detail").$type<Record<string, unknown>>().notNull().default({}),56    createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),57  },58  (t) => [index("security_events_kind_idx").on(t.kind, t.createdAt)],59);6061export const auditEvents = pgTable(62  "audit_events",63  {64    id: uuid("id").primaryKey().defaultRandom(),65    actorType: text("actor_type").notNull(), // admin | worker | system66    actorId: text("actor_id"),67    action: text("action").notNull(),68    targetType: text("target_type"),69    targetId: text("target_id"),70    detail: jsonb("detail").$type<Record<string, unknown>>().notNull().default({}),71    ip: inet("ip"),72    createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),73  },74  (t) => [75    index("audit_events_target_idx").on(t.targetType, t.targetId),76    index("audit_events_created_idx").on(t.createdAt),77  ],78);7980export const loginAttempts = pgTable(81  "login_attempts",82  {83    id: uuid("id").primaryKey().defaultRandom(),84    username: text("username").notNull(),85    ip: inet("ip"),86    success: boolean("success").notNull(),87    createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),88  },89  (t) => [90    index("login_attempts_ip_idx").on(t.ip, t.createdAt),91    index("login_attempts_user_idx").on(t.username, t.createdAt),92  ],93);94
Files · ad78891b6b969902dedc372df227e1adc3cd93ba
Menu

Theme