Add folder and sort order functionality to database schema and operations

This commit is contained in:
2026-01-31 18:43:53 +01:00
parent f5bc0dbe69
commit 740af2021c

View File

@@ -6,6 +6,17 @@ export type Text = {
created_at: number;
updated_at: number;
last_saved_version_id: string | null;
folder_id: string | null;
sort_order: number | null;
};
export type Folder = {
id: string;
name: string;
parent_id: string | null;
created_at: number;
updated_at: number;
sort_order: number | null;
};
export type TextVersion = {
@@ -26,6 +37,15 @@ export type TextDraft = {
const MIGRATIONS = [
"PRAGMA foreign_keys = ON;",
`CREATE TABLE IF NOT EXISTS folders(
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
sort_order INTEGER,
FOREIGN KEY(parent_id) REFERENCES folders(id)
);`,
`CREATE TABLE IF NOT EXISTS prompts(
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
@@ -50,7 +70,10 @@ const MIGRATIONS = [
FOREIGN KEY(prompt_id) REFERENCES prompts(id) ON DELETE CASCADE
);`,
"CREATE INDEX IF NOT EXISTS idx_prompt_versions_prompt_time ON prompt_versions(prompt_id, created_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_prompts_updated ON prompts(updated_at DESC);"
"CREATE INDEX IF NOT EXISTS idx_prompts_updated ON prompts(updated_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id);",
"CREATE INDEX IF NOT EXISTS idx_folders_updated ON folders(updated_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_prompts_folder ON prompts(folder_id);"
];
let dbPromise: Promise<Database> | null = null;
@@ -59,6 +82,21 @@ async function migrate(db: Database) {
for (const statement of MIGRATIONS) {
await db.execute(statement);
}
await ensureColumn(db, "prompts", "folder_id", "TEXT REFERENCES folders(id) ON DELETE SET NULL");
await ensureColumn(db, "prompts", "sort_order", "INTEGER");
}
async function ensureColumn(
db: Database,
table: string,
column: string,
definition: string
) {
const columns = await db.select<{ name: string }[]>(
`PRAGMA table_info(${table})`
);
if (columns.some((col) => col.name === column)) return;
await db.execute(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
}
export async function getDb(): Promise<Database> {
@@ -79,7 +117,11 @@ export async function initDb() {
export async function listTexts(): Promise<Text[]> {
const db = await getDb();
const rows = await db.select<Text[]>(
"SELECT * FROM prompts ORDER BY updated_at DESC"
`SELECT * FROM prompts
ORDER BY
CASE WHEN sort_order IS NULL THEN 1 ELSE 0 END,
sort_order ASC,
updated_at DESC`
);
return rows;
}
@@ -97,7 +139,10 @@ export async function searchTexts(term: string): Promise<Text[]> {
WHERE v.prompt_id = p.id
AND LOWER(v.body) LIKE $1
)
ORDER BY p.updated_at DESC`,
ORDER BY
CASE WHEN p.sort_order IS NULL THEN 1 ELSE 0 END,
p.sort_order ASC,
p.updated_at DESC`,
[normalized]
);
return rows;
@@ -144,7 +189,9 @@ export async function getDraft(promptId: string): Promise<TextDraft | null> {
export async function createText(
title: string,
body: string
body: string,
folderId: string | null = null,
sortOrder: number | null = null
): Promise<{ textId: string; versionId: string; createdAt: number }> {
const db = await getDb();
const now = Date.now();
@@ -152,8 +199,8 @@ export async function createText(
const versionId = crypto.randomUUID();
await db.execute(
"INSERT INTO prompts(id, title, created_at, updated_at, last_saved_version_id) VALUES($1, $2, $3, $4, $5)",
[textId, title, now, now, versionId]
"INSERT INTO prompts(id, title, created_at, updated_at, last_saved_version_id, folder_id, sort_order) VALUES($1, $2, $3, $4, $5, $6, $7)",
[textId, title, now, now, versionId, folderId, sortOrder]
);
await db.execute(
@@ -166,6 +213,118 @@ export async function createText(
return { textId, versionId, createdAt: now };
}
export async function listFolders(): Promise<Folder[]> {
const db = await getDb();
return db.select<Folder[]>(
`SELECT * FROM folders
ORDER BY
CASE WHEN sort_order IS NULL THEN 1 ELSE 0 END,
sort_order ASC,
updated_at DESC`
);
}
export async function createFolder(
name: string,
parentId: string | null = null,
sortOrder: number | null = null
): Promise<{ folderId: string; createdAt: number }> {
const db = await getDb();
const now = Date.now();
const folderId = crypto.randomUUID();
await db.execute(
"INSERT INTO folders(id, name, parent_id, created_at, updated_at, sort_order) VALUES($1, $2, $3, $4, $5, $6)",
[folderId, name, parentId, now, now, sortOrder]
);
return { folderId, createdAt: now };
}
export async function updateFolderName(folderId: string, name: string) {
const db = await getDb();
const now = Date.now();
await db.execute(
"UPDATE folders SET name = $1, updated_at = $2 WHERE id = $3",
[name, now, folderId]
);
}
export async function moveFolder(
folderId: string,
parentId: string | null,
sortOrder: number | null = null
) {
const db = await getDb();
const now = Date.now();
await db.execute(
"UPDATE folders SET parent_id = $1, sort_order = $2, updated_at = $3 WHERE id = $4",
[parentId, sortOrder, now, folderId]
);
}
export async function setFolderOrder(folderIds: string[]) {
const db = await getDb();
await db.execute("BEGIN TRANSACTION");
try {
for (let i = 0; i < folderIds.length; i += 1) {
await db.execute("UPDATE folders SET sort_order = $1 WHERE id = $2", [
i,
folderIds[i]
]);
}
await db.execute("COMMIT");
} catch (error) {
await db.execute("ROLLBACK");
throw error;
}
}
export async function deleteFolder(folderId: string) {
const db = await getDb();
const rows = await db.select<{ parent_id: string | null }[]>(
"SELECT parent_id FROM folders WHERE id = $1 LIMIT 1",
[folderId]
);
const parentId = rows[0]?.parent_id ?? null;
await db.execute("UPDATE folders SET parent_id = $1 WHERE parent_id = $2", [
parentId,
folderId
]);
await db.execute("UPDATE prompts SET folder_id = $1 WHERE folder_id = $2", [
parentId,
folderId
]);
await db.execute("DELETE FROM folders WHERE id = $1", [folderId]);
}
export async function moveTextToFolder(
textId: string,
folderId: string | null,
sortOrder: number | null = null
) {
const db = await getDb();
await db.execute(
"UPDATE prompts SET folder_id = $1, sort_order = $2 WHERE id = $3",
[folderId, sortOrder, textId]
);
}
export async function setTextOrder(textIds: string[]) {
const db = await getDb();
await db.execute("BEGIN TRANSACTION");
try {
for (let i = 0; i < textIds.length; i += 1) {
await db.execute("UPDATE prompts SET sort_order = $1 WHERE id = $2", [
i,
textIds[i]
]);
}
await db.execute("COMMIT");
} catch (error) {
await db.execute("ROLLBACK");
throw error;
}
}
export async function saveManualVersion(
promptId: string,
title: string,