import { int, mysqlEnum, mysqlTable, text, timestamp, varchar, longtext, json, } from "drizzle-orm/mysql-core"; /** * Core user table backing auth flow. * Extend this file with additional tables as your product grows. * Columns use camelCase to match both database fields and generated types. */ export const users = mysqlTable("users", { /** * Surrogate primary key. Auto-incremented numeric value managed by the database. * Use this for relations between tables. */ id: int("id").autoincrement().primaryKey(), /** Manus OAuth identifier (openId) returned from the OAuth callback. Unique per user. */ openId: varchar("openId", { length: 64 }).notNull().unique(), name: text("name"), email: varchar("email", { length: 320 }), loginMethod: varchar("loginMethod", { length: 64 }), role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(), // Section 2: User tier for rate limiting and feature access tier: varchar("tier", { length: 50 }).default("free").notNull(), ipAddress: varchar("ipAddress", { length: 45 }), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(), }); export type User = typeof users.$inferSelect; export type InsertUser = typeof users.$inferInsert; /** * Section 2: Conversations table * Stores conversation metadata per user */ export const conversations = mysqlTable("conversations", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), title: text("title"), mode: mysqlEnum("mode", ["ask", "imagine"]).default("ask").notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type Conversation = typeof conversations.$inferSelect; export type InsertConversation = typeof conversations.$inferInsert; /** * Section 2: Messages table * Stores individual messages in conversations */ export const messages = mysqlTable("messages", { id: int("id").autoincrement().primaryKey(), conversationId: int("conversationId").notNull(), role: mysqlEnum("role", ["user", "assistant"]).notNull(), content: longtext("content").notNull(), // Section 5: Reasoning for DeepSeek-style thoughts reasoning: text("reasoning"), // Metadata for search results, model used, etc. metadata: json("metadata"), createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type Message = typeof messages.$inferSelect; export type InsertMessage = typeof messages.$inferInsert; /** * Section 8: Images table * Stores generated images from Imagine mode */ export const images = mysqlTable("images", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), conversationId: int("conversationId"), prompt: text("prompt").notNull(), url: text("url").notNull(), // Metadata: model used, generation time, etc. metadata: json("metadata"), createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type Image = typeof images.$inferSelect; export type InsertImage = typeof images.$inferInsert; /** * Section 2: Feedback table * Stores user feedback (likes/dislikes) for Google Sheets logging */ export const feedback = mysqlTable("feedback", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), messageId: int("messageId"), imageId: int("imageId"), rating: mysqlEnum("rating", ["like", "dislike"]).notNull(), comment: text("comment"), createdAt: timestamp("createdAt").defaultNow().notNull(), }); export type Feedback = typeof feedback.$inferSelect; export type InsertFeedback = typeof feedback.$inferInsert;