SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%
6.0 KB · 178 lines sql
Raw Blame History
1-- ─────────────────────────────────────────────2--  SPB Drive — Personal Cloud Drive3-- ─────────────────────────────────────────────4--  Author  : Simon-Pierre Boucher5--  Contact : contact@spboucher.ai6--  File    : src/db/schema.sql7--  Purpose : Full SQLite schema — nodes tree, blobs, shares, sessions, FTS58--  License : MIT © Simon-Pierre Boucher9-- ─────────────────────────────────────────────1011PRAGMA journal_mode = WAL;1213CREATE TABLE IF NOT EXISTS meta (14  key   TEXT PRIMARY KEY,15  value TEXT NOT NULL16);1718-- Content-addressed blob registry. Path on disk: files/<sha[0:2]>/<sha[2:4]>/<sha>19CREATE TABLE IF NOT EXISTS blobs (20  sha      TEXT PRIMARY KEY,21  size     INTEGER NOT NULL,22  refcount INTEGER NOT NULL DEFAULT 0,23  created  INTEGER NOT NULL24);2526-- Virtual folder tree. Root is id=1 (parent_id NULL, type 'folder').27CREATE TABLE IF NOT EXISTS nodes (28  id          INTEGER PRIMARY KEY AUTOINCREMENT,29  parent_id   INTEGER REFERENCES nodes(id) ON DELETE CASCADE,30  name        TEXT NOT NULL,31  type        TEXT NOT NULL CHECK (type IN ('file', 'folder')),32  blob_sha    TEXT REFERENCES blobs(sha),33  size        INTEGER NOT NULL DEFAULT 0,34  mime        TEXT,35  created     INTEGER NOT NULL,36  modified    INTEGER NOT NULL,37  starred     INTEGER NOT NULL DEFAULT 0,38  color       TEXT,39  emoji       TEXT,40  trashed_at  INTEGER,41  trash_orig_parent INTEGER42);4344CREATE INDEX IF NOT EXISTS idx_nodes_parent  ON nodes(parent_id, trashed_at);45CREATE INDEX IF NOT EXISTS idx_nodes_blob    ON nodes(blob_sha);46CREATE INDEX IF NOT EXISTS idx_nodes_trashed ON nodes(trashed_at) WHERE trashed_at IS NOT NULL;47CREATE INDEX IF NOT EXISTS idx_nodes_starred ON nodes(starred) WHERE starred = 1;48CREATE INDEX IF NOT EXISTS idx_nodes_recent  ON nodes(modified DESC);4950CREATE TABLE IF NOT EXISTS shares (51  id             INTEGER PRIMARY KEY AUTOINCREMENT,52  token          TEXT NOT NULL UNIQUE,53  node_id        INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,54  created        INTEGER NOT NULL,55  expires_at     INTEGER,56  password_hash  TEXT,57  max_downloads  INTEGER,58  downloads      INTEGER NOT NULL DEFAULT 0,59  visits         INTEGER NOT NULL DEFAULT 0,60  allow_download INTEGER NOT NULL DEFAULT 1,61  label          TEXT,62  revoked_at     INTEGER63);6465CREATE INDEX IF NOT EXISTS idx_shares_node ON shares(node_id);6667CREATE TABLE IF NOT EXISTS share_events (68  id       INTEGER PRIMARY KEY AUTOINCREMENT,69  share_id INTEGER NOT NULL REFERENCES shares(id) ON DELETE CASCADE,70  ts       INTEGER NOT NULL,71  kind     TEXT NOT NULL CHECK (kind IN ('visit', 'download')),72  ip       TEXT,73  ua       TEXT74);7576CREATE INDEX IF NOT EXISTS idx_share_events_share ON share_events(share_id, ts DESC);7778CREATE TABLE IF NOT EXISTS tags (79  id    INTEGER PRIMARY KEY AUTOINCREMENT,80  name  TEXT NOT NULL UNIQUE,81  color TEXT NOT NULL DEFAULT '#4f8cff'82);8384CREATE TABLE IF NOT EXISTS node_tags (85  node_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,86  tag_id  INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,87  PRIMARY KEY (node_id, tag_id)88);8990-- Session ids are stored hashed (sha256) — a DB leak never leaks live cookies.91CREATE TABLE IF NOT EXISTS sessions (92  id         INTEGER PRIMARY KEY AUTOINCREMENT,93  token_hash TEXT NOT NULL UNIQUE,94  created    INTEGER NOT NULL,95  last_seen  INTEGER NOT NULL,96  expires_at INTEGER NOT NULL,97  remember   INTEGER NOT NULL DEFAULT 0,98  ip         TEXT,99  ua         TEXT100);101102CREATE TABLE IF NOT EXISTS api_tokens (103  id         INTEGER PRIMARY KEY AUTOINCREMENT,104  name       TEXT NOT NULL,105  token_hash TEXT NOT NULL UNIQUE,106  created    INTEGER NOT NULL,107  last_used  INTEGER108);109110CREATE TABLE IF NOT EXISTS activity (111  id      INTEGER PRIMARY KEY AUTOINCREMENT,112  ts      INTEGER NOT NULL,113  kind    TEXT NOT NULL,114  node_id INTEGER,115  detail  TEXT,116  ip      TEXT117);118119CREATE INDEX IF NOT EXISTS idx_activity_ts ON activity(ts DESC);120121CREATE TABLE IF NOT EXISTS login_attempts (122  ip           TEXT PRIMARY KEY,123  fail_count   INTEGER NOT NULL DEFAULT 0,124  last_fail    INTEGER,125  locked_until INTEGER126);127128-- Resumable upload sessions survive server restarts.129CREATE TABLE IF NOT EXISTS uploads (130  id         TEXT PRIMARY KEY,131  parent_id  INTEGER NOT NULL,132  name       TEXT NOT NULL,133  size       INTEGER NOT NULL,134  chunk_size INTEGER NOT NULL,135  n_chunks   INTEGER NOT NULL,136  created    INTEGER NOT NULL137);138139-- Version history: superseded blobs kept when a file is replaced or edited.140-- Each row holds one refcount on its blob (released when the row is pruned).141CREATE TABLE IF NOT EXISTS node_versions (142  id          INTEGER PRIMARY KEY AUTOINCREMENT,143  node_id     INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,144  blob_sha    TEXT NOT NULL REFERENCES blobs(sha),145  size        INTEGER NOT NULL,146  mime        TEXT,147  created     INTEGER NOT NULL,148  replaced_at INTEGER NOT NULL,149  origin      TEXT NOT NULL DEFAULT 'replace' CHECK (origin IN ('replace', 'edit', 'restore'))150);151152CREATE INDEX IF NOT EXISTS idx_versions_node ON node_versions(node_id, replaced_at DESC);153154-- File requests: public links that let anyone upload INTO a chosen folder.155CREATE TABLE IF NOT EXISTS file_requests (156  id         INTEGER PRIMARY KEY AUTOINCREMENT,157  token      TEXT NOT NULL UNIQUE,158  folder_id  INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,159  label      TEXT,160  created    INTEGER NOT NULL,161  expires_at INTEGER,162  max_files  INTEGER,163  received   INTEGER NOT NULL DEFAULT 0,164  closed_at  INTEGER165);166167-- Full-text search over name, tags and extracted text content.168CREATE VIRTUAL TABLE IF NOT EXISTS fts USING fts5(169  name, tags, content,170  tokenize = 'unicode61 remove_diacritics 2'171);172173-- Map fts rowid <-> node id explicitly (fts rowid == node id).174CREATE TABLE IF NOT EXISTS fts_state (175  node_id     INTEGER PRIMARY KEY,176  content_sha TEXT177);178