- Consolidate 36 markdown files → 14 (plus TODO.md) - Merge overlapping docs into authoritative files: - database.md (from DATABASE_SPECIFICATION + QUICK_SCHEMA_REFERENCE + DATABASE_CONFIG + SETUP) - deployment.md (from SERVER_SETUP + COMPLETE_DEPLOYMENT_GUIDE + DEPLOYMENT_STEPS) - security.md (from SECURITY_ANALYSIS + TODO.SECURITY) - development.md (from DEVELOPMENT_GUIDE + LIVE_RELOAD_SETUP + TEST_CENTRALIZATION) - migration-history.md (from 11 past migration docs) - Standardise all filenames to lowercase - Remove non-doc files (Context.md research notes, chat export) - Remove superseded docs (SECURITY.md pre-SQLite, SECURITY_IMPLEMENTATION, README_SECURE_SEARCH) - Fix stale cross-references
10 KiB
Database Reference
Post-ERG SQLite database — schema, configuration, and operations.
Version: 1.0 · Engine: SQLite 3 · Mode: WAL
Quick Start
cd database/
sqlite3 posterg.db < schema.sql # Create DB
sqlite3 posterg.db "SELECT name FROM sqlite_master WHERE type='table';"
sqlite3 posterg.db "SELECT * FROM orientations;" # Verify seed data
Configuration
Database paths are centralized in config/bootstrap.php:
- Development:
APP_ROOT . '/storage/test.db'(gitignored) - Production:
APP_ROOT . '/storage/posterg.db'
The Database class (src/Database.php) auto-detects: if test.db exists → use it, otherwise → use posterg.db. Override with DB_ENV env var (test or prod) or pass a custom path to the constructor.
Schema Overview
Entity Relationship
authors ──1:N──► thesis_authors ──N:1──► theses
supervisors ──1:N──► thesis_supervisors ──N:1──► theses
keywords ──1:N──► thesis_keywords ──N:1──► theses
languages ──1:N──► thesis_languages ──N:1──► theses
format_types ──1:N──► thesis_formats ──N:1──► theses
orientations ──N:1──► theses
ap_programs ──N:1──► theses
finality_types ──N:1──► theses
access_types ──N:1──► theses
license_types ──N:1──► theses
thesis_files ──N:1──► theses
Table Categories
| Category | Tables |
|---|---|
| Core | theses, authors, supervisors, thesis_files, pages |
| Lookup | orientations (15), ap_programs (4), finality_types (3), languages (2+), format_types (7), access_types (3), license_types, keywords (dynamic) |
| Junction | thesis_authors, thesis_supervisors, thesis_keywords, thesis_languages, thesis_formats |
| Views | v_theses_full (admin), v_theses_public (published only) |
Core Tables
theses
| Column | Type | Required | Description |
|---|---|---|---|
id |
INTEGER PK | auto | Primary key |
identifier |
TEXT UNIQUE | no | Human-readable ID (e.g., "2025-002") |
title |
TEXT | yes | Thesis title |
subtitle |
TEXT | no | Optional subtitle |
year |
INTEGER | yes | Academic year |
is_doctoral |
BOOLEAN | no | 0=TFE, 1=Doctoral |
orientation_id |
INTEGER FK | no | → orientations |
ap_program_id |
INTEGER FK | no | → ap_programs |
finality_id |
INTEGER FK | no | → finality_types |
synopsis |
TEXT | no | ~200 word summary |
context_note |
TEXT | no | Jury president note (max 150 words) |
remarks |
TEXT | no | Internal remarks |
duration_minutes |
INTEGER | no | For audio/video |
duration_pages |
INTEGER | no | For written works |
file_size_info |
TEXT | no | Free-form size description |
access_type_id |
INTEGER FK | no | → access_types |
license_id |
INTEGER FK | no | → license_types |
jury_points |
DECIMAL(4,2) | no | Grade (0–20) |
jury_note_added |
BOOLEAN | no | Jury context note flag |
submitted_at |
DATETIME | no | Student submission |
defense_date |
DATETIME | no | Defense date |
published_at |
DATETIME | no | Publication date |
is_published |
BOOLEAN | no | Publication status |
baiu_link |
TEXT | no | Institutional repository link |
created_at |
DATETIME | auto | Record creation |
updated_at |
DATETIME | auto | Last update (trigger) |
Indexes: idx_theses_year, idx_theses_published, idx_theses_identifier, idx_theses_orientation, idx_theses_ap_program, idx_theses_access_type
authors
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto |
name |
TEXT NOT NULL | Full name |
email |
TEXT | Contact email (optional) |
created_at / updated_at |
DATETIME | Auto timestamps |
Index: idx_authors_email
supervisors
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto |
name |
TEXT NOT NULL | Full name |
created_at / updated_at |
DATETIME | Auto timestamps |
thesis_files
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto |
thesis_id |
INTEGER FK | → theses (CASCADE) |
file_type |
TEXT | main, annex, written_part, other |
file_path |
TEXT | Relative path |
file_name |
TEXT | Original filename |
file_size |
INTEGER | Size in bytes |
mime_type |
TEXT | MIME type |
description |
TEXT | Optional |
uploaded_at |
DATETIME | Upload timestamp |
pages
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto |
slug |
TEXT UNIQUE | URL identifier |
title |
TEXT NOT NULL | Page title |
content |
TEXT | Markdown/HTML |
is_published |
BOOLEAN | Default 1 |
created_at / updated_at |
DATETIME | Auto timestamps |
Pre-loaded: charte, about, licenses, contact
Lookup Tables
orientations (15 predefined)
Arts Numériques, Dessin, Cinéma d'animation, Installation-Performance, Peinture, Photographie, Sculpture, Vidéographie, Graphisme, Typographie, Design Numérique, Illustration, Bande-Dessinée, Sérigraphie, Gravure
ap_programs (4)
| Code | Name |
|---|---|
| — | Narration Spéculative |
| DPM | Design et Politique du Multiple |
| APS | Atelier Pratiques Situées |
| LIENS | Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes |
finality_types (3)
Approfondi, Enseignement, Spécialisé
format_types (7)
Site web, Audio, Vidéo, Performance, Objet éditorial, Installation, Autre
access_types (3)
| Name | Description |
|---|---|
| Libre | Full access online + library |
| Interne | Physical only; note online |
| Interdit | No access; note only |
Business rule: Access can only be restricted (Libre → Interne → Interdit), never opened.
languages
Français, Anglais (expandable)
keywords
Dynamic, grows organically. Max 10 per thesis (application-enforced).
Junction Tables
All use composite PKs (thesis_id, *_id) with ON DELETE CASCADE.
| Table | Links | Order column |
|---|---|---|
thesis_authors |
theses ↔ authors | author_order |
thesis_supervisors |
theses ↔ supervisors | supervisor_order |
thesis_keywords |
theses ↔ keywords | — |
thesis_languages |
theses ↔ languages | — |
thesis_formats |
theses ↔ format_types | — |
Indexes on junction tables: idx_thesis_keywords_thesis, idx_thesis_keywords_keyword (and equivalents for authors)
Views
v_theses_full — Admin view
All theses with joined relationships (GROUP_CONCAT for authors, supervisors, keywords, languages, formats, plus human-readable names for orientation, AP, finality, access type, license).
v_theses_public — Public view
Same as v_theses_full filtered to is_published = 1. Unpublished theses never exposed.
Automatic Features
- Auto-increment IDs: All PKs use
AUTOINCREMENT - Auto timestamps:
created_atdefaults toCURRENT_TIMESTAMP;updated_atrefreshed by triggers on UPDATE - Cascade deletes: Deleting a thesis removes all junction + file records
Common Operations
Querying
-- Published theses
SELECT * FROM v_theses_public ORDER BY year DESC;
-- Single thesis (admin)
SELECT * FROM v_theses_full WHERE id = ?;
-- By year + orientation
SELECT * FROM v_theses_public WHERE year = 2025 AND orientation = 'Arts Numériques';
-- By keyword
SELECT DISTINCT t.* FROM theses t
JOIN thesis_keywords tk ON t.id = tk.thesis_id
JOIN keywords k ON tk.keyword_id = k.id
WHERE k.keyword = 'é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;
-- Unpublished (admin)
SELECT identifier, title, submitted_at FROM theses
WHERE submitted_at IS NOT NULL AND is_published = 0 ORDER BY submitted_at DESC;
Inserting
INSERT INTO authors (name, email) VALUES ('Marie Dupont', 'marie@example.com');
INSERT INTO theses (identifier, title, year, orientation_id, finality_id, synopsis)
VALUES ('2026-001', 'Mon Titre', 2026, 8, 1, 'Synopsis...');
INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (1, 5, 1);
INSERT OR IGNORE INTO keywords (keyword) VALUES ('performance');
INSERT INTO thesis_keywords (thesis_id, keyword_id)
SELECT 1, id FROM keywords WHERE keyword = 'performance';
Updating
UPDATE theses SET is_published = 1, published_at = CURRENT_TIMESTAMP WHERE id = 5;
UPDATE theses SET jury_points = 16.5, context_note = '…', jury_note_added = 1 WHERE id = 5;
Backup & Maintenance
Backup
# File copy (simplest)
cp posterg.db backups/posterg_$(date +%Y%m%d).db
# SQL dump (portable)
sqlite3 posterg.db .dump > backups/posterg_$(date +%Y%m%d).sql
Maintenance
sqlite3 posterg.db "VACUUM;" # Reclaim space (after large deletes, monthly)
sqlite3 posterg.db "ANALYZE;" # Update query stats (after schema/data changes)
sqlite3 posterg.db "PRAGMA integrity_check;" # Verify → should output "ok"
sqlite3 posterg.db "PRAGMA journal_mode=WAL;" # Enable WAL for better concurrency
Recovery
sqlite3 posterg.db ".recover" | sqlite3 recovered.db # Corrupted DB
sqlite3 posterg.db .dump | sqlite3 new.db # Dump + reimport
Performance Notes
- All critical foreign keys and search fields are indexed
- Views pre-compute joins for common queries
- For 1000+ theses: ensure WAL mode, run
ANALYZEperiodically, considerVACUUM - Cache size:
PRAGMA cache_size=-64000;(64MB) - Memory-mapped I/O:
PRAGMA mmap_size=268435456;(256MB)
Schema Changes
Making changes
- Always backup first:
cp posterg.db posterg_before.db - Test on backup:
sqlite3 posterg_test.db < migration.sql - Use transactions: wrap ALTER/INSERT in
BEGIN; … COMMIT; - Document in
storage/migrations/with numbered SQL files
Change request format
Table: [table_name]
Change: [add/modify/remove]
Column: [column_name]
Type: [data_type]
Reason: [why needed]
Example: [sample data]