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, andconfig/bootstrap.php. Those are obsolete — the schema now lives underapp/storage/and migrations are handled byapp/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 byapp/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_fullfiltered tois_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
languagescarrydeleted_at; hardDELETEis 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_*areGROUP_CONCATaggregates — 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
- Back up first:
cp app/storage/xamxam.db app/storage/xamxam.db.bak - Add a numbered migration file under
app/migrations/applied/(.sqlfor DDL/DML,.phpwhen data logic is needed). - Run
just migrate(orjust deploy-migrateon the server) — idempotent, tracked via_migrations. - When appropriate, regenerate
app/storage/schema.sqlfrom 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]