Add folder and sort order functionality to database schema and operations
This commit is contained in:
171
src/lib/db.ts
171
src/lib/db.ts
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user