mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
feat: extract MediaController, wire into Dispatcher, delete media.php
This commit is contained in:
0
app/storage/.gitkeep
Normal file
0
app/storage/.gitkeep
Normal file
74
app/storage/Database_TFE_test.csv
Normal file
74
app/storage/Database_TFE_test.csv
Normal file
@@ -0,0 +1,74 @@
|
||||
,,,,,,,,,,,,,,,,,,,,
|
||||
Identifiant,Titre,Sous-titre,Auteur·ice(s),Contact,Promoteur·ice(s),Format,Année,AP,Orientation,Finalité,Mots-clés,Synopsis,Contexte,Remarques,Langue,Autorisation,License,taille,Points sur 20,lien BAIU
|
||||
,,,,,,,,,,,séparer par une virgule — max. 10,,,,,,,,,
|
||||
Column1,Column2,Column3,Column4,Column5,Column6,Column7,Column8,Column9,Column10,Column11,Column12,Column13,Column14,Column15,Column16,Column17,Column18,Column19,Column20,
|
||||
2025-002,"Plus haut, la poussière ira plus loin",,Lucie Jacquet,luciejacquetlucie@gmail.com,"Silvia Mesturini, Sasha Newell",objet éditorial,2025,,SC,Approfondi,"carrière, mémoire collective, frictions, folklore, récits","Ce mémoire aborde les frictions générées par les sites d’extraction de pierre calcaire en Wallonie.
|
||||
Ces espaces à ciel ouvert qui creusent le paysage et réveillent des tensions entre exploitation
|
||||
industrielle, transformations environnementales et vécus du vivant. Au-delà des plaies visibles,
|
||||
ce travail cherche à faire entendre les silences des carrières, à travers des récits fragmentés
|
||||
mêlant observations, entretiens et souvenirs personnels.",,,français,,,128 pages,,
|
||||
2025-003,La berge du verre,,Yasmine Salem,yasminexsalem@gmail.com,"Nicolas Prignot, Olivier Gosselain","vidéo, objet éditorial",2025,,VI,Approfondi,"patrimoine, apprentissage, technique du vitrail, artisanat, documentaire,","Lorsque Pierre Majerus décède, il donne l’atelier de vitrail aux aspirants
|
||||
maitres-verriers. D’ailleurs, il disait : « apprendre un métier, c’est le
|
||||
voler à quelqu’un d’autre ». Après son décès, Marcelle son épouse,
|
||||
continuera à fabriquer les vitraux avec Vincent, le fidèle collaborateur.
|
||||
Ensuite, Saskia a rejoint l’atelier pour donner des cours de vitraux avec
|
||||
Vincent. Il y a aussi quelques verriers inconnus, le couple François
|
||||
et Amélie et maintenant, Youri qui s’essaie à la technique du vitrail à
|
||||
l’atelier Pierre Majerus. Désormais, Marcelle et Vincent accompagnent
|
||||
toutes les arrivées et départs de ces aspirants maitres-verriers. Sauf le
|
||||
mardi et le mercredi, quand la salle de cours se remplit d’apprenantes et
|
||||
d’apprenants. Vincent rejoint Saskia pour donner les cours de vitraux
|
||||
avec les petits gâteaux et les « Alors ? C’est pas mal du tout ça ! »
|
||||
Marcelle sort de sa véranda et se dirige vers la salle de cours avec ses
|
||||
verres et ses outils dans un panier en osier. Devenir maitre-verrier ?
|
||||
Ça s’apprend toujours pour Marcelle malgré une soixantaine d’année à
|
||||
coconstruire un atelier de vitrail.",,Dvd : avoir un lecteur dvd externe,français,,,78 pages + ?? minutes,,
|
||||
2025-005,Reflet fantôme,"Mémoire, trauma et narration – Le trauma comme plan cinématographique",Darika Peou,darika.peou@gmail.com,"Silvia Mesturini, Vanessa Frangville",objet éditorial,2025,LIENS,CA,Approfondi,"mémoire, trauma, narration, cambodge, film, héritage, animation, génocide","""Reflet fantôme"" interroge la manière dont le cinéma peut reconstruire un récit identitaire à partir d’un héritage khmer traumatique et fragmenté. À travers trois films sur le génocide cambodgien, j’explore comment les choix narratifs et esthétiques traduisent le trauma et ouvrent un espace pour transmettre et réinventer la mémoire. Ce mémoire croise analyse filmique, études sur la mémoire traumatique et réflexion sur la transmission transgénérationnelle.",,,français,,,70 pages,,
|
||||
2025-006,Tohu-bohu,Vol. 1 – Nitescences Spectrales,Lilo Joris,lilo.joris@erg.school,"Caroline Godart, David Berliner",objet éditorial,2025,DPM,,Approfondi,"mémoire, amnésie, mort, médiation, rêve, utopie, narration, magazine, anthropologie, poésie","Ce mémoire est le deuxième volume de la revue poétique Tohu-Bohu, le premier étant Vol. 0 : L’Iridescence du Brouhaha.
|
||||
Il tente de mélanger différents champs référentiels en les considérant de manière égale, tout en bien tenant compte de leurs spécificités. Il varie entre approche scientifique et approche plus poétique et sensible, proposant ainsi de faire apparaître le « poétique » à travers le « scientifique ».
|
||||
Nitescences Spectrales vient poser la question de la médiation de nos mort·es. Comment est-ce qu’on {ré}agit aux mort·es ? Qu’est-ce qu’iels laissent derrière elleux ? Qu’est-ce qu’iels emmènent avec ellux ? Et qu’est-ce qu’on peut faire des éclats amnésiques qui restent après leur « départ » ? Ces questions demandent à comprendre comment nos récits se forment malgré leurs trous, ainsi que de comprendre comment l’émotion se transforme (de manière sensible ou de manière utilitaire) au fil du temps – et donc de revoir la construction même du temps. L'enjeu sous-jacent du mémoire est d’observer les effets que produisent nos dialogues avec nos mort·es – que ce soit des personnes ou des moments – afin de comprendre comment demain se met en place et les implications que nous y avons.",,,français,,,92 pages + annexes,,
|
||||
2025-007,La Prophétie des Bâtards,Danser et faire re(co)nnaître au monde des corps qui ne comptent pas,Moriane Richard,moriane.richard@gmail.com,"Hélène Bernard, Flavio Rodrigo Orzari Ferreira, Valérie Piette",objet éditorial,2025,,IP,Approfondi,"art, danse, performance, cartographies somatiques du pouvoir, justice, médicine, transe","
|
||||
Nous vivons une épidémie invisible : l’inflammation chronique des systèmes nerveux et sociaux sous l’effet cumulé des traumatismes individuels et structurels (racisme, patriarcat, impérialisme, validisme). Cette inflammation traverse les chairs, défait les liens, atrophie l’attention, durcit la pensée. L’Art—et la danse en particulier—peut y répondre par des pratiques d’attention et de reliance : danser, toucher, trembler pour ré-accorder le c.o.r.p.s.e.s.p.r.i.t, ré-ouvrir l’imaginaire et relancer la capacité relationnelle.
|
||||
|
||||
Je travaille en auto-ethnographie incarnée, depuis des expériences qui débordent le discours : douleurs chroniques, gestes d’auto-défense somatique, apprentissages empiriques. Je croise pratiques somatiques (TRE®—tremblement neurogénique guidé, Body-Mind Centering®, contact-impro) et enquêtes théoriques (traumatologie, écologies affectives), pour élaborer une recherche-création qui pense avec le mouvement et par le toucher. Ce cadre a donné naissance à une méthode en devenir : la DarkDance—un protocole souple, subversif et sans dogme, qui considère muscles, fascias, organes et liquides comme des milieux pensants, et les engage dans une politique de la sensation.
|
||||
|
||||
Les enjeux sont de déplacer la critique des systèmes de domination dans les tissus mêmes des corps (cartographies somatiques du pouvoir). Et d’ouvrir des espaces de re(co)naissance pour celles et ceux dont l’archive est manquante—« les corps qui ne comptent pas ».",,,français,,,99 pages,,
|
||||
2025-008,"L'école, le parlement et la cuisine",,Alice Néron,alice.neron@outlook.com,"Ayoh Kré Duchâtelet,Karolina Svobodova",objet éditorial,2025,,DN,Approfondi,"art participatif, assemblée, bruxelles, joie, conversation, affects, collectif, pouvoir d'agir","L’école, le parlement et la cuisine revient sur trois situations d’art participatif engagées à Bruxelles ces cinq dernières années à partir de mon expérience: Bodies of Knowledge, le Code du numérique et «Cuisiner (...)». Bodies of Knowledge («L’école»), de l’artiste performeuse Sarah Vanhee, est une «salle de classe» nomade installée dans l'espace public pour apprendre des savoirs de vie des habitant·es. Le Code du Numérique («le parlement»),porté par l’asbl reconnue en éducation permanente Habitant·es des images, est un faux vrai code de loi écrit à partir de témoignages pour réglementer le numérique. Il s'écrit pendant les «parlements humains»: un outil d'animation qui met en scène une assemblée législative. Les ateliers «Cuisiner (...)» organisés avec Zeste Le Reste visent à cuisiner des problèmes collectifs en même temps qu’un repas.
|
||||
|
||||
Ce mémoire questionne les potentiels transformateurs de ces trois dispositifs. Comment peuvent-ils participer ou non à nous faire sentir plus capables ensemble et à faire émerger des capacités de sentir, de penser et d’agir en commun ? Comment peuvent-ils, ou non, nous permettre (artistes et participant·es) de reprendre joyeusement prise sur des affects tristes ? Que nous font-ils faire ?
|
||||
",,,français,,,160 pages en plusieurs brochures,,
|
||||
2025-009,Santurantikuy LIVE,"Objets, gestes, transactions",LALESHKA SALAS SALAZAR,laleshka.salas@gmail.com,Ivo Provoost,objet éditorial,2025,LIENS,,Enseignement,"Performance, Pérou, Art Populaire, Hawkaypata, Connexions Partielles, Andes, Andean Culture, Human-Non human, Postcolonial Studies","Ce travail de fin d’études, intitulé Santurantikuy LIVE : Objets, gestes, transactions, constitue
|
||||
une exploration scénique du caractère spatial et performatif de la foire Santurantikuy, à travers
|
||||
trois axes dramatiques : les croyances animistes, le commerce, et les racines communautaires.
|
||||
Instaurée au XVIe siècle par l’Église catholique dans le cadre des mesures du Concile de Trente,
|
||||
la foire du Santurantikuy avait pour objectif d’éradiquer les croyances andines en promouvant la
|
||||
vente de figurines et de saints chrétiens. Cependant, cet objectif n’a jamais été entièrement at
|
||||
teint.
|
||||
Aujourd’hui, la foire constitue un espace où la cosmovision andine et la religion chrétienne coexis
|
||||
tent dans un réseau complexe de relations. Par ailleurs, des tensions persistent entre les enjeux
|
||||
commerciaux et la préservation des traditions.
|
||||
Le choix de ce sujet découle d’un ancrage personnel : la foire du Santurantikuy représente une
|
||||
tradition profondément significative pour moi, car elle fut un moment de partage avec ma grand
|
||||
mère et ma tante. Elles évoquaient leurs souvenirs de la foire pendant que nous y étions, et c’est
|
||||
cette mémoire d’un moment en train de se vivre qui m’a donné envie de revisiter et explorer cet
|
||||
espace à travers une approche performative et scénique.",,,français,,,170 pages,,
|
||||
2025-011,Apprendre comme pratique de la liberté au XXIe siècle ?,,Zem Azem,a.zem@myyahoo.com,Stéphane Noël,audio,2025,,TY,Enseignement,"éducation critique, numérique, émancipation, bell hooks, Paulo Freire, co-création, savoirs situés, logiciel libre, pédagogie, transmission","Ce mémoire, sous forme de podcast en cinq épisodes,
|
||||
explore la pensée de bell hooks et de Paulo Freire
|
||||
à travers la question : qu’ est-ce qu’ apprendre comme pratique de la liberté au XXIe siècle ? En partant des fondements
|
||||
de la pédagogie critique, ce mémoire interroge la manière dont l’éducation peut encore aujourd’hui être un outil d’émancipation, de dialogue et de transformation sociale.
|
||||
|
||||
Dans un monde traversé par le numérique, les algorithmes, la post-vérité et la marchandisation du savoir, la question devient : comment rendre l’ apprentissage à nouveau
|
||||
collectif, sensible et politique ? À travers des enquêtes,
|
||||
des récits personnels, des références théoriques et des interviews d’artistes, de chercheur·ses et de praticien·nes du libre, ce podcast propose de penser autrement l’acte d’apprendre, en dehors des logiques verticales
|
||||
et productivistes.
|
||||
|
||||
Chaque épisode explore une facette de cette problématique, de l’éducation critique à la culture numérique, en passant
|
||||
par la typographie comme outil de réappropriation
|
||||
du langage. Ce projet se veut à la fois réflexion
|
||||
et expérimentation, à la croisée de la pédagogie,
|
||||
du design, du militantisme et des communs.",,Audio + annexes en objet éditorial (brochures pour la bibliographie etc),français,,,68 minutes,,
|
||||
2024-026,DepNum,,Théophile Gervreau-Mercier,theophile.gervreaumercier@erg.school,Audrey Samson,site web,2024,,,Spécialisé,"blog, dépendance, numérique, personnelle, collective, informatique, technologies, autobiographie, addiction","Mon mémoire de Master à l'ERG est un blog autobiographique sur ma dépendance numérique. Chaque post mêle vécu personnel, questions et recherche. J'explore les dynamiques complexes de notre dépendance collective aux technologies numériques, en croisant expérience individuelle et réflexion systémique.",,,français,,,,,https://ils.bib.uclouvain.be/global/documents/3830452
|
||||
2024-036,Les sons de ma maison,,ségolène Chateau,chateau.segolene@icloud.com,Sylvie bouteiller,Audio,2024,,,Spécialisé,"Sons, maison, bruit, extension de bâtiment, Recording","Ce travail sera constitué de 5 pièces sonores, résultant d’enregistrements effectués dans différentes pièces de ma maison.
|
||||
Le but étant de prendre conscience de notre environnement sonore, et du fait que nous sommes des êtres entendants, sensibles et réceptifs aux bruits qui nous entourent.
|
||||
Il consiste également à rendre de nouveau audibles ces sons, oubliés mais pourtant bien présents dans nos environnements personnels et familiers.",,,français,,,,,https://ils.bib.uclouvain.be/global/documents/3828970
|
||||
2024-043,So You!,,Nascimo Clette,nascimo.clette@gmail.com,Alexander Schellow,vidéo,2024,,,Spécialisé,"Mémoire, cinéma amateur, archivage, rapport matérialiste au médium vidéo, pratique naïve","So You! est un film que j'ai réalisé en 2024 pour mon Mémoire de fin de Master à l'ERG en Vidéographie. Il s'agit d'un montage de rush que j'ai rassemblé sur un petit caméscope depuis que j'ai 8 ans. Je me suis intéressé au fonctionnement de la mémoire et au développement d'une archive personnelle au travers du médium vidéographique. Une image en entraîne une autre, au rythme des souvenirs qui remontent et au gré de ce qu'ils m'évoquent. Interroger le geste commun d’un archivage personnel, comment ce geste est modelé par la technologie et comment produit-il des regards et relations uniques avec le monde. La mémoire crée des connexions incongrues a travers le temps et l'espace et nos archives sont précieuses, car elle permettent de s'approprier des micro-récits qui sont souvent réduits au silence.",,,français,,,,,https://ils.bib.uclouvain.be/global/documents/3830451
|
||||
|
222
app/storage/README.md
Normal file
222
app/storage/README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Database Documentation
|
||||
|
||||
Complete documentation for the Post-ERG thesis database.
|
||||
|
||||
## 📚 Available Documentation
|
||||
|
||||
### 1. **[DATABASE_SPECIFICATION.md](DATABASE_SPECIFICATION.md)** ⭐
|
||||
**Complete technical specification** - 25KB comprehensive document
|
||||
|
||||
**Contents:**
|
||||
- Complete table definitions with all columns
|
||||
- Entity relationship diagrams
|
||||
- Junction table specifications
|
||||
- Lookup table values
|
||||
- Business rules and workflows
|
||||
- Sample queries and use cases
|
||||
- Instructions for requesting schema changes
|
||||
|
||||
**Use when:** You need complete technical details about the database structure.
|
||||
|
||||
---
|
||||
|
||||
### 2. **[QUICK_SCHEMA_REFERENCE.md](QUICK_SCHEMA_REFERENCE.md)** 🚀
|
||||
**Quick reference guide** - 5KB at-a-glance reference
|
||||
|
||||
**Contents:**
|
||||
- Table summary
|
||||
- Key relationships diagram
|
||||
- Core fields reference
|
||||
- Predefined lookup values
|
||||
- Common SQL queries
|
||||
- Constraint summary
|
||||
|
||||
**Use when:** You need quick lookup or common query examples.
|
||||
|
||||
---
|
||||
|
||||
### 3. **[schema.sql](schema.sql)** 💾
|
||||
**The actual SQL schema** - Executable SQL file
|
||||
|
||||
**Contents:**
|
||||
- Complete CREATE TABLE statements
|
||||
- Indexes and triggers
|
||||
- Predefined data (orientations, AP programs, etc.)
|
||||
- Views for common queries
|
||||
|
||||
**Use when:** Setting up or resetting the database.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### View Database Schema
|
||||
```bash
|
||||
# Read the quick reference
|
||||
cat database/QUICK_SCHEMA_REFERENCE.md
|
||||
|
||||
# Or full specification
|
||||
cat database/DATABASE_SPECIFICATION.md
|
||||
```
|
||||
|
||||
### Initialize Database
|
||||
```bash
|
||||
# Create test database from schema
|
||||
just init-test-db
|
||||
|
||||
# Create with sample data
|
||||
just create-fixtures
|
||||
```
|
||||
|
||||
### Query Database
|
||||
```bash
|
||||
# Open SQLite prompt
|
||||
just query-db
|
||||
|
||||
# Show specific thesis
|
||||
just show-thesis 42
|
||||
```
|
||||
|
||||
## 📝 Making Schema Changes
|
||||
|
||||
### Step 1: Document Your Request
|
||||
|
||||
Format:
|
||||
```
|
||||
**Table:** [table_name]
|
||||
**Change Type:** [add/modify/remove]
|
||||
**What:** [description]
|
||||
**Why:** [reason/use case]
|
||||
**Example Data:** [samples]
|
||||
```
|
||||
|
||||
### Step 2: Specify Details
|
||||
|
||||
For **new columns**:
|
||||
- Column name
|
||||
- Data type (TEXT, INTEGER, BOOLEAN, DATETIME)
|
||||
- NULL/NOT NULL
|
||||
- Default value
|
||||
- Indexes needed?
|
||||
|
||||
For **new tables**:
|
||||
- Table name
|
||||
- All columns
|
||||
- Relationships to existing tables
|
||||
- Sample data
|
||||
|
||||
### Step 3: Provide Context
|
||||
|
||||
Include:
|
||||
- Use case scenario
|
||||
- Who will use it?
|
||||
- How will it be displayed?
|
||||
- Any constraints?
|
||||
|
||||
### Example Request
|
||||
|
||||
```
|
||||
**Table:** theses
|
||||
**Change Type:** add column
|
||||
**What:** Add column to track if thesis won an award
|
||||
**Why:** Need to highlight award-winning theses on homepage
|
||||
**Column Name:** has_award
|
||||
**Data Type:** BOOLEAN
|
||||
**Default:** 0 (false)
|
||||
**Example:** 1 for "Prix du Jury 2025" winner
|
||||
```
|
||||
|
||||
## 🗂️ Database Structure Overview
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ theses │ ◄── Main table (500+ records/year)
|
||||
└──────┬──────┘
|
||||
│
|
||||
├──► authors (via thesis_authors)
|
||||
├──► supervisors (via thesis_supervisors)
|
||||
├──► keywords (via thesis_keywords)
|
||||
├──► languages (via thesis_languages)
|
||||
├──► formats (via thesis_formats)
|
||||
├──► thesis_files (attachments)
|
||||
│
|
||||
└──► Lookup tables:
|
||||
• orientations
|
||||
• ap_programs
|
||||
• finality_types
|
||||
• access_types
|
||||
• license_types
|
||||
```
|
||||
|
||||
## 📊 Key Statistics
|
||||
|
||||
- **Core tables:** 3 (theses, authors, supervisors)
|
||||
- **Junction tables:** 5 (many-to-many relationships)
|
||||
- **Lookup tables:** 7 (predefined values)
|
||||
- **Support tables:** 2 (files, pages)
|
||||
- **Views:** 2 (full data, public only)
|
||||
- **Indexes:** 11 (for performance)
|
||||
- **Triggers:** 4 (auto-update timestamps)
|
||||
|
||||
## 🔍 Common Scenarios
|
||||
|
||||
### Scenario 1: Student Submits Thesis
|
||||
1. Create record in `theses` (is_published=0)
|
||||
2. Add author to `authors`, link via `thesis_authors`
|
||||
3. Add supervisor(s) to `supervisors`, link via `thesis_supervisors`
|
||||
4. Set `orientation_id`, `ap_program_id`, `finality_id`
|
||||
5. Upload file to `thesis_files`
|
||||
6. Add keywords via `thesis_keywords`
|
||||
7. Set `submitted_at` timestamp
|
||||
|
||||
### Scenario 2: Admin Publishes Thesis
|
||||
1. Verify all required fields present
|
||||
2. Set `defense_date`
|
||||
3. Set `jury_points`
|
||||
4. Optional: add `context_note`
|
||||
5. Set `is_published = 1`
|
||||
6. Set `published_at = CURRENT_TIMESTAMP`
|
||||
|
||||
### Scenario 3: Public User Searches
|
||||
Query `v_theses_public` view with filters:
|
||||
- By year
|
||||
- By orientation
|
||||
- By keyword
|
||||
- By author name
|
||||
- Full-text search in title/synopsis
|
||||
|
||||
## 🛠️ Development Workflow
|
||||
|
||||
### Local Development
|
||||
1. Use `test.db` for development
|
||||
2. Create via `just init-test-db`
|
||||
3. Populate with `just create-fixtures`
|
||||
4. Test queries before deployment
|
||||
|
||||
### Schema Changes
|
||||
1. Update `schema.sql`
|
||||
2. Update `DATABASE_SPECIFICATION.md`
|
||||
3. Test on `test.db`
|
||||
4. Deploy to production (manual migration)
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run tests on local database
|
||||
just test-public-all
|
||||
|
||||
# Check database stats
|
||||
just stats-public
|
||||
```
|
||||
|
||||
## 📞 Need Help?
|
||||
|
||||
1. **Quick lookup** → Read `QUICK_SCHEMA_REFERENCE.md`
|
||||
2. **Complete details** → Read `DATABASE_SPECIFICATION.md`
|
||||
3. **Schema changes** → Follow format in this README
|
||||
4. **SQL examples** → Check `QUICK_SCHEMA_REFERENCE.md`
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Deployment Guide](../nginx/DEPLOYMENT_COMPLETE.md)
|
||||
- [Repository Structure](../REPOSITORY_STRUCTURE_ANALYSIS.md)
|
||||
- [Test Database Guide](../nginx/TEST_DATABASE_SETUP.md)
|
||||
0
app/storage/cache/.gitkeep
vendored
Normal file
0
app/storage/cache/.gitkeep
vendored
Normal file
1
app/storage/cache/rate_limit/ad921d60486366258809553a3db49a4a.json
vendored
Normal file
1
app/storage/cache/rate_limit/ad921d60486366258809553a3db49a4a.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[1776449542]
|
||||
253
app/storage/fixtures/CreateTestDatabase.php
Normal file
253
app/storage/fixtures/CreateTestDatabase.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/**
|
||||
* Script to create a test database with sample data
|
||||
* Run this script once to set up test.db for development
|
||||
*/
|
||||
|
||||
$dbPath = __DIR__ . '/../test.db';
|
||||
|
||||
// Remove existing database if it exists
|
||||
if (file_exists($dbPath)) {
|
||||
unlink($dbPath);
|
||||
echo "Removed existing test database\n";
|
||||
}
|
||||
|
||||
try {
|
||||
// Create database connection
|
||||
$pdo = new PDO('sqlite:' . $dbPath);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
echo "Created new database: $dbPath\n";
|
||||
|
||||
// Read and execute schema
|
||||
$schemaPath = __DIR__ . '/../schema.sql';
|
||||
if (!file_exists($schemaPath)) {
|
||||
throw new Exception("Schema file not found: $schemaPath");
|
||||
}
|
||||
|
||||
$schema = file_get_contents($schemaPath);
|
||||
$pdo->exec($schema);
|
||||
echo "Schema created successfully\n";
|
||||
|
||||
// Insert sample authors
|
||||
$authors = [
|
||||
['name' => 'Marie Dubois', 'email' => 'marie.dubois@example.com'],
|
||||
['name' => 'Jean Martin', 'email' => 'jean.martin@example.com'],
|
||||
['name' => 'Sophie Bernard', 'email' => 'sophie.bernard@example.com'],
|
||||
['name' => 'Lucas Petit', 'email' => 'lucas.petit@example.com'],
|
||||
['name' => 'Emma Leroy', 'email' => 'emma.leroy@example.com'],
|
||||
];
|
||||
|
||||
foreach ($authors as $author) {
|
||||
$stmt = $pdo->prepare("INSERT INTO authors (name, email) VALUES (:name, :email)");
|
||||
$stmt->execute($author);
|
||||
}
|
||||
echo "Inserted " . count($authors) . " sample authors\n";
|
||||
|
||||
// Insert sample supervisors
|
||||
$supervisors = [
|
||||
['name' => 'Prof. Claire Fontaine'],
|
||||
['name' => 'Dr. Thomas Moreau'],
|
||||
['name' => 'Prof. Anne Laurent'],
|
||||
];
|
||||
|
||||
foreach ($supervisors as $supervisor) {
|
||||
$stmt = $pdo->prepare("INSERT INTO supervisors (name) VALUES (:name)");
|
||||
$stmt->execute($supervisor);
|
||||
}
|
||||
echo "Inserted " . count($supervisors) . " sample supervisors\n";
|
||||
|
||||
// Insert sample tags (formerly keywords)
|
||||
$sampleKeywords = [
|
||||
'spéculation', 'narration', 'urbanisme', 'patrimoine', 'intime',
|
||||
'collectivité', 'film', 'cinéma', 'sociologie', 'anthropologie',
|
||||
'éphémérité', 'queer', 'écriture', 'poésie', 'écologie',
|
||||
'technologies', 'design', 'performance', 'installation', 'art numérique'
|
||||
];
|
||||
|
||||
foreach ($sampleKeywords as $keyword) {
|
||||
$stmt = $pdo->prepare("INSERT INTO tags (name) VALUES (:name)");
|
||||
$stmt->execute(['name' => $keyword]);
|
||||
}
|
||||
echo "Inserted " . count($sampleKeywords) . " sample tags\n";
|
||||
|
||||
// Insert sample theses
|
||||
$theses = [
|
||||
[
|
||||
'identifier' => '2024-001',
|
||||
'title' => 'Espaces Urbains et Narration Collective',
|
||||
'subtitle' => 'Une exploration des récits de la ville',
|
||||
'year' => 2024,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 1, // Arts Numériques
|
||||
'ap_program_id' => 1, // Narration Spéculative
|
||||
'finality_id' => 1, // Approfondi
|
||||
'synopsis' => 'Ce travail explore la manière dont les espaces urbains génèrent des récits collectifs. À travers une série d\'installations vidéo et sonores, je documente les histoires cachées des quartiers en transformation. Le projet interroge la mémoire collective et la façon dont l\'architecture influence nos récits personnels et communautaires.',
|
||||
'access_type_id' => 1, // Libre
|
||||
'is_published' => 1,
|
||||
],
|
||||
[
|
||||
'identifier' => '2024-002',
|
||||
'title' => 'Corps et Technologies',
|
||||
'subtitle' => 'Interfaces sensorielles',
|
||||
'year' => 2024,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 4, // Installation-Performance
|
||||
'ap_program_id' => 2, // Design et Politique du Multiple
|
||||
'finality_id' => 1, // Approfondi
|
||||
'synopsis' => 'Cette recherche artistique examine la relation entre le corps humain et les technologies numériques. À travers des performances interactives, j\'explore comment les interfaces technologiques transforment notre perception corporelle et créent de nouvelles formes de présence. Le projet questionne l\'hybridation entre organique et numérique.',
|
||||
'access_type_id' => 1, // Libre
|
||||
'is_published' => 1,
|
||||
],
|
||||
[
|
||||
'identifier' => '2024-003',
|
||||
'title' => 'Poétiques du Quotidien',
|
||||
'subtitle' => NULL,
|
||||
'year' => 2024,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 6, // Photographie
|
||||
'ap_program_id' => 3, // Atelier Pratiques Situées
|
||||
'finality_id' => 2, // Enseignement
|
||||
'synopsis' => 'Ce projet photographique documente les gestes ordinaires et les moments éphémères du quotidien. En utilisant une approche documentaire mêlée de fiction, je cherche à révéler la poésie cachée dans les rituels banals. Le travail questionne notre rapport au temps et à l\'attention dans un monde accéléré.',
|
||||
'access_type_id' => 1, // Libre
|
||||
'is_published' => 1,
|
||||
],
|
||||
[
|
||||
'identifier' => '2023-015',
|
||||
'title' => 'Écologies Affectives',
|
||||
'subtitle' => 'Cartographies sensibles des liens',
|
||||
'year' => 2023,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 9, // Graphisme
|
||||
'ap_program_id' => 4, // LIENS
|
||||
'finality_id' => 1, // Approfondi
|
||||
'synopsis' => 'Ce travail de design graphique développe une méthodologie visuelle pour cartographier les relations affectives et les réseaux de soin. À travers des visualisations de données sensibles et des éditions expérimentales, le projet explore comment représenter l\'invisible des liens humains et des solidarités.',
|
||||
'access_type_id' => 1, // Libre
|
||||
'is_published' => 1,
|
||||
],
|
||||
[
|
||||
'identifier' => '2023-020',
|
||||
'title' => 'Mémoires Spéculatives',
|
||||
'subtitle' => 'Archives du futur',
|
||||
'year' => 2023,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 10, // Typographie
|
||||
'ap_program_id' => 1, // Narration Spéculative
|
||||
'finality_id' => 3, // Spécialisé
|
||||
'synopsis' => 'Un projet éditorial qui imagine des archives futures à partir de traces présentes. En utilisant la typographie comme outil de spéculation temporelle, je crée des documents fictionnels qui interrogent notre rapport à l\'histoire et à la transmission. Le travail questionne la matérialité de la mémoire.',
|
||||
'access_type_id' => 2, // Interne
|
||||
'is_published' => 1,
|
||||
],
|
||||
[
|
||||
'identifier' => '2025-002',
|
||||
'title' => 'Performance et Politique du Geste',
|
||||
'subtitle' => NULL,
|
||||
'year' => 2025,
|
||||
'is_doctoral' => 0,
|
||||
'orientation_id' => 4, // Installation-Performance
|
||||
'ap_program_id' => 3, // Atelier Pratiques Situées
|
||||
'finality_id' => 1, // Approfondi
|
||||
'synopsis' => 'Cette recherche performative explore la dimension politique des gestes quotidiens. À travers une série de performances filmées, j\'examine comment les micro-actions peuvent constituer des formes de résistance. Le projet s\'intéresse aux corps en mouvement et à leur capacité à transformer l\'espace public.',
|
||||
'access_type_id' => 1, // Libre
|
||||
'is_published' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($theses as $thesis) {
|
||||
$columns = implode(', ', array_keys($thesis));
|
||||
$placeholders = ':' . implode(', :', array_keys($thesis));
|
||||
$stmt = $pdo->prepare("INSERT INTO theses ($columns) VALUES ($placeholders)");
|
||||
$stmt->execute($thesis);
|
||||
}
|
||||
echo "Inserted " . count($theses) . " sample theses\n";
|
||||
|
||||
// Link authors to theses
|
||||
$thesisAuthors = [
|
||||
['thesis_id' => 1, 'author_id' => 1, 'author_order' => 1],
|
||||
['thesis_id' => 2, 'author_id' => 2, 'author_order' => 1],
|
||||
['thesis_id' => 3, 'author_id' => 3, 'author_order' => 1],
|
||||
['thesis_id' => 4, 'author_id' => 4, 'author_order' => 1],
|
||||
['thesis_id' => 5, 'author_id' => 5, 'author_order' => 1],
|
||||
['thesis_id' => 6, 'author_id' => 1, 'author_order' => 1],
|
||||
];
|
||||
|
||||
foreach ($thesisAuthors as $link) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (:thesis_id, :author_id, :author_order)");
|
||||
$stmt->execute($link);
|
||||
}
|
||||
echo "Linked authors to theses\n";
|
||||
|
||||
// Link supervisors to theses
|
||||
$thesisSupervisors = [
|
||||
['thesis_id' => 1, 'supervisor_id' => 1, 'supervisor_order' => 1],
|
||||
['thesis_id' => 2, 'supervisor_id' => 2, 'supervisor_order' => 1],
|
||||
['thesis_id' => 3, 'supervisor_id' => 1, 'supervisor_order' => 1],
|
||||
['thesis_id' => 4, 'supervisor_id' => 3, 'supervisor_order' => 1],
|
||||
['thesis_id' => 5, 'supervisor_id' => 2, 'supervisor_order' => 1],
|
||||
['thesis_id' => 6, 'supervisor_id' => 3, 'supervisor_order' => 1],
|
||||
];
|
||||
|
||||
foreach ($thesisSupervisors as $link) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (:thesis_id, :supervisor_id, :supervisor_order)");
|
||||
$stmt->execute($link);
|
||||
}
|
||||
echo "Linked supervisors to theses\n";
|
||||
|
||||
// Link tags to theses (thesis_tags junction)
|
||||
$thesisKeywords = [
|
||||
['thesis_id' => 1, 'tag_id' => 3], // urbanisme
|
||||
['thesis_id' => 1, 'tag_id' => 2], // narration
|
||||
['thesis_id' => 1, 'tag_id' => 6], // collectivité
|
||||
['thesis_id' => 2, 'tag_id' => 16], // technologies
|
||||
['thesis_id' => 2, 'tag_id' => 18], // performance
|
||||
['thesis_id' => 2, 'tag_id' => 20], // art numérique
|
||||
['thesis_id' => 3, 'tag_id' => 14], // poésie
|
||||
['thesis_id' => 3, 'tag_id' => 11], // éphémérité
|
||||
['thesis_id' => 3, 'tag_id' => 5], // intime
|
||||
['thesis_id' => 4, 'tag_id' => 15], // écologie
|
||||
['thesis_id' => 4, 'tag_id' => 17], // design
|
||||
['thesis_id' => 5, 'tag_id' => 1], // spéculation
|
||||
['thesis_id' => 5, 'tag_id' => 4], // patrimoine
|
||||
['thesis_id' => 6, 'tag_id' => 18], // performance
|
||||
['thesis_id' => 6, 'tag_id' => 9], // sociologie
|
||||
];
|
||||
|
||||
foreach ($thesisKeywords as $link) {
|
||||
$stmt = $pdo->prepare("INSERT OR IGNORE INTO thesis_tags (tag_id, thesis_id) VALUES (:tag_id, :thesis_id)");
|
||||
$stmt->execute($link);
|
||||
}
|
||||
echo "Linked tags to theses\n";
|
||||
|
||||
// Link languages to theses (all in French)
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (:thesis_id, 1)");
|
||||
$stmt->execute(['thesis_id' => $i]);
|
||||
}
|
||||
echo "Linked languages to theses\n";
|
||||
|
||||
// Link formats to theses
|
||||
$thesisFormats = [
|
||||
['thesis_id' => 1, 'format_id' => 3], // Vidéo
|
||||
['thesis_id' => 1, 'format_id' => 6], // Installation
|
||||
['thesis_id' => 2, 'format_id' => 4], // Performance
|
||||
['thesis_id' => 3, 'format_id' => 5], // Objet éditorial
|
||||
['thesis_id' => 4, 'format_id' => 1], // Site web
|
||||
['thesis_id' => 4, 'format_id' => 5], // Objet éditorial
|
||||
['thesis_id' => 5, 'format_id' => 5], // Objet éditorial
|
||||
['thesis_id' => 6, 'format_id' => 4], // Performance
|
||||
['thesis_id' => 6, 'format_id' => 3], // Vidéo
|
||||
];
|
||||
|
||||
foreach ($thesisFormats as $link) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (:thesis_id, :format_id)");
|
||||
$stmt->execute($link);
|
||||
}
|
||||
echo "Linked formats to theses\n";
|
||||
|
||||
echo "\n✅ Test database created successfully!\n";
|
||||
echo "Database location: $dbPath\n";
|
||||
echo "\nYou can now test the search feature at: http://localhost/front-backend/repertoire.php\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
106
app/storage/migrations/001_rename_keywords_to_tags.sql
Normal file
106
app/storage/migrations/001_rename_keywords_to_tags.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
-- Migration 001: Rename keywords→tags, thesis_keywords→thesis_tags, keyword column→name
|
||||
-- SQLite does not support ALTER TABLE RENAME COLUMN before 3.25, so we recreate tables.
|
||||
-- This migration is safe to run after 004 and 005 (no dependency ordering required
|
||||
-- since SQLite processes this in one transaction).
|
||||
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. Create new tags table
|
||||
CREATE TABLE tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 2. Copy data from keywords → tags (keyword column → name)
|
||||
INSERT INTO tags (id, name, created_at)
|
||||
SELECT id, keyword, created_at FROM keywords;
|
||||
|
||||
-- 3. Create new junction table thesis_tags
|
||||
CREATE TABLE thesis_tags (
|
||||
tag_id INTEGER NOT NULL,
|
||||
thesis_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (tag_id, thesis_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 4. Copy junction data (keyword_id → tag_id)
|
||||
INSERT INTO thesis_tags (tag_id, thesis_id)
|
||||
SELECT keyword_id, thesis_id FROM thesis_keywords;
|
||||
|
||||
-- 5. Drop old tables
|
||||
DROP TABLE thesis_keywords;
|
||||
DROP TABLE keywords;
|
||||
|
||||
-- 6. Recreate indexes with canonical names
|
||||
CREATE INDEX idx_tags_name ON tags(name);
|
||||
CREATE INDEX idx_thesis_tags_thesis ON thesis_tags(thesis_id);
|
||||
CREATE INDEX idx_thesis_tags_tag ON thesis_tags(tag_id);
|
||||
|
||||
-- 7. Rebuild views to reference new tables
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
t.banner_path,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.id
|
||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
|
||||
CREATE VIEW v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
9
app/storage/migrations/002_add_visibility.sql
Normal file
9
app/storage/migrations/002_add_visibility.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- Migration 002: Wire visibility states to existing access_type_id column.
|
||||
-- The access_types table already has Libre (1), Interne (2), Interdit (3).
|
||||
-- No structural changes needed — this migration is a no-op for the schema.
|
||||
-- It documents the intent and ensures access_types seed rows exist.
|
||||
|
||||
INSERT OR IGNORE INTO access_types (id, name, description) VALUES
|
||||
(1, 'Libre', 'TFE en libre accès à tout le monde sur la plateforme et en bibliothèque'),
|
||||
(2, 'Interne', 'TFE accessible uniquement sur place en physique. Une note descriptive est disponible sur le site'),
|
||||
(3, 'Interdit', 'TFE non disponible en physique ni sur le site. Une note descriptive est disponible sur le site');
|
||||
19
app/storage/migrations/003_seed_license_types.sql
Normal file
19
app/storage/migrations/003_seed_license_types.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Migration 003: Seed license_types table
|
||||
-- Safe to run on existing databases (INSERT OR IGNORE)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS license_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO license_types (name) VALUES
|
||||
('CC BY 4.0'),
|
||||
('CC BY-SA 4.0'),
|
||||
('CC BY-ND 4.0'),
|
||||
('CC BY-NC 4.0'),
|
||||
('CC BY-NC-SA 4.0'),
|
||||
('CC BY-NC-ND 4.0'),
|
||||
('Tous droits réservés'),
|
||||
('Domaine public');
|
||||
66
app/storage/migrations/004_jury_roles.sql
Normal file
66
app/storage/migrations/004_jury_roles.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
-- Migration 004: Add jury role and is_external columns to thesis_supervisors
|
||||
-- Existing rows get role = 'promoteur', is_external = 0 (no data loss)
|
||||
|
||||
ALTER TABLE thesis_supervisors ADD COLUMN role TEXT NOT NULL DEFAULT 'promoteur';
|
||||
ALTER TABLE thesis_supervisors ADD COLUMN is_external INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Recreate v_theses_full to include jury role columns
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT k.keyword) as keywords
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.id
|
||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||
LEFT JOIN thesis_keywords tk ON t.id = tk.thesis_id
|
||||
LEFT JOIN keywords k ON tk.keyword_id = k.id
|
||||
GROUP BY t.id;
|
||||
|
||||
-- Recreate public view
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
|
||||
CREATE VIEW v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
65
app/storage/migrations/005_add_banner.sql
Normal file
65
app/storage/migrations/005_add_banner.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- Migration 005: Add banner_path column to theses for home page card thumbnails
|
||||
|
||||
ALTER TABLE theses ADD COLUMN banner_path TEXT;
|
||||
|
||||
-- Recreate v_theses_full to include banner_path
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
t.banner_path,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.id
|
||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
-- Recreate public view
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
|
||||
CREATE VIEW v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
8
app/storage/migrations/006_add_composite_index.sql
Normal file
8
app/storage/migrations/006_add_composite_index.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Migration 006: Add composite covering index (is_published, year DESC) on theses
|
||||
--
|
||||
-- Every public-facing query filters on is_published = 1 AND orders/filters by year.
|
||||
-- The existing separate idx_theses_published and idx_theses_year force the query
|
||||
-- planner to pick one index and sort the other via a temp B-tree.
|
||||
-- This single covering index eliminates the extra sort pass.
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC);
|
||||
8
app/storage/migrations/007_system_cache.sql
Normal file
8
app/storage/migrations/007_system_cache.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Migration 007: Add system_cache table for admin system page caching
|
||||
-- Stores JSON-encoded status snapshots keyed by section with a TTL mechanism.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_cache (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
89
app/storage/migrations/008_formulaire_settings.sql
Normal file
89
app/storage/migrations/008_formulaire_settings.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
-- Migration 008: Formulaire settings + contact visibility
|
||||
-- Adds site_settings key-value table for admin-configurable options
|
||||
-- Adds show_contact column to authors table
|
||||
-- Adds author_email + author_show_contact to views
|
||||
|
||||
-- ── 1. site_settings ─────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL DEFAULT '',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Default formulaire settings:
|
||||
-- access_type_interdit_enabled = 1 (Interdit is available in the add form)
|
||||
-- access_type_interne_enabled = 1 (Interne is available in the add form)
|
||||
-- access_type_libre_enabled = 0 (Libre is NOT yet available — next academic year)
|
||||
INSERT OR IGNORE INTO site_settings (key, value) VALUES
|
||||
('access_type_interdit_enabled', '1'),
|
||||
('access_type_interne_enabled', '1'),
|
||||
('access_type_libre_enabled', '0');
|
||||
|
||||
-- ── 2. show_contact on authors ────────────────────────────────────────────────
|
||||
-- NOTE: SQLite has no IF NOT EXISTS for ALTER TABLE.
|
||||
-- The migrate.sh script guards against re-running; ignore errors on existing DBs.
|
||||
ALTER TABLE authors ADD COLUMN show_contact INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- ── 3. Rebuild views to expose author_email and author_show_contact ───────────
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW IF NOT EXISTS v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
t.banner_path,
|
||||
t.access_type_id,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords,
|
||||
-- First author's email and contact-visibility flag
|
||||
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_email,
|
||||
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_show_contact
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.id
|
||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
CREATE VIEW IF NOT EXISTS v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
14
app/storage/migrations/009_share_links.sql
Normal file
14
app/storage/migrations/009_share_links.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Share links table: enables students to submit TFEs via unique, optional-password-protected URLs
|
||||
CREATE TABLE IF NOT EXISTS share_links (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
slug TEXT NOT NULL UNIQUE, -- Format: YYYYMMDD-<random>, e.g. 20260416-a3f9k2
|
||||
password_hash TEXT, -- bcrypt hash; NULL = no password required
|
||||
is_active INTEGER NOT NULL DEFAULT 1, -- 1 = active, 0 = disabled
|
||||
usage_count INTEGER NOT NULL DEFAULT 0, -- Number of successful submissions via this link
|
||||
created_by INTEGER NOT NULL, -- admin user ID (references admin_sessions or admin_users)
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at DATETIME -- NULL = never expires
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_share_links_slug ON share_links(slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_share_links_active ON share_links(is_active);
|
||||
14
app/storage/migrations/010_apropos_contents.sql
Normal file
14
app/storage/migrations/010_apropos_contents.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- ── apropos_contents table (structured data for the "À propos" page) ───────
|
||||
-- Replaces config/apropos.php.
|
||||
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
key TEXT NOT NULL UNIQUE, -- 'contacts', 'credits', 'erg_url'
|
||||
value TEXT, -- JSON array for contacts/credits, plain string for erg_url
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Seed with the current defaults from config/apropos.php
|
||||
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
||||
('contacts', '[{"name":"Laurent Leprince","role":"Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :","email":"laurent.leprince@uclouvain.be"},{"name":"Xavier Gorgol","role":"Responsable des mémoires de l''ERG :","email":"xavier.gorgol@erg.be"},{"name":"Brigitte Ledune","role":"Cours de suivi de mémoire :","email":"brigitte.ledune@erg.be"}]'),
|
||||
('credits', '[{"label":"Design & développement","value":"Olivia Marly, Théophile Gerveau-Mercie & Théo Hennequin"},{"label":"Typographies","value":"Ductus (Amélie Dumont) & BBB DM Sans"}]'),
|
||||
('erg_url', 'https://erg.be');
|
||||
46
app/storage/migrations/011_apropos_entries.sql
Normal file
46
app/storage/migrations/011_apropos_entries.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- Transform apropos data: each row has a label/role and an entries[] of {text, url}.
|
||||
-- Contacts also include email per entry.
|
||||
|
||||
UPDATE apropos_contents SET value = '
|
||||
[
|
||||
{
|
||||
"label": "Design & développement",
|
||||
"entries": [
|
||||
{"text": "Olivia Marly", "url": ""},
|
||||
{"text": "Théophile Gerveau-Mercie", "url": ""},
|
||||
{"text": "Théo Hennequin", "url": ""}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Typographies",
|
||||
"entries": [
|
||||
{"text": "Ductus (Amélie Dumont)", "url": ""},
|
||||
{"text": "BBB DM Sans", "url": ""}
|
||||
]
|
||||
}
|
||||
]' WHERE key = 'credits';
|
||||
|
||||
UPDATE apropos_contents SET value = '
|
||||
[
|
||||
{
|
||||
"role": "Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :",
|
||||
"entries": [
|
||||
{"text": "Laurent Leprince", "url": "", "email": "laurent.leprince@uclouvain.be"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "Responsable des mémoires de l''ERG :",
|
||||
"entries": [
|
||||
{"text": "Xavier Gorgol", "url": "", "email": "xavier.gorgol@erg.be"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "Cours de suivi de mémoire :",
|
||||
"entries": [
|
||||
{"text": "Brigitte Ledune", "url": "", "email": "brigitte.ledune@erg.be"}
|
||||
]
|
||||
}
|
||||
]' WHERE key = 'contacts';
|
||||
|
||||
-- Remove erg_url from the table (hardcoded in template now)
|
||||
DELETE FROM apropos_contents WHERE key = 'erg_url';
|
||||
22
app/storage/migrations/012_smtp_settings.sql
Normal file
22
app/storage/migrations/012_smtp_settings.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- SMTP relay credentials stored in the database.
|
||||
-- A single active row is read at send-time for flexibility (change provider,
|
||||
-- rotate passwords, etc. without touching code or env vars).
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smtp_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton row
|
||||
host TEXT NOT NULL DEFAULT '',
|
||||
port INTEGER NOT NULL DEFAULT 587,
|
||||
encryption TEXT NOT NULL DEFAULT 'tls', -- 'tls' | 'ssl' | 'none'
|
||||
username TEXT NOT NULL DEFAULT '',
|
||||
password TEXT NOT NULL DEFAULT '', -- stored in clear for now; encrypt later
|
||||
from_email TEXT NOT NULL DEFAULT '',
|
||||
from_name TEXT NOT NULL DEFAULT 'Post-ERG',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default empty row so the settings form can start working immediately.
|
||||
INSERT OR IGNORE INTO smtp_settings (id) VALUES (1);
|
||||
|
||||
-- Helper view so callers always read the same row.
|
||||
CREATE VIEW IF NOT EXISTS v_smtp_active AS
|
||||
SELECT * FROM smtp_settings WHERE id = 1;
|
||||
11
app/storage/migrations/013_admin_password.sql
Normal file
11
app/storage/migrations/013_admin_password.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Migration 013: Store admin password hash in site_settings
|
||||
--
|
||||
-- Previously stored in config/admin_credentials.php as the constant ADMIN_PASSWORD_HASH.
|
||||
-- Now stored alongside SMTP credentials in the site_settings key-value table.
|
||||
--
|
||||
-- After applying this migration, import your existing hash manually:
|
||||
-- UPDATE site_settings SET value = '$2y$12$...' WHERE key = 'admin_password_hash';
|
||||
-- Or simply set a new one via the admin panel UI.
|
||||
|
||||
INSERT OR IGNORE INTO site_settings (key, value) VALUES
|
||||
('admin_password_hash', '');
|
||||
BIN
app/storage/posterg.db
Normal file
BIN
app/storage/posterg.db
Normal file
Binary file not shown.
516
app/storage/schema.sql
Normal file
516
app/storage/schema.sql
Normal file
@@ -0,0 +1,516 @@
|
||||
-- Post-ERG Thesis Database Schema
|
||||
-- SQLite Database for managing final thesis projects (TFE) and doctoral theses
|
||||
|
||||
-- ============================================================================
|
||||
-- CORE ENTITIES
|
||||
-- ============================================================================
|
||||
|
||||
-- Students/Authors table
|
||||
CREATE TABLE IF NOT EXISTS authors (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
show_contact INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Supervisors/Promoters table
|
||||
CREATE TABLE IF NOT EXISTS supervisors (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- PREDEFINED REFERENCE DATA (lookup tables)
|
||||
-- ============================================================================
|
||||
|
||||
-- Orientations (predefined list from specifications)
|
||||
CREATE TABLE IF NOT EXISTS orientations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert predefined orientations
|
||||
INSERT OR IGNORE INTO orientations (name) VALUES
|
||||
('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 (Ateliers Pratiques) - predefined list
|
||||
CREATE TABLE IF NOT EXISTS ap_programs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
code TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert predefined AP programs
|
||||
INSERT OR IGNORE INTO ap_programs (name, code) VALUES
|
||||
('Narration Spéculative', NULL),
|
||||
('Design et Politique du Multiple', 'DPM'),
|
||||
('Atelier Pratiques Situées', 'APS'),
|
||||
('Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'LIENS');
|
||||
|
||||
-- Master finality types
|
||||
CREATE TABLE IF NOT EXISTS finality_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO finality_types (name) VALUES
|
||||
('Approfondi'),
|
||||
('Enseignement'),
|
||||
('Spécialisé');
|
||||
|
||||
-- Languages
|
||||
CREATE TABLE IF NOT EXISTS languages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO languages (name) VALUES
|
||||
('Français'),
|
||||
('Anglais');
|
||||
|
||||
-- Format types (can select multiple)
|
||||
CREATE TABLE IF NOT EXISTS format_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO format_types (name) VALUES
|
||||
('Site web'),
|
||||
('Audio'),
|
||||
('Vidéo'),
|
||||
('Performance'),
|
||||
('Objet éditorial'),
|
||||
('Installation'),
|
||||
('Autre');
|
||||
|
||||
-- Tags (keywords — canonical M2M table; formerly 'keywords'/'keyword' column)
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name);
|
||||
|
||||
-- Access authorization types
|
||||
CREATE TABLE IF NOT EXISTS access_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO access_types (name, description) VALUES
|
||||
('Libre', 'TFE en libre accès à tout le monde sur la plateforme et en bibliothèque'),
|
||||
('Interne', 'TFE accessible uniquement sur place en physique. Une note descriptive est disponible sur le site'),
|
||||
('Interdit', 'TFE non disponible en physique ni sur le site. Une note descriptive est disponible sur le site');
|
||||
|
||||
-- License types
|
||||
CREATE TABLE IF NOT EXISTS license_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO license_types (name) VALUES
|
||||
('CC BY 4.0'),
|
||||
('CC BY-SA 4.0'),
|
||||
('CC BY-ND 4.0'),
|
||||
('CC BY-NC 4.0'),
|
||||
('CC BY-NC-SA 4.0'),
|
||||
('CC BY-NC-ND 4.0'),
|
||||
('Tous droits réservés'),
|
||||
('Domaine public');
|
||||
|
||||
-- ============================================================================
|
||||
-- MAIN THESIS TABLE
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS theses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
identifier TEXT UNIQUE, -- e.g., "2025-002"
|
||||
|
||||
-- Basic information
|
||||
title TEXT NOT NULL,
|
||||
subtitle TEXT,
|
||||
year INTEGER NOT NULL,
|
||||
|
||||
-- Type of work
|
||||
is_doctoral BOOLEAN DEFAULT 0, -- 0 for TFE, 1 for doctoral thesis
|
||||
|
||||
-- Academic details
|
||||
orientation_id INTEGER,
|
||||
ap_program_id INTEGER,
|
||||
finality_id INTEGER,
|
||||
|
||||
-- Content
|
||||
synopsis TEXT, -- ~200 words
|
||||
context_note TEXT, -- Note added by jury president (max 150 words)
|
||||
remarks TEXT, -- Internal remarks
|
||||
|
||||
-- Duration/size
|
||||
duration_minutes INTEGER, -- For audio/video works
|
||||
duration_pages INTEGER, -- For written works
|
||||
file_size_info TEXT, -- e.g., "128 pages", "78 pages + ?? minutes"
|
||||
|
||||
-- Access and licensing
|
||||
access_type_id INTEGER,
|
||||
license_id INTEGER,
|
||||
|
||||
-- Jury information
|
||||
jury_points DECIMAL(4,2), -- Points out of 20
|
||||
jury_note_added BOOLEAN DEFAULT 0, -- Whether jury president added a note
|
||||
|
||||
-- Publication status
|
||||
submitted_at DATETIME, -- When student submitted
|
||||
defense_date DATETIME, -- Date of defense/soutenance
|
||||
published_at DATETIME, -- When made public (after jury review)
|
||||
is_published BOOLEAN DEFAULT 0,
|
||||
|
||||
-- External links
|
||||
baiu_link TEXT, -- Link to institutional repository
|
||||
|
||||
-- Home page card banner (optional, landscape image)
|
||||
banner_path TEXT, -- path relative to STORAGE_ROOT (e.g. "banners/abc.jpg")
|
||||
|
||||
-- Timestamps
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Foreign keys
|
||||
FOREIGN KEY (orientation_id) REFERENCES orientations(id),
|
||||
FOREIGN KEY (ap_program_id) REFERENCES ap_programs(id),
|
||||
FOREIGN KEY (finality_id) REFERENCES finality_types(id),
|
||||
FOREIGN KEY (access_type_id) REFERENCES access_types(id),
|
||||
FOREIGN KEY (license_id) REFERENCES license_types(id)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- JUNCTION TABLES (Many-to-Many relationships)
|
||||
-- ============================================================================
|
||||
|
||||
-- Authors per thesis (can have multiple authors)
|
||||
CREATE TABLE IF NOT EXISTS thesis_authors (
|
||||
thesis_id INTEGER NOT NULL,
|
||||
author_id INTEGER NOT NULL,
|
||||
author_order INTEGER DEFAULT 1, -- Order of authors if multiple
|
||||
PRIMARY KEY (thesis_id, author_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Supervisors per thesis (jury: president, promoteur, lecteurs)
|
||||
CREATE TABLE IF NOT EXISTS thesis_supervisors (
|
||||
thesis_id INTEGER NOT NULL,
|
||||
supervisor_id INTEGER NOT NULL,
|
||||
supervisor_order INTEGER DEFAULT 1,
|
||||
role TEXT NOT NULL DEFAULT 'promoteur', -- 'president'|'promoteur'|'lecteur'
|
||||
is_external INTEGER NOT NULL DEFAULT 0, -- 0 = internal, 1 = external
|
||||
PRIMARY KEY (thesis_id, supervisor_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supervisor_id) REFERENCES supervisors(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Languages per thesis (can be multilingual)
|
||||
CREATE TABLE IF NOT EXISTS thesis_languages (
|
||||
thesis_id INTEGER NOT NULL,
|
||||
language_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (thesis_id, language_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (language_id) REFERENCES languages(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Formats per thesis (can have multiple formats)
|
||||
CREATE TABLE IF NOT EXISTS thesis_formats (
|
||||
thesis_id INTEGER NOT NULL,
|
||||
format_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (thesis_id, format_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (format_id) REFERENCES format_types(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tags per thesis (max 10 as per specs; formerly thesis_keywords)
|
||||
CREATE TABLE IF NOT EXISTS thesis_tags (
|
||||
tag_id INTEGER NOT NULL,
|
||||
thesis_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (tag_id, thesis_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- FILE ATTACHMENTS
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS thesis_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
thesis_id INTEGER NOT NULL,
|
||||
file_type TEXT NOT NULL, -- 'main', 'annex', 'written_part', 'other'
|
||||
file_path TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_size INTEGER, -- in bytes
|
||||
mime_type TEXT,
|
||||
description TEXT,
|
||||
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- SITE SETTINGS
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL DEFAULT '',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO site_settings (key, value) VALUES
|
||||
('access_type_interdit_enabled', '1'),
|
||||
('access_type_interne_enabled', '1'),
|
||||
('access_type_libre_enabled', '0'),
|
||||
('admin_password_hash', '');
|
||||
|
||||
-- ============================================================================
|
||||
-- STATIC PAGES / CONTENT MANAGEMENT
|
||||
-- ============================================================================
|
||||
|
||||
-- For managing editable static pages (charte, about, licenses, contact)
|
||||
CREATE TABLE IF NOT EXISTS system_cache (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
slug TEXT NOT NULL UNIQUE, -- 'charte', 'about', 'licenses', 'contact'
|
||||
title TEXT NOT NULL,
|
||||
content TEXT, -- Markdown or HTML content
|
||||
is_published BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Initialize default pages
|
||||
INSERT OR IGNORE INTO pages (slug, title, content) VALUES
|
||||
('charte', 'Charte', 'Contenu à venir'),
|
||||
('about', 'À propos', 'Contenu à venir'),
|
||||
('licenses', 'Licences', 'Contenu à venir');
|
||||
|
||||
-- ============================================================================
|
||||
-- SMTP SETTINGS
|
||||
-- ============================================================================
|
||||
|
||||
-- Singleton row — id is always 1. Credentials stored in clear for now.
|
||||
CREATE TABLE IF NOT EXISTS smtp_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
host TEXT NOT NULL DEFAULT '',
|
||||
port INTEGER NOT NULL DEFAULT 587,
|
||||
encryption TEXT NOT NULL DEFAULT 'tls', -- 'tls' | 'ssl' | 'none'
|
||||
username TEXT NOT NULL DEFAULT '',
|
||||
password TEXT NOT NULL DEFAULT '', -- stored in clear for now; encrypt later
|
||||
from_email TEXT NOT NULL DEFAULT '',
|
||||
from_name TEXT NOT NULL DEFAULT 'Post-ERG',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO smtp_settings (id) VALUES (1);
|
||||
|
||||
-- ============================================================================
|
||||
-- APROPOS CONTENTS (structured data for the "À propos" page)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
key TEXT NOT NULL UNIQUE, -- 'contacts', 'credits', 'erg_url'
|
||||
value TEXT, -- JSON array or plain string
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
||||
('contacts', '[
|
||||
{"role":"Bibliothèque d'architecture, d'ingénierie architecturale, d'urbanisme (BAIU) :", "entries":[
|
||||
{"text":"Laurent Leprince", "email":"laurent.leprince@uclouvain.be"}
|
||||
]},
|
||||
{"role":"Responsable des mémoires de l'ERG :", "entries":[
|
||||
{"text":"Xavier Gorgol", "email":"xavier.gorgol@erg.be"}
|
||||
]},
|
||||
{"role":"Cours de suivi de mémoire :", "entries":[
|
||||
{"text":"Brigitte Ledune", "email":"brigitte.ledune@erg.be"}
|
||||
]}
|
||||
]'),
|
||||
('credits', '[
|
||||
{"label":"Design & développement", "entries":[
|
||||
{"text":"Olivia Marly"},
|
||||
{"text":"Théophile Gerveau-Mercie"},
|
||||
{"text":"Théo Hennequin"}
|
||||
]},
|
||||
{"label":"Typographies", "entries":[
|
||||
{"text":"Ductus (Amélie Dumont)"},
|
||||
{"text":"BBB DM Sans"}
|
||||
]}
|
||||
]');INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
||||
('contacts', '[
|
||||
{"role":"Bibliothèque d\u0027architecture, d\u0027ingénierie architecturale, d\u0027urbanisme (BAIU) :", "entries":[
|
||||
{"text":"Laurent Leprince", "email":"laurent.leprince@uclouvain.be"}
|
||||
]},
|
||||
{"role":"Responsable des mémoires de l\u0027ERG :", "entries":[
|
||||
{"text":"Xavier Gorgol", "email":"xavier.gorgol@erg.be"}
|
||||
]},
|
||||
{"role":"Cours de suivi de mémoire :", "entries":[
|
||||
{"text":"Brigitte Ledune", "email":"brigitte.ledune@erg.be"}
|
||||
]}
|
||||
]'),
|
||||
('credits', '[
|
||||
{"label":"Design & développement", "entries":[
|
||||
{"text":"Olivia Marly"},
|
||||
{"text":"Théophile Gerveau-Mercie"},
|
||||
{"text":"Théo Hennequin"}
|
||||
]},
|
||||
{"label":"Typographies", "entries":[
|
||||
{"text":"Ductus (Amélie Dumont)"},
|
||||
{"text":"BBB DM Sans"}
|
||||
]}
|
||||
]');
|
||||
|
||||
-- ============================================================================
|
||||
-- INDEXES for performance
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_year ON theses(year);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_published ON theses(is_published);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_identifier ON theses(identifier);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_orientation ON theses(orientation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_ap_program ON theses(ap_program_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_theses_access_type ON theses(access_type_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_authors_email ON authors(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_thesis_authors_thesis ON thesis_authors(thesis_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_thesis_authors_author ON thesis_authors(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_thesis_tags_thesis ON thesis_tags(thesis_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_thesis_tags_tag ON thesis_tags(tag_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- TRIGGERS for automatic timestamp updates
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_theses_timestamp
|
||||
AFTER UPDATE ON theses
|
||||
BEGIN
|
||||
UPDATE theses SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_authors_timestamp
|
||||
AFTER UPDATE ON authors
|
||||
BEGIN
|
||||
UPDATE authors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_supervisors_timestamp
|
||||
AFTER UPDATE ON supervisors
|
||||
BEGIN
|
||||
UPDATE supervisors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_pages_timestamp
|
||||
AFTER UPDATE ON pages
|
||||
BEGIN
|
||||
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS update_apropos_contents_timestamp
|
||||
AFTER UPDATE ON apropos_contents
|
||||
BEGIN
|
||||
UPDATE apropos_contents SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END;
|
||||
|
||||
-- ============================================================================
|
||||
-- VIEWS for common queries
|
||||
-- ============================================================================
|
||||
|
||||
-- Full thesis information view
|
||||
CREATE VIEW IF NOT EXISTS v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.access_type_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
t.banner_path,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords,
|
||||
-- First author's email and contact-visibility flag
|
||||
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_email,
|
||||
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_show_contact
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.id
|
||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
-- Published theses only (for public view)
|
||||
CREATE VIEW IF NOT EXISTS v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
BIN
app/storage/test.db
Normal file
BIN
app/storage/test.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user