From c226669a09d33c416a7d975f0c4c37ec026a728a Mon Sep 17 00:00:00 2001 From: Luciano Laratelli Date: Fri, 14 Mar 2025 16:12:36 -0400 Subject: [PATCH] automate database init from CLI. add sqlite_history may god bless simon willison --- Makefile | 16 ++++++++ dev/migration.clj | 15 ++++++++ dev/sqlite-history.py | 70 ++++++++++++++++++++++++++++++++++ dev/tasks.clj | 45 ++++++++++++++++++++++ src/com/biffweb/my_project.clj | 3 -- 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 Makefile create mode 100644 dev/migration.clj create mode 100644 dev/sqlite-history.py create mode 100644 dev/tasks.clj diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a78e38b --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: tidy +tidy: + clojure-lsp clean-ns && clojure-lsp format && clojure-lsp diagnostics + +.PHONY: storage/site.db +storage/site.db: + rm -f storage/* + sqlite3 storage/site.db 'PRAGMA journal_mode=WAL;' + clj -M dev/migration.clj + uv run dev/sqlite-history.py + +.PHONY: db +db: storage/site.db + +repl: storage/site.db + clj -M:dev dev diff --git a/dev/migration.clj b/dev/migration.clj new file mode 100644 index 0000000..1b1770f --- /dev/null +++ b/dev/migration.clj @@ -0,0 +1,15 @@ +(ns migration + #_{:clj-kondo/ignore [:unused-namespace]} + (:require [migratus.core :as migratus] + [taoensso.telemere.timbre :as log])) + +(def config {:store :database + :migration-dir "migrations/" + :db {:dbtype "sqlite" + :dbname "storage/site.db"}}) + +;initialize the database using the 'init.sql' script +;; (migratus/init config) + +;apply pending migrations +(migratus/migrate config) diff --git a/dev/sqlite-history.py b/dev/sqlite-history.py new file mode 100644 index 0000000..74a8248 --- /dev/null +++ b/dev/sqlite-history.py @@ -0,0 +1,70 @@ +# /// script +# requires-python = ">=3.13" +# dependencies = [ +# "sqlite-history", +# ] +# /// + + +import sqlite_history +import sqlite3 + + +def configure_triggers(database_path, tables): + db = sqlite3.connect(database_path) + for table in tables: + if table.startswith("_") and table.endswith("_history"): + continue + # Does a history table exist already? + history_table_name = f"_{table}_history" + cursor = db.execute( + f"SELECT name FROM sqlite_master WHERE type='table' AND name='{history_table_name}';" + ) + if cursor.fetchone(): + print(f"History table {history_table_name} already exists - skipping.") + continue + sqlite_history.configure_history(db, table) + print(f"configured trigger for {table}") + + +def all_regular_tables(database_path): + """'Regular' excludes FTS and related tables.""" + conn = sqlite3.connect(database_path) + cursor = conn.cursor() + hidden_tables = [ + r[0] + for r in ( + cursor.execute( + """ + SELECT NAME FROM sqlite_master + WHERE type = 'table' + AND ( + sql LIKE '%VIRTUAL TABLE%USING FTS%' + ) OR name IN ('sqlite_stat1', 'sqlite_stat2', 'sqlite_stat3', 'sqlite_stat4') + """ + ) + ).fetchall() + ] + hidden_tables_copy = hidden_tables[:] + regular_tables = [] + for row in cursor.execute( + "SELECT name FROM sqlite_master WHERE type='table';" + ).fetchall(): + table_name = row[0] + should_be_hidden = any( + table_name.startswith(hidden_table) for hidden_table in hidden_tables_copy + ) + if not should_be_hidden: + regular_tables.append(table_name) + return regular_tables + + +def main() -> None: + database_path = "storage/site.db" + tables = all_regular_tables(database_path) + + configure_triggers(database_path, tables) + + +if __name__ == "__main__": + main() diff --git a/dev/tasks.clj b/dev/tasks.clj new file mode 100644 index 0000000..1d4983f --- /dev/null +++ b/dev/tasks.clj @@ -0,0 +1,45 @@ +(ns tasks + (:require [com.biffweb.task-runner :refer [run-task]] + [com.biffweb.tasks :as tasks] + [com.biffweb.tasks.lazy.babashka.fs :as fs] + [com.biffweb.tasks.lazy.babashka.process :refer [shell]] + [com.biffweb.tasks.lazy.clojure.java.io :as io] + [com.biffweb.tasks.lazy.com.biffweb.config :as config])) + +(def config (delay (config/use-aero-config {:biff.config/skip-validation true}))) + +(defn dev + "Starts the app locally. + + After running, wait for the `System started` message. Connect your editor to + nrepl port 7888 (by default). Whenever you save a file, Biff will: + + - Evaluate any changed Clojure files + - Regenerate static HTML files + - Run tests" + [] + (if-not (fs/exists? "target/resources") + ;; This is an awful hack. We have to run the app in a new process, otherwise + ;; target/resources won't be included in the classpath. Downside of not + ;; using bb tasks anymore -- no longer have a lightweight parent process + ;; that can create the directory before starting the JVM. + (do + (io/make-parents "target/resources/_") + (shell "clj" "-M:dev" "dev")) + (let [{:keys [biff.tasks/main-ns biff.nrepl/port] :as _ctx} @config] + + (when-not (fs/exists? "config.env") + (run-task "generate-config")) + (when (fs/exists? "package.json") + (shell "npm install")) + (spit ".nrepl-port" port) + ((requiring-resolve (symbol (str main-ns) "-main")))))) + +;; Tasks should be vars (#'hello instead of hello) so that `clj -M:dev help` can +;; print their docstrings. +(def custom-tasks + {"dev" #'dev}) + +(def tasks (merge tasks/tasks custom-tasks)) + +(comment tasks) diff --git a/src/com/biffweb/my_project.clj b/src/com/biffweb/my_project.clj index c9f701f..3cb631c 100644 --- a/src/com/biffweb/my_project.clj +++ b/src/com/biffweb/my_project.clj @@ -3,7 +3,6 @@ [clojure.test :as test] [com.biffweb :as biff] [com.biffweb.my-project.app :as app] - [com.biffweb.my-project.email :as email] [com.biffweb.my-project.middleware :as mid] [com.biffweb.my-project.ui :as ui] [migratus.core :as migratus] @@ -40,7 +39,6 @@ (def initial-system {:biff/modules #'modules :biff/merge-context-fn identity - :biff/send-email #'email/send-email :biff/handler #'handler :biff.beholder/on-save #'on-save :biff.middleware/on-error #'ui/on-error @@ -51,7 +49,6 @@ (defn ctx->migratus-config [ctx] {:store :database :migration-dir "migrations/" - :migration-table-name "migrations" :db {:connection (jdbc/get-connection (:example/db-url ctx)) :managed-connection? true}})