diff --git a/db/Database_TFE_test.csv b/db/Database_TFE_test.csv new file mode 100644 index 0000000..4335581 --- /dev/null +++ b/db/Database_TFE_test.csv @@ -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 et 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 diff --git a/db/Database_TFE_test.ods b/db/Database_TFE_test.ods new file mode 100644 index 0000000..1d99549 Binary files /dev/null and b/db/Database_TFE_test.ods differ diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..336e165 --- /dev/null +++ b/db/README.md @@ -0,0 +1,244 @@ +# Post-ERG Thesis Database Schema + +SQLite database schema for managing final thesis projects (TFE) and doctoral theses at ERG. + +## Overview + +This schema supports all requirements from the technical specifications (`posterg_fiche-technique.md`): + +- Multiple metadata categories (orientation, AP, finality, languages, formats, keywords) +- Multiple authors and supervisors per thesis +- Access control (Libre/Interne/Interdit) +- Licensing management +- File uploads (main TFE, annexes, written parts) +- Jury notes and points +- Publication workflow (submission → defense → publication) +- Editable static pages (charte, about, licenses, contact) +- Distinction between TFEs and doctoral theses + +## Database Structure + +### Core Tables + +**`theses`** - Main thesis information +- Basic metadata (title, subtitle, year, identifier) +- Academic details (orientation, AP program, finality) +- Content (synopsis, jury notes, duration/size) +- Access control and licensing +- Publication workflow status + +**`authors`** - Student/author information +- Name and contact email + +**`supervisors`** - Thesis promoters +- Name of supervisor/promoter + +**`thesis_files`** - Uploaded files +- Main TFE, annexes, written parts +- File metadata (path, size, MIME type) + +**`pages`** - Static content pages +- Charte, about, licenses, contact pages +- Easily editable content + +### Reference Tables (Predefined Lists) + +- `orientations` - Arts Numériques, Dessin, Cinéma d'animation, etc. +- `ap_programs` - Narration Spéculative, DPM, APS, LIENS +- `finality_types` - Approfondi, Enseignement, Spécialisé +- `languages` - Français, Anglais, etc. (expandable) +- `format_types` - Site web, Audio, Vidéo, Performance, etc. +- `keywords` - Dynamic, expandable keyword list (max 10 per thesis) +- `access_types` - Libre, Interne, Interdit +- `license_types` - To be defined + +### Junction Tables (Many-to-Many) + +- `thesis_authors` - Links theses to authors +- `thesis_supervisors` - Links theses to supervisors +- `thesis_languages` - Multiple languages per thesis +- `thesis_formats` - Multiple formats per thesis +- `thesis_keywords` - Max 10 keywords per thesis + +## Key Features + +### 1. Flexible Metadata +- Multiple authors, supervisors, languages, formats, and keywords per thesis +- Predefined lists with ability to add new entries +- Proper normalization to avoid data duplication + +### 2. Access Control +Three levels of access as specified: +- **Libre**: Freely accessible online and in library +- **Interne**: Physical access only, descriptive note online +- **Interdit**: No physical/online access, descriptive note only + +**Important**: Access can be restricted but never opened (as per specs) + +### 3. Publication Workflow +The schema tracks the complete lifecycle: + +1. **Submission** (`submitted_at`) - Student submits TFE +2. **Defense** (`defense_date`) - Soutenance takes place +3. **Jury Review** (`jury_note_added`, `jury_points`, `context_note`) +4. **Publication** (`published_at`, `is_published = 1`) + +**Important**: TFEs are NOT published immediately upon submission. They must wait for: +- Defense to occur +- Jury to add optional context note (max 150 words) +- Jury points to be recorded + +### 4. File Management +Support for multiple file types per thesis: +- Main TFE work +- Annexes +- Written part +- Other supporting files + +### 5. Views for Easy Querying + +**`v_theses_full`** - Complete thesis information with all related data +- Joins all tables +- Concatenates multiple values (authors, supervisors, keywords, etc.) +- Use for backend/admin interfaces + +**`v_theses_public`** - Only published theses +- Filtered to `is_published = 1` +- Use for public-facing website + +## Usage + +### Initialize Database + +```bash +sqlite3 posterg.db < schema.sql +``` + +### Example Queries + +#### Get all published theses from 2025 +```sql +SELECT * FROM v_theses_public WHERE year = 2025; +``` + +#### Get theses by orientation +```sql +SELECT * FROM v_theses_full +WHERE orientation = 'Vidéographie'; +``` + +#### Get theses with specific keyword +```sql +SELECT t.* FROM v_theses_full t +JOIN thesis_keywords tk ON t.id = tk.thesis_id +JOIN keywords k ON tk.keyword_id = k.id +WHERE k.keyword = 'performance'; +``` + +#### Get theses awaiting publication (submitted but not published) +```sql +SELECT * FROM theses +WHERE submitted_at IS NOT NULL + AND is_published = 0; +``` + +#### Update access type (can only restrict, not open) +```sql +-- Allowed: from Libre to Interne +UPDATE theses SET access_type_id = 2 WHERE id = 1; + +-- Not allowed per specs: from Interdit to Libre +-- This should be enforced in application logic +``` + +## Data Import Notes + +Based on `Database_TFE_test.csv`: + +### Current CSV Structure +- Identifiant (e.g., "2025-002") +- Titre, Sous-titre +- Auteur·ice(s) - comma-separated if multiple +- Contact - email +- Promoteur·ice(s) - comma-separated if multiple +- Format - comma-separated if multiple +- Année +- AP - abbreviation (DPM, LIENS, etc.) +- Orientation - abbreviation (SC, VI, CA, etc.) +- Finalité +- Mots-clés - comma-separated, max 10 +- Synopsis +- Contexte - jury context note +- Remarques - internal notes +- Langue - language(s) +- Autorisation - access type +- License - license type +- taille - duration/size info +- Points sur 20 - jury points +- lien BAIU - institutional repository link + +### Import Considerations + +1. **Parse comma-separated values** for: + - Authors (split and create entries in `authors` table) + - Supervisors (split and create entries in `supervisors` table) + - Formats (map to `format_types`) + - Keywords (split and create/link in `keywords`) + - Languages (split and map to `languages`) + +2. **Map abbreviations**: + - Orientations: SC → Sculpture, VI → Vidéographie, CA → Cinéma d'animation, etc. + - AP: DPM, LIENS, APS (exact match) + +3. **Handle missing data**: + - Some fields in CSV are empty (AP, Orientation for some entries) + - Use NULL in database + +4. **Parse duration/size**: + - Examples: "128 pages", "78 pages + ?? minutes", "68 minutes" + - Extract numeric values for `duration_pages` and `duration_minutes` + - Store original string in `file_size_info` + +## Schema Design Decisions + +### Why SQLite? +- Self-contained, serverless +- Easy to backup (single file) +- Good performance for this use case +- Simple to integrate with various tools + +### Normalization Level +- 3rd Normal Form (3NF) for most tables +- Denormalized views for read performance +- Balance between flexibility and simplicity + +### Extensibility +- New languages can be added via `languages` table +- Keywords are dynamic and grow with content +- License types can be defined later +- Static pages can be added via `pages` table + +### Constraints +- CASCADE deletes on junction tables +- UNIQUE constraints on lookup table names +- NOT NULL on critical fields +- Automatic timestamps via triggers + +## Important Business Rules + +1. **No immediate publication**: TFEs must go through defense before publication +2. **Access restriction is one-way**: Can restrict but not open access +3. **Max 10 keywords** per thesis (enforce in application) +4. **Jury context note max 150 words** (enforce in application) +5. **Synopsis ~200 words** (guideline, not hard limit) +6. **Multiple selections allowed** for: languages, formats, authors, supervisors, keywords +7. **Doctoral theses**: Use `is_doctoral = 1` to distinguish from TFEs + +## Next Steps + +1. Create import script to load CSV data +2. Define license types +3. Build backend API for CRUD operations +4. Implement authorization checks +5. Create admin interface for easy editing +6. Build public-facing website using views diff --git a/db/SETUP.md b/db/SETUP.md new file mode 100644 index 0000000..d2fa450 --- /dev/null +++ b/db/SETUP.md @@ -0,0 +1,1375 @@ +# Post-ERG Thesis Database - Setup Guide + +Complete guide for setting up and managing the SQLite database for the Post-ERG thesis archive platform. + +--- + +## Table of Contents + +1. [Quick Start](#quick-start) +2. [Prerequisites](#prerequisites) +3. [Database Setup](#database-setup) +4. [Schema Overview](#schema-overview) +5. [Detailed Schema Description](#detailed-schema-description) +6. [Common Operations](#common-operations) +7. [Backup & Maintenance](#backup--maintenance) +8. [Troubleshooting](#troubleshooting) + +--- + +## Quick Start + +For the impatient, here's the fastest way to get started: + +```bash +# Navigate to the database directory +cd /home/padlock/dev/posterg/db + +# Create the database and apply schema +sqlite3 posterg.db < schema.sql + +# Verify the database was created +sqlite3 posterg.db "SELECT name FROM sqlite_master WHERE type='table';" + +# Check predefined data was loaded +sqlite3 posterg.db "SELECT * FROM orientations;" +``` + +You now have a fully initialized Post-ERG thesis database! + +--- + +## Prerequisites + +### Required Software + +- **SQLite 3** (version 3.8.0 or higher recommended) + - Check version: `sqlite3 --version` + - Install on Linux: `sudo apt-get install sqlite3` + - Install on macOS: `brew install sqlite3` (usually pre-installed) + - Install on Windows: Download from [sqlite.org/download.html](https://sqlite.org/download.html) + +### Optional Tools + +- **DB Browser for SQLite** - GUI tool for database management + - Download: [sqlitebrowser.org](https://sqlitebrowser.org/) + - Great for visual exploration and testing + +- **sqlite-web** - Web-based SQLite database browser + ```bash + pip install sqlite-web + sqlite_web posterg.db + ``` + +--- + +## Database Setup + +### Step 1: Project Structure + +Ensure your directory structure looks like this: + +``` +/home/padlock/dev/posterg/db/ +├── schema.sql # Database schema definition +├── Database_TFE_test.csv # Sample/test CSV data +├── posterg_fiche-technique.md # Technical specifications +├── SETUP.md # This file +├── README.md # Schema documentation +└── posterg.db # Database file (created in next step) +``` + +### Step 2: Create the Database + +Create an empty SQLite database and apply the schema: + +```bash +# Method 1: Using shell redirection (recommended) +sqlite3 posterg.db < schema.sql + +# Method 2: Interactive mode +sqlite3 posterg.db +sqlite> .read schema.sql +sqlite> .quit + +# Method 3: One-liner +cat schema.sql | sqlite3 posterg.db +``` + +### Step 3: Verify Installation + +Check that all tables were created successfully: + +```bash +sqlite3 posterg.db <= 2024 +ORDER BY year DESC, title; +``` + +#### Search by Keyword + +```sql +SELECT t.title, t.year, GROUP_CONCAT(k.keyword) as keywords +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 LIKE '%performance%' +GROUP BY t.id; +``` + +#### Find Theses by Author + +```sql +SELECT t.title, t.year, a.name as author +FROM theses t +JOIN thesis_authors ta ON t.id = ta.thesis_id +JOIN authors a ON ta.author_id = a.id +WHERE a.name LIKE '%Lucie%' +ORDER BY t.year DESC; +``` + +#### Get Unpublished Theses (Admin) + +```sql +SELECT identifier, title, submitted_at, defense_date +FROM theses +WHERE submitted_at IS NOT NULL + AND is_published = 0 +ORDER BY submitted_at DESC; +``` + +### Inserting Data + +#### Add a New Author + +```sql +INSERT INTO authors (name, email) VALUES + ('Marie Dupont', 'marie.dupont@example.com'); +``` + +#### Add a New Thesis (Basic) + +```sql +INSERT INTO theses ( + identifier, title, year, orientation_id, finality_id, synopsis +) VALUES ( + '2026-001', + 'Mon Titre de TFE', + 2026, + 8, -- Vidéographie + 1, -- Approfondi + 'Un synopsis fascinant de mon travail...' +); +``` + +#### Link Thesis to Author + +```sql +-- Get thesis ID and author ID first +INSERT INTO thesis_authors (thesis_id, author_id, author_order) +VALUES (1, 5, 1); +``` + +#### Add Keywords to Thesis + +```sql +-- First, ensure keyword exists +INSERT OR IGNORE INTO keywords (keyword) VALUES ('performance'); + +-- Then link it +INSERT INTO thesis_keywords (thesis_id, keyword_id) +SELECT 1, id FROM keywords WHERE keyword = 'performance'; +``` + +### Updating Data + +#### Update Thesis Status to Published + +```sql +UPDATE theses +SET is_published = 1, + published_at = CURRENT_TIMESTAMP +WHERE id = 5; +``` + +#### Add Jury Points and Note + +```sql +UPDATE theses +SET jury_points = 16.5, + context_note = 'Ce travail remarquable explore...', + jury_note_added = 1 +WHERE id = 5; +``` + +#### Restrict Access (Libre → Interne) + +```sql +UPDATE theses +SET access_type_id = (SELECT id FROM access_types WHERE name = 'Interne') +WHERE id = 10; +``` + +#### Update Page Content + +```sql +UPDATE pages +SET content = 'Nouveau contenu de la page...', + updated_at = CURRENT_TIMESTAMP +WHERE slug = 'about'; +``` + +### Deleting Data + +**Warning**: Deletes are permanent in SQLite! + +#### Delete a Thesis (and all related data) + +```sql +-- This cascades to thesis_authors, thesis_keywords, etc. +DELETE FROM theses WHERE id = 10; +``` + +#### Remove Keyword from Thesis + +```sql +DELETE FROM thesis_keywords +WHERE thesis_id = 5 AND keyword_id = 12; +``` + +#### Delete Unused Keywords + +```sql +-- Remove keywords not linked to any thesis +DELETE FROM keywords +WHERE id NOT IN (SELECT DISTINCT keyword_id FROM thesis_keywords); +``` + +--- + +## Backup & Maintenance + +### Backup Strategies + +#### Method 1: File Copy (Simplest) + +```bash +# Copy the database file +cp posterg.db posterg_backup_$(date +%Y%m%d).db + +# Or with compression +tar -czf posterg_backup_$(date +%Y%m%d).tar.gz posterg.db +``` + +#### Method 2: SQL Dump (Most Portable) + +```bash +# Export entire database to SQL +sqlite3 posterg.db .dump > posterg_backup.sql + +# Restore from backup +sqlite3 new_posterg.db < posterg_backup.sql +``` + +#### Method 3: Automated Backups + +Create a backup script (`backup.sh`): + +```bash +#!/bin/bash +BACKUP_DIR="/home/padlock/dev/posterg/db/backups" +DATE=$(date +%Y%m%d_%H%M%S) +DB_FILE="/home/padlock/dev/posterg/db/posterg.db" + +mkdir -p "$BACKUP_DIR" +sqlite3 "$DB_FILE" ".backup '$BACKUP_DIR/posterg_$DATE.db'" +echo "Backup created: $BACKUP_DIR/posterg_$DATE.db" + +# Keep only last 30 backups +ls -t "$BACKUP_DIR"/posterg_*.db | tail -n +31 | xargs rm -f +``` + +Run daily with cron: +```bash +# Edit crontab +crontab -e + +# Add daily backup at 2am +0 2 * * * /home/padlock/dev/posterg/db/backup.sh +``` + +### Database Maintenance + +#### Optimize Database (Vacuum) + +Reclaim unused space and optimize performance: + +```bash +sqlite3 posterg.db "VACUUM;" +``` + +**When to run**: After large deletions or monthly. + +#### Analyze Database + +Update query optimizer statistics: + +```bash +sqlite3 posterg.db "ANALYZE;" +``` + +**When to run**: After significant data changes. + +#### Check Integrity + +Verify database integrity: + +```bash +sqlite3 posterg.db "PRAGMA integrity_check;" +``` + +**Expected output**: `ok` + +#### Database Statistics + +```sql +-- Database size +SELECT page_count * page_size / 1024 / 1024.0 AS size_mb +FROM pragma_page_count(), pragma_page_size(); + +-- Row counts +SELECT 'theses' as table_name, COUNT(*) as rows FROM theses +UNION ALL +SELECT 'authors', COUNT(*) FROM authors +UNION ALL +SELECT 'keywords', COUNT(*) FROM keywords; + +-- Index usage +SELECT name, tbl_name FROM sqlite_master +WHERE type = 'index' +ORDER BY tbl_name; +``` + +### Migration Best Practices + +When updating the schema: + +1. **Always backup first**: + ```bash + cp posterg.db posterg_before_migration.db + ``` + +2. **Test migration on backup**: + ```bash + sqlite3 posterg_test.db < migration.sql + ``` + +3. **Use transactions**: + ```sql + BEGIN TRANSACTION; + -- Your changes here + ALTER TABLE theses ADD COLUMN new_field TEXT; + -- Test queries + SELECT * FROM theses LIMIT 1; + COMMIT; -- or ROLLBACK if something went wrong + ``` + +4. **Document changes**: + Create migration files like `migrations/001_add_new_field.sql` + +--- + +## Troubleshooting + +### Common Issues + +#### Database is Locked + +**Symptom**: `Error: database is locked` + +**Cause**: Another process has the database open for writing. + +**Solution**: +```bash +# Find processes using the database +lsof posterg.db + +# Or force close +fuser -k posterg.db + +# Prevent by using WAL mode +sqlite3 posterg.db "PRAGMA journal_mode=WAL;" +``` + +#### Foreign Key Violations + +**Symptom**: `FOREIGN KEY constraint failed` + +**Cause**: Trying to insert a reference to a non-existent record. + +**Solution**: +```sql +-- Enable foreign key enforcement (check if it's on) +PRAGMA foreign_keys = ON; + +-- Verify referenced record exists +SELECT id FROM orientations WHERE id = 8; +``` + +#### Unique Constraint Violation + +**Symptom**: `UNIQUE constraint failed` + +**Solution**: +```sql +-- Use INSERT OR IGNORE to skip duplicates +INSERT OR IGNORE INTO keywords (keyword) VALUES ('performance'); + +-- Or INSERT OR REPLACE to update +INSERT OR REPLACE INTO keywords (id, keyword) VALUES (1, 'performance'); +``` + +#### Cannot Find Database File + +**Symptom**: `Error: unable to open database file` + +**Solution**: +```bash +# Use absolute path +sqlite3 /home/padlock/dev/posterg/db/posterg.db + +# Or navigate to directory first +cd /home/padlock/dev/posterg/db +sqlite3 posterg.db +``` + +### Performance Issues + +#### Slow Queries + +**Diagnosis**: +```sql +-- Enable query timer +.timer on + +-- Explain query plan +EXPLAIN QUERY PLAN +SELECT * FROM theses WHERE year = 2025; +``` + +**Solutions**: +- Add indexes on frequently queried columns +- Use views for complex queries +- Run `ANALYZE;` to update statistics + +#### Large Database + +**Solutions**: +```bash +# Compress old data +sqlite3 posterg.db "VACUUM;" + +# Use WAL mode for better concurrency +sqlite3 posterg.db "PRAGMA journal_mode=WAL;" + +# Archive old theses to separate database +``` + +### Data Quality Issues + +#### Find Orphaned Records + +```sql +-- Authors with no theses +SELECT a.* FROM authors a +LEFT JOIN thesis_authors ta ON a.id = ta.author_id +WHERE ta.author_id IS NULL; + +-- Theses missing required fields +SELECT id, identifier, title FROM theses +WHERE orientation_id IS NULL OR finality_id IS NULL; +``` + +#### Validate Keyword Count + +```sql +-- Theses with more than 10 keywords +SELECT thesis_id, COUNT(*) as keyword_count +FROM thesis_keywords +GROUP BY thesis_id +HAVING keyword_count > 10; +``` + +### Recovery Procedures + +#### Restore from Backup + +```bash +# From SQL dump +sqlite3 posterg_restored.db < posterg_backup.sql + +# From database file +cp posterg_backup_20260127.db posterg.db +``` + +#### Corrupted Database + +```bash +# Try to recover +sqlite3 posterg.db ".recover" | sqlite3 recovered.db + +# Or dump and reimport +sqlite3 posterg.db .dump | sqlite3 new_posterg.db +``` + +--- + +## Advanced Tips + +### Performance Optimization + +```sql +-- Enable Write-Ahead Logging (WAL) for better concurrency +PRAGMA journal_mode=WAL; + +-- Increase cache size (in KB) +PRAGMA cache_size=-64000; -- 64MB cache + +-- Enable memory-mapped I/O (in bytes) +PRAGMA mmap_size=268435456; -- 256MB + +-- Synchronous mode (less safe but faster) +PRAGMA synchronous=NORMAL; -- Default is FULL +``` + +### Useful SQLite Commands + +```sql +-- Export table to CSV +.mode csv +.output theses.csv +SELECT * FROM v_theses_public; +.output stdout + +-- Import CSV +.mode csv +.import data.csv table_name + +-- Show execution time +.timer on + +-- Show query plan +.eqp on + +-- Pretty formatting +.mode column +.headers on +.width 10 40 20 + +-- Save frequently used queries +.save my_queries.sql +``` + +### Custom Functions (Application Level) + +When building your application, you can create custom SQLite functions: + +**Python example**: +```python +import sqlite3 + +def keyword_count(thesis_id): + """Custom function to count keywords""" + # Implementation + pass + +conn = sqlite3.connect('posterg.db') +conn.create_function('keyword_count', 1, keyword_count) +``` + +--- + +## Next Steps + +After setting up the database: + +1. **Import existing data** from `Database_TFE_test.csv` + - Create import script (Python/Node.js recommended) + - Parse CSV and map to schema + - Handle comma-separated values + - Validate data quality + +2. **Define license types** + - Consult with legal/admin + - Populate `license_types` table + +3. **Build application layer** + - REST API or GraphQL + - Authentication/authorization + - File upload handling + - Email notifications + +4. **Create admin interface** + - CRUD operations for all entities + - Bulk import/export + - User management + - Workflow management + +5. **Build public website** + - Search and filter + - Thesis display + - Respect access controls + - Static pages management + +--- + +## Resources + +### SQLite Documentation +- Official docs: https://sqlite.org/docs.html +- SQL syntax: https://sqlite.org/lang.html +- Datatypes: https://sqlite.org/datatype3.html + +### Tools +- DB Browser: https://sqlitebrowser.org/ +- sqlite-web: https://github.com/coleifer/sqlite-web +- SQLite CLI: https://sqlite.org/cli.html + +### Best Practices +- Always use transactions for multiple operations +- Enable foreign keys: `PRAGMA foreign_keys = ON;` +- Backup before schema changes +- Use prepared statements in applications +- Index frequently queried columns + +--- + +## Support + +For issues related to: +- **Schema design**: Review this document and README.md +- **Data import**: Check CSV format and data types +- **Performance**: Run `ANALYZE` and check indexes +- **Corruption**: Restore from backup + +--- + +**Last Updated**: 2026-01-27 +**Schema Version**: 1.0 +**Database**: SQLite 3 diff --git a/db/posterg_fiche-technique.md b/db/posterg_fiche-technique.md new file mode 100644 index 0000000..c595407 --- /dev/null +++ b/db/posterg_fiche-technique.md @@ -0,0 +1,170 @@ + +# Fiche technique + +## Différentes catégories / métadonnées + +**• Titre du TFE** + +**• Sous-titre (si applicable)** + +**• Auteur·ice(s)** + +**• Contact (optionnel) [mail/site/insta/etc.]** + +**• Promoteur·ice(s)** + +**• Année** + +**• Orientation [liste prédéfinie]** + + * Arts Numériques / Dessin / Cinéma d'animation / Installation-Performance / Peinture / Photographie / Sculpture / Vidéographie / Graphisme / Typographie / Design Numérique / Illustration / Bande-Dessinés / Sérigraphie / Gravure +**• AP [liste prédéfinie]** + + * Narration Spéculative / Design et Politique du Multiple [DPM] / Atelier Pratiques Situées [APS] / Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes [L.I.E.N.S.] +**• Finalité du master [liste prédéfinie]** + + * Approfondi / Enseignement /Spécialisé +**• Langue du TFE [liste prédéfinie + option de créer des nouvelles langues]** + + * Français / Anglais / autre : [imput] —> possible d'en sélectionner plusieurs en même temps +**• Format [liste pré-définie + une case autre "fourre-tout"]** + + * Site web / Audio / Vidéo / Performance / Objet éditorial / Installation / Etc. / Autre + * —> possible d'en sélectionner plusieurs en même temps +**• Mots-clés (max 10) [liste avec les mots clés déjà existants + option d'en créer des nouveaux]** + + * spéculation / narration / urbanisme / patrimoine / intime / collectivité / film / cinéma / sociologie / anthropologie / éphémérité / queer / écriture / poésie / écologies affectives / technologies / autre : [imput] +**• Synopsis (environ 200 mots ; pas nécessairement de max – à voir si c'est nécessaire côté technique)** + +**• Durée du TFE (si applicable) [faire en choix entre minutes/pages : [imput]]** + + + +**• J'autorise l'erg à archiver mon TFE de la manière suivante ;** + +[]x Libre ;[] mon TFE est en libre accès à tout le monde sur la plateforme des TFE ainsi que dans la bibliothèque de l'erg. + +[]x Interne ;[] mon TFE n'est accessible que sur place en physique. Une note descriptive est disponible sur le site. + +[]x Interdit ;[] mon TFE n'est pas disponible en physique ni sur le site. Une note descriptive est disponible sur le site. + +L'étudiant·e peut, à tout moment, décider de restreindre son propre choix. Iel ne peut par contre pas l'ouvrir. + + + +**• Licence du TFE ; dropdown avec plusieurs choix pré-établis + ouverture pour en donner d'autres ?** + + * Les options précises sont encore au travail. + + +**• Upload du TFE** + +**• Upload des annexes éventuelles** + +**• Upload de la partie écrite** + + + +**• Système pour que læ président·e du jury puisse rajouter une note de max 150 mots qui contextualiserait le TFE.** + +**• Points du jury** + + + +[]/!\ Quand l'étudiant·e dépose le TFE, celui-ci ne doit pas immédiatement être publié. Il faut attendre que la soutenance ait eu lieu et que læ président·e puisse éventuellement y ajouter un texte ainsi que les points. [] + +[]—> trouver un système pour rendre ça le plus fluide possible pour læ présidant·e ainsi que l'étudiant·e.[] + + + + + +## Design + +• Prévoir un « onglet » Charte / à propos (texte à venir – doit être facile à adapter sans avoir à coder). + +• Prévoir un « onglet » licences (texte à venir – doit être facile à adapter sans avoir à coder). + +• Prévoir un « onglet » contact (texte à venir – doit être facile à adapter sans avoir à coder). + +• Il faut prévoir un espace ou quelque chose pour différencier les thèses (doctorats) des TFE. + + + + + +## Points importants + +• Important que le "back-office" soit accessible / pas trop complexe pour qu'on puisse adapter, supprimer, ajouter, corriger les données des TFE (relativement) facilement. + + + +• Important que le texte des différents onglets soit éditable (relativement) facilement. + + + +• Important que le statut de monstration "libre", "interne", "interdit" soit facilement changeable. + + + + + +## Création Base de Données + +• Engagement début décembre + + * Min 5h/semaine à horaire libre + * On espère qu'un premier draft de base donnée arrivera mi-décembre pour pouvoir expérimenter avec. On vous enverra un fichier csv dès qu'on a une base solide. + + +• Collecte et assemblage des différentes années (au moins 2 ans – idéalement tout [lol]) + + * —> il faudra demander aux ancien·nes étudiant·es s'iels sont d'accord que leurs données soient publiées. Un mail sera envoyé après la récolte. + * —> voir avec Karim ce qu'on a le droit de montrer s'il n'y a pas de réponse (fiche descriptive, TFE en physique ?) + + +• Établir une liste de mots clés prédéfinis / voir s'il y a des lacunes et/ou problèmes quelque part + + + +• On est au travail pour la partie doctorats. On vous tient au courant dès qu'il y a plus d'informations à ce sujet. + + + + + +## Technique + +• Hébergement et intégration avec les outils existants à voir avec Joan. + + + + + +## Retroplanning + +• Mi-décembre ; envoi d'un semblant de base de donnée pour permettre à l'équipe posterg d'expérimenter + +• Journées pédagogiques du 15 \& 16 janvier ; travail sur les TFE, la place du jury dans sa publication etc. + +• Mi-février ; envoi d'un mail aux ancien·nes étudiant·es et aux profs en vue de la publication digitale des TFE + +• Mi-février ; finalisation de la maquette du site Post-ERG + +• Mi-mars ; base de donnée des ancien·nes étudiant·es finalisée (fichier .cvs) + +• Mi-avril ; date de remise du projet – site finalisé + +• Début mai ; mise en ligne du site + +• Mi-mai ; dépôt des TFE de 1e session (les TFE ne sont pas publiés publiquement à ce moment) + +• Mi-juin ; publication publique des TFE (après éventuelle note du jury) + + + + + + + + diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..00b357b --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,385 @@ +-- 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, + 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'); + +-- Keywords (expandable list) +CREATE TABLE IF NOT EXISTS keywords ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + keyword TEXT NOT NULL UNIQUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 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 +); + +-- ============================================================================ +-- 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 + + -- 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 (can have multiple promoters) +CREATE TABLE IF NOT EXISTS thesis_supervisors ( + thesis_id INTEGER NOT NULL, + supervisor_id INTEGER NOT NULL, + supervisor_order INTEGER DEFAULT 1, + 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 +); + +-- Keywords per thesis (max 10 as per specs) +CREATE TABLE IF NOT EXISTS thesis_keywords ( + thesis_id INTEGER NOT NULL, + keyword_id INTEGER NOT NULL, + PRIMARY KEY (thesis_id, keyword_id), + FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE, + FOREIGN KEY (keyword_id) REFERENCES keywords(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 +); + +-- ============================================================================ +-- STATIC PAGES / CONTENT MANAGEMENT +-- ============================================================================ + +-- For managing editable static pages (charte, about, licenses, contact) +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'), + ('contact', 'Contact', 'Contenu à venir'); + +-- ============================================================================ +-- 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_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_keywords_thesis ON thesis_keywords(thesis_id); +CREATE INDEX IF NOT EXISTS idx_thesis_keywords_keyword ON thesis_keywords(keyword_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; + +-- ============================================================================ +-- 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.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 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; + +-- Published theses only (for public view) +CREATE VIEW IF NOT EXISTS v_theses_public AS +SELECT * FROM v_theses_full +WHERE is_published = 1; diff --git a/formulaire/.htaccess b/formulaire/.htaccess new file mode 100644 index 0000000..0bd1a24 --- /dev/null +++ b/formulaire/.htaccess @@ -0,0 +1,36 @@ +# Security headers + + # Prevent clickjacking + Header always set X-Frame-Options "SAMEORIGIN" + + # Prevent MIME type sniffing + Header always set X-Content-Type-Options "nosniff" + + # Enable XSS protection + Header always set X-XSS-Protection "1; mode=block" + + # Referrer policy + Header always set Referrer-Policy "strict-origin-when-cross-origin" + + # Content Security Policy (adjust as needed) + Header always set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" + + +# Prevent directory listing +Options -Indexes + +# Protect sensitive files + + Require all denied + + + + Require all denied + + +# PHP security settings (if .htaccess can override) + + php_flag display_errors Off + php_flag log_errors On + php_value error_log error.log + diff --git a/formulaire/SECURITY.md b/formulaire/SECURITY.md new file mode 100644 index 0000000..253c599 --- /dev/null +++ b/formulaire/SECURITY.md @@ -0,0 +1,163 @@ +# Security Improvements + +## Changes Made + +### 1. Critical Vulnerability Fixes + +#### Path Traversal in thanks.php (CRITICAL) +- **Before**: User could access ANY file on the system via `?file=../../../../etc/passwd` +- **After**: + - Validates file path using `realpath()` to resolve symlinks + - Ensures file is within allowed `data/yaml/` directory + - Verifies file extension is `.yaml` + - Proper error handling without exposing system paths + +#### CSRF Protection +- **Before**: Form could be submitted from any website +- **After**: + - Session-based CSRF tokens generated for each form load + - Token validated on submission using timing-safe comparison (`hash_equals()`) + - Token cleared after successful submission + +### 2. Input Validation & Sanitization + +#### Deprecated Functions Replaced +- **Before**: Used `FILTER_SANITIZE_STRING` (deprecated in PHP 8.1+) +- **After**: Custom `sanitize_string()` function using `htmlspecialchars()` and `strip_tags()` + +#### Enhanced Validation +- Required fields properly validated with custom `validate_required()` function +- Email validation using `FILTER_VALIDATE_EMAIL` +- URL validation using `FILTER_VALIDATE_URL` +- Year validation with reasonable range checking (2000 to current year + 1) +- Comprehensive error messages for validation failures + +### 3. File Upload Security + +#### Random Filenames +- **Before**: Used original or predictable filenames (author + timestamp) +- **After**: + - Generates cryptographically secure random filenames using `random_bytes()` + - Prevents file overwrites + - Prevents path traversal attacks via malicious filenames + - Stores mapping to original filename for reference + +#### Enhanced File Validation +- MIME type checking using `finfo` +- File extension whitelist +- File size limits (50MB max) +- Proper error handling for upload errors +- Cover image restricted to JPEG/PNG only + +### 4. Bug Fixes + +- Fixed undefined variable `$memoireFolder` (used before definition) +- Fixed undefined variable `$resume` (should be `$description`) +- Fixed variable ordering (generate `$uniqueId` before using it) +- Added proper `__DIR__` prefix for absolute paths + +### 5. Error Handling + +- Try-catch block wraps entire form processing +- Detailed error logging (not exposed to users) +- User-friendly error messages +- Proper exit after redirect +- No system path exposure in error messages + +## Nginx Configuration Notes + +Since this form is behind nginx password authentication, additional security layers: + +### Recommended nginx config: +```nginx +location /formulaire { + auth_basic "Restricted Access"; + auth_basic_user_file /etc/nginx/.htpasswd; + + # Rate limiting + limit_req zone=form_limit burst=5 nodelay; + + # File upload size + client_max_body_size 100M; + + # Timeout settings + client_body_timeout 60s; + + # Prevent access to sensitive files + location ~ /\. { + deny all; + } + + location ~ /(vendor|composer\.(json|lock)|error\.log)$ { + deny all; + } +} +``` + +## Additional Recommendations + +### 1. Database Migration (In Progress) +Moving to SQLite will provide: +- Structured data storage +- Better query capabilities +- Easier data management +- Prepared statements for SQL injection prevention + +### 2. File Storage +- Consider moving uploaded files outside web root +- Serve files through PHP script with access control +- Implement file scanning for malware if possible + +### 3. Monitoring +- Regularly review `error.log` for suspicious activity +- Monitor file upload patterns +- Set up alerts for failed CSRF validations + +### 4. Backup Strategy +- Regular backups of `data/` directory +- Version control for code changes +- Test restore procedures + +### 5. PHP Configuration +Ensure these settings in php.ini: +```ini +file_uploads = On +upload_max_filesize = 100M +post_max_size = 100M +max_execution_time = 60 +max_input_time = 60 +memory_limit = 256M + +# Security +expose_php = Off +allow_url_fopen = Off +allow_url_include = Off +display_errors = Off +log_errors = On +``` + +## Testing Checklist + +- [ ] Form submission with all fields +- [ ] Form submission with minimal required fields +- [ ] Invalid email format +- [ ] Invalid URL format +- [ ] Invalid year +- [ ] File upload (various formats) +- [ ] Large file upload (>50MB, should fail) +- [ ] Invalid file types +- [ ] Multiple file uploads +- [ ] Cover image upload +- [ ] CSRF token validation (try submitting with wrong token) +- [ ] Path traversal attempt in thanks.php +- [ ] Error handling for missing directories + +## Known Limitations + +1. **No atomic transactions**: File operations and YAML save not atomic +2. **No rollback**: Failed submissions may leave partial files +3. **Session storage**: CSRF tokens in default PHP session (consider database sessions) +4. **No upload progress**: Large files have no progress indicator +5. **No duplicate detection**: Same submission can be made multiple times + +These limitations will be addressed in the SQLite migration. diff --git a/formulaire/formulaire.php b/formulaire/formulaire.php index d0a5da8..b6c01c9 100644 --- a/formulaire/formulaire.php +++ b/formulaire/formulaire.php @@ -5,6 +5,16 @@ ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', 'error.log'); +// Start session for CSRF protection +session_start(); + +// Verify CSRF token +if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || + !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { + error_log("CSRF token validation failed"); + die("Erreur de sécurité : token invalide. Veuillez recharger le formulaire."); +} + // Log the content of the $_FILES array error_log("FILES array: " . print_r($_FILES, true)); @@ -12,136 +22,211 @@ require_once 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; use Behat\Transliterator\Transliterator; +// Helper function to sanitize string input (replacement for deprecated FILTER_SANITIZE_STRING) +function sanitize_string($input) { + return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); +} + +// Helper function to validate required field +function validate_required($value, $fieldName) { + if (empty($value)) { + throw new Exception("Le champ '$fieldName' est requis."); + } + return $value; +} + // Define variables -$yamlFolder = "data/yaml/"; +$yamlFolder = __DIR__ . "/data/yaml/"; $date = date("Y-m-d"); +$errors = []; -// Sanitize input data -$auteurice = filter_var($_POST["auteurice"], FILTER_SANITIZE_STRING); -$annee = filter_var($_POST["année"], FILTER_SANITIZE_NUMBER_INT); -$mail = filter_var($_POST["mail"], FILTER_SANITIZE_EMAIL); -$titre = filter_var($_POST["titre"], FILTER_SANITIZE_STRING); -$tag = filter_var($_POST["tag"], FILTER_SANITIZE_STRING); -$promoteurice = filter_var($_POST["promoteurice"], FILTER_SANITIZE_STRING); -$problematique = filter_var($_POST["problématique"], FILTER_SANITIZE_STRING); -$description = filter_var($_POST["description"], FILTER_SANITIZE_STRING); -$orientation = filter_var($_POST["orientation"], FILTER_SANITIZE_STRING); -$ap = filter_var($_POST["ap"], FILTER_SANITIZE_STRING); -$lien = filter_var($_POST["lien"], FILTER_SANITIZE_STRING); -$couverture = $_FILES["couverture"]; -$files = $_FILES["files"]; +try { + // Validate and sanitize input data with proper error handling + $auteurice = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo"); -// Transformation du string de mot-clé en un array. -$tagArray = explode(', ', $tag); - - -$coverFolder = $memoireFolder . "data/cover/"; -if (!file_exists($coverFolder)) { - mkdir($coverFolder, 0755, true); -} - -$couverturePath = ""; -if ($couverture["error"] === UPLOAD_ERR_OK) { - $fileExtension = pathinfo($couverture["name"], PATHINFO_EXTENSION); - $newCouvertureName = $auteurice . "_" . $annee . "_" . $uniqueId . "." . $fileExtension; - $targetFile = $coverFolder . $newCouvertureName; - if (move_uploaded_file($couverture["tmp_name"], $targetFile)) { - chmod($targetFile, 0644); - $couverturePath = $targetFile; - } else { - error_log("Failed to move uploaded couverture file: " . $couverture["name"]); + $annee = filter_var($_POST["année"] ?? '', FILTER_VALIDATE_INT); + if ($annee === false || $annee < 2000 || $annee > (int)date('Y') + 1) { + throw new Exception("Année invalide. Veuillez entrer une année valide."); } -} -$uploadedFiles = []; + $mail = filter_var($_POST["mail"] ?? '', FILTER_VALIDATE_EMAIL); + if ($mail === false && !empty($_POST["mail"])) { + throw new Exception("Adresse email invalide."); + } -// Create necessary directories -$memoireFolder = "data/content/{$annee}/{$auteurice}/"; -if (!file_exists($yamlFolder)) { - mkdir($yamlFolder, 0755, true); -} -if (!file_exists($memoireFolder)) { - mkdir($memoireFolder, 0755, true); -} + $titre = validate_required(sanitize_string($_POST["titre"] ?? ''), "Titre du mémoire"); + $tag = sanitize_string($_POST["tag"] ?? ''); + $promoteurice = sanitize_string($_POST["promoteurice"] ?? ''); + $problematique = sanitize_string($_POST["problématique"] ?? ''); + $description = sanitize_string($_POST["description"] ?? ''); -$targetDir = $memoireFolder; + $orientation = validate_required(sanitize_string($_POST["orientation"] ?? ''), "Orientation"); + $ap = validate_required(sanitize_string($_POST["ap"] ?? ''), "Atelier Pratique"); -// Generate unique file name -$uniqueId = time() . "_" . rand(1000, 9999); -$sanitizedAuteurice = Transliterator::transliterate($auteurice); -$uniqueFileName = $sanitizedAuteurice . "_" . $date . "_" . $uniqueId; - -// Define security constraints -$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip']; -$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip']; -$maxFileSize = 50 * 1024 * 1024; // 50 MB - -// Process uploaded files -if (is_array($files["name"])) { - for ($i = 0; $i < count($files["name"]); $i++) { - // Log the file being processed - error_log("Processing file: " . $files["name"][$i]); - // Check for file upload errors - if ($files["error"][$i] !== UPLOAD_ERR_OK) { - error_log("File upload error: " . $files["name"][$i]); - continue; + // Validate URL if provided + $lien = $_POST["lien"] ?? ''; + if (!empty($lien)) { + $lien = filter_var($lien, FILTER_VALIDATE_URL); + if ($lien === false) { + throw new Exception("Lien URL invalide."); } + } - // Check MIME type and file extension + $couverture = $_FILES["couverture"] ?? null; + $files = $_FILES["files"] ?? null; + + // Transformation du string de mot-clé en un array. + $tagArray = !empty($tag) ? array_map('trim', explode(',', $tag)) : []; + + // Generate unique identifiers FIRST (before using them) + $uniqueId = time() . "_" . rand(1000, 9999); + $sanitizedAuteurice = Transliterator::transliterate($auteurice); + $uniqueFileName = $sanitizedAuteurice . "_" . $date . "_" . $uniqueId; + + // Create necessary directories + $memoireFolder = __DIR__ . "/data/content/{$annee}/{$auteurice}/"; + $coverFolder = __DIR__ . "/data/cover/"; + + if (!file_exists($yamlFolder)) { + mkdir($yamlFolder, 0755, true); + } + if (!file_exists($memoireFolder)) { + mkdir($memoireFolder, 0755, true); + } + if (!file_exists($coverFolder)) { + mkdir($coverFolder, 0755, true); + } + + $targetDir = $memoireFolder; + $uploadedFiles = []; + $couverturePath = ""; + + // Define security constraints + $allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip']; + $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip']; + $maxFileSize = 50 * 1024 * 1024; // 50 MB + + // Process cover image first + if ($couverture && isset($couverture["error"]) && $couverture["error"] === UPLOAD_ERR_OK) { + // Security: validate MIME type $finfo = new finfo(FILEINFO_MIME_TYPE); - $mimeType = $finfo->file($files["tmp_name"][$i]); - $fileExtension = pathinfo($files["name"][$i], PATHINFO_EXTENSION); + $mimeType = $finfo->file($couverture["tmp_name"]); + $fileExtension = strtolower(pathinfo($couverture["name"], PATHINFO_EXTENSION)); - if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) { - error_log("Invalid file type or extension: " . $files["name"][$i]); - continue; - } + // Only allow image files for cover + if (in_array($mimeType, ['image/jpeg', 'image/png']) && + in_array($fileExtension, ['jpg', 'jpeg', 'png'])) { - // Check file size - if ($files["size"][$i] > $maxFileSize) { - error_log("File is too large: " . $files["name"][$i]); - continue; - } - // Move and set permissions for the uploaded file - $targetFile = $targetDir . basename($files["name"][$i]); - if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) { - // Log successful file move - error_log("File successfully moved: " . $targetFile); - chmod($targetFile, 0644); - $uploadedFiles[] = $targetFile; + // Security: Generate random filename to prevent overwrites and path traversal + $randomName = bin2hex(random_bytes(16)); + $newCouvertureName = $randomName . "." . $fileExtension; + $targetFile = $coverFolder . $newCouvertureName; + + if (move_uploaded_file($couverture["tmp_name"], $targetFile)) { + chmod($targetFile, 0644); + $couverturePath = "data/cover/" . $newCouvertureName; + error_log("Cover image uploaded: " . $newCouvertureName); + } else { + error_log("Failed to move uploaded couverture file: " . $couverture["name"]); + } } else { - error_log("Failed to move uploaded file: " . $files["name"][$i]); + error_log("Invalid cover image type: " . $mimeType); } } + // Process uploaded files + if ($files && is_array($files["name"])) { + for ($i = 0; $i < count($files["name"]); $i++) { + // Skip if no file was uploaded for this slot + if ($files["error"][$i] === UPLOAD_ERR_NO_FILE) { + continue; + } + + // Log the file being processed + error_log("Processing file: " . $files["name"][$i]); + + // Check for file upload errors + if ($files["error"][$i] !== UPLOAD_ERR_OK) { + error_log("File upload error code " . $files["error"][$i] . ": " . $files["name"][$i]); + continue; + } + + // Check MIME type and file extension + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->file($files["tmp_name"][$i]); + $fileExtension = strtolower(pathinfo($files["name"][$i], PATHINFO_EXTENSION)); + + if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) { + error_log("Invalid file type or extension: " . $files["name"][$i] . " (MIME: $mimeType, Ext: $fileExtension)"); + continue; + } + + // Check file size + if ($files["size"][$i] > $maxFileSize) { + error_log("File is too large: " . $files["name"][$i] . " (" . $files["size"][$i] . " bytes)"); + continue; + } + + // Security: Generate random filename to prevent overwrites and path traversal + $randomName = bin2hex(random_bytes(16)); + $safeFileName = $randomName . "." . $fileExtension; + $targetFile = $targetDir . $safeFileName; + + if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) { + // Log successful file move + error_log("File successfully moved: " . $safeFileName); + chmod($targetFile, 0644); + $uploadedFiles[] = [ + 'path' => "data/content/{$annee}/{$auteurice}/" . $safeFileName, + 'original_name' => basename($files["name"][$i]), + 'size' => $files["size"][$i] + ]; + } else { + error_log("Failed to move uploaded file: " . $files["name"][$i]); + } + } + } + + + // Prepare form data for YAML + $formData = [ + 'auteurice' => $auteurice, + 'année' => $annee, + 'email' => $mail ?: '', + 'titre' => $titre, + 'tag' => $tagArray, + 'promoteurice' => $promoteurice, + 'problématique' => $problematique, + 'description' => $description, // Fixed: was $resume + 'orientation' => $orientation, + 'ap' => $ap, + 'lien' => $lien, + 'couverture' => $couverturePath, + 'files' => $uploadedFiles + ]; + + // Convert form data to YAML + $yamlData = Yaml::dump($formData); + + // Save YAML file + $yamlFilePath = $yamlFolder . $uniqueFileName . ".yaml"; + if (file_put_contents($yamlFilePath, $yamlData) === false) { + throw new Exception("Erreur lors de l'écriture du fichier YAML."); + } + + error_log("Form submission saved: " . $yamlFilePath); + + // Clear CSRF token after successful submission + unset($_SESSION['csrf_token']); + + // Redirect to the thank you page + header('Location: thanks.php?file=' . urlencode($yamlFilePath)); + exit(); + +} catch (Exception $e) { + error_log("Form processing error: " . $e->getMessage()); + die("Erreur lors du traitement du formulaire : " . htmlspecialchars($e->getMessage()) . + "

