mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
262 lines
9.0 KiB
Markdown
262 lines
9.0 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
32 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 |
|
|
| `one_time_tokens` | Generic single-use, expiring tokens (password reset, etc.); only SHA-256 hashes stored |
|
|
|
|
### 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_value` / `duration_unit` | REAL / TEXT | Normalised duration (`pages` or `minutes`) |
|
|
| `duration_pages`, `duration_minutes` | INT | Legacy per-unit duration fields (combined handling 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`, `date_depot`, `defense_date`, `published_at` | DATETIME | Lifecycle timestamps (`date_depot` = real deposit date from CSV bulk import; Brussels-local parse → UTC storage) |
|
|
| `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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```bash
|
|
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](deployment.md)).
|
|
|
|
Manual maintenance:
|
|
|
|
```bash
|
|
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]
|
|
```
|