Files
xamxam/docs/database.md
T

8.6 KiB

Database Reference

XAMXAM SQLite database — schema, configuration, and operations.

The database lives at app/storage/xamxam.db. The canonical schema is app/storage/schema.sql (base tables + seed data); incremental changes are applied through versioned migrations in app/migrations/ (see the Schema changes section).

Legacy docs referenced posterg.db, database/schema.sql, and config/bootstrap.php. Those are obsolete — the schema now lives under app/storage/ and migrations are handled by app/migrations/run.php.


Engine & mode

  • Engine: SQLite 3
  • Mode: WAL (journal_mode=wal) for safe concurrent reads + hot backups
  • Driver: PDO (ext-pdo + ext-sqlite3), wrapped by app/src/Database.php

Development quick start

just migrate          # run pending migrations (creates DB from schema if missing)
just init-db          # create DB from app/storage/schema.sql
just reset-db         # rm xamxam.db + init-db
just query            # open an interactive sqlite3 shell on app/storage/xamxam.db
sqlite3 app/storage/xamxam.db ".tables"

Migrations

Migrations live in app/migrations/. They are run via app/migrations/run.php (wrapped by just migrate) and tracked in the _migrations table. Apply the same way in production with just deploy-migrate.

Each migration is a numbered file:

app/migrations/applied/
├── 001_add_objet_column.sql
├── …
├── 041_combined_duration.sql
├── 042_fix_contact_columns.sql
└── 043_relabel_promoteur_ulb.php      # PHP migrations can run logic too

schema.sql is the fully-migrated baseline; it is regenerated from the local DB so it always reflects the applied set of migrations.


Tables

31 tables. Grouped by purpose:

Core

Table Purpose
theses The main entity (a TFE or doctoral work)
authors People who authored a thesis
supervisors Jury members (promoteur / lecteur / président)
thesis_files Uploaded files attached to a thesis
theses subtypes via objet column (tfe, these, …) and is_doctoral

Lookup / reference

Table Notes
orientations (15) Arts Numériques, Dessin, …, Gravure
ap_programs (5) Narration Spéculative, DPM, APS, LIENS, PACS
finality_types (3) Approfondie, Enseignement, Spécialisée
languages français, anglais, néerlandais, italian (lowercase; expandable)
format_types Audio, Vidéo, Site web, Performance, Objet éditorial, Installation, Autre
access_types (3) Libre / Interne / Interdit
license_types CC BY 4.0 …, Domaine public, Tous droits réservés
tags Keywords (dynamic, lowercase-normalised, max 10/thesis)

Junction (many-to-many)

thesis_authors, thesis_supervisors, thesis_languages, thesis_formats, thesis_tags.

Sharing / access

Table Purpose
share_links Student/partage submission links (slug, password, expiry, archived)
file_access_requests Requests to access restricted files
file_access_tokens One-time access tokens
file_access_sessions Session tokens for granted access
file_access_audit Audit trail of access events

Integrations / settings / content

Table Purpose
peertube_settings PeerTube video-hosting config (instance, channel, labels)
smtp_settings SMTP relay config + notify email (singleton id=1)
site_settings Feature flags (access types, objets, upload, admin password hash)
pages Editable content pages (about, charte, licenses)
apropos_contents Structured contact / about content
form_help_blocks Help texts on admin/partage forms
system_cache Key/value cache

Auditing

Table Purpose
admin_audit_log Audit log of admin operations (resource, action, status, IP/UA)
audit_log Low-level row audit (actor, action, before/after JSON)

Key columns — theses