Retour au formulaire"); } - -// Prepare form data for YAML -$formData = [ - 'auteurice' => $auteurice, - 'année' => $annee, - 'email' => $mail, - 'titre' => $titre, - 'tag' => $tagArray, - 'promoteurice' => $promoteurice, - 'problématique' => $problematique, - 'description' => $resume, - 'orientation' => $orientation, - 'ap' => $ap, - 'lien' => $lien, - 'couverture' => $couverturePath, - 'files' => $uploadedFiles -]; - -// Convert form data to YAML -$yamlData = Yaml::dump($formData); - -// Save YAML file -$yamlFilePath = $yamlFolder . $uniqueFileName . ".yaml"; -file_put_contents($yamlFilePath, $yamlData); - -// Redirect to the thank you page -header('Location: thanks.php?file=' . urlencode($yamlFilePath)); - ?> \ No newline at end of file diff --git a/formulaire/index.php b/formulaire/index.php index 7463b38..93d29ec 100644 --- a/formulaire/index.php +++ b/formulaire/index.php @@ -1,4 +1,10 @@ - + @@ -19,6 +25,8 @@
+ + diff --git a/formulaire/thanks.php b/formulaire/thanks.php index 8323732..c274e9c 100644 --- a/formulaire/thanks.php +++ b/formulaire/thanks.php @@ -8,8 +8,39 @@ require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; -$yamlFile = isset($_GET['file']) ? urldecode($_GET['file']) : ''; -$data = Yaml::parseFile($yamlFile); +// Security: Validate file parameter to prevent path traversal +$yamlFile = ''; +$data = null; +$error = null; + +if (isset($_GET['file'])) { + $requestedFile = urldecode($_GET['file']); + + // Security: Only allow files from the yaml directory + $yamlFolder = realpath(__DIR__ . '/data/yaml/'); + $requestedPath = realpath($requestedFile); + + // Verify the file exists and is within the allowed directory + if ($requestedPath && + $yamlFolder && + strpos($requestedPath, $yamlFolder) === 0 && + file_exists($requestedPath) && + pathinfo($requestedPath, PATHINFO_EXTENSION) === 'yaml') { + + try { + $data = Yaml::parseFile($requestedPath); + $yamlFile = $requestedPath; + } catch (Exception $e) { + error_log("Error parsing YAML file: " . $e->getMessage()); + $error = "Erreur lors de la lecture du fichier."; + } + } else { + error_log("Invalid file access attempt: " . $requestedFile); + $error = "Fichier non valide ou accès refusé."; + } +} else { + $error = "Aucun fichier spécifié."; +} ?> @@ -30,11 +61,19 @@ $data = Yaml::parseFile($yamlFile);

Merci 💜

-

d'avoir rempli le formulaire. Le contenu soumis a été sauvegardé et est en attente de traitement.

+ +

⚠️

+

Pour revenir au formulaire.

+ +

d'avoir rempli le formulaire. Le contenu soumis a été sauvegardé et est en attente de traitement.

Voici les informations que vous avez encodées dans le formulaire, affiché tel que c'est stocké, en yaml:

Pour revenir au formulaire.

+ +

Aucune donnée à afficher.

+

Pour revenir au formulaire.

+