From f350ca988af4f48672c6dc8f657dc9e5121fe35c Mon Sep 17 00:00:00 2001 From: Chris Griffing Date: Thu, 25 Jan 2024 15:14:10 -0800 Subject: [PATCH] feat: seed initial data of a book with a chapter and a snippet --- src/App.tsx | 21 +++++-- src/components/SettingsModal.tsx | 8 ++- src/data/db.ts | 36 +++++------- ...2024-01-23T23:15:28.004Z-initial-schema.ts | 56 ++++++++++++++++--- src/routes/Chapter.tsx | 2 +- src/state/main.ts | 45 ++++++++++++++- 6 files changed, 128 insertions(+), 40 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 71c8ad2..49b2ea4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,7 @@ import { CreateOrUpdateModal } from "./components/CreateOrUpdateModal"; import { SettingsModal } from "./components/SettingsModal"; import { + appReady, currentBooks, currentChapter, currentModel, @@ -70,15 +71,28 @@ export function App() { const [currentBook, setCurrentBook] = useState(); - const [books] = useAtom(currentBooks); + const [ready, setAppReady] = useAtom(appReady); const [, setFetchTimestamp] = useAtom(fetchTimestamp); + const [books] = useAtom(currentBooks); const [whisperModel] = useAtom(currentModel); useEffect(() => { - if (!whisperModel) { + async function initialize() { + await DBUtils.seed(); + startTransition(() => { + setAppReady(true); + setFetchTimestamp(Date.now()); + }); + } + + initialize(); + }, [setAppReady, setFetchTimestamp]); + + useEffect(() => { + if (!whisperModel && ready) { openSettingsModal(); } - }, [whisperModel, openSettingsModal]); + }, [whisperModel, openSettingsModal, ready]); return ( <> @@ -265,7 +279,6 @@ export function App() { leftSection={} onClick={(e) => { e.stopPropagation(); - console.log("hmm", node.data.book); setCurrentBook(node.data.book); openEditBookModal(); }} diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index cb20fe6..b363700 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -43,11 +43,11 @@ export function SettingsModal({ const [threads, setThreads] = useState(2); useEffect(() => { - if (settings.selectedModel) { + if (settings?.selectedModel) { setSelectedModel(settings.selectedModel as WhisperModelName); } - if (settings.threads) { + if (settings?.threads) { setThreads(settings.threads); } }, [settings]); @@ -83,6 +83,10 @@ export function SettingsModal({ return; } + if (!settings) { + return; + } + // TODO: pass values to be saved at App level // FOR NOW: save directly from here diff --git a/src/data/db.ts b/src/data/db.ts index bccc5cc..a620c69 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -9,8 +9,10 @@ import * as snippetSchema from "./models/snippets"; import { RuntimeMigrationProvider } from "./migrations/provider"; +const dbFileName = "database.sqlite3"; + const { driver, sql, getDatabaseFile, overwriteDatabaseFile } = - new SQLocalDrizzle("database.sqlite3"); + new SQLocalDrizzle(dbFileName); export const db = drizzle(driver, { schema: { @@ -20,7 +22,7 @@ export const db = drizzle(driver, { }, }); -const { dialect } = new SQLocalKysely("database.sqlite3"); +const { dialect } = new SQLocalKysely(dbFileName); const kyselyDb = new Kysely({ dialect, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -33,33 +35,23 @@ const migrator = new Migrator({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).resetDB = async function () { - await sql`DROP TABLE IF EXISTS books`; - await sql`DROP TABLE IF EXISTS chapters`; - await sql`DROP TABLE IF EXISTS snippets`; - await sql`DROP TABLE IF EXISTS settings`; + // await sql`DROP TABLE IF EXISTS books`; + // await sql`DROP TABLE IF EXISTS chapters`; + // await sql`DROP TABLE IF EXISTS snippets`; + // await sql`DROP TABLE IF EXISTS settings`; + await migrator.migrateDown(); + await kyselyDb.destroy(); + (await navigator.storage.getDirectory()).removeEntry(dbFileName); }; -// IIFE to make sure that the schema is created before the app starts -(async () => { +async function seed() { await sql`PRAGMA foreign_keys = ON;`; - - await sql`INSERT OR IGNORE INTO settings(id,user_id,threads,selected_model,created_at,modified_at) -VALUES (0,1,2,'',0,0)`; - await migrator.migrateToLatest(); - - // get available books - // if empty, run seed process - - // seed book - - // seed chapters for book - - // seed snippets for chapters -})(); +} export const DBUtils = { getDatabaseFile, overwriteDatabaseFile, sql, + seed, }; diff --git a/src/data/migrations/2024-01-23T23:15:28.004Z-initial-schema.ts b/src/data/migrations/2024-01-23T23:15:28.004Z-initial-schema.ts index ba7c288..a300796 100644 --- a/src/data/migrations/2024-01-23T23:15:28.004Z-initial-schema.ts +++ b/src/data/migrations/2024-01-23T23:15:28.004Z-initial-schema.ts @@ -1,4 +1,4 @@ -import { Kysely } from "kysely"; +import { Kysely, sql } from "kysely"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function up(db: Kysely): Promise { @@ -26,8 +26,16 @@ export async function up(db: Kysely): Promise { .addColumn("modified_at", "integer", (col) => col.defaultTo(Date.now()).notNull() ) - .addColumn("book_id", "integer", (col) => col.notNull().onDelete("cascade")) - .addForeignKeyConstraint("book_id_fk", ["book_id"], "books", ["id"]) + .addColumn("book_id", "integer", (col) => col.notNull()) + .addForeignKeyConstraint( + "book_id_fk", + ["book_id"], + "books", + ["id"], + (key) => { + return key.onDelete("cascade"); + } + ) .ifNotExists() .execute(); @@ -47,12 +55,16 @@ export async function up(db: Kysely): Promise { .addColumn("processed_at", "integer", (col) => col.defaultTo(0)) .addColumn("finished_at", "integer", (col) => col.defaultTo(0)) .addColumn("raw_recording_content", "text", (col) => col.defaultTo("")) - .addColumn("chapter_id", "integer", (col) => - col.notNull().onDelete("cascade") + .addColumn("chapter_id", "integer", (col) => col.notNull()) + .addForeignKeyConstraint( + "chapter_id_fk", + ["chapter_id"], + "chapters", + ["id"], + (key) => { + return key.onDelete("cascade"); + } ) - .addForeignKeyConstraint("chapter_id_fk", ["chapter_id"], "chapters", [ - "id", - ]) .ifNotExists() .execute(); @@ -70,6 +82,34 @@ export async function up(db: Kysely): Promise { ) .ifNotExists() .execute(); + + // Seed initial data for user + + try { + await sql` + INSERT OR IGNORE INTO settings(id,user_id,threads,selected_model,created_at,modified_at) + VALUES (0,1,2,'',0,0) +`.execute(db); + + await sql` + INSERT INTO books(title) + VALUES ('Introduction') +`.execute(db); + + await sql` + INSERT INTO chapters(book_id,label,sort_order) + VALUES (1,'Welcome',0) +`.execute(db); + + const now = Date.now() + 60 * 60 * 1000; + + await sql` + INSERT INTO snippets(chapter_id,label,content,sort_order,recorded_at,processed_at,finished_at) + VALUES (1,'Dark and Stormy Night', 'It was a dark and stormy night; the rain fell in torrents—except at occasional intervals, when it was checked by a violent gust of wind which swept up the streets (for it is in London that our scene lies), rattling along the housetops, and fiercely agitating the scanty flame of the lamps that struggled against the darkness.',0,${now},${now},${now}) +`.execute(db); + } catch (e) { + console.log("Error in migration", { e }); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/routes/Chapter.tsx b/src/routes/Chapter.tsx index e07a59d..d548d51 100644 --- a/src/routes/Chapter.tsx +++ b/src/routes/Chapter.tsx @@ -115,7 +115,7 @@ export function Chapter() { - {snippets.length > 0 && ( + {snippets?.length && snippets?.length > 0 && ( { diff --git a/src/state/main.ts b/src/state/main.ts index 524a17a..97552d0 100644 --- a/src/state/main.ts +++ b/src/state/main.ts @@ -6,12 +6,18 @@ import { SettingsQueries } from "../data/repositories/settings"; import { loadOrGetModel } from "../utils/model-data"; import { WhisperModelName } from "../types"; +export const appReady = atom(false); export const fetchTimestamp = atom(0); // Books export const currentBooks = atom(async (get) => { get(fetchTimestamp); + const ready = get(appReady); + if (!ready) { + return []; + } + return BooksQueries.getBooksWithChapters(); }); export const currentBook = atom(undefined); @@ -21,13 +27,27 @@ export const currentBook = atom(undefined); export const currentChapterId = atom(undefined); export const currentChapter = atom((get) => { get(fetchTimestamp); + const ready = get(appReady); + if (!ready) { + return; + } + const chapterId = get(currentChapterId); - return ChaptersCRUD.read(chapterId || -1); + if (chapterId === 0 || chapterId) { + return ChaptersCRUD.read(chapterId || -1); + } }); export const currentSnippets = atom((get) => { get(fetchTimestamp); + const ready = get(appReady); + if (!ready) { + return []; + } + const chapterId = get(currentChapterId); - return SnippetsQueries.getSnippetsForChapter(chapterId || -1); + if (chapterId === 0 || chapterId) { + return SnippetsQueries.getSnippetsForChapter(chapterId); + } }); // Snippets @@ -35,14 +55,26 @@ export const currentSnippets = atom((get) => { export const currentSnippetId = atom(undefined); export const currentSnippet = atom((get) => { get(fetchTimestamp); + const ready = get(appReady); + if (!ready) { + return; + } + const snippetId = get(currentSnippetId); - return SnippetsCRUD.read(snippetId || -1); + if (snippetId === 0 || snippetId) { + return SnippetsCRUD.read(snippetId); + } }); // Settings export const currentSettings = atom(async (get) => { get(fetchTimestamp); + const ready = get(appReady); + if (!ready) { + return; + } + return SettingsQueries.getSettingsForUser(); }); @@ -52,6 +84,10 @@ declare const Module: any; export const currentModel = atom( async (get) => { const settings = await get(currentSettings); + if (!settings) { + return; + } + return loadOrGetModel(settings.selectedModel as WhisperModelName, () => {}); }, (_get, _set, update) => { @@ -61,6 +97,9 @@ export const currentModel = atom( export const whisperInstance = atom(async (get) => { const model = await get(currentModel); const settings = await get(currentSettings); + if (!settings) { + return; + } if (model) { try {