/** * User Schema - Drizzle ORM * Defines users, sessions, SSH keys, and OAuth accounts */ import { relations } from "drizzle-orm"; import { boolean, index, pgTable, primaryKey, text, timestamp, } from "drizzle-orm/pg-core"; export const users = pgTable("users", { id: text("id").primaryKey(), username: text("username").notNull().unique(), email: text("email").notNull().unique(), passwordHash: text("password_hash"), displayName: text("display_name"), bio: text("bio"), avatarUrl: text("avatar_url"), location: text("location"), website: text("website"), company: text("company"), isAdmin: boolean("is_admin").default(false), isActive: boolean("is_active").default(true), emailVerified: boolean("email_verified").default(false), twoFactorEnabled: boolean("two_factor_enabled").default(false), twoFactorSecret: text("two_factor_secret"), lastLoginAt: timestamp("last_login_at"), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), aiConfig: text("ai_config"), // JSON: { provider, apiKeys: { openai, groq, bytez } } }); export const sessions = pgTable( "sessions", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), token: text("token").notNull().unique(), userAgent: text("user_agent"), ipAddress: text("ip_address"), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => ({ userIdx: index("sessions_user_idx").on(t.userId), expiresIdx: index("sessions_expires_idx").on(t.expiresAt), }), ); export const sshKeys = pgTable( "ssh_keys", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), title: text("title").notNull(), fingerprint: text("fingerprint").notNull().unique(), publicKey: text("public_key").notNull(), keyType: text("key_type").notNull(), // ssh-rsa, ssh-ed25519, etc. lastUsedAt: timestamp("last_used_at"), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => ({ userIdx: index("ssh_keys_user_idx").on(t.userId), }), ); export const oauthAccounts = pgTable("oauth_accounts", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), provider: text("provider").notNull(), // github, google, gitlab providerAccountId: text("provider_account_id").notNull(), accessToken: text("access_token"), refreshToken: text("refresh_token"), expiresAt: timestamp("expires_at"), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const emailVerificationTokens = pgTable("email_verification_tokens", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), token: text("token").notNull().unique(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const passwordResetTokens = pgTable("password_reset_tokens", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), token: text("token").notNull().unique(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const gpgKeys = pgTable("gpg_keys", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), keyId: text("key_id").notNull(), publicKey: text("public_key").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const personalAccessTokens = pgTable("personal_access_tokens", { id: text("id").primaryKey(), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), token: text("token").notNull().unique(), expiresAt: timestamp("expires_at"), lastUsedAt: timestamp("last_used_at"), // Fine-grained scopes (WS4-01): JSON array of scopes like // ["repo:read", "repo:write", "admin", "notifications"] // null/[] = legacy full-access token scopes: text("scopes"), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const userFollowers = pgTable( "user_followers", { followerId: text("follower_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), followeeId: text("followee_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => ({ pk: primaryKey({ columns: [t.followerId, t.followeeId] }), }), ); // Relations export const usersRelations = relations(users, ({ many }) => ({ sessions: many(sessions), sshKeys: many(sshKeys), oauthAccounts: many(oauthAccounts), gpgKeys: many(gpgKeys), personalAccessTokens: many(personalAccessTokens), followers: many(userFollowers, { relationName: "user_followers_followee_id_users_id", }), following: many(userFollowers, { relationName: "user_followers_follower_id_users_id", }), })); export const sessionsRelations = relations(sessions, ({ one }) => ({ user: one(users, { fields: [sessions.userId], references: [users.id], }), })); export const sshKeysRelations = relations(sshKeys, ({ one }) => ({ user: one(users, { fields: [sshKeys.userId], references: [users.id], }), })); export const oauthAccountsRelations = relations(oauthAccounts, ({ one }) => ({ user: one(users, { fields: [oauthAccounts.userId], references: [users.id], }), })); export const emailVerificationTokensRelations = relations( emailVerificationTokens, ({ one }) => ({ user: one(users, { fields: [emailVerificationTokens.userId], references: [users.id], }), }), ); export const passwordResetTokensRelations = relations( passwordResetTokens, ({ one }) => ({ user: one(users, { fields: [passwordResetTokens.userId], references: [users.id], }), }), ); export const gpgKeysRelations = relations(gpgKeys, ({ one }) => ({ user: one(users, { fields: [gpgKeys.userId], references: [users.id], }), })); export const personalAccessTokensRelations = relations( personalAccessTokens, ({ one }) => ({ user: one(users, { fields: [personalAccessTokens.userId], references: [users.id], }), }), ); export const userFollowersRelations = relations(userFollowers, ({ one }) => ({ follower: one(users, { fields: [userFollowers.followerId], references: [users.id], relationName: "user_followers_follower_id_users_id", }), followee: one(users, { fields: [userFollowers.followeeId], references: [users.id], relationName: "user_followers_followee_id_users_id", }), })); // Types export type User = typeof users.$inferSelect; export type NewUser = typeof users.$inferInsert; export type Session = typeof sessions.$inferSelect; export type NewSession = typeof sessions.$inferInsert; export type SSHKey = typeof sshKeys.$inferSelect; export type NewSSHKey = typeof sshKeys.$inferInsert; export type OAuthAccount = typeof oauthAccounts.$inferSelect; export type GPGKey = typeof gpgKeys.$inferSelect; export type NewGPGKey = typeof gpgKeys.$inferInsert; export type PersonalAccessToken = typeof personalAccessTokens.$inferSelect; export type NewPersonalAccessToken = typeof personalAccessTokens.$inferInsert; export type UserFollower = typeof userFollowers.$inferSelect; export type NewUserFollower = typeof userFollowers.$inferInsert;