245 lines
8.3 KiB
TypeScript
245 lines
8.3 KiB
TypeScript
import { eq, and, desc, asc } from "drizzle-orm";
|
|
import { drizzle } from "drizzle-orm/mysql2";
|
|
import {
|
|
InsertUser, users,
|
|
projects, InsertProject, Project,
|
|
sequences, InsertSequence,
|
|
frames, InsertFrame,
|
|
layers, InsertLayer,
|
|
characters, InsertCharacter,
|
|
aiEngines, InsertAiEngine,
|
|
generationJobs, InsertGenerationJob,
|
|
assistantMessages, InsertAssistantMessage,
|
|
} from "../drizzle/schema";
|
|
import { ENV } from './_core/env';
|
|
|
|
let _db: ReturnType<typeof drizzle> | null = null;
|
|
|
|
export async function getDb() {
|
|
if (!_db && process.env.DATABASE_URL) {
|
|
try {
|
|
_db = drizzle(process.env.DATABASE_URL);
|
|
} catch (error) {
|
|
console.warn("[Database] Failed to connect:", error);
|
|
_db = null;
|
|
}
|
|
}
|
|
return _db;
|
|
}
|
|
|
|
// ============ USERS ============
|
|
export async function upsertUser(user: InsertUser): Promise<void> {
|
|
if (!user.openId) throw new Error("User openId is required for upsert");
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const values: InsertUser = { openId: user.openId };
|
|
const updateSet: Record<string, unknown> = {};
|
|
const textFields = ["name", "email", "loginMethod"] as const;
|
|
type TextField = (typeof textFields)[number];
|
|
const assignNullable = (field: TextField) => {
|
|
const value = user[field];
|
|
if (value === undefined) return;
|
|
const normalized = value ?? null;
|
|
values[field] = normalized;
|
|
updateSet[field] = normalized;
|
|
};
|
|
textFields.forEach(assignNullable);
|
|
if (user.lastSignedIn !== undefined) { values.lastSignedIn = user.lastSignedIn; updateSet.lastSignedIn = user.lastSignedIn; }
|
|
if (user.role !== undefined) { values.role = user.role; updateSet.role = user.role; }
|
|
else if (user.openId === ENV.ownerOpenId) { values.role = 'admin'; updateSet.role = 'admin'; }
|
|
if (!values.lastSignedIn) values.lastSignedIn = new Date();
|
|
if (Object.keys(updateSet).length === 0) updateSet.lastSignedIn = new Date();
|
|
await db.insert(users).values(values).onDuplicateKeyUpdate({ set: updateSet });
|
|
}
|
|
|
|
export async function getUserByOpenId(openId: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
// ============ PROJECTS ============
|
|
export async function createProject(data: InsertProject) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(projects).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function getProject(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(projects).where(eq(projects.id, id)).limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
export async function listProjects(userId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(projects).where(eq(projects.userId, userId)).orderBy(desc(projects.createdAt));
|
|
}
|
|
|
|
export async function updateProject(id: number, data: Partial<InsertProject>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(projects).set(data).where(eq(projects.id, id));
|
|
}
|
|
|
|
// ============ SEQUENCES ============
|
|
export async function createSequence(data: InsertSequence) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(sequences).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function listSequences(projectId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(sequences).where(eq(sequences.projectId, projectId)).orderBy(asc(sequences.startFrame));
|
|
}
|
|
|
|
export async function updateSequence(id: number, data: Partial<InsertSequence>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(sequences).set(data).where(eq(sequences.id, id));
|
|
}
|
|
|
|
// ============ FRAMES ============
|
|
export async function createFrames(data: InsertFrame[]) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
if (data.length === 0) return;
|
|
await db.insert(frames).values(data);
|
|
}
|
|
|
|
export async function getFrame(projectId: number, frameIndex: number) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(frames)
|
|
.where(and(eq(frames.projectId, projectId), eq(frames.frameIndex, frameIndex)))
|
|
.limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
export async function updateFrame(id: number, data: Partial<InsertFrame>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(frames).set(data).where(eq(frames.id, id));
|
|
}
|
|
|
|
// ============ LAYERS ============
|
|
export async function createLayer(data: InsertLayer) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(layers).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function listLayers(projectId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(layers).where(eq(layers.projectId, projectId)).orderBy(asc(layers.order));
|
|
}
|
|
|
|
export async function updateLayer(id: number, data: Partial<InsertLayer>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(layers).set(data).where(eq(layers.id, id));
|
|
}
|
|
|
|
export async function deleteLayer(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.delete(layers).where(eq(layers.id, id));
|
|
}
|
|
|
|
// ============ CHARACTERS ============
|
|
export async function createCharacter(data: InsertCharacter) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(characters).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function listCharacters(projectId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(characters).where(eq(characters.projectId, projectId));
|
|
}
|
|
|
|
export async function updateCharacter(id: number, data: Partial<InsertCharacter>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(characters).set(data).where(eq(characters.id, id));
|
|
}
|
|
|
|
// ============ AI ENGINES ============
|
|
export async function createAiEngine(data: InsertAiEngine) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(aiEngines).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function listAiEngines() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(aiEngines).orderBy(desc(aiEngines.createdAt));
|
|
}
|
|
|
|
export async function updateAiEngine(id: number, data: Partial<InsertAiEngine>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(aiEngines).set(data).where(eq(aiEngines.id, id));
|
|
}
|
|
|
|
export async function deleteAiEngine(id: number) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.delete(aiEngines).where(eq(aiEngines.id, id));
|
|
}
|
|
|
|
export async function getDefaultEngine(taskType: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(aiEngines)
|
|
.where(and(eq(aiEngines.taskType, taskType as any), eq(aiEngines.isDefault, true), eq(aiEngines.isActive, true)))
|
|
.limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
// ============ GENERATION JOBS ============
|
|
export async function createGenerationJob(data: InsertGenerationJob) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
const result = await db.insert(generationJobs).values(data);
|
|
return { id: result[0].insertId };
|
|
}
|
|
|
|
export async function listGenerationJobs(projectId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(generationJobs).where(eq(generationJobs.projectId, projectId)).orderBy(desc(generationJobs.createdAt));
|
|
}
|
|
|
|
export async function updateGenerationJob(id: number, data: Partial<InsertGenerationJob>) {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.update(generationJobs).set(data).where(eq(generationJobs.id, id));
|
|
}
|
|
|
|
// ============ ASSISTANT MESSAGES ============
|
|
export async function createAssistantMessage(data: InsertAssistantMessage) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("DB not available");
|
|
await db.insert(assistantMessages).values(data);
|
|
}
|
|
|
|
export async function listAssistantMessages(projectId: number) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(assistantMessages).where(eq(assistantMessages.projectId, projectId)).orderBy(asc(assistantMessages.createdAt));
|
|
}
|