Column Type Notes
id INTEGER PK Auto
identifier TEXT Human-readable id (e.g. 2025-003)
title TEXT NOT NULL
subtitle TEXT
year INTEGER NOT NULL
is_doctoral BOOLEAN 0=TFE, 1=doctoral
objet TEXT 'tfe' default; other objets (these, frart) gated by site_settings
orientation_id / ap_program_id / finality_id INT FK Lookup refs
synopsis / context_note / remarks TEXT
duration_pages, duration_minutes INT Combined duration handled at app level
has_annexes BOOLEAN
access_type_id INT FK Libre / Interne / Interdit
license_id INT FK → license_types
cc2r BOOLEAN CC2r licence checkbox
license_custom TEXT Free-form licence text
jury_points DECIMAL(4,2) Grade / 20
jury_note_added BOOLEAN
contact_visible TEXT 'public'/'internal' control for author contact
submitted_at, defense_date, published_at DATETIME Lifecycle timestamps
is_published BOOLEAN Public visibility flag
status TEXT Default 'active'
baiu_link TEXT Institutional repo link
exemplaire_baiu, exemplaire_erg BOOLEAN Physical copy flags
deleted_at DATETIME Soft delete
created_at / updated_at DATETIME Auto (updated_at trigger)

thesis_supervisors

Tracked via an explicit role/flag system rather than separate entity tables:

Column Notes
role promoteur, lecteur, president
is_external Lecturer is external
is_ulb Promoteur is from the university (UCLouvain) side

Views

  • v_theses_full — all theses with joined, human-readable fields (orientations, AP, finality, access, license, authors, supervisors with role splits, languages, formats, tags/keywords, contact fields).
  • v_theses_public — v_theses_full filtered to is_published = 1.
  • v_smtp_active — active SMTP settings row.

Scaffolded / helper tables

  • _migrations — applied migration bookkeeping (used by the runner).

Business rules

  • Access: can only be restricted (Libre → Interne → Interdit), never opened back up — enforced in the UI/flow.
  • Keywords (tags): lowercase-normalised, de-duplicated, min 3 (form) / max 10 per thesis.
  • Soft deletes: reference tables such as languages carry deleted_at; hard DELETE is avoided where recoverability matters.
  • Files: stored outside the webroot under app/storage/; DB rows use storage-relative paths (theses/<year>/<year>_<AUTHOR>/…).

Common operations

-- Published theses
SELECT * FROM v_theses_public ORDER BY year DESC;

-- Single thesis (admin view)
SELECT * FROM v_theses_full WHERE id = ?;

-- By year + orientation
SELECT * FROM v_theses_public WHERE year = 2025 AND orientation = 'Arts Numériques';

-- Search by keyword (tag)
SELECT DISTINCT t.* FROM theses t
JOIN thesis_tags tt ON t.id = tt.thesis_id
JOIN tags g ON tt.tag_id = g.id
WHERE g.name = 'écologie' AND t.is_published = 1;

-- Theses per year
SELECT year, COUNT(*) FROM theses WHERE is_published = 1 GROUP BY year ORDER BY year DESC;

-- Publish a thesis
UPDATE theses SET is_published = 1, published_at = CURRENT_TIMESTAMP WHERE id = 5;

Note: keyword/contact fields in v_theses_* are GROUP_CONCAT aggregates — prefer joining the junction tables (thesis_tags/tags) when you need per-row access.


Backup & maintenance

just backup            # SQL dump → app/storage/backup_<timestamp>.sql
just backup-snapshot   # WAL-safe hot backup + gzip → app/storage/backups/

On the server, backups are scheduled to /var/backups/xamxam/ via /etc/cron.d/xamxam-backup (see deployment.md).

Manual maintenance:

sqlite3 app/storage/xamxam.db "VACUUM;"              # after large deletes
sqlite3 app/storage/xamxam.db "ANALYZE;"
sqlite3 app/storage/xamxam.db "PRAGMA integrity_check;"   # expect "ok"

Schema changes

  1. Back up first: cp app/storage/xamxam.db app/storage/xamxam.db.bak
  2. Add a numbered migration file under app/migrations/applied/ (.sql for DDL/DML, .php when data logic is needed).
  3. Run just migrate (or just deploy-migrate on the server) — idempotent, tracked via _migrations.
  4. When appropriate, regenerate app/storage/schema.sql from the applied set so it stays a faithful baseline.

Change request format:

Table: [table_name]
Change: [add/modify/remove]
Column: [column_name]
Type: [data_type]
Reason: [why needed]
Example: [sample data]