server: reconcile ai_sessions persistence to Chunk 9 AISession shape
Reshape the ai_sessions table (migration 003) and mapping/routes to the full AISession contract: title, kind, notes, created_at, last_message_at; drop legacy role/model/settings. toAISession now maps the new columns and routes create/patch/messages handlers use the new shape.
This commit is contained in:
+12
-7
@@ -1,6 +1,5 @@
|
||||
import type {
|
||||
AISession,
|
||||
AISessionRole,
|
||||
AuditLogEntry,
|
||||
Board,
|
||||
Book,
|
||||
@@ -11,6 +10,7 @@ import type {
|
||||
PlotBeat,
|
||||
Project,
|
||||
ProjectMeta,
|
||||
SessionKind,
|
||||
User,
|
||||
WorldItem,
|
||||
} from "@pubscript/core";
|
||||
@@ -98,12 +98,14 @@ export interface PlotBeatRow {
|
||||
export interface AISessionRow {
|
||||
id: string;
|
||||
project_id: string;
|
||||
role: string;
|
||||
model: string;
|
||||
settings: string;
|
||||
title: string;
|
||||
kind: string;
|
||||
messages: string;
|
||||
suggestions: string;
|
||||
notes: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
last_message_at: string | null;
|
||||
}
|
||||
|
||||
// ---- Row -> core types ----
|
||||
@@ -235,11 +237,14 @@ export function toAISession(r: AISessionRow): AISession {
|
||||
return {
|
||||
id: r.id,
|
||||
projectId: r.project_id,
|
||||
role: r.role as AISessionRole,
|
||||
model: r.model,
|
||||
settings: JSON.parse(r.settings),
|
||||
title: r.title,
|
||||
kind: r.kind as SessionKind,
|
||||
messages: JSON.parse(r.messages),
|
||||
suggestions: JSON.parse(r.suggestions),
|
||||
notes: JSON.parse(r.notes),
|
||||
createdAt: r.created_at,
|
||||
updatedAt: r.updated_at,
|
||||
lastMessageAt: r.last_message_at,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Chunk #9: reshape ai_sessions to the full AISession shape.
|
||||
-- Adds title, kind, notes, created_at, last_message_at; drops role, model, settings.
|
||||
CREATE TABLE ai_sessions_new (
|
||||
id TEXT PRIMARY KEY,
|
||||
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
kind TEXT NOT NULL DEFAULT 'session',
|
||||
messages TEXT NOT NULL DEFAULT '[]',
|
||||
suggestions TEXT NOT NULL DEFAULT '[]',
|
||||
notes TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
last_message_at TEXT
|
||||
);
|
||||
|
||||
INSERT INTO ai_sessions_new (id, project_id, title, kind, messages, suggestions, notes, created_at, updated_at, last_message_at)
|
||||
SELECT id, project_id, '', 'session', messages, suggestions, '[]', updated_at, updated_at, NULL
|
||||
FROM ai_sessions;
|
||||
|
||||
DROP TABLE ai_sessions;
|
||||
|
||||
ALTER TABLE ai_sessions_new RENAME TO ai_sessions;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_sessions_project ON ai_sessions(project_id);
|
||||
+35
-17
@@ -92,11 +92,14 @@ const beatCreate = z.object({
|
||||
});
|
||||
const aiSessionCreate = z.object({
|
||||
id: z.string().optional(),
|
||||
role: z.string().min(1),
|
||||
model: z.string().min(1),
|
||||
settings: z.record(z.unknown()).default({}),
|
||||
messages: z.array(z.any()).optional(),
|
||||
suggestions: z.array(z.any()).optional(),
|
||||
title: z.string().default(""),
|
||||
kind: z.enum(["session", "agent"]).default("session"),
|
||||
messages: z.array(z.any()).default([]),
|
||||
suggestions: z.array(z.any()).default([]),
|
||||
notes: z.array(z.any()).default([]),
|
||||
createdAt: z.string().optional(),
|
||||
updatedAt: z.string().optional(),
|
||||
lastMessageAt: z.string().nullable().optional(),
|
||||
});
|
||||
const aiMessageAppend = z.object({ role: z.string(), content: z.string() });
|
||||
const aiSuggestionAdd = z.object({ kind: z.string(), text: z.string() });
|
||||
@@ -565,14 +568,15 @@ export function registerRoutes(app: FastifyInstance): void {
|
||||
const sid = body.id ?? ulid();
|
||||
const t = now();
|
||||
db.prepare(
|
||||
`INSERT INTO ai_sessions (id, project_id, role, model, settings, messages, suggestions, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`INSERT INTO ai_sessions (id, project_id, title, kind, messages, suggestions, notes, created_at, updated_at, last_message_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
project_id = excluded.project_id, role = excluded.role, model = excluded.model,
|
||||
settings = excluded.settings, messages = excluded.messages, suggestions = excluded.suggestions,
|
||||
updated_at = excluded.updated_at`,
|
||||
).run(sid, id, body.role, body.model, JSON.stringify(body.settings),
|
||||
JSON.stringify(body.messages ?? []), JSON.stringify(body.suggestions ?? []), t);
|
||||
title = excluded.title, kind = excluded.kind,
|
||||
messages = excluded.messages, suggestions = excluded.suggestions, notes = excluded.notes,
|
||||
updated_at = excluded.updated_at, last_message_at = excluded.last_message_at`,
|
||||
).run(sid, id, body.title, body.kind, JSON.stringify(body.messages),
|
||||
JSON.stringify(body.suggestions), JSON.stringify(body.notes),
|
||||
body.createdAt ?? t, t, body.lastMessageAt ?? null);
|
||||
const row = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(sid) as AISessionRow;
|
||||
return { session: toAISession(row) };
|
||||
});
|
||||
@@ -590,11 +594,25 @@ export function registerRoutes(app: FastifyInstance): void {
|
||||
const row = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow | undefined;
|
||||
if (!row) throw app.httpErrors.create(404, { error: "not_found" });
|
||||
requireProjectAccess(app, req, row.project_id, true);
|
||||
const body = z.object({ settings: z.record(z.unknown()).optional(), model: z.string().optional() }).parse(req.body);
|
||||
const settings = body.settings ? JSON.stringify(body.settings) : row.settings;
|
||||
const model = body.model ?? row.model;
|
||||
const body = z.object({
|
||||
title: z.string().optional(),
|
||||
kind: z.enum(["session", "agent"]).optional(),
|
||||
messages: z.array(z.any()).optional(),
|
||||
suggestions: z.array(z.any()).optional(),
|
||||
notes: z.array(z.any()).optional(),
|
||||
lastMessageAt: z.string().nullable().optional(),
|
||||
}).parse(req.body);
|
||||
const t = now();
|
||||
db.prepare("UPDATE ai_sessions SET settings = ?, model = ?, updated_at = ? WHERE id = ?").run(settings, model, t, id);
|
||||
db.prepare(
|
||||
`UPDATE ai_sessions SET title = ?, kind = ?, messages = ?, suggestions = ?, notes = ?, last_message_at = ?, updated_at = ? WHERE id = ?`
|
||||
).run(
|
||||
body.title ?? row.title, body.kind ?? row.kind,
|
||||
JSON.stringify(body.messages ?? JSON.parse(row.messages)),
|
||||
JSON.stringify(body.suggestions ?? JSON.parse(row.suggestions)),
|
||||
JSON.stringify(body.notes ?? JSON.parse(row.notes)),
|
||||
body.lastMessageAt ?? row.last_message_at,
|
||||
t, id,
|
||||
);
|
||||
const updated = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow;
|
||||
return { session: toAISession(updated) };
|
||||
});
|
||||
@@ -617,7 +635,7 @@ export function registerRoutes(app: FastifyInstance): void {
|
||||
const messages = JSON.parse(row.messages) as any[];
|
||||
messages.push({ role, content, at: now() });
|
||||
const t = now();
|
||||
db.prepare("UPDATE ai_sessions SET messages = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(messages), t, id);
|
||||
db.prepare("UPDATE ai_sessions SET messages = ?, last_message_at = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(messages), t, t, id);
|
||||
const updated = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow;
|
||||
return { session: toAISession(updated) };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user