import { dbGlobal } from "drizzle-pkg/lib/db"; import { characterSessions, characterMessages } from "drizzle-pkg/lib/schema/character"; import { eq, desc, asc, and, isNull, sql, max } from "drizzle-orm"; import type { CharacterSessionRow, CharacterMessageRow } from "./types"; export async function createCharacterSession(params: { userId?: number | null; tempToken?: string | null; cardId?: number | null; modelId?: number | null; expiresAt?: Date | null; }): Promise { const id = `cs_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`; const [row] = await dbGlobal .insert(characterSessions) .values({ id, userId: params.userId ?? null, tempToken: params.tempToken ?? null, cardId: params.cardId ?? null, modelId: params.modelId ?? null, expiresAt: params.expiresAt ?? null, }) .returning(); return row!; } export async function getCharacterSessionById(id: string): Promise { const [row] = await dbGlobal .select() .from(characterSessions) .where(and(eq(characterSessions.id, id), isNull(characterSessions.deletedAt))) .limit(1); return row ?? null; } export async function getCharacterSessionByIdAndUser( id: string, userId: number | null, tempToken: string | null, ): Promise { const conditions = [eq(characterSessions.id, id), isNull(characterSessions.deletedAt)]; if (userId) { conditions.push(eq(characterSessions.userId, userId)); } else if (tempToken) { conditions.push(eq(characterSessions.tempToken, tempToken)); } else { return null; } const [row] = await dbGlobal .select() .from(characterSessions) .where(and(...conditions)) .limit(1); return row ?? null; } export async function listCharacterSessions(params: { userId?: number | null; tempToken?: string | null; cardId?: number | null; page?: number; pageSize?: number; }): Promise<{ list: CharacterSessionRow[]; total: number; page: number; pageSize: number; totalPages: number }> { const { page = 1, pageSize = 30 } = params; const conditions = [isNull(characterSessions.deletedAt)]; if (params.userId) { conditions.push(eq(characterSessions.userId, params.userId)); } else if (params.tempToken) { conditions.push(eq(characterSessions.tempToken, params.tempToken)); } else { return { list: [], total: 0, page, pageSize, totalPages: 0 }; } if (params.cardId) { conditions.push(eq(characterSessions.cardId, params.cardId)); } const where = and(...conditions); const [countResult] = await dbGlobal .select({ count: sql`count(*)` }) .from(characterSessions) .where(where); const total = countResult?.count ?? 0; const totalPages = Math.ceil(total / pageSize); const list = await dbGlobal .select() .from(characterSessions) .where(where) .orderBy(desc(characterSessions.lastActiveAt)) .limit(pageSize) .offset((page - 1) * pageSize); return { list, total, page, pageSize, totalPages }; } export async function updateCharacterSession( id: string, data: { title?: string; cardId?: number | null; modelId?: number | null; lastActiveAt?: Date; }, ): Promise { const updates: Record = {}; if (data.title !== undefined) updates.title = data.title; if (data.cardId !== undefined) updates.cardId = data.cardId; if (data.modelId !== undefined) updates.modelId = data.modelId; if (data.lastActiveAt !== undefined) updates.lastActiveAt = data.lastActiveAt; if (Object.keys(updates).length === 0) return; await dbGlobal.update(characterSessions).set(updates).where(eq(characterSessions.id, id)); } export async function softDeleteCharacterSession(id: string): Promise { await dbGlobal .update(characterSessions) .set({ deletedAt: new Date() }) .where(eq(characterSessions.id, id)); } export async function getMessagesByCharacterSession( sessionId: string, params?: { limit?: number; latest?: boolean }, ): Promise { const { limit = 50, latest = false } = params ?? {}; const baseQuery = () => dbGlobal.select().from(characterMessages).where(eq(characterMessages.sessionId, sessionId)); if (latest) { const rows = await baseQuery().orderBy(desc(characterMessages.sortOrder)).limit(limit); return rows.reverse(); } return baseQuery().orderBy(asc(characterMessages.sortOrder)).limit(limit); } export async function getMaxCharacterSortOrder(sessionId: string): Promise { const [result] = await dbGlobal .select({ maxSort: max(characterMessages.sortOrder) }) .from(characterMessages) .where(eq(characterMessages.sessionId, sessionId)); return result?.maxSort ?? 0; } export async function saveCharacterMessage(params: { sessionId: string; role: string; content: string; sortOrder: number; }): Promise { const id = `cm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`; const [row] = await dbGlobal .insert(characterMessages) .values({ id, sessionId: params.sessionId, role: params.role, content: params.content, sortOrder: params.sortOrder, }) .returning(); return row!; } export async function countCharacterMessagesByRole(sessionId: string, role: string): Promise { const [result] = await dbGlobal .select({ cnt: sql`cast(count(*) as integer)` }) .from(characterMessages) .where(and(eq(characterMessages.sessionId, sessionId), eq(characterMessages.role, role))); return result?.cnt ?? 0; } export async function touchCharacterSession(sessionId: string): Promise { await dbGlobal .update(characterSessions) .set({ lastActiveAt: new Date() }) .where(eq(characterSessions.id, sessionId)); }