2begyb's picture
Upload folder using huggingface_hub
e80793a verified
import { eq, desc, asc, sql } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import { InsertUser, User, users, projects, iterations, codeReferences, statistics } from "../drizzle/schema";
import { ENV } from './_core/env';
let _db: ReturnType<typeof drizzle> | null = null;
// In-memory storage for fallback mode
const memoryStore = {
users: [] as User[],
projects: [] as any[],
iterations: [] as any[],
codeReferences: [] as any[],
statistics: [] as any[],
};
// Lazily create the drizzle instance so local tooling can run without a DB.
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, using memory fallback:", error);
_db = null;
}
}
return _db;
}
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) {
console.warn("[Database] Using memory fallback for upsertUser");
const existingIdx = memoryStore.users.findIndex(u => u.openId === user.openId);
const userData: User = {
id: existingIdx >= 0 ? memoryStore.users[existingIdx].id : memoryStore.users.length + 1,
openId: user.openId,
name: user.name ?? null,
email: user.email ?? null,
loginMethod: user.loginMethod ?? null,
role: user.role ?? (user.openId === ENV.ownerOpenId ? 'admin' : 'user'),
createdAt: existingIdx >= 0 ? memoryStore.users[existingIdx].createdAt : new Date(),
updatedAt: new Date(),
lastSignedIn: user.lastSignedIn ?? new Date(),
};
if (existingIdx >= 0) {
memoryStore.users[existingIdx] = userData;
} else {
memoryStore.users.push(userData);
}
return;
}
try {
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,
});
} catch (error) {
console.error("[Database] Failed to upsert user:", error);
throw error;
}
}
export async function getUserByOpenId(openId: string) {
const db = await getDb();
if (!db) {
return memoryStore.users.find(u => u.openId === openId);
}
const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
return result.length > 0 ? result[0] : undefined;
}
export async function getOrCreateGuestUser() {
const guestOpenId = "guest-user";
let user = await getUserByOpenId(guestOpenId);
if (!user) {
await upsertUser({
openId: guestOpenId,
name: "Guest User",
email: "guest@example.com",
role: "admin",
});
user = await getUserByOpenId(guestOpenId);
}
return user;
}
// Projects queries
export async function createProject(userId: number, data: {
name: string;
description?: string;
mode: string;
contentType: string;
originalPrompt: string;
}) {
const db = await getDb();
if (!db) {
const newProject = {
id: memoryStore.projects.length + 1,
userId,
name: data.name,
description: data.description ?? null,
mode: data.mode,
contentType: data.contentType,
originalPrompt: data.originalPrompt,
status: "pending",
createdAt: new Date(),
updatedAt: new Date(),
finalOutput: null,
finalScore: 0,
completedAt: null,
};
memoryStore.projects.push(newProject);
return newProject;
}
const result = await db.insert(projects).values({
userId,
name: data.name,
description: data.description,
mode: data.mode as any,
contentType: data.contentType as any,
originalPrompt: data.originalPrompt,
status: "pending",
});
const projectId = (result as any).insertId || 0;
if (projectId > 0) {
const newProject = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
return newProject[0] || null;
}
return null;
}
export async function getUserProjects(userId: number, limit = 50) {
const db = await getDb();
if (!db) {
return memoryStore.projects
.filter(p => p.userId === userId)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.slice(0, limit);
}
return await db.select().from(projects)
.where(eq(projects.userId, userId))
.orderBy(desc(projects.createdAt))
.limit(limit);
}
export async function getProjectById(projectId: number) {
const db = await getDb();
if (!db) {
return memoryStore.projects.find(p => p.id === projectId) || null;
}
const result = await db.select().from(projects)
.where(eq(projects.id, projectId))
.limit(1);
return result[0] || null;
}
export async function updateProjectStatus(projectId: number, status: string, finalOutput?: string, finalScore?: number) {
const db = await getDb();
if (!db) {
const project = memoryStore.projects.find(p => p.id === projectId);
if (project) {
project.status = status;
if (finalOutput !== undefined) project.finalOutput = finalOutput;
if (finalScore !== undefined) project.finalScore = finalScore;
project.updatedAt = new Date();
if (status === "completed") project.completedAt = new Date();
return true;
}
return false;
}
await db.update(projects)
.set({
status: status as any,
finalOutput,
finalScore,
completedAt: status === "completed" ? new Date() : undefined,
})
.where(eq(projects.id, projectId));
return true;
}
// Iterations queries
export async function addIteration(projectId: number, version: number, data: {
qwenOutput?: string;
deepseekAnalysis?: string;
score: number;
passed: boolean;
scorecard?: any;
feedback?: any;
}) {
const db = await getDb();
if (!db) {
const newIteration = {
id: memoryStore.iterations.length + 1,
projectId,
version,
qwenOutput: data.qwenOutput ?? null,
deepseekAnalysis: data.deepseekAnalysis ?? null,
score: data.score,
passed: data.passed ? 1 : 0,
scorecard: data.scorecard ? JSON.stringify(data.scorecard) : null,
feedback: data.feedback ? JSON.stringify(data.feedback) : null,
createdAt: new Date(),
};
memoryStore.iterations.push(newIteration);
return { insertId: newIteration.id };
}
const result = await db.insert(iterations).values({
projectId,
version,
qwenOutput: data.qwenOutput,
deepseekAnalysis: data.deepseekAnalysis,
score: data.score,
passed: data.passed ? 1 : 0,
scorecard: data.scorecard ? JSON.stringify(data.scorecard) : null,
feedback: data.feedback ? JSON.stringify(data.feedback) : null,
});
return result;
}
export async function getProjectIterations(projectId: number) {
const db = await getDb();
if (!db) {
return memoryStore.iterations
.filter(i => i.projectId === projectId)
.sort((a, b) => a.version - b.version);
}
return await db.select().from(iterations)
.where(eq(iterations.projectId, projectId))
.orderBy(asc(iterations.version));
}
// Code References queries
export async function uploadCodeReference(userId: number, data: {
filename: string;
category: string;
description?: string;
content?: string;
s3Key?: string;
s3Url?: string;
mimeType?: string;
fileSize?: number;
analysis?: any;
tags?: string[];
}) {
const db = await getDb();
if (!db) {
const newRef = {
id: memoryStore.codeReferences.length + 1,
userId,
filename: data.filename,
category: data.category,
description: data.description ?? null,
content: data.content ?? null,
s3Key: data.s3Key ?? null,
s3Url: data.s3Url ?? null,
mimeType: data.mimeType ?? null,
fileSize: data.fileSize ?? null,
analysis: data.analysis ? JSON.stringify(data.analysis) : null,
tags: data.tags ? JSON.stringify(data.tags) : null,
createdAt: new Date(),
updatedAt: new Date(),
};
memoryStore.codeReferences.push(newRef);
return { insertId: newRef.id };
}
const result = await db.insert(codeReferences).values({
userId,
filename: data.filename,
category: data.category as any,
description: data.description,
content: data.content,
s3Key: data.s3Key,
s3Url: data.s3Url,
mimeType: data.mimeType,
fileSize: data.fileSize,
analysis: data.analysis ? JSON.stringify(data.analysis) : null,
tags: data.tags ? JSON.stringify(data.tags) : null,
});
return result;
}
export async function getUserCodeReferences(userId: number, category?: string) {
const db = await getDb();
if (!db) {
return memoryStore.codeReferences
.filter(r => r.userId === userId && (!category || r.category === category))
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
return await db.select().from(codeReferences)
.where(eq(codeReferences.userId, userId))
.orderBy(desc(codeReferences.createdAt));
}
export async function searchCodeReferences(userId: number, searchTerm: string) {
const db = await getDb();
if (!db) {
return memoryStore.codeReferences
.filter(r => r.userId === userId && (r.filename.includes(searchTerm) || (r.description && r.description.includes(searchTerm))))
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
return await db.select().from(codeReferences)
.where(eq(codeReferences.userId, userId))
.orderBy(desc(codeReferences.createdAt));
}
// Statistics queries
export async function updateDailyStatistics(date: string) {
const db = await getDb();
if (!db) return true;
const projectsData = await db.select({
total: sql`COUNT(*) as total`,
successful: sql`SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as successful`,
failed: sql`SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed`,
avgScore: sql`AVG(finalScore) as avgScore`,
}).from(projects)
.where(sql`DATE(createdAt) = ${date}`);
const data = projectsData[0] || {};
await db.insert(statistics).values({
date,
totalProjects: (data.total as number) || 0,
successfulProjects: (data.successful as number) || 0,
failedProjects: (data.failed as number) || 0,
avgScore: Math.round((data.avgScore as number) || 0),
}).onDuplicateKeyUpdate({
set: {
totalProjects: (data.total as number) || 0,
successfulProjects: (data.successful as number) || 0,
failedProjects: (data.failed as number) || 0,
avgScore: Math.round((data.avgScore as number) || 0),
},
});
return true;
}