import { pgTable, text, timestamp, boolean, integer, pgEnum, primaryKey, } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; export const userRoleEnum = pgEnum("user_role", ["user", "moderator", "admin"]); export const tierEnum = pgEnum("tier", ["free", "pro"]); export const unitPrefEnum = pgEnum("unit_pref", ["metric", "imperial"]); export const apiKeyScopeEnum = pgEnum("api_key_scope", ["full", "read"]); export const users = pgTable("users", { id: text("id").primaryKey(), email: text("email").notNull().unique(), emailVerified: boolean("email_verified").notNull().default(false), name: text("name").notNull(), avatarUrl: text("avatar_url"), hasCustomAvatar: boolean("has_custom_avatar").notNull().default(false), bio: text("bio"), privateBio: text("private_bio"), isPrivate: boolean("is_private").notNull().default(false), username: text("username").unique(), role: userRoleEnum("role").notNull().default("user"), tier: tierEnum("tier").notNull().default("free"), stripeCustomerId: text("stripe_customer_id").unique(), unitPref: unitPrefEnum("unit_pref").notNull().default("metric"), locale: text("locale").notNull().default("en"), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); // Better Auth requires these tables export const sessions = pgTable("sessions", { id: text("id").primaryKey(), expiresAt: timestamp("expires_at").notNull(), token: text("token").notNull().unique(), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), }); export const accounts = pgTable("accounts", { id: text("id").primaryKey(), accountId: text("account_id").notNull(), providerId: text("provider_id").notNull(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), accessToken: text("access_token"), refreshToken: text("refresh_token"), idToken: text("id_token"), accessTokenExpiresAt: timestamp("access_token_expires_at"), refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), scope: text("scope"), password: text("password"), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); export const verifications = pgTable("verifications", { id: text("id").primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); export const userFollows = pgTable("user_follows", { followerId: text("follower_id").notNull().references(() => users.id, { onDelete: "cascade" }), followingId: text("following_id").notNull().references(() => users.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => [ primaryKey({ columns: [t.followerId, t.followingId] }), ]); export const userBlocks = pgTable("user_blocks", { blockerId: text("blocker_id").notNull().references(() => users.id, { onDelete: "cascade" }), blockedId: text("blocked_id").notNull().references(() => users.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => [ primaryKey({ columns: [t.blockerId, t.blockedId] }), ]); export const userBlocksRelations = relations(userBlocks, ({ one }) => ({ blocker: one(users, { fields: [userBlocks.blockerId], references: [users.id], relationName: "blocker" }), blocked: one(users, { fields: [userBlocks.blockedId], references: [users.id], relationName: "blocked" }), })); export const apiKeys = pgTable("api_keys", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), keyHash: text("key_hash").notNull().unique(), scope: apiKeyScopeEnum("scope").notNull().default("full"), lastUsedAt: timestamp("last_used_at"), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const userAiKeys = pgTable("user_ai_keys", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), provider: text("provider").notNull(), encryptedKey: text("encrypted_key").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const usersRelations = relations(users, ({ many }) => ({ sessions: many(sessions), accounts: many(accounts), followers: many(userFollows, { relationName: "following" }), following: many(userFollows, { relationName: "follower" }), apiKeys: many(apiKeys), aiKeys: many(userAiKeys), })); export const userAiKeysRelations = relations(userAiKeys, ({ one }) => ({ user: one(users, { fields: [userAiKeys.userId], references: [users.id] }), })); export const userFollowsRelations = relations(userFollows, ({ one }) => ({ follower: one(users, { fields: [userFollows.followerId], references: [users.id], relationName: "follower" }), following: one(users, { fields: [userFollows.followingId], references: [users.id], relationName: "following" }), })); export const pushSubscriptions = pgTable("push_subscriptions", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), endpoint: text("endpoint").notNull().unique(), p256dh: text("p256dh").notNull(), auth: text("auth").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const userNutritionGoals = pgTable("user_nutrition_goals", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(), caloriesKcal: integer("calories_kcal"), proteinG: integer("protein_g"), carbsG: integer("carbs_g"), fatG: integer("fat_g"), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); export const userNotificationPrefs = pgTable("user_notification_prefs", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(), follow: boolean("follow").notNull().default(true), comment: boolean("comment").notNull().default(true), reply: boolean("reply").notNull().default(true), reaction: boolean("reaction").notNull().default(true), rating: boolean("rating").notNull().default(true), mention: boolean("mention").notNull().default(true), leftoverExpiring: boolean("leftover_expiring").notNull().default(true), shoppingList: boolean("shopping_list").notNull().default(true), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); export const pushSubscriptionsRelations = relations(pushSubscriptions, ({ one }) => ({ user: one(users, { fields: [pushSubscriptions.userId], references: [users.id] }), })); export const userNotificationPrefsRelations = relations(userNotificationPrefs, ({ one }) => ({ user: one(users, { fields: [userNotificationPrefs.userId], references: [users.id] }), })); export const userNutritionGoalsRelations = relations(userNutritionGoals, ({ one }) => ({ user: one(users, { fields: [userNutritionGoals.userId], references: [users.id] }), })); export const userModelPrefs = pgTable("user_model_prefs", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(), textProvider: text("text_provider"), textModel: text("text_model"), visionProvider: text("vision_provider"), visionModel: text("vision_model"), mealPlanProvider: text("meal_plan_provider"), mealPlanModel: text("meal_plan_model"), updatedAt: timestamp("updated_at").notNull().defaultNow(), }); export const userModelPrefsRelations = relations(userModelPrefs, ({ one }) => ({ user: one(users, { fields: [userModelPrefs.userId], references: [users.id] }), }));