Fosi Fossil Service
Small teams. Open source. Built on Fossil.

CaaS / Lamina

Unified Management for Fossil Repositories

Public | Owner: CaaS | Default branch: trunk | Last update: 2026-10-25 00:41

Artifact 30f95f5ee82a765a593ba4a56fca0eacb3e4b3e198a05ad88eee4fc951cda157:


;; SQLite3 平台元数据库访问层(设计文档 §5)
;; 连接保存在 persistent_term;本应用单进程内串行访问,无需连接池。
(defmodule lamina_db
  (export all))

(defun conn-key () 'lamina_db_conn)

(defun to-int (v)
  (if (is_binary v) (binary_to_integer v) (list_to_integer v)))

;;---- 打开与迁移 ----

(defun open (db-path)
  (filelib:ensure_dir (filename:join (filename:dirname db-path) "x"))
  (case (esqlite3:open db-path)
    (`#(ok ,conn)
     (persistent_term:put (conn-key) conn)
     (exec "PRAGMA journal_mode=WAL;")
     (ensure-schema conn))
    (other other)))

(defun close ()
  (case (persistent_term:get (conn-key) 'undefined)
    ('undefined 'ok)
    (conn
     (progn (esqlite3:close conn)
            (persistent_term:erase (conn-key))
            'ok))))

;;---- 查询 ----

(defun conn ()
  (persistent_term:get (conn-key)))

(defun q (sql) (esqlite3:q (conn) sql))

(defun q (sql params) (esqlite3:q (conn) sql params))

(defun exec (sql) (esqlite3:exec (conn) sql))

(defun last-rowid () (esqlite3:last_insert_rowid (conn)))

(defun changes () (esqlite3:changes (conn)))

;;---- schema 迁移 ----

(defun ensure-schema (conn)
  (esqlite3:exec conn "CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);")
  (let ((rows (esqlite3:q conn "SELECT value FROM meta WHERE key='schema_version'")))
    (let ((current (case rows
                     ('() 0)
                     (`((,v)) (to-int v))
                     (_ 0))))
      (run-migrations conn current))))

(defun run-migrations (conn current)
  (let ((target (fold-migrations conn current (migrations))))
    `#(ok ,target)))

(defun fold-migrations (conn current pending)
  (case pending
    ('() current)
    (`((,version . ,statements) . ,rest)
     (if (=< version current)
         (fold-migrations conn current rest)
         (let ((final (apply-migration conn version statements)))
           (fold-migrations conn final rest))))))

(defun apply-migration (conn version statements)
  (lists:foreach
   (lambda (ddl) (esqlite3:exec conn ddl))
   statements)
  (esqlite3:exec
   conn
   (lists:flatten
    (io_lib:format
     "INSERT OR REPLACE INTO meta(key,value) VALUES('schema_version','~p');"
     (list version))))
  version)

;; 迁移清单:每个元素 (版本号 . DDL语句列表)。只追加,不修改已发布版本。
(defun migrations ()
  (list
   (cons 1 (v1-ddl))
   (cons 2 (v2-ddl))))

(defun v2-ddl ()
  (list "ALTER TABLE repositories ADD COLUMN repo_status TEXT NOT NULL DEFAULT 'up';"))

(defun v1-ddl ()
  (list
   "CREATE TABLE IF NOT EXISTS admin_user (
      id INTEGER PRIMARY KEY,
      username TEXT NOT NULL UNIQUE,
      password_hash TEXT NOT NULL,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL);"
   "CREATE TABLE IF NOT EXISTS site_settings (
      key TEXT PRIMARY KEY, value TEXT NOT NULL);"
   "CREATE TABLE IF NOT EXISTS organizations (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL UNIQUE,
      description TEXT NOT NULL DEFAULT '',
      created_at TEXT NOT NULL);"
   "CREATE TABLE IF NOT EXISTS repositories (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL,
      organization_id INTEGER REFERENCES organizations(id),
      display_name TEXT NOT NULL DEFAULT '',
      description TEXT NOT NULL DEFAULT '',
      source TEXT NOT NULL CHECK(source IN ('local','remote')),
      remote_url TEXT,
      sync_interval_minutes INTEGER,
      setup_username TEXT NOT NULL,
      setup_password_enc BLOB NOT NULL,
      git_enabled INTEGER NOT NULL DEFAULT 0,
      git_url TEXT,
      git_username TEXT,
      git_token_enc BLOB,
      git_auto_push INTEGER NOT NULL DEFAULT 0,
      fossil_path TEXT NOT NULL UNIQUE,
      last_timeline_at TEXT,
      last_synced_at TEXT,
      last_sync_status TEXT,
      last_pushed_at TEXT,
      last_push_status TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL,
      UNIQUE(name, organization_id));"
   "CREATE INDEX IF NOT EXISTS idx_repos_org ON repositories(organization_id);"
   "CREATE TABLE IF NOT EXISTS sync_log (
      id INTEGER PRIMARY KEY,
      repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
      started_at TEXT NOT NULL,
      finished_at TEXT,
      status TEXT NOT NULL,
      message TEXT DEFAULT '');"
   "CREATE TABLE IF NOT EXISTS push_log (
      id INTEGER PRIMARY KEY,
      repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
      started_at TEXT NOT NULL,
      finished_at TEXT,
      status TEXT NOT NULL,
      message TEXT DEFAULT '');"
   "CREATE INDEX IF NOT EXISTS idx_sync_repo ON sync_log(repository_id, started_at DESC);"
   "CREATE INDEX IF NOT EXISTS idx_push_repo ON push_log(repository_id, started_at DESC);"))

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/CaaS/Lamina

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.