automate database init from CLI. add sqlite_history

may god bless simon willison
This commit is contained in:
Luciano Laratelli 2025-03-14 16:12:36 -04:00
parent 0110cc17e2
commit c226669a09
5 changed files with 146 additions and 3 deletions

16
Makefile Normal file
View file

@ -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

15
dev/migration.clj Normal file
View file

@ -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)

70
dev/sqlite-history.py Normal file
View file

@ -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()

45
dev/tasks.clj Normal file
View file

@ -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)

View file

@ -3,7 +3,6 @@
[clojure.test :as test] [clojure.test :as test]
[com.biffweb :as biff] [com.biffweb :as biff]
[com.biffweb.my-project.app :as app] [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.middleware :as mid]
[com.biffweb.my-project.ui :as ui] [com.biffweb.my-project.ui :as ui]
[migratus.core :as migratus] [migratus.core :as migratus]
@ -40,7 +39,6 @@
(def initial-system (def initial-system
{:biff/modules #'modules {:biff/modules #'modules
:biff/merge-context-fn identity :biff/merge-context-fn identity
:biff/send-email #'email/send-email
:biff/handler #'handler :biff/handler #'handler
:biff.beholder/on-save #'on-save :biff.beholder/on-save #'on-save
:biff.middleware/on-error #'ui/on-error :biff.middleware/on-error #'ui/on-error
@ -51,7 +49,6 @@
(defn ctx->migratus-config [ctx] (defn ctx->migratus-config [ctx]
{:store :database {:store :database
:migration-dir "migrations/" :migration-dir "migrations/"
:migration-table-name "migrations"
:db {:connection (jdbc/get-connection (:example/db-url ctx)) :db {:connection (jdbc/get-connection (:example/db-url ctx))
:managed-connection? true}}) :managed-connection? true}})