Deduplicate and standardise documentation

- Consolidate 36 markdown files → 14 (plus TODO.md)
- Merge overlapping docs into authoritative files:
  - database.md (from DATABASE_SPECIFICATION + QUICK_SCHEMA_REFERENCE + DATABASE_CONFIG + SETUP)
  - deployment.md (from SERVER_SETUP + COMPLETE_DEPLOYMENT_GUIDE + DEPLOYMENT_STEPS)
  - security.md (from SECURITY_ANALYSIS + TODO.SECURITY)
  - development.md (from DEVELOPMENT_GUIDE + LIVE_RELOAD_SETUP + TEST_CENTRALIZATION)
  - migration-history.md (from 11 past migration docs)
- Standardise all filenames to lowercase
- Remove non-doc files (Context.md research notes, chat export)
- Remove superseded docs (SECURITY.md pre-SQLite, SECURITY_IMPLEMENTATION, README_SECURE_SEARCH)
- Fix stale cross-references
This commit is contained in:
Pontoporeia
2026-04-14 18:25:34 +02:00
parent 5c5054d744
commit 3cd96ed28a
42 changed files with 1104 additions and 11268 deletions

View File

@@ -1,600 +0,0 @@
# POSTERG Migration Analysis: YAML to SQLite
## Executive Summary
This analysis examines the migration of the POSTERG thesis archive system from static YAML files to a SQLite database. The goal is to improve performance, scalability, and usability while maintaining the project's core mission of preserving and sharing masters theses from ERG art school.
---
## Current System Architecture
### Repository Structure
The project consists of two interconnected repositories:
1. **posterg-formulaire**: Submission interface
- PHP-based web form for students to submit thesis metadata
- Handles file uploads (PDFs, images, videos, ZIP archives)
- Generates YAML files with unique identifiers
- Stores uploaded content in structured directories
2. **posterg-website**: Public display interface
- PHP-based browsing and viewing application
- Reads YAML files on each request
- Presents paginated gallery view
- Individual thesis detail pages with embedded media
### Data Model (Current)
Each thesis entry contains:
- **auteurice**: Author name
- **année**: Graduation year
- **email**: Contact email
- **titre**: Thesis title
- **tag**: Array of keywords/tags
- **promoteurice**: Thesis supervisor(s)
- **problématique**: Problem statement
- **description**: Full description/abstract
- **orientation**: Program/department
- **ap**: Additional program designation
- **couverture**: Cover image path
- **files**: Array of associated file paths
### Storage Structure
```
data/
├── yaml/ # Metadata files (13 currently)
├── content/ # Uploaded thesis files
│ └── {year}/
│ └── {author}/
│ └── [files]
└── cover/ # Cover images
```
Current data volume: 69MB
---
## Performance Bottlenecks Identified
### Critical Issues
1. **File System Scanning on Every Request**
- `glob("data/yaml/*.yaml")` scans directory for every page load
- No caching mechanism
- Linear time complexity O(n) for file discovery
2. **Complete Dataset Loading**
- All YAML files parsed on every request
- Memory consumption scales linearly with dataset size
- Even pagination requires loading complete dataset
3. **Sorting After Loading**
- All records loaded into memory before sorting
- Year-based sorting happens at runtime
- No pre-computed order
4. **No Query Optimization**
- Cannot filter at data source
- No indexes for common access patterns
- Tag searches would require parsing all files
### Scalability Concerns
With current architecture at 100 theses:
- 100 file system calls per request
- 100 YAML parse operations per request
- Full dataset held in memory for sorting
With projected growth to 1000+ theses:
- System becomes unusable
- Memory exhaustion likely
- Response times in seconds
---
## SQLite Migration Benefits
### Performance Improvements
1. **Query Optimization**
- Direct pagination with LIMIT/OFFSET
- Index-based lookups (year, author, tags)
- Sorted results without in-memory processing
- Query only needed fields
2. **Search Capabilities**
- Full-text search on titles and descriptions
- Tag-based filtering with JOIN tables
- Year range queries
- Author/supervisor lookups
- Multi-criteria searches
3. **Caching Potential**
- Database query results cacheable
- Prepared statements for repeated queries
- Connection pooling
### Functional Enhancements
1. **Advanced Filtering**
- Filter by year, program, supervisor
- Multi-tag selection
- Combined criteria (AND/OR logic)
- Exclusion filters
2. **Statistical Views**
- Theses per year graphs
- Most common tags
- Popular programs/orientations
- Supervisor contribution counts
3. **Related Content**
- "Similar theses" based on tags
- Same supervisor works
- Year cohort browsing
4. **Data Validation**
- Schema enforcement
- Required field validation
- Foreign key constraints
- Data type checking
---
## Proposed Database Schema
### Core Tables
**theses**
- id: INTEGER PRIMARY KEY AUTOINCREMENT
- author: TEXT NOT NULL
- year: INTEGER NOT NULL
- email: TEXT
- title: TEXT NOT NULL
- supervisor: TEXT
- problem_statement: TEXT
- description: TEXT NOT NULL
- orientation: TEXT
- additional_program: TEXT
- cover_image_path: TEXT
- external_link: TEXT
- created_at: DATETIME DEFAULT CURRENT_TIMESTAMP
- updated_at: DATETIME DEFAULT CURRENT_TIMESTAMP
**tags**
- id: INTEGER PRIMARY KEY AUTOINCREMENT
- name: TEXT UNIQUE NOT NULL
**thesis_tags** (many-to-many junction)
- thesis_id: INTEGER FOREIGN KEY → theses(id)
- tag_id: INTEGER FOREIGN KEY → tags(id)
- PRIMARY KEY (thesis_id, tag_id)
**files**
- id: INTEGER PRIMARY KEY AUTOINCREMENT
- thesis_id: INTEGER FOREIGN KEY → theses(id)
- file_path: TEXT NOT NULL
- file_type: TEXT NOT NULL (pdf, image, video, archive)
- file_size: INTEGER
- mime_type: TEXT
- uploaded_at: DATETIME DEFAULT CURRENT_TIMESTAMP
### Indexes for Performance
```sql
CREATE INDEX idx_theses_year ON theses(year DESC);
CREATE INDEX idx_theses_author ON theses(author);
CREATE INDEX idx_theses_orientation ON theses(orientation);
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);
CREATE INDEX idx_files_thesis ON files(thesis_id);
```
### Full-Text Search
```sql
CREATE VIRTUAL TABLE theses_fts USING fts5(
title,
description,
problem_statement,
content=theses,
content_rowid=id
);
```
---
## Migration Strategy
### Phase 1: Database Setup
1. Create SQLite database file
2. Implement schema with all tables
3. Add indexes and constraints
4. Set up full-text search tables
5. Create database access layer (PDO wrapper)
### Phase 2: Data Migration
1. Build migration script to:
- Read all existing YAML files
- Parse and validate data
- Insert into database tables
- Maintain file path references
- Create tag associations
- Generate migration log
2. Verification process:
- Count validation (YAML count = DB count)
- Sample data comparison
- File path integrity check
- Tag relationship validation
### Phase 3: Application Refactoring
**Formulaire Changes:**
1. Replace YAML generation with database INSERT
2. Maintain file storage structure (unchanged)
3. Add database transaction handling
4. Implement tag normalization
5. Add duplicate detection
**Website Changes:**
1. Replace glob/parse operations with SQL queries
2. Implement prepared statements for security
3. Add search functionality
4. Create filtering UI components
5. Implement pagination with SQL LIMIT/OFFSET
### Phase 4: Enhanced Features
1. Search interface (full-text + filters)
2. Tag cloud visualization
3. Year-based browsing
4. Statistics dashboard
5. Related theses suggestions
6. Advanced filtering controls
### Phase 5: Backward Compatibility
1. Keep YAML export functionality
2. Generate YAML on thesis update (optional)
3. Maintain file structure unchanged
4. Preserve URL patterns
5. Create database backup mechanism
---
## Implementation Considerations
### Data Integrity
1. **Validation Rules**
- Required fields: author, year, title, description
- Year range validation (1900-current)
- Email format validation
- File path existence verification
2. **Constraint Handling**
- Unique file paths within thesis
- Orphan file prevention (foreign keys)
- Tag name uniqueness (case-insensitive)
3. **Transaction Safety**
- Atomic thesis creation (metadata + files + tags)
- Rollback on failure
- File cleanup on database failure
### Security Improvements
1. **SQL Injection Prevention**
- PDO prepared statements throughout
- Never concatenate user input into queries
- Parameterized queries only
2. **File System Security**
- Maintain current upload restrictions
- Database stores sanitized paths only
- Path traversal prevention
3. **Data Exposure**
- Email address handling policy
- Optional field visibility controls
- Query result limiting
### Performance Optimization
1. **Connection Management**
- Persistent database connections
- Connection pooling for concurrent requests
- Prepared statement caching
2. **Query Optimization**
- Avoid SELECT * queries
- Use pagination efficiently
- Leverage indexes for all searches
- Cache expensive aggregate queries
3. **File Handling**
- Database stores paths only (not binary data)
- File serving remains filesystem-based
- Consider CDN for static assets
### Maintenance Requirements
1. **Database Maintenance**
- Regular VACUUM operations for database compaction
- Backup strategy (daily automated backups)
- Full-text search index updates
- Periodic ANALYZE for query optimization
2. **Migration Tools**
- YAML export functionality for portability
- Database schema versioning
- Update scripts for schema evolution
3. **Monitoring**
- Query performance logging
- Slow query identification
- Database size monitoring
- Error logging and alerting
---
## Usability Enhancements
### New User Features
1. **Search Functionality**
- Free-text search across titles and descriptions
- Tag-based filtering (AND/OR logic)
- Year range selection
- Program/orientation filters
- Supervisor search
- Combined multi-criteria searches
2. **Navigation Improvements**
- Alphabetical author index
- Chronological timeline view
- Tag cloud with frequency indicators
- "Browse by" menus (year, program, supervisor)
3. **Discovery Features**
- "Similar theses" recommendations
- Tag-based related content
- Same supervisor works
- Recent additions feed
- Random thesis discovery
4. **Submission Experience**
- Duplicate detection warnings
- Tag suggestions from existing tags
- Form validation with helpful messages
- Upload progress indicators
- Preview before submission
### Administrative Features
1. **Content Management**
- Edit thesis metadata post-submission
- Bulk tag operations
- Merge duplicate tags
- Thesis approval workflow
- Content moderation tools
2. **Analytics Dashboard**
- Total theses count
- Submissions per year graph
- Most popular tags
- Program distribution
- File type statistics
- Storage usage metrics
3. **Data Export**
- CSV export for analysis
- JSON API for integrations
- YAML backup generation
- Bulk download capabilities
---
## Performance Projections
### Current System (YAML)
- 13 theses: ~50ms response time
- 100 theses: ~400ms estimated
- 1000 theses: ~4000ms estimated (unusable)
- Memory: Linear growth O(n)
### SQLite System (Projected)
- 13 theses: ~5ms response time
- 100 theses: ~8ms response time
- 1000 theses: ~15ms response time
- 10,000 theses: ~30ms response time (with proper indexes)
- Memory: Constant per request O(page_size)
### Scalability Metrics
Current bottleneck: 100 theses before major performance issues
Post-migration capacity: 10,000+ theses with acceptable performance
---
## Risk Analysis
### Migration Risks
1. **Data Loss**
- Mitigation: Comprehensive backup before migration
- Verification: Automated comparison scripts
- Rollback: Keep YAML files as archive
2. **Downtime**
- Mitigation: Migrate offline database first
- Testing: Staging environment validation
- Deployment: Blue-green deployment strategy
3. **Bug Introduction**
- Mitigation: Extensive testing suite
- Validation: User acceptance testing
- Monitoring: Error tracking system
### Operational Risks
1. **Database Corruption**
- Mitigation: Regular automated backups
- Recovery: Point-in-time restore capability
- Prevention: Write-ahead logging enabled
2. **Concurrency Issues**
- Mitigation: SQLite WAL mode for better concurrency
- Testing: Load testing before production
- Monitoring: Lock timeout tracking
3. **Schema Evolution**
- Mitigation: Migration script versioning
- Documentation: Schema change log
- Testing: Migration testing on copies
---
## Timeline Estimation
### Development Phases
**Phase 1: Database Design & Setup** (1 week)
- Schema finalization
- Database creation scripts
- Index design
- Testing framework setup
**Phase 2: Migration Script Development** (1 week)
- YAML parser improvements
- Database insertion logic
- Validation and verification
- Error handling and logging
**Phase 3: Application Refactoring** (2 weeks)
- Database access layer
- Query optimization
- Form submission updates
- Display page refactoring
- URL structure preservation
**Phase 4: Feature Enhancement** (2 weeks)
- Search implementation
- Filter UI components
- Statistics dashboard
- Related content logic
- Tag management interface
**Phase 5: Testing & Deployment** (1 week)
- Integration testing
- Performance testing
- User acceptance testing
- Staging deployment
- Production migration
**Total Estimated Timeline: 7 weeks**
---
## Success Criteria
### Technical Metrics
1. **Performance**
- Page load time < 100ms for gallery view
- Search response time < 200ms
- Support 1000+ theses without degradation
2. **Reliability**
- 99.9% uptime
- Zero data loss during migration
- All URLs preserved or redirected
3. **Functionality**
- All existing features maintained
- Search accuracy > 95%
- Filter combinations work correctly
### User Experience Metrics
1. **Usability**
- Submission form completion rate > 90%
- Search usage > 30% of visits
- Average session duration increase
2. **Content Discovery**
- Pages per session increase
- Tag-based navigation usage
- Related thesis click-through rate
3. **Satisfaction**
- User feedback surveys positive
- Error rate < 1%
- Form abandonment rate decrease
---
## Post-Migration Roadmap
### Immediate Next Steps
1. Monitor performance metrics
2. Gather user feedback
3. Address any bugs or issues
4. Optimize slow queries
5. Document new workflows
### Future Enhancements
1. **API Development**
- RESTful API for external access
- JSON endpoints for integrations
- Authentication for write operations
2. **Advanced Features**
- Citation generation tools
- DOI/persistent identifier integration
- Version control for thesis updates
- Collaborative annotations
3. **Integration Opportunities**
- Library catalog integration (BAIU)
- ERG website integration
- Academic search engines
- Social media sharing
4. **Archival Features**
- Long-term preservation planning
- Format migration for obsolete files
- Redundant backup locations
- Metadata standards compliance (Dublin Core)
---
## Conclusion
The migration from YAML static files to SQLite database represents a critical infrastructure improvement for the POSTERG project. The current system, while functional for the initial prototype phase, faces severe scalability limitations that will prevent the project from fulfilling its mission of comprehensive thesis archival and accessibility.
### Key Benefits Summary
1. **Performance**: 10-50x faster page loads, sustainable with 1000+ theses
2. **Functionality**: Full-text search, advanced filtering, statistics, related content
3. **Maintainability**: Structured data, validation, easier debugging and updates
4. **User Experience**: Better discovery, faster browsing, richer information access
5. **Sustainability**: Scalable architecture supporting long-term growth
### Recommendation
Proceed with SQLite migration as outlined in this analysis. The investment in migration and refactoring will pay immediate dividends in performance and enable the feature enhancements that will make POSTERG a truly valuable resource for the ERG community. The proposed timeline of 7 weeks is realistic and provides adequate time for thorough testing and quality assurance.
The mission of preserving and democratizing access to ERG theses—challenging the traditional library model that prioritizes grades over accessibility—deserves a technical foundation that can grow with the archive. This migration provides that foundation.
---
*This analysis prepared for the POSTERG project migration initiative, January 2026.*

View File

@@ -1,400 +0,0 @@
# Complete Deployment Guide - New Structure
## Overview
This guide walks you through deploying the new secure directory structure:
- **Old:** `/var/www/html/` (everything exposed)
- **New:** `/var/www/posterg/` with `public/` subdirectory (only public/ exposed)
## Step-by-Step Deployment
### ⚠️ IMPORTANT: Do NOT delete /var/www/html/ yet!
Keep it as a backup until you confirm the new structure works.
---
### Step 1: Setup Server Directory (Manual - One Time)
SSH to server and create the new directory:
```bash
ssh posterg
# Backup current site
sudo cp -r /var/www/html /var/www/html.backup
# Create new directory
sudo mkdir -p /var/www/posterg
# Set ownership (www-data = web server user)
sudo chown www-data:posterg /var/www/posterg
# Set permissions
sudo chmod 775 /var/www/posterg
# Verify
ls -ld /var/www/posterg
# Should show: drwxrwxr-x 2 www-data posterg ...
# Exit server
exit
```
---
### Step 2: Deploy Application Files
From your local machine:
```bash
just deploy
```
This will:
- Upload all files to `/var/www/posterg/`
- Exclude unnecessary files (tests, docs, etc.)
- Set initial ownership to `www-data:posterg`
You should see:
```
sending incremental file list
public/index.php
public/search.php
public/memoire.php
...
```
---
### Step 3: Deploy Nginx Configuration
```bash
just deploy-nginx
```
This will:
1. Check that nginx config has correct DocumentRoot (`/var/www/posterg/public`)
2. Upload `posterg.conf` to `/tmp/` on server
3. Upload `deploy-production-new.sh` to `/tmp/` on server
Expected output:
```
✅ nginx config looks correct
✅ Files uploaded to /tmp/ on server
```
---
### Step 4: Apply Nginx Configuration on Server
SSH to server and run the deployment script:
```bash
ssh posterg
sudo bash /tmp/deploy-production.sh
```
The script will:
1. Fix file permissions on `/var/www/posterg/`
2. Backup existing nginx config
3. Install new nginx config
4. Test nginx configuration
5. Show you the reload command
Expected output:
```
🚀 Post-ERG Production Deployment (NEW STRUCTURE)
==================================================
📋 Step 1: Fixing file permissions...
✓ Changed ownership to www-data:posterg
✓ Set directory permissions to 755
✓ Set file permissions to 644
✓ Made database directory group-writable (775)
✓ Fixed database file permissions (660)
📋 Step 2: Deploying nginx configuration...
✓ Backed up existing config
✓ Installed new nginx config
📋 Step 3: Testing nginx configuration...
✓ Nginx configuration is valid
📋 Step 4: Summary...
✓ Permissions fixed
✓ Nginx config installed
✓ Configuration validated
Ready to reload nginx!
Run: sudo systemctl reload nginx
```
---
### Step 5: Reload Nginx
Still on the server:
```bash
sudo systemctl reload nginx
```
If successful, you'll see no output (which is good!).
Check status:
```bash
sudo systemctl status nginx
# Should show "active (running)"
```
Exit server:
```bash
exit
```
---
### Step 6: Verify Deployment
From your local machine:
```bash
just server-status
```
Expected output:
```
🔍 Server Status
================
✓ Nginx running
✓ PHP-FPM running
Site check:
• Public: 200 ✓
• Admin: 200 ✓
```
---
### Step 7: Security Verification
Test these URLs in your browser or with curl:
```bash
# Should work (200 OK):
curl -I https://posterg.erg.be/
curl -I https://posterg.erg.be/admin/
# Should be 404 (SECURITY - private files):
curl -I https://posterg.erg.be/storage/test.db
curl -I https://posterg.erg.be/config/bootstrap.php
curl -I https://posterg.erg.be/includes/header.php
curl -I https://posterg.erg.be/lib/Database.php
```
**All private files must return 404!** If they don't, nginx is not configured correctly.
---
### Step 8: Deploy Database (If Needed)
If you need to update the database:
```bash
just deploy-database
```
This will warn you before overwriting.
---
### Step 9: Test Thoroughly
- [ ] Visit https://posterg.erg.be/
- [ ] Browse thesis list
- [ ] Open a thesis detail page
- [ ] Try search functionality
- [ ] Access admin panel (should require login)
- [ ] Verify private files return 404
---
### Step 10: Keep Backup for a While
**Do NOT delete `/var/www/html/` immediately!**
Keep it for a few days to ensure everything works. If you need to rollback:
```bash
ssh posterg
sudo nano /etc/nginx/sites-available/posterg
# Change: root /var/www/posterg/public;
# Back to: root /var/www/html;
sudo systemctl reload nginx
```
Your old site will work immediately.
---
## After Confirming Everything Works
A few days later, once you're confident:
```bash
ssh posterg
sudo rm -rf /var/www/html.backup
sudo rm -rf /var/www/html
```
---
## What Changed?
### Directory Structure
```
OLD: NEW:
/var/www/html/ /var/www/posterg/
├── index.php ├── public/ ← DocumentRoot
├── search.php │ ├── index.php
├── admin/ │ ├── search.php
├── assets/ │ ├── memoire.php
├── database/ ❌ exposed │ ├── admin/
├── lib/ ❌ exposed │ └── assets/
└── ... ├── includes/ ✅ private
├── config/ ✅ private
├── database/ ✅ private
└── lib/ ✅ private
```
### Nginx Configuration
```nginx
# OLD
root /var/www/html;
location ^~ /formulaire/ { ... }
# NEW
root /var/www/posterg/public;
location ^~ /admin/ { ... }
```
### Security Impact
| Resource | Old | New |
|----------|-----|-----|
| Database | ❌ Accessible if nginx misconfigured | ✅ Physically outside web root |
| Config files | ❌ One deny rule away | ✅ Physically private |
| Source code | ❌ Exposed | ✅ Physically private |
| Admin panel | /formulaire/ | /admin/ |
---
## Troubleshooting
### Site returns 404 for everything
**Cause:** Nginx still pointing to old location or wrong DocumentRoot
**Fix:**
```bash
ssh posterg
sudo cat /etc/nginx/sites-available/posterg | grep "root "
# Should show: root /var/www/posterg/public;
```
If wrong:
```bash
sudo nano /etc/nginx/sites-available/posterg
# Fix the root directive
sudo systemctl reload nginx
```
### Database errors
**Cause:** Wrong permissions on database file
**Fix:**
```bash
ssh posterg
sudo chown www-data:posterg /var/www/posterg/storage/test.db
sudo chmod 660 /var/www/posterg/storage/test.db
```
### Admin upload errors
**Cause:** Upload directories not writable
**Fix:**
```bash
ssh posterg
sudo chmod 775 /var/www/posterg/public/admin/data
sudo find /var/www/posterg/public/admin/data -type d -exec chmod 775 {} \;
```
### Private files still accessible
**Cause:** Nginx serving from wrong directory
**Fix:** Verify nginx DocumentRoot and reload
---
## Rollback Procedure
If you need to go back to the old structure:
```bash
# 1. SSH to server
ssh posterg
# 2. Restore old nginx config
sudo nano /etc/nginx/sites-available/posterg
# Change: root /var/www/posterg/public;
# To: root /var/www/html;
# Change: location ^~ /admin/
# To: location ^~ /formulaire/
# 3. Reload nginx
sudo systemctl reload nginx
# 4. Verify
curl -I https://posterg.erg.be/
```
Your old site should work immediately (as long as you didn't delete `/var/www/html/`).
---
## Quick Reference
| Task | Command |
|------|---------|
| Deploy files | `just deploy` |
| Deploy nginx | `just deploy-nginx` |
| Deploy database | `just deploy-database` |
| Check status | `just server-status` |
| View logs | `just server-logs` |
---
## Success Checklist
- [ ] `/var/www/posterg/` created with correct permissions
- [ ] Files deployed with `just deploy`
- [ ] Nginx config deployed with `just deploy-nginx`
- [ ] Permissions fixed with `deploy-production.sh`
- [ ] Nginx reloaded successfully
- [ ] Site accessible at https://posterg.erg.be/
- [ ] Admin accessible at https://posterg.erg.be/admin/
- [ ] Private files return 404 (security verified)
- [ ] Database working (can view theses)
- [ ] Search working
- [ ] Old `/var/www/html/` kept as backup
---
**After deployment, your site will be significantly more secure!** 🔒

View File

@@ -1,463 +0,0 @@
1 Mémoire POSTERG, a life after erg
Mémoire POSTERG, a life after erg
Un centre de partage et diffusion...
Théo Hennequin — www.theohennequin.com
Théophile Gervreau-Mercier — tgm.happyngreen.fr
Olivia Marly — oli98marly@gmail.com
Site - Index
Partager son mémoire
À propos.html
Contact.html
visuel interface
visuel interface
visuel interface
Visuel interface
_______________________________________________________________________
NOTRE SITE WEB :
http://work.designnumerique.be/2022-2023/DN-Posterg/
PAD DE RECHERCHE : https://pads.erg.be/p/DN-pcollectif-edition-serveur
GIT sur terminal pour codeberg : https://designnumerique.be/wiki/Cheatsheet_Git
XML pour hébergement : http://codedrops.net/xml-collection-object/
_______________________________________________________________________
SCENARIO / EXPLICATION / INTENTION
Nous sommes un groupe d'étudiant·e·s en design numérique avec projet concernant les mémoires de l'année passée qui demande votre aide !
Quand est-il des mémoires après notre master ? Quelle est la visibilité de notre travail après notre départ de l'ERG ? Certains mémoires finissent à la bibliothèque exposés, mais lesquels et pourquoi ? Actuellement, la bibliothèque (BAUI) sert de lieux d'archives (collection de documents anciens, classés à des fins historiques ; lieu où les archives sont conservées) des mémoires pour l'erg, st-Luc et UCL mais pourquoi sont-ils si peu à être exposés ?
Actuellement, les mémoires sélectionnés sont ceux avec une grande distinction (16/20). Cette note obtenue dépend de la cotation de lecture de mémoire et sa défense.
Mais pourquoi cette moyenne de 14/20 ? Et où finissent les autres mémoires ?
En l'occurrence, la bibliothèque n'est pas un lieu de diffusion et de monstration " juste ", car les mémoires dépendent de la note attribuée en fin de Master et de la place disponible dans les étagères ; sans parler de l'état déplorable de certains mémoires due aux conditions de stockages : couverture plastifiée, stickers, etc - nous travaillons un visuel qui finalement sera " dégradé " lors de son exposition à la bibliothèque, si exposé. De plus, les mémoires sont visible en bibliothèque de manière tangible (style édition).
Qu'en t-il des formats numérique, audio ou vidéo ? De fait, notre recherche se pencherait sur un dispositif de partage/diffusion plus adéquat et en phase avec la multitude de format et forme de monstration plus contemporain.
Notre lieu d'archive/exposition prendrait la forme d'un site web, idéalement en ligne (ou en local en fonction du RE - propriété intellectuelle et droit d'auteur ?). Il contiendrait tout types de mémoire ainsi qu'une interrogation autour de sa licence et sa notion de partage. En parallèle, nous donnerons quelques conseils et bon plans pour : "comment licencier son mémoire : pour protéger ses valeurs et notions de partage s'il-y-a" . Dans un premier temps, nous allons collecter un maximum de mémoires et tenter de recontacter leur auteurice pour échanger avec eux et obtenir des pdf, vidéos, photos. Dans un second temps, nous trouverons un relais pour les futures étudiant.e.s et organiseront un formulaire qui publiera automatiquement les mémoires sur notre site.
_______________________________________________________________________
PERSONNES à CONTACTER DANS LE CADRE DE LA RECHERCHE :
BAIU : laurent.leprince@uclouvain.be (nouveaux mémoires, et/ou anciens format vidéos/audio/site web)
BAIU : laurent.leprince@uclouvain.be
Marie Lécrivain : lecrivainmarie@gmail.com fddl
Loraine Furter : info@lorainefurter.net fddl
Brigitte Ledune : Avoir les mémoires de l'année passée et la liste des étudiant·es
Rideaux de perles : Collaboration pour le scan des mémoires.
_______________________________________________________________________
DESIGN DE L'INTERFACE SUR PENPOT
https://design.penpot.app/#/workspace/036dc80b-fe30-8148-8001-b90bac520952/840175e6-8ff7-812c-8001-b90bf153f05c?page-id=840175e6-8ff7-812c-8001-b90bf153f05d&layout=assets
_______________________________________________________________________
INSPIRATIONS
https://www.instagram.com/p/COP6ycahU7G/
https://www.instagram.com/p/CJWVlO1Hnso/
Projets diplomants st-luc
https://www.instagram.com/ephemeride.fun/
Projet étudiant (Nancy), partage de jury (accrochage, exposition) de diplôme en design & art
http://fdddl.be/#
Fonds de documentation et de lecture de l'erg, éditions print issus de dons >>>> Fondatrice : Marie Lécrivain !!
https://www.memo-dg.fr/
Plateforme de partage de DNSEP (diplome national supérieur d'expression plastique) en design graphique, France
_______________________________________________________________________
Possibilité de Projets
- travail sur les mémoires de l'erg,
- comment mettre sous licence
- comment on les publier/diffuser et rendre public
- une vie après la défense de mémoire, laisser une trace, les faire parler, un dialogue, ...
TITRE DE NOTRE STRUCTURE EDITORIALE????
- MEMOIRE POST-ERG
- A LIFE AFTER ERG
- INTO THE WILD x)
- ERG - INTO THE WILD ?
- LICENCES SURVIVAL PACK - ERG MEMOIRE
__________________________________________________________________
REFLEXION
> concevoir, construire et mettre en action une "structure de publication" numérique (rendre accessible, de rendre public (publier) et de diffuser un certain contenu consultable au moins en partie sur écran)
> Nous penserons un·e serveur·rice comme une structure éditoriale :
> espace de structure éditoriale : un SSG (structure de dossier avec templates et il insère le contenu qui est souvent en markdown) + GIT + CODEBERG PAGES
https://codeberg.org/Pontoporeia/pages
> à qui s'adresse-t-il >>> à l'ERG et ses alentours/admirateur.ices
> sur quels modèles s'appuie-t-il ? Site web généré avec un SSG, un site qui contient nos revues/numéros avec échange de l'auteurice sur le mémoire et sa licence choisies. Il y'aura également un point de téléchargement du mémoire (texte, audio, vidéo, pad, html, ...)
> en quoi publier est-il un acte politique qui interroge aussi les lieux de diffusion ? Les mémoires finissent à la bibliothèque exposé (si bien jugé ou malheureusement dans les caves de la bibli.......). Actuellement, la bibliothèque n'est pas un lieu de diffusion " juste " car les mémoires dépendent d'une note et de la place disponible dans les étagères... c'est n'est pas le bon dispositif de partage. De plus, les mémoires sont en bibliothèque de manière tangible (style édition) mais qu'en t-il des formats de mémoire audio ou vidéo ? Un site serait accessible pour tout le monde et n'importe quand, de plus il contiendrait tout types de mémoire ainsi qu'une interrogation autour de sa licence et comment licencier son mémoire pour protéger ses valeurs et notions de partage s'il y'a.
> quels contenus? Pour les étudiant.e.s en M1/M2, nous vous proposons de penser à des contenus liés à vos recherches; entretiens, collections d'images, articles trouvés, articles commandités, journal de bord, etc. Ces contenus peuvent être ceux d'autres personnes que vous, la particularité d'un éditeur·ice c'est qu'il·elle est aussi un·e curateur·rice de contenu.
> quelle fréquence de publication? Est-ce que le contenu sera d'abord rassemblé puis publié à intervalles réguliers, à la manière d'une revue? Est-ce qu'il s'agira plutôt d'une publication "en continu" au sein de laquelle le contenu est ajouté progressivement à mesure qu'il est produit, à la manière d'un blog? Progressivement en fonction du contenue et des échange avec les créateur.rices des mémoires
> quel contexte de diffusion? Est-ce que cette structure est accessible globalement sur le web ou plutôt localement, dans un ou des lieux/espaces précis? GLOBALEMENT SUR LE WEB
> quel.le(s) acteur.ice(s) machines? Quelles seront les machines qui interviendront au sein de cette structure, avec quels logiciels, quels protocoles?
PROTOCOLE :
mettre à disposition le mémoire (photo, scan, lien video, pdf reader)
email ? échange avec l'auteurice.
- le mémoire est-il édité ?
- quelles sont les licences utilisées ?
- pourquoi ce choix ?
> quels canaux de communication? Affiches (affichées où et quand)? Réseaux sociaux (lesquels et quand)?
- Un visuel/flyer pour ERG mail (communication de l'ERG en fin d'année et rendu mémoire)
__________________________________________________________________
RÈGLEMENT DE L'ÉCOLE
19.2 Propriété intellectuelle et droits dauteur De linscription de létudiant à lESA Saint-Luc Bruxelles découle que létablissement est considéré comme coproducteur des travaux réalisés par létudiant dans le cadre de sa formation. Ceci concerne lensemble des formes de production et de supports, matériels ou dématérialisés et les travaux individuels aussi bien que les travaux de groupe.
Ceci induit un partage des droits dauteur entre létudiant et lécole et implique :
- pour létudiant : de mentionner lannée de création et le nom de lécole (ESA Saint-Luc Bruxelles) lorsquil utilisera ou diffusera ces travaux ;
- pour lESA Saint-Luc Bruxelles : de mentionner le nom de létudiant et lannée de création des travaux quelle souhaiterait diffuser, à des fins pédagogiques, artistiques ou promotionnelles uniquement, ce durant toute la durée de protection de ces travaux par le droit dauteur. En aucun cas, lécole nest autorisée à vendre les travaux produits par un étudiant, ni à percevoir aucun droit lié à leur utilisation ou leur diffusion par des tiers.
19.2.1 Quandlétudiantremporteunprixenespècedanslecadredunconcoursauquellecursusdanslequel il est inscrit a participé avec le suivi dun/de professeur(s), 50 % du montant de ce prix sera versé sur le compte de loption, lautre moitié sera versée sur le compte de létudiant.
_____________________________________________________________________________
Ce travail éditorial autour des mémoires et leur diffusion est né d'une recherche concernant les licences sur les mémoires et leur placement à la bibliothèque voire DISPARITION...
Les licences libres - MEMOIRES ERG, Un rapport de pouvoir sur les valeurs et la diffusion nos travaux
Ce travail éditorial de 2021-2022 est né d'une recherche menée par :
- Defez Aurélie
- Gervreau-Mercier Théophile
- Debaene Justine
- Troadec Marie
- Marly Olivia
- Goldberg Jacquemain Elodie
Il permet de mieux sinformer sur le libre et sert également de guide aux étudiants qui souhaiteraient trouver des alternatives libre de création dans le numérique et dans la rédaction/mise sous licence de leur futur mémoire. Ces licences renversent le rapport de pouvoir habituel dans les écoles, ils seront maître.sse de la liberté dutilisation, copie, étude et distribution de leurs travaux.
Open Source vs Licences libres
Parfois, les personnes qui découvrent le sujet imaginent qu'il s'agit de types de logiciels distincts, les uns sous licence libre, les autres sous licence open source ; sauf que toutes les licences libres (reconnues par la FSF) sont aussi open source (reconnues par l'Open Source Initiative) et vice versa, à quelques exceptions près. Puis, ces personnes s'imaginent qu'il s'agit d'une différence dans les méthodes de développement, ce qui est aussi faux. Ensuite, elles diront par exemple que l'open source est « libéral » quand le libre est « antilibéral », ou de façon plus parlante encore, que l'open source est de droite quand le libre est de gauche.
En tentant d'illustrer la distinction entre « le libre » et « l'open source », on ne fait que redistribuer des oppositions idéologiques qui existent en dehors. On a en partie raison, car ces oppositions sont bien au coeur des projets de société très éloignés d'ESR et de RMS. Mais on a aussi tort, car en faisant cela, on donne l'impression de mouvements qui proposeraient réellement deux projets de société bien définis. Ceux qui militent pour le logiciel libre n'adhèrent pas nécessairement au projet de société de RMS, pas plus que ceux qui défendent l'open source n'adhèrent au projet de société d'ESR. Il y a une chose qu'ESR et RMS ont en commun, c'est la croyance dans le fait qu'il faut défendre la liberté individuelle des utilisateurs, et lutter contre l'asymétrie de droits entre les développeurs et les utilisateurs. À partir de ce principe central peuvent se déployer des projets de société très variés. Pendant des années, le mouvement du Libre s'est opposé de manière frontale au mouvement Open source. Même si elles proviennent de deux approches différentes, les deux définitions sont équivalentes.
À la base les licences Open source ont été crées par lOpen Source Initiative (OSI), la suite à des divergences philosophiques avec la Free Software Fundation (FSF) promouvant, quant à elle, les licences libres. LOSI a créé une « Open Source Definition » (OSD) décrivant les 10 conditions requises pour quune licence appliquée à un logiciel soit considérée comme Open source et accréditée. Globalement, les logiciels répondant aux 10 critères pour être Open source selon la définition de lOSI accordent également les 4 libertés fondamentales définissant un logiciel libre selon la FSF, et réciproquement. Pour éviter les débats entre Free software, pour désigner les logiciels sous licence libre au sens de la FSF, et Open source software, pour désigner les logiciels sous licences Open source au sens de lOSI, on voit souvent utilisé le terme de FOSS (Free and Open source Software) ou FLOSS (Free/Libre and Open source Software), on peut également parler de licences FLOS (Free/Libre and Open Source).
Les différentes licences
Description technique :
Une licence, qui peut aussi être appelée contrat de licence ou licence dexploitation, est un élément qui concerne le droit de propriété intellectuelle et qui encadre lutilisation et la diffusion dun produit. Le type de licence choisi par le propriétaire définit les conditions dutilisation et de partage pour lutilisateur, qui est tenu de les respecter.
Il existe différents types de licences : les licences propriétaires, qui nautorisent quun usage privé de loeuvre par lacquéreur légitime, et les licences ouvertes ou licences libres, qui elles-mêmes englobent des sous-catégories de licences libres.
Les licences libres accordent, selon leurs types, tout ou une partie des libertés dutiliser loeuvre. Ces libertés sont la liberté dutilisation privée, la liberté d'étudier le code, la liberté de redistribution et la liberté de création dérivée.
Dans les licences libres, on retrouve dabord les licences permissives, définies comme « non-copyleft », qui ne contraignent lutilisateur quà la seule obligation dattribuer les portions de code sous licence à leurs développeurs dorigine, que ce soit dans le code quils ont développé à partir de celui-ci ou dans leur documentation.
On retrouve dans cette catégorie la licence MIT, la licence BSD ou encore la licence Apache, ainsi que la licence Creative Commons BY. La licence MIT est une licence de logiciel libre ou open source qui provient du MIT College et qui date des années 80. Elle donne le droit illimité dutiliser, de copier, de modifier, de publier, de distribuer, de vendre et dincorporer le code sous licence dans une autre licence. La seule obligation est de mettre la notice de licence et de copyright dans toutes les copies.
La licence BSD pour Berkeley Software Distribution est aussi une licence libre utilisée pour la distribution de logiciels. Cest une des moins restrictives, mais qui permet de protéger les auteurs par rapport à linscription de leur nom dans les produits dérivés et qui les décharge de la responsabilité déventuels problèmes lors de lutilisation du code.
La licence Apache est une autre licence de logiciel open source qui autorise la modification et la distribution du code que ce soit sous forme libre, propriétaire, gratuite ou commerciale, en nobligeant seulement à fournir une copie de la licence et la notice avec le code source et dindiquer clairement le nom du développeur dorigine et que les fichiers ont été modifiés.
La licence Creative Commons BY, ou attribution en français, permet dutiliser librement lœuvre à condition de lattribuer à lauteur en citant son nom. A part les licences permissives, on retrouve aussi dans les licences libres les licences appelées réciproques, ou « copyleft », qui impliquent une obligation de réciprocité, ce qui signifie que les oeuvres dérivées doivent être distribuées sous la même licence obligatoirement. On retrouve dans cette catégorie la licence GNU GPL, la licence Art Libre, la licence OFL ou la licence Creative Commons BY-SA.
La licence GNU GPL ou licence publique générale GNU fixe les conditions légales de distribution de logiciels libres. Cest la première licence libre créée par la FSF. C'est la licence de logiciel libre la plus utilisée. Elle garantie la liberté dutiliser le logiciel pour nimporte quel usage, laccès au codes sources, la liberté de redistribuer des copies, et que les versions modifiées seront diffusées avec les mêmes libertés.
La licence Art Libre est une licence qui sapplique à la création artistique. Elle autorise à copier, transformer ou diffuser une oeuvre librement, dans une visée commerciale ou non, à la seule condition quil soit toujours possible daccéder à loeuvre originale.
La licence OFL ou SIL Open Font Licence est une licence conçue par SIL International pour distribuer ses typographies, qui peuvent donc être utilisées, modifiées et distribuées librement, à la seule condition quelles restent sous la même licence.
La licence Creative Commons BY-SA ou Attribution-ShareAlike reprend les principes de la licence Creative Commons BY en ajoutant que les oeuvres dérivées doivent être proposées au public avec les mêmes libertés que loeuvre originale.
A côtés des licences permissives et réciproque, il y a une dernière sous-catégorie de licences libres qui sont appelées asymétriques, qui regroupent notamment la licence Creative Commons BY-NC et Creative Commons BY-ND. Ce sont des licences libre diffusion mais avec chacune des restrictions particulière
La licence Creative Commons BY-NC pour Non Commercial reprend les caractéristiques de la licence Creative Commons BY mais nautorisent quune utilisation non commerciale du produit. Une autorisation commerciale du produit est interdite sauf si une demande dautorisation est acceptée.
La licence Creative Commons BY-ND pour No Derivative Works nautorise à copier, distribuer et utiliser que des versions non- modifiées de loeuvre. Il est possible de cumuler ces deux licences, ce qui donne la licence CC BY-NC-ND, qui autorisent lutilisation et la redistribution de loeuvre mais pas de manière commerciale ni les oeuvres dérivées.
On peut aussi renoncer à la quasi totalité de ses droits. Dans ce cas on peut mettre son oeuvre dans le domaine public en optant pour une licence Creative Commons 0 ou CC0 ou une licence Do What The Fuck you want to Public License ou WTFPL.
Multi-licenciement dans le cadre d'un mémoire ou du thèse?
-> besoin de licencié les différents médias sous des licenses appropriées.
-> possibilité de moduler en fonction de tes besoins/envies le licensiement des différents éléments de ton mémoire.
Les travaux publié avec une multi-license plusieurs licenses, souvent pour permettre une utilisation commerciales ou propriétaire en même temps qu'une license open-source. Cela permet d'utiliser une création dans un projet propriétaire, ou au contraire utiliser un version copyleft et faire hérité son travail qui incorpore cette création de la license copyleft.
Free différent de gratuit
Éléments économiques autour de la licence :
- changement dans la manière de consommer linformation, tout est devenu plus accessible
- bien rivaux/non-rivaux
- capitalisme cognitif
- externalités positives qui profitent aux marchands et non-marchand
- externalités négatives : environnement + questions des droits sociaux // ex Wikipédia, communautés produisent des biens communs non-rémunérées, « intelligence collective »
Le Libre et l'Open Source ne sont pas des termes qui sous-entendent gratuit :
Le terme « Free » dans lexpression « free software » signifie « libre » et non «gratuit». Il est clairement stipulé dans la GPL, par exemple, que le distributeur peut se faire rémunérer pour l « acte physique de transférer une copie » (GPL art. 1). Ainsi lon peut vendre des copies de logiciels libres. Stallman lui-même, se faisait rémunérer près de 150 $ par bande distribuée contenant les premiers « packages GNU». Cependant, une fois en possession dune copie, lacheteur peut utiliser, reproduire et redistribuer le programme autant de fois quil le désire. La vente de copies de programmes nest certes pas la principale source de revenus des adeptes du libre.
Cependant, la question de la rémunération restent un problème récurrents dans le mondes des projets open-sources. La situation est aggravé par l'utilisation des SaaS (Software As A Service) où une compagnie utilise un logiciels libres sans redistribuer et ouvrire le code du logiciels. Ils utilise une faille dans certaines licenses Copyleft, en ne redistribuant pas le code source. À la place, l'accès au logiciel se fait à travers une interface à distance, et le code source, bien qu'utilisé, n'est jamais distribué.
C'est pour cela que la licenses Affrero GPL à vus le jours. Cette licenses à une clause stipulant spécifiquement que la distribution à travers un réseaux compte comme de la distribution et donc active la licenses Copyleft.
Il existe à jours plusieurs méthodes de rémunération adopté par des developpeurs de logiciels open-sources: le mécénat, ou "patreon" en anglais, ou des utilisateurs généreux verse régulièrements de l'argent pour financer le développement continue du programmes, les donations, souvent à des fondations, qui sont des dons spontanée souvent ponctuels, et pour finir, l'achat de "licenses" premiums qui activent des fonctionnalités payantes, encourage //parler du mécénat et des demandes de donations, ainsi que les fondations qui se developpe pour s'assurrer qu'un ou des projets open-sources libres sous leurs tutelles ne deviennent propriétaire ou payant. Lidée sous-jacente du mouvement libre nest pas de sapproprier les programmes informatiques afin den faire payer lusage mais plutôt de se faire rémunérer sur base de services ayant trait à ces programmes (installation, customisation,...).
En effet, le monde du logiciel libre est un monde dinformaticiens. Lorsque lon parle de « libre copie » et daccès aux sources de logiciels, il sagit souvent de « parties de programmes » disséminées à travers lInternet et inutilisables par le commun des mortels. Développer un « environnement » à partir de ces différentes bases requiert une personne qualifiée. De plus, une fois lenvironnement créé sur un ordinateur, la maintenance ainsi que les mises à jour de cet environnement devront également être effectuées par des professionnels. Qui serait plus qualifié pour ce genre de travail que les experts en logiciels libres ?
En outre, les informaticiens se servent de leur travail sur des projets libres pour se faire une réputation, qui les aidera dans leur carrière de fournisseur de service. On ne monnaiera que les améliorations apportées à lœuvre dorigine -> Ainsi se crée un cercle vertueux incitant à la multiplication dapports de qualité.
Une des bases des logiciels libres et open-source est la contributions venant dautres utilisateurs. Grâce à la force de travail venant des contributeurs, les logiciels libres restent sécurisé et performant.
Cependant, la question de la rémunération restent un problème récurrents dans le mondes des projets open-sources. La situation est aggravé par lutilisation des SaaS (Software As A Service) où une compagnie utilise un logiciels libres sans redistribuer et ouvrire le code du logiciels. Ils utilise une faille dans certaines licenses Copyleft, en ne redistribuant pas le code source. À la place, laccès au logiciel se fait à travers une interface à distance, et le code source, bien quutilisé, nest jamais distribué.
Cest pour cela que la licenses Affrero GPL à vus le jours. Cette licenses à une clause stipulant spécifiquement que la distribution à travers un réseaux compte comme de la distribution et donc active la licenses Copyleft.
Il existe à jours plusieurs méthodes de rémunération adopté par des developpeurs de logiciels open-sources: le mécénat, ou «patreon» en anglais, ou des utilisateurs généreux verse régulièrements de largent pour financer le développement continue du programmes, les donations, souvent à des fondations, qui sont des dons spontanée souvent ponctuels, et pour finir, lachat de «licenses» premiums qui activent des fonctionnalités payantes, encouragents le payement du développeur.
Il est important de noter que la formation des fondations se développe pour sassurer quun ou des projets open-sources libres sous leurs tutelles ne deviennent propriétaire ou payant. Cest le cas de la fondation Linux, la fondation Gnome ou la Document Fundation pour en citer quelques uns.
Propriété intellectuelle ?
Les licences libres sont nées en réaction au droit de propriété intellectuelle jugé trop restrictif, et aux licences dîtes propriétaires. En effet, les licences libres permettent, au travers dun texte, de céder une partie des droits naturels accordés à lauteur.e à la création dune œuvre de lesprit (à savoir quil.elle a seul.e le droit de diffuser et/ou de distribuer son œuvre).
Au-delà de leur valeur juridique, elles ont également pour but de remettre en question nos relations à linformation. En effet depuis la démocratisation dinternet, une œuvre de lesprit, peut être copiée, modifiée et diffusée pour un coût négligeable.
Les licences libres sancrent dans le mouvement de la culture libre. Celui-ci promeut la liberté de distribuer et de modifier des œuvres de lesprit. Le but recherché est dencourager de manière simple et licite la circulation des œuvres, léchange et la créativité.
Cependant, aujourdhui, la revente dœuvres originales ou de copies constitue lune des principales manières pour certains créateurs de se rémunérer. Cest un argument souvent invoqué par les personnes réticentes voire contre une distribution libre de linformation. Cela met en lumière que le système de propriété actuel encourage les créateurs à utiliser leurs droits pour se rémunérer. Par exemple, en France, les personnes exerçant une activité dartiste/auteur.e doivent nécessairement générer leurs revenus par la vente de ces droits, que cela concerne la vente doriginal de copie ou la cession de ces droits pour la diffusion. Sous ce régime, il nest pas possible de se rémunérer par un contrat à taux horaire ou par des prestations de services (des ateliers par exemple, de la manintenance, tout ce qui est de l'ordre de l'execution simple et non de la création)
Droit dauteur
La notion de droit d'auteur est profondément ancrée dans la culture occidentale, on la considère souvent comme une extension du droit naturel (ce qui peut relever d'une idéologie philosophique).
L'open-source est peut-être une manière de s'en éloigner --> monde post-droits d'auteur
Un dispositif de rapport de pouvoir même dans le libre...
Les licences choisies par l'auteur/l'artiste, ont un pouvoir sur l'utilisateur et sur les œuvres puisqu'elles définissent la manière dont ces œuvres pourront ou non être modifiées, diffusées, appropriées, exploitées etc. Les licences ont le pouvoir d'influencer le contexte dans lequel une œuvre comme : un programme, un logiciel, une typo, une musique, ... sera " manié ".
"Une licence de libre diffusion (parfois abrégé LLD) ou licence ouverte est une licence s'appliquant à une œuvre de l'esprit par laquelle l'auteur concède certains des droits que lui offre le droit d'auteur quant à l'utilisation, à la modification, à la rediffusion et à la réutilisation de l'œuvre dans des œuvres dérivées. Tout comme les licences libres dont elles dérivent et qu'elles englobent, elles facilitent la prolifération d'une œuvre en autorisant la copie et l'usage sous certaines conditions. Cependant le courant de pensée qui sous-tend les licences libres est animé par une volonté éthique d'égalité.
Les licences de libre diffusion ont pour seul fondement de faciliter la diffusion, que ce soit dans une démarche d'équité ou simplement pour populariser une œuvre en limitant les frais publicitaires.
On peut compter parmi les licences de libre diffusion les licences Creative Commons."
Les licences libres respectent la liberté des utilisateurs, c'est une dimension éthique et sociale qui défend la liberté, l'égalité et la collaboration. Cependant, quel est le rapport de pouvoir entre une licence libre et le système économique lui-même ? Les licences libres ont le plus souvent des clauses qui permettent de définir le système de diffusion d'une œuvre et de contourner les lois en vigueur liées à la propriété intellectuelle, et par extension propriété privée.
Il est favorable d'utiliser une license plutôt que pas de license : Par exemple, un code sans license est un code fermé ; on part souvent du principe que ce qui est partagé en ligne est rendu disponible. "Contre-instinct" dans un monde régi par le droit d'auteur.
_______________________________________________________________________
Interview avec Laurent Leprince, directeur de la BAIU (espace de consultation de documents en arts et en architecture commun aux Instituts Saint-Luc et à la faculté darchitecture LOCI de lUCL).
Résumé: Interview BAIU
La BAIU fonctionne, depuis 2005, comme une sorte de dépôt des mémoires des institution de Saint Luc (UCLouvain, lERG et certaines sections de lESA). Les mémoires sont consultables sur place, ils ne sortent pas de la bibliothèque. Seul les mémoires ayant obtenu 70% des points sont en accès libre dans les rayons, le reste des mémoires est rangé dans les archives, où il faut remplir un document spécial afin de les consulter. Pour lUCLouvain il existe aussi le logiciel Dial où lon peut consulter numériquement les mémoires. Malheureusement certains étudiants restreignent laccès à leurs mémoires, par peur du plagiat. Un des travail de Laurent est donc de réussir à convaincre les étudiants de lintérêt davoir son mémoire bien indexé, disponible, et sur le fait que limiter laccès nest pas nécessairement une meilleure protection. Une des difficultés pour lERG de numériser certains projets se comprend aussi par le format non-académiques de certains mémoires.
À la BAIU, les mémoires ne sont pas consultables numériquement mais grâce à un catalogue on peut avoir accès aux informations principales, savoir quils existent, et quels thèmes ont été travaillés. On peut aussi, si cest un mémoire numérique, avoir accès au site web du projet. La BAIU fait un peu au cas par cas.
Cela permet aux étudiants de consulter les travaux déjà fait sur une certaine thématique afin daborder un angle plus original pour leur propre travail ou aux professeurs encadrant de guider les élèves en leur montrant les bons et moins bon exemples de travaux. À lERG, cest Xavier Gorgol qui les amène, car lécole na pas la possibilité de les stocker.
*Lancement de linterview*
Aurélie: On travaille sur les licences libres, ce que ça veut dire… et la question quon sest posée, et cest pour ça quon a voulu vous rencontrer, cest, si par exemple à lerg ou dans une autre école on cherche à licencier son mémoire, de manière à le diffuser plus largement, est-ce que cest possible? Au niveau de la bibliothèque et des mémoires comment est-ce que ça se passe? Je ne sais pas de quelles écoles vous avez les mémoires ici, est-ce quil y a des contrats entre les écoles et vous?
Laurent Leprince: Alors ici cest un peu compliqué parce quon est à cheval sur tous les instituts Saint-Luc et lUCLouvain, et cest vrai que les approches sont un peu différentes, donc ça peut être intéressant pour le travail. Alors au niveau des mémoires on a les mémoires darchitecture de lUCLouvain, et puis les mémoires de lerg et de lesa, et lesa cest uniquement certaines sections. Il y a aussi dautres instituts qui sont liés à la bibliothèque, donc Saint-Luc secondaire, où il ny a pas de mémoire, et les cours du soir, les ateliers Saint-Luc, où il ny en a pas non plus, sauf une section qui est lISRE, la formation en urbanisme mais là leurs mémoires sont conservés chez eux, donc ils ne sont pas ici. Au niveau de luniversité, pour commencer, donc la formation en architecture de lUCLouvain, jusquen 2017-18 cétait tous les mémoires au format papier, et après ils ont fait un dépôt institutionnel sur Dial, qui est un outil numérique de luniversité. Ce quon a fait cest quon a descendu en archives tous les mémoires papier, qui sont encore consultables, et Dial est accessible en ligne. À lUCL, quand létudiant fait son dépôt dans Dial, il a la possibilité entre différents types de statut pour son mémoire, donc le mémoire peut être entièrement accessible, il peut déterminer un embargo, il peut le bloquer complètement, etc. En sachant quen architecture cest parfois un peu spécifique. Par exemple on a eu des étudiants dont le TFE, chez eux ils appellent ça le Travail de Fin dÉtudes, consistait en la création dune brique en papier, en matériaux recyclés et ils ont vraiment envisagé de breveter, et donc là ils ont fermé les accès. Alors quen art, je pense que la réflexion est peut-être différente. Là le processus est très clair, létudiant fait son dépôt électronique lui-même, il reçoit une formation pour ça, les professeurs les informent sur les différentes possibilités. Alors nous, on a constaté au tout début, que les étudiants fermaient les accès. Jen ai discuté avec des jobistes: «Pourquoi? Est-ce que cest vraiment dans ton intérêt? «et il y en a qui ont dit «Je sais comment ça marche, je nai pas envie quon récupère des informations et donc je préfère fermer». Alors Dial est accessible, vous pouvez aller voir et vous verrez les statistiques vous pouvez même aller année par année. Nous, on a essayé de faire passer le message douvrir les accès, alors certes le travail nest pas disponible, mais on voit où il a été déposé. Il y a aussi des logiciels comme Compilatio, qui est un logiciel de plagiat. Cest un logiciel qui moissonne toute sorte de ressources, les professeurs encodent des paragraphes et voient si ça se retrouve ailleurs. Et par exemple, à lULB ils utilisent ce type doutils, et si par exemple létudiant bloque laccès de son mémoire, ou donne un accès uniquement sur place, lULB na pas accès et ne peut pas inclure ce type de travail dans ses bases de données, et donc le travail est moins protégé. Donc nous on essaye davoir une discussion avec ces étudiants, sur le fait que ça peut être intéressant que le travail soit bien indexé, disponible, et sur le fait que limiter laccès nest pas nécessairement une meilleure protection. Alors lerg, lesa, etc, ce ne sont que des documents papiers, cest plus un dépôt, on a pas de contrat avec lécole, je pense que le contrat est signé entre létudiant et lécole au moment du dépôt du mémoire.
Olivia: Je pense que cest plus au début, le papier quon doit signer à la rentrée.
LL: Après le fait quun mémoire soit consultable ici ne veut pas nécessairement dire que létudiant autorise tout. Ici on fonctionne plus comme dépôt, cest à dire que cest plus un service, ce sont des mémoires papier qui sont consultables uniquement sur place, les mémoires ne sortent pas. Et alors quest-ce quil se passe? On reçoit chaque année un certain nombre de mémoires de lesa et de lerg et puis nous on les encode dans le catalogue de la bibliothèque, qui est celui de lUCLouvain, et ça par contre ça a été décidé dans le cadre dune commission qui est la commission Art, qui est une commission qui lie la bibliothèque et les différents instituts, et cest une commission qui vise surtout à tout ce qui est acquisition, promotion de la bibliothèque, formation... Et là on a décidé de mettre en rayon les mémoires qui ont eux 70% des points et de mettre en archives les autres, alors en archives ça ne veut pas dire quils ne peuvent pas être consultés, si quelquun veut le consulter il y a un document à compléter et on le monte, mais dans tous les cas les mémoires ne sortiront pas. Cest parce quici lidée cest plutôt de faire une sorte de dépôt, dabord parce quà lerg par exemple il ny a pas la place pour, ça permet que les travaux soient disponibles pour lensemble des instituts Saint-Luc. En art lapproche est différente quen architecture, le mémoire est un objet, un livre dartiste, au niveau de la mise en page, du type de reliure, etc., il y a tout un travail qui est fait quil ny a pas toujours dans les mémoires darchitecture. Alors ça permet de vous inspirer, de connaitre les thématiques déjà traitées, si un étudiant veut être plus original par exemple ou aborder la thématique avec un angle différent, donc ce sont aussi des outils, et ça sert aussi, par exemple quand il y a des visites de profs, par exemples des profs qui encadrent un travail de recherche documentaire, ou qui encadrent le travail de mémoire, ils peuvent passer ici et dire aux étudiants quils peuvent trouver les mémoires ici pour sen inspirer, montrer des bons ou des moins bons exemples, des choses comme ça. Et alors au niveau de lerg, cest Xavier Gorgol qui les amène. Alors parfois quand je vais là-bas, je vois Laurence Rassel qui me donne parfois des catalogues dexpos, des choses comme ça quelle a sur son bureau, et sil y a des mémoires qui trainent elle les met. Parce que cest plus lidée que là-bas il ny a pas la place pour les stocker, et que comme ça cest accessible à tout le monde. À lesa cest par section, on contacte les responsables des sections et on va les chercher. Alors dans le nombre de mémoires demandés, il y en a un pour le jury, il y en a aussi un pour la bibliothèque.
A: Est-ce que cest déjà arrivé quun étudiant refuse de donner son mémoire? Est-ce que vous êtes au courant de ça?
LL: Ça je ne sais pas, en fait je reçois les mémoires et je demande une feuille de côte, la côte nest pas affichée, elle est en message privé, elle est visible par les bibliothécaires mais elle nest pas disponible en consultation dans le catalogue. Mais donc ça nous permet de voir que tel mémoire na pas eu les 70%, et donc il est bien en bas. Mais je nai pas eu de cas où quelquun est venu en demandant de retirer son mémoire.
A: À lesa vous disiez que cest seulement certaines sections, ce sont lesquelles?
LL: Alors il y a Bande dessinée, Création dintérieur, Scénographie, en fait cest les masters, ce sont les sections qui sont passées au niveau master.
Élodie: Est-ce quil y a des licences qui sont associées au dépôt des mémoires ou est-ce quil sagit plutôt dun contrat officieux?
LL: Je ne sais pas du tout, parce quon est plus un lieu de stockage. Par exemple javais travaillé au secrétariat à lISRE, où il y a des mémoires, et là cest un contrat entre linstitut et létudiant au moment du dépôt.
O: Avec Théophile, on avait un peu feuilleté ceux qui sont sur les étagères et dans certains il y a indiqué une licence et dans dautres pas, donc ça dépend de létudiant, de sil a pris le temps. Théophile: Et il ny a pas cette discussion avec la bibliothèque?
LL: Dans le sens de limiter laccès? Non dans le sens douvrir?
T: Oui, dans le sens douvrir.
LL: Dans le but de citer ou vraiment de récupérer?
E: Oui ça peut être copier... T: Les licences libres permettent à la fois dutiliser, détudier, de copier, de modifier et de distribuer.
LL: Nous on travaille plutôt dans loptique dun travail académique. On peut citer, mais on mentionne ses sources, et donc après ça peut être dans lidée de «je vais distribuer mais dans le cadre de mon propre travail», et là cest repris dans la bibliographie. Tandis que quand cest penser à peut-être récupérer un chapitre ou des illustrations et les diffuser ou les intégrer dans un autre travail, cest autre chose.
T: Après les licences peuvent demander quil y ait attribution, donc quil y ait quand même une citation de lauteur. Ça marche peut-être plus pour des travaux de type artistique, comme vous disiez cest un objet, cest un tout, et il y a peut-être moyen de samuser avec ça. O: Selon vous, cest mieux que ce soit libre et quil y ait pas trop dencadrement comme à lerg, plutôt quon leur laisse le choix et quau final les accès soient limités?
LL: Je ne sais pas, après je ne sais pas comment ça se passe à lerg, il y a peut-être un choix fait au moment du dépôt.
O: Mais jai limpression que même nous, étudiants, on est pas vraiment au courant, même quil y a des mémoires ici à la bibliothèque, je pense que beaucoup lapprennent par après.
LL: Ils sen réjouissent ou pas?
T: On ne sait pas, on devrait essayer de voir ça. O: En tout cas, on dirait quils ne sont pas tant au courant de ça quil y a des mémoires ici.
LL: Par exemple je sais quil y a des profs qui ont fait un travail, je ne sais plus si cétait en graphisme, qui ont fait une réédition de certains mémoires de qualité, ça vous dit quelque chose?
A: Ah oui jai vu ça.
LL: Ils ont demandé de pouvoir consulter tous les mémoires, et là on a remonté même ceux qui navaient pas obtenus les 70%, et il y a eu une présélection par les étudiants, cétait avec Renaud Huberlant.
Justine: Et ceux dont les mémoires ont été sélectionnés ont été contactés?
LL: Ça je ne sais pas.
T: Et comment ils sont listés, il y a un index de ceux qui sont en-dessous de 70%?
LL: Il y a un index, il y a moyen dans le catalogue de les retrouver. Ils sont tous dans le catalogue.
A: Et la limite des 70%, elle a été définie comment?
LL: Cest par la commission Art, ça a du être dans des PV, mais cest plus pour des questions pédagogiques, cest parce que le but ici cest plus de mettre à disposition des exemples, et cest un signal pour dire que cest un travail de qualité.
A: Jai encore une question, ici, il y a les mémoires darchitecture, de lerg et de Saint-Luc, est-ce quil y a dautres endroits où se trouvent ces mémoires ou cest exclusif à la bibliothèque?
LL: Cest exclusif à la bibliothèque mais le catalogue est commun avec lUCLouvain. Mais comme les mémoires ne bougent pas, ils ne peuvent être consultés quici. Sauf sur Dial, la base de données en ligne, et là effectivement ils sont consultables partout, ou limités au réseau UCLouvain, et là cest consultable seulement sur place. Après ça cest une question dinfrastructures aussi, cest des serveurs, cest assez lourds, parce que ce sont des serveurs qui servent aussi pour les publications scientifiques, et puis cest aussi des différences dobjets, il y a pas mal de mémoires artistiques, ça serait plus difficile de les mettre sur ce genre de plateforme, parce que cest quand même assez formaté, cest du pdf. En architecture, il y a parfois aussi plus de créativité, parce quen architecture cest aussi différent, cest un TFE en et sur larchitecture, et chez eux le projet prime, donc en fait, ils ont un TFE à faire, mais ça sarticule avec leur travail datelier et le TFE approfondit souvent un aspect, et la thématique ou le site ou le bâtiment sont liés, et dans une partie plus théorique ils vont traiter dune thématique liée. Il y a aussi des évolutions par exemple à lesa. Au début les mémoires de lerg étaient beaucoup plus créatifs, à lesa ils étaient très scolaires, moins objets, et maintenant il y a plus de recherche, par exemple avec des formats différents, etc. Mais à lesa ça reste plus une réflexion sur, alors quà lerg le mémoire est parfois un objet artistique aussi, même sil est pensé, indexé, réfléchi, cest assez différent parfois, et ça varie aussi avec les sections je pense. Je vais vous montrer Dial. Vous pouvez choisir par année, par promoteur, par faculté, par type de diplôme, et ici vous avez les informations sur les accès: restreint, libre, interdit, mixte ou embargo. Embargo, cest à dire que ça sera accessible après un certain nombre dannées, par exemple en médecine, en pharmacie, chez les ingénieurs ça peut se justifier, par exemple sil y a des brevets ou des choses comme ça. Ou alors parfois même en sciences économiques, quand il y a des gens qui travaillent avec des sociétés, lentreprise peut dire: «Vous pouvez faire le travail avec nous mais il ne doit être disponible que dans 5 ans pour des raisons de concurrence», ou il y a des données confidentielles... Libre cest quil ny a pas de restrictions, interdit cest quand rien du tout nest accessible, restreint cest restreint au réseau UCL, et mixte ça veut dire quil y a plusieurs documents et que tous les documents nont pas la même protection. Par exemple, mon travail théorique je le rends public mais les annexes, dans le cadre dun travail avec une entreprise ou autre, je mets un embargo ou jinterdis laccès. Par contre le titre du mémoire, le résumé, sont publics, et on retrouve les promoteurs... Libellule cest le catalogue de la bibliothèque, il dessert lUCLouvain, LUnamur et LUniversité Saint-Louis. Les mémoires sont répertoriés dedans. Ça vaut peut-être le coup daller voir comment ça se passe pour les mémoires au niveau des écoles dart en France, ou du côté néerlandophone aussi. Après je sais quil y a aussi du coté des universités, lULiège par exemple, ont beaucoup développé tout ce qui est lopen-source etc.
A: Jai encore une question, est-ce que cest déjà arrivé, quune fois quun étudiant sorte de master, il finisse par publier son mémoire, avec un éditeur par exemple?
LL: Je nen ai pas connaissance, je sais quà lesa, jai entendu parler détudiantes qui publiaient des articles, je sais quil y a un étudiant qui a fait un très bon travail sur les chalets norvégiens à lesa, et lui a peut-être envie de faire une publication. Je sais quil a eu un prix, mais pour le moment le mémoire est consultable ici.
T: Faut se dépêcher.
LL: (rires) Après je pense que ça va être retravaillé. On a quelques ouvrages en architecture qui sont devenus des références aussi mais ce sont plus des ouvrages qui recensent par exemple tous les bâtiments construits par un architecte, des gens qui font une sorte dinventaire qui nexiste nulle part ailleurs, et donc ça devient une référence que les gens consultent régulièrement.
Visite des archives*
A: Au niveau des mémoires, est-ce que vous avez une obligation de conservation, une responsabilité, par exemple si les mémoires qui sont en haut...
LL:... disparaissent?
A: Oui ou si avec le temps ils sabîment, parce que ce sont des choses qui sont relativement uniques, ce sont des choses qui sont édités à 3 ou 4 exemplaires.
LL: Je ne sais pas, jai un collègue qui fait une formation en archivistique, qui a eu pas mal de cours sur les aspects juridiques.
O: Et ça fait combien de temps que les mémoires sont ici, quils sont archivés? Et la bibliothèques existe depuis combien de temps?
LL: Alors ici cest depuis 2011, et avant cétait un centre de documentation à Saint-Luc, rue dIrlande, depuis plus longtemps encore. Alors là ce sont les mémoires, ici ce sont dautres institutions, on conserve des mémoires ou des thèses de doctorat parce que ce sont des thématiques intéressantes, il y a aussi un centre darchives darchitectes, ce sont des architectes dont les familles ont fait don des archives, ça peut être des plans... Et cest disponible ici pour les consulter. Par exemple Bastin, cest un architecte qui a réalisé une aile du musée dart moderne, un jour il y a eu des fuites et le bâtiment était menacé, des gens chargés de la restauration sont venus ici consulter les archives pour savoir comment ça avait été fait, les matériaux, etc.
T: La limite des 70%, cest pour toutes les écoles?
LL: Oui cest pour tout le monde. Et il y a autre chose que je nai pas dit. Cest vrai que la limite des 70% continue à sappliquer à luniversité. Si létudiant décide que laccès est libre, mais que le travail na pas atteint les 70%, luniversité peut bloquer laccès, peut décider que louvrage nest pas consultable. Par contre si létudiant interdit laccès, luniversité ne peut pas ly obliger. Et ça cest surtout pour quil ny ait pas des travaux qui circulent, qui ne soient pas assez bons.
O: Est-ce que pour lerg, qui fonctionne plus comme un dépôt, par exemple lannée passée avec le COVID, il y a des mémoires qui manquent, par quils nont pas été imprimés ou autre?
LL: Alors effectivement il y a des trous.
T: Vous savez ça comment?
LL: En comparant les mémoires quon reçoit et les PV de délibérations. Alors parfois, par exemple jai dit à un jobiste dont le mémoire nétait pas arrivé quil pouvait le ramener, il ne la pas fait, mais par contre si un étudiant vient en disant quon a pas reçu son mémoire et quil voudrait quil figure à la bibliothèque, on na aucun soucis.
T: Vous ne stockez que les versions papiers, cest ce qui est requis par lécole si je comprends bien?
LL: Oui.
T: Mais du coup si quelquun rendait un travail numérique?
LL: Ça cest un peu un problème, on a eu quelques fois le cas avec des travaux de lerg, où le truc, cest parfois juste une sorte de petite étiquette avec un code barre ou des choses comme ça, alors ça cest un peu délicat.
T: Vous navez pas despace numérique?
LL: Non, et en fait Dial est vraiment limité à lUCLouvain. Donc souvent dans ce cas-là, ce que les étudiants font, cest quils ont une sorte de domaine internet et ils publient leur mémoire là- dessus, et puis 3/4 ans après ils se disent: «Ça ne mintéresse plus, je ne paye plus» et le travail disparaît. Des étudiants de lUCLouvain avaient proposé une solution, je crois quil y a moyen darchiver en ligne. On lavait fait, parce quon pouvait se le permettre, ça concernait un cas ou deux. Mais si ça devient de plus en plus régulier, alors ça pose la question dun serveur ou dautres choses.
T: Est-ce quon peut voir dans le catalogue le nombre de mémoires qui ont été faits au format numérique?
LL: Je ne pense pas, parfois cest sous forme de podcast, cest de laudio, alors parfois on garde ça sur un CD-Rom, mais bon les CDs ont une durée de vie limitée, mais bon commencer à archiver ça sur un disque dur cest complexe. Parce que cest vrai que tout a des fins pédagogiques, la base cest que cest à des fins pédagogiques. Donc tu parlais dobligation tout à lheure, on est plus dans lidée darchivage, parce quil ny a pas la place à lerg, et donc plutôt que de laisser ça dans un placard, ça vient ici, parce que cest une manière de rendre les choses accessibles et consultables par les profs et les étudiants. Cest une autre finalité.
O: Et donc pour lerg ça commence en 2005?
LL: Oui cest ça. Et je pense quau tout début, il y avait énormément de mémoires ici, parce que la qualité ne suivait pas, mais petit à petit ça sest relevé. Quand les étudiants de Renaud sont venus, ils ont récupérés une partie de ceux-là, parce quils regardaient surtout lobjet. Cest compliqué davoir quelquun qui a une démarche scientifique et aussi artistique. Alors il y en a certains que lon conserve un double, cest un peu ce que tu disais, sil y en a qui disparaissent. Et on ne les numérise pas nous.
T: Jai vu quil y avait écrit «numériser»sur des cartons juste derrière, quest-ce que vous numérisez du coup?
LL: Ça ce sont des VHS, et Aurélien les numérise.
A: Jai encore des questions. Quand cest un mémoire numérique, sous forme de site web par exemple, est-ce quils sont répertoriés quelque part?
LL: Ils sont doffice répertoriés dans le catalogue, et donc dans le type, il y aura «papier, autant de page», ou alors ça sera écrit «site web». Alors nous on essayait, quand cétait rare, de conserver un back-up si létudiant nous en fournissaient un, ou si ce nest quun site on fournissait le lien, mais si un jour létudiant arrête de payer son nom de domaine, il ny a plus rien. Après, je sais quon lavais fait une fois, on avait cherché des solutions intermédiaires, on avait archivé en ligne. Mais cest vraiment au cas par cas, il ny a rien de prévu de manière automatique.
T: Oui, il faudrait voir alors.
LL: Oui, prévoir un serveur à lerg.
O: Ou que la bibliothèque soit aussi un appui numérique. T: Je suis pas sûr quil faille un serveur, juste un disque dur ça doit suffire, il faut juste un espace de stockage numérique. O: Depuis combien de temps la bibliothèque existe?
LL: Le centre de documentation à StLuc date de 1968.
________________________________________________________
CONCLUSION du travail sur les licences
Au premier quadrimestre, nous avons abordé les thémes du libre et de l'open source (licences et logiciels), et les rapports de pouvoir qui s'y jouent, à un niveau théorique. Au second quadrimestre, nous avons voulu voir les implications du libre d'un point de vue plus pratique, et plus spécifiquement, en rapport avec les mémoires, travail qui nous concerne toustes. Existe-t-il une licence qui s'applique automatiquement aux mémoires de l'ERG ? Si oui, comment faisons-nous pour l'appliquer à nos projets ? Avons-nous la possibilité d'utiliser différentes licences et comment ? Comment les adapter en fonction des spécificités de certains types de mémoires (numériques, artistiques) ? Où sont stockés nos travaux ? Son-ils accessibles ?
Dans nos recherches, nous avons interrogé des acteurs de l'administration qui encadre le suivi et l'archivage des travaux (Laurent Leprince de la BAIU et Xavier Gorgol de l'ERG). Nous avons pu discuter des raisons qui poussaient certains étudiants à fermer l'accès à leurs mémoires, en pensant, à tort, mieux les protéger.
On a également commencé une base de données pour comparer les différentes caractéristiques des licences libres. À partir de cette base de données, nous avons mis en forme un flyers contenant les licences libres les plus réccurentes dans le cadre des projets artistiques, afin de clarifier et d'aider les étudiant à licencier leurs mémoires. Pas seulement un outil de diffusion, les licences peuvent aussi être support d'une vision ; exemple avec la collective Bye bye Binary qui a travers ses recherches typographiques et sa licence cherche à diffuser l'écriture inclusive. Leur licence libre OIFL (OpenInclusif..veFonteLicense) ,es tune adaptation en français et en écriture inclusive de l'OFL. Autre exemple, la revue trimestrielle Médor qui adopte le parti de ne fonctionner qu'avec des outils libres et dont le code du site internet est émis en licence libre (licence BSD). Le livret est un compte-rendu de tout le travail effectué durant ce quadrimestre et des recherches engagées. Il permet de mieux sinformer sur le libre et sert également de guide aux étudiants qui souhaiteraient trouver des alternatives libre de création dans le numérique et dans la rédaction/mise sous licence de leur futur mémoire.

View File

@@ -1,181 +0,0 @@
# Database Configuration
This document explains the centralized database configuration for the Post-ERG thesis management system.
## Overview
Database paths are centralized in `shared/config.php` to provide a single source of truth for:
- Test database location (development)
- Production database location (deployment)
- Environment detection logic
## Database Locations
```
database/
├── test.db # Development/testing database
└── posterg.db # Production database
```
## Configuration File
The `shared/config.php` file defines:
```php
// Test database (development)
DB_TEST_PATH = '/path/to/storage/test.db'
// Production database (server)
DB_PROD_PATH = '/path/to/storage/posterg.db'
```
## How It Works
### Automatic Detection
By default, the system automatically determines which database to use:
1. **If `storage/test.db` exists** → Use test database (development mode)
2. **Otherwise** → Use production database (production mode)
This means:
- Developers get test database automatically when it exists
- Production server uses production database (no test.db present)
### Manual Override
You can force a specific database using the `DB_ENV` environment variable:
```bash
# Force test database
export DB_ENV=test
php apps/public/index.php
# Force production database
export DB_ENV=prod
php apps/public/index.php
```
### Custom Path Override
You can also pass a custom database path directly:
```php
// Use specific database file
$db = new Database('/custom/path/to/database.db');
// Or with singleton
$db = Database::getInstance('/custom/path/to/database.db');
```
## Deployment Workflow
### Development
When working locally:
```bash
# Create test database with fixtures
just create-fixtures
# Start development server (uses test.db automatically)
just serve-public
just serve-admin
```
### Production
When deploying to server:
```bash
# Deploy applications and shared code (excludes test.db automatically)
just deploy
# The deploy command explicitly excludes:
# - test.db (test database)
# - *.db (all database files)
# - tests/ (test suites)
# - cache/ (temporary files)
# - *.md (documentation)
# Result: Only posterg.db exists on server
# Application automatically uses production database
```
### Deploying Test Database (Explicitly)
If you need to deploy the test database to the server (for testing):
```bash
# This is a separate, explicit command
just test-deploy
# ⚠️ Warning: This will overwrite the remote test.db file
```
### Deploying Database Structure Only
To deploy schema and fixtures without databases:
```bash
# Deploy schema.sql and fixtures/ only (excludes all .db files)
just deploy-database
```
### Testing on Production
To test with production data locally:
```bash
# Download production database (optional)
scp posterg:/var/www/html/storage/posterg.db database/
# Remove test database to force production mode
rm database/test.db
# Or set environment variable
export DB_ENV=prod
php apps/public/index.php
```
## Benefits of Centralized Configuration
1. **Single source of truth** - All database paths defined in one place
2. **Environment-aware** - Automatically detects development vs production
3. **Override capability** - Can force specific database when needed
4. **Maintainable** - Easy to update paths or add new environments
5. **Safe deployment** - Test database never deployed to production
## Integration with Database Class
The `Database` class automatically uses this configuration:
```php
require_once 'shared/Database.php';
// Automatically uses correct database based on environment
$db = new Database();
```
The path resolution order is:
1. Custom path (if provided)
2. `DB_ENV` environment variable
3. Auto-detection (test.db exists → test mode, otherwise → production)
## Helper Functions
`shared/config.php` provides helper functions:
```php
// Get current database path
$path = getDatabasePath();
// Check if running in test mode
if (isTestMode()) {
echo "Using test database";
}
```
## Notes
- **Deployment safety**: The `deploy`, `deploy-public`, and `deploy-admin` recipes explicitly exclude `test.db` and all `*.db` files
- **Explicit test deploy**: Use `just test-deploy` to explicitly deploy test.db when needed
- **Git ignored**: Test database is in `.gitignore` and never committed
- **Backups**: Production database should be backed up regularly
- **Schema**: Both databases use the same schema (`storage/schema.sql`)
- **Verification**: Run `rsync --dry-run` to preview what will be deployed before deploying

View File

@@ -1,887 +0,0 @@
# Post-ERG Database Specification
Complete technical specification of the Post-ERG thesis database schema.
**Version:** 1.0
**Database:** SQLite
**Last Updated:** February 5, 2026
---
## 📋 Table of Contents
1. [Overview](#overview)
2. [Entity Relationship Diagram](#entity-relationship-diagram)
3. [Core Tables](#core-tables)
4. [Lookup Tables](#lookup-tables)
5. [Junction Tables](#junction-tables)
6. [Support Tables](#support-tables)
7. [Views](#views)
8. [Indexes](#indexes)
9. [Triggers](#triggers)
10. [Data Types Reference](#data-types-reference)
11. [Business Rules](#business-rules)
12. [Sample Queries](#sample-queries)
---
## Overview
### Purpose
Database for managing and publishing ERG final thesis projects (TFE - Travaux de Fin d'Études) and doctoral theses.
### Key Features
- Multi-author thesis support
- Multiple supervisors per thesis
- Flexible format types (web, audio, video, print, etc.)
- Access control (public, internal, restricted)
- File attachment management
- Keyword tagging system
- Full-text search capability
- Academic metadata tracking
### Database Size Estimates
- **Expected records**: 100-500 theses/year
- **Growth rate**: ~10-15% annually
- **Average record size**: ~5KB (metadata only)
- **File storage**: External (linked via file paths)
---
## Entity Relationship Diagram
```
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ authors │◄──────│ thesis_authors │──────►│ theses │
└─────────────┘ 1:N └──────────────────┘ N:1 └─────────────┘
┌─────────────┐ ┌──────────────────┐ │
│supervisors │◄──────│thesis_supervisors│──────────────┘
└─────────────┘ 1:N └──────────────────┘ N:1
┌─────────────┐ ┌──────────────────┐
│ keywords │◄──────│ thesis_keywords │──────────────┐
└─────────────┘ 1:N └──────────────────┘ N:1 │
┌─────────────┐ ┌──────────────────┐ │
│ languages │◄──────│ thesis_languages │──────────────┤
└─────────────┘ 1:N └──────────────────┘ N:1 │
┌─────────────┐ ┌──────────────────┐ │
│format_types │◄──────│ thesis_formats │──────────────┤
└─────────────┘ 1:N └──────────────────┘ N:1 │
┌─────────────┐ │
│orientations │──────────────────────────────────────────┤
└─────────────┘ 1:N N:1 │
┌─────────────┐ │
│ ap_programs │──────────────────────────────────────────┤
└─────────────┘ 1:N N:1 │
┌─────────────┐ │
│finality_types│─────────────────────────────────────────┤
└─────────────┘ 1:N N:1 │
┌─────────────┐ │
│access_types │──────────────────────────────────────────┤
└─────────────┘ 1:N N:1 │
┌─────────────┐ │
│license_types│──────────────────────────────────────────┤
└─────────────┘ 1:N N:1 │
┌─────────────┐ │
│thesis_files │──────────────────────────────────────────┘
└─────────────┘ N:1
```
---
## Core Tables
### `theses`
**Purpose:** Main table storing thesis/dissertation information.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `identifier` | TEXT | YES | NULL | Unique identifier (e.g., "2025-002") |
| `title` | TEXT | NO | - | Thesis title |
| `subtitle` | TEXT | YES | NULL | Optional subtitle |
| `year` | INTEGER | NO | - | Academic year of submission |
| `is_doctoral` | BOOLEAN | NO | 0 | 0 = TFE (Master), 1 = Doctoral thesis |
| `orientation_id` | INTEGER | YES | NULL | FK to `orientations` |
| `ap_program_id` | INTEGER | YES | NULL | FK to `ap_programs` (Ateliers Pratiques) |
| `finality_id` | INTEGER | YES | NULL | FK to `finality_types` |
| `synopsis` | TEXT | YES | NULL | ~200 word summary |
| `context_note` | TEXT | YES | NULL | Note by jury president (max 150 words) |
| `remarks` | TEXT | YES | NULL | Internal administrative remarks |
| `duration_minutes` | INTEGER | YES | NULL | For audio/video works |
| `duration_pages` | INTEGER | YES | NULL | For written works |
| `file_size_info` | TEXT | YES | NULL | Human-readable size (e.g., "128 pages + 45 minutes") |
| `access_type_id` | INTEGER | YES | NULL | FK to `access_types` |
| `license_id` | INTEGER | YES | NULL | FK to `license_types` |
| `jury_points` | DECIMAL(4,2) | YES | NULL | Grade out of 20 |
| `jury_note_added` | BOOLEAN | NO | 0 | Whether jury added a context note |
| `submitted_at` | DATETIME | YES | NULL | Student submission timestamp |
| `defense_date` | DATETIME | YES | NULL | Date of thesis defense |
| `published_at` | DATETIME | YES | NULL | Public publication timestamp |
| `is_published` | BOOLEAN | NO | 0 | Publication status |
| `baiu_link` | TEXT | YES | NULL | Link to institutional repository (BAIU) |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
**Indexes:**
- `idx_theses_year` ON `year`
- `idx_theses_published` ON `is_published`
- `idx_theses_identifier` ON `identifier`
- `idx_theses_orientation` ON `orientation_id`
- `idx_theses_ap_program` ON `ap_program_id`
- `idx_theses_access_type` ON `access_type_id`
**Constraints:**
- `identifier` must be UNIQUE
- `year` must be > 1950 (implicit validation)
- `jury_points` must be between 0 and 20 (implicit validation)
---
### `authors`
**Purpose:** Store student/author information.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Author full name |
| `email` | TEXT | YES | NULL | Contact email |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
**Indexes:**
- `idx_authors_email` ON `email`
**Notes:**
- Same author can have multiple theses
- Email is optional (privacy)
- No uniqueness constraint on name (same names possible)
---
### `supervisors`
**Purpose:** Store thesis supervisor/promoter information.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Supervisor full name |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
**Notes:**
- Reusable across multiple theses
- No email/contact info stored (administrative data)
---
## Lookup Tables
### `orientations`
**Purpose:** Predefined list of artistic orientations.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Orientation name |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
1. Arts Numériques
2. Dessin
3. Cinéma d'animation
4. Installation-Performance
5. Peinture
6. Photographie
7. Sculpture
8. Vidéographie
9. Graphisme
10. Typographie
11. Design Numérique
12. Illustration
13. Bande-Dessinée
14. Sérigraphie
15. Gravure
**Constraints:**
- `name` must be UNIQUE
---
### `ap_programs`
**Purpose:** Practical workshops programs (Ateliers Pratiques).
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Program full name |
| `code` | TEXT | YES | NULL | Short code/acronym |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
1. Narration Spéculative (no code)
2. Design et Politique du Multiple (DPM)
3. Atelier Pratiques Situées (APS)
4. Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes (LIENS)
**Constraints:**
- `name` must be UNIQUE
---
### `finality_types`
**Purpose:** Master degree finality types.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Finality type name |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
1. Approfondi (Research-focused)
2. Enseignement (Teaching)
3. Spécialisé (Specialized)
**Constraints:**
- `name` must be UNIQUE
---
### `languages`
**Purpose:** Languages used in thesis.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Language name |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
1. Français
2. Anglais
**Notes:**
- Expandable if needed (Dutch, etc.)
- Thesis can be multilingual (junction table)
**Constraints:**
- `name` must be UNIQUE
---
### `format_types`
**Purpose:** Physical/digital format types.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Format type name |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
1. Site web
2. Audio
3. Vidéo
4. Performance
5. Objet éditorial (printed matter)
6. Installation
7. Autre (other)
**Notes:**
- Multiple formats per thesis allowed
- "Autre" for edge cases
**Constraints:**
- `name` must be UNIQUE
---
### `access_types`
**Purpose:** Define thesis accessibility levels.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | Access type name |
| `description` | TEXT | YES | NULL | Detailed description |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Predefined Values:**
| ID | Name | Description |
|----|------|-------------|
| 1 | Libre | Full access online and in library |
| 2 | Interne | Physical access only; descriptive note online |
| 3 | Interdit | No access; descriptive note online only |
**Constraints:**
- `name` must be UNIQUE
---
### `license_types`
**Purpose:** Creative Commons and other license types.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `name` | TEXT | NO | - | License name (e.g., "CC BY-SA 4.0") |
| `description` | TEXT | YES | NULL | License description |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Expected Values:**
- CC BY 4.0
- CC BY-SA 4.0
- CC BY-NC 4.0
- CC BY-NC-SA 4.0
- CC0 1.0
- All Rights Reserved
- Custom (text description)
**Constraints:**
- `name` must be UNIQUE
---
### `keywords`
**Purpose:** Expandable keyword/tag list.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `keyword` | TEXT | NO | - | Keyword/tag text |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
**Notes:**
- Keywords are case-insensitive (normalized to lowercase)
- Maximum 10 keywords per thesis (enforced in application)
- Auto-created when first used
- Can be reused across theses
**Constraints:**
- `keyword` must be UNIQUE
---
## Junction Tables
### `thesis_authors`
**Purpose:** Many-to-many relationship between theses and authors.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `author_id` | INTEGER | NO | - | FK to `authors.id` |
| `author_order` | INTEGER | NO | 1 | Display order (1, 2, 3...) |
**Primary Key:** (`thesis_id`, `author_id`)
**Cascade Rules:**
- ON DELETE CASCADE (both FKs)
**Notes:**
- Single author: `author_order = 1`
- Multiple authors: ordered by `author_order`
---
### `thesis_supervisors`
**Purpose:** Many-to-many relationship between theses and supervisors.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `supervisor_id` | INTEGER | NO | - | FK to `supervisors.id` |
| `supervisor_order` | INTEGER | NO | 1 | Display order |
**Primary Key:** (`thesis_id`, `supervisor_id`)
**Cascade Rules:**
- ON DELETE CASCADE (both FKs)
---
### `thesis_languages`
**Purpose:** Many-to-many relationship between theses and languages.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `language_id` | INTEGER | NO | - | FK to `languages.id` |
**Primary Key:** (`thesis_id`, `language_id`)
**Cascade Rules:**
- ON DELETE CASCADE (both FKs)
---
### `thesis_formats`
**Purpose:** Many-to-many relationship between theses and format types.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `format_id` | INTEGER | NO | - | FK to `format_types.id` |
**Primary Key:** (`thesis_id`, `format_id`)
**Cascade Rules:**
- ON DELETE CASCADE (both FKs)
---
### `thesis_keywords`
**Purpose:** Many-to-many relationship between theses and keywords.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `keyword_id` | INTEGER | NO | - | FK to `keywords.id` |
**Primary Key:** (`thesis_id`, `keyword_id`)
**Indexes:**
- `idx_thesis_keywords_thesis` ON `thesis_id`
- `idx_thesis_keywords_keyword` ON `keyword_id`
**Cascade Rules:**
- ON DELETE CASCADE (both FKs)
**Business Rules:**
- Maximum 10 keywords per thesis (enforced in application layer)
---
## Support Tables
### `thesis_files`
**Purpose:** Store file attachments for theses.
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `thesis_id` | INTEGER | NO | - | FK to `theses.id` |
| `file_type` | TEXT | NO | - | Type: 'main', 'annex', 'written_part', 'other' |
| `file_path` | TEXT | NO | - | Relative path to file |
| `file_name` | TEXT | NO | - | Original filename |
| `file_size` | INTEGER | YES | NULL | Size in bytes |
| `mime_type` | TEXT | YES | NULL | MIME type (e.g., 'application/pdf') |
| `description` | TEXT | YES | NULL | File description |
| `uploaded_at` | DATETIME | NO | CURRENT_TIMESTAMP | Upload timestamp |
**Cascade Rules:**
- ON DELETE CASCADE on `thesis_id`
**File Types:**
- **main**: Primary thesis document (PDF, HTML, etc.)
- **annex**: Supplementary materials
- **written_part**: Written component of practice-based thesis
- **other**: Additional files
**Notes:**
- Files stored in `/var/www/html/formulaire/data/theses/`
- Cover images stored in `/var/www/html/formulaire/data/covers/`
---
### `pages`
**Purpose:** Static content management (About, Licenses, Contact, etc.).
| Column | Type | Null | Default | Description |
|--------|------|------|---------|-------------|
| `id` | INTEGER | NO | AUTOINCREMENT | Primary key |
| `slug` | TEXT | NO | - | URL-friendly identifier |
| `title` | TEXT | NO | - | Page title |
| `content` | TEXT | YES | NULL | Page content (Markdown/HTML) |
| `is_published` | BOOLEAN | NO | 1 | Publish status |
| `created_at` | DATETIME | NO | CURRENT_TIMESTAMP | Record creation time |
| `updated_at` | DATETIME | NO | CURRENT_TIMESTAMP | Last update time |
**Predefined Pages:**
- `charte` - Site charter/policy
- `about` - About page
- `licenses` - License information
- `contact` - Contact page
**Constraints:**
- `slug` must be UNIQUE
---
## Views
### `v_theses_full`
**Purpose:** Complete thesis information with all relationships joined.
**Columns:**
- All columns from `theses`
- `orientation` (TEXT) - Orientation name
- `ap_program` (TEXT) - AP program name
- `finality_type` (TEXT) - Finality type name
- `access_type` (TEXT) - Access type name
- `license_type` (TEXT) - License name
- `authors` (TEXT) - Comma-separated author names
- `supervisors` (TEXT) - Comma-separated supervisor names
- `languages` (TEXT) - Comma-separated language names
- `formats` (TEXT) - Comma-separated format names
- `keywords` (TEXT) - Comma-separated keywords
**Usage:**
```sql
SELECT * FROM v_theses_full WHERE id = 123;
```
**Notes:**
- Uses `GROUP_CONCAT` for many-to-many relationships
- Results are comma-delimited strings
- May need post-processing for proper arrays
---
### `v_theses_public`
**Purpose:** Published theses only (for public website).
**Definition:**
```sql
SELECT * FROM v_theses_full WHERE is_published = 1;
```
**Usage:**
```sql
SELECT * FROM v_theses_public ORDER BY year DESC, title;
```
---
## Indexes
### Performance Indexes
| Index Name | Table | Columns | Purpose |
|------------|-------|---------|---------|
| `idx_theses_year` | `theses` | `year` | Filter by year |
| `idx_theses_published` | `theses` | `is_published` | Public/private filtering |
| `idx_theses_identifier` | `theses` | `identifier` | Unique lookup |
| `idx_theses_orientation` | `theses` | `orientation_id` | Filter by orientation |
| `idx_theses_ap_program` | `theses` | `ap_program_id` | Filter by AP program |
| `idx_theses_access_type` | `theses` | `access_type_id` | Access control |
| `idx_authors_email` | `authors` | `email` | Author lookup |
| `idx_thesis_authors_thesis` | `thesis_authors` | `thesis_id` | Join optimization |
| `idx_thesis_authors_author` | `thesis_authors` | `author_id` | Join optimization |
| `idx_thesis_keywords_thesis` | `thesis_keywords` | `thesis_id` | Join optimization |
| `idx_thesis_keywords_keyword` | `thesis_keywords` | `keyword_id` | Keyword search |
---
## Triggers
### Timestamp Update Triggers
**`update_theses_timestamp`**
```sql
AFTER UPDATE ON theses
UPDATE theses SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
```
**`update_authors_timestamp`**
```sql
AFTER UPDATE ON authors
UPDATE authors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
```
**`update_supervisors_timestamp`**
```sql
AFTER UPDATE ON supervisors
UPDATE supervisors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
```
**`update_pages_timestamp`**
```sql
AFTER UPDATE ON pages
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
```
---
## Data Types Reference
### SQLite Data Types Used
| Type | SQLite Affinity | Description | Example Values |
|------|----------------|-------------|----------------|
| `INTEGER` | INTEGER | Signed integer | 1, 42, 2025 |
| `TEXT` | TEXT | Variable-length text | "Title", "Name" |
| `BOOLEAN` | INTEGER | 0 or 1 | 0 (false), 1 (true) |
| `DATETIME` | TEXT | ISO8601 timestamp | "2025-02-05 12:00:00" |
| `DECIMAL(4,2)` | REAL | Decimal number | 15.50, 18.75 |
### Boolean Convention
- `FALSE` = 0
- `TRUE` = 1
- NULL = undefined/not set
---
## Business Rules
### Thesis Submission Workflow
1. **Draft Creation** (`is_published = 0`)
- Student creates initial entry
- Required fields: title, year, at least one author
2. **Complete Metadata**
- Add orientation, AP program, finality
- Upload files
- Add keywords (max 10)
- Set languages, formats
3. **Submission** (`submitted_at` set)
- Student marks as ready for review
- Email notification to administrators
4. **Defense** (`defense_date` set)
- After thesis defense
- Jury adds grade (`jury_points`)
- Optional context note by jury president
5. **Publication** (`is_published = 1`, `published_at` set)
- Administrator approves
- Appears on public website
- Respects `access_type` rules
### Data Validation Rules
**Required Fields (for publication):**
- `title`
- `year`
- At least one author (via `thesis_authors`)
- `orientation_id`
- `access_type_id`
**Optional but Recommended:**
- `synopsis` (~200 words)
- `keywords` (3-10 recommended)
- At least one file attachment
- `license_id`
**Constraints:**
- `year`: Must be ≥ 1950
- `jury_points`: 0.00 to 20.00
- `keywords`: Maximum 10 per thesis
- `author_order`: Must be sequential (1, 2, 3...)
- `identifier`: Unique across all theses
### Access Control Rules
| Access Type | Public View | Library Access | File Download |
|-------------|-------------|----------------|---------------|
| **Libre** | Full metadata + abstract | Yes | Yes |
| **Interne** | Metadata + descriptive note | Physical only | No |
| **Interdit** | Metadata + descriptive note | No | No |
---
## Sample Queries
### Common Queries
**Get all published theses from 2025:**
```sql
SELECT * FROM v_theses_public
WHERE year = 2025
ORDER BY title;
```
**Find theses by author name:**
```sql
SELECT t.* 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 '%Dupont%'
AND t.is_published = 1;
```
**Get thesis with all relationships:**
```sql
SELECT * FROM v_theses_full WHERE id = 42;
```
**List theses by orientation:**
```sql
SELECT t.title, t.year, o.name as orientation
FROM theses t
JOIN orientations o ON t.orientation_id = o.id
WHERE o.name = 'Arts Numériques'
AND t.is_published = 1
ORDER BY t.year DESC;
```
**Full-text search in titles and synopses:**
```sql
SELECT * FROM v_theses_public
WHERE title LIKE '%design%'
OR synopsis LIKE '%design%'
ORDER BY year DESC;
```
**Get theses by keyword:**
```sql
SELECT DISTINCT t.* FROM theses t
JOIN thesis_keywords tk ON t.id = tk.thesis_id
JOIN keywords k ON tk.keyword_id = k.id
WHERE k.keyword = 'écologie'
AND t.is_published = 1;
```
**Count theses per year:**
```sql
SELECT year, COUNT(*) as count
FROM theses
WHERE is_published = 1
GROUP BY year
ORDER BY year DESC;
```
**Get theses with files:**
```sql
SELECT t.title, tf.file_name, tf.file_type
FROM theses t
JOIN thesis_files tf ON t.id = tf.thesis_id
WHERE t.is_published = 1
ORDER BY t.title;
```
**Find theses without keywords:**
```sql
SELECT t.* FROM theses t
LEFT JOIN thesis_keywords tk ON t.id = tk.thesis_id
WHERE tk.thesis_id IS NULL
AND t.is_published = 1;
```
### Administrative Queries
**Recently submitted theses (pending review):**
```sql
SELECT title, submitted_at
FROM theses
WHERE submitted_at IS NOT NULL
AND is_published = 0
ORDER BY submitted_at DESC;
```
**Theses missing required metadata:**
```sql
SELECT id, title, year
FROM theses
WHERE (orientation_id IS NULL
OR access_type_id IS NULL
OR id NOT IN (SELECT thesis_id FROM thesis_authors))
AND is_published = 0;
```
**Most used keywords:**
```sql
SELECT k.keyword, COUNT(*) as usage_count
FROM keywords k
JOIN thesis_keywords tk ON k.id = tk.keyword_id
GROUP BY k.keyword
ORDER BY usage_count DESC
LIMIT 20;
```
**Theses by supervisor:**
```sql
SELECT s.name as supervisor, COUNT(*) as thesis_count
FROM supervisors s
JOIN thesis_supervisors ts ON s.id = ts.supervisor_id
JOIN theses t ON ts.thesis_id = t.id
WHERE t.is_published = 1
GROUP BY s.name
ORDER BY thesis_count DESC;
```
---
## Making Schema Changes
### How to Request Changes
When requesting schema changes, please specify:
1. **What needs to change**
- Table name
- Column name(s)
- Relationship
2. **Type of change**
- Add new table
- Add new column
- Modify existing column
- Remove column/table
- Change relationship
3. **Why it's needed**
- Use case
- Business requirement
- Performance issue
4. **Example data**
- Sample values
- Expected format
### Example Change Request
```
**Change Request:** Add support for thesis awards/distinctions
**Type:** Add new table + relationship
**Reason:** Need to track prizes and awards given to theses
(e.g., "Best TFE 2025", "Jury Prize")
**Proposed Structure:**
- Table: `awards`
- id (INT, PK)
- name (TEXT) - Award name
- description (TEXT) - Award description
- year (INT) - Year established
- Table: `thesis_awards`
- thesis_id (INT, FK)
- award_id (INT, FK)
- awarded_date (DATETIME)
**Example Data:**
- "Prix du Jury 2025"
- "Meilleur TFE Arts Numériques"
- "Prix de l'Innovation"
```
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2026-02-05 | Initial specification document |
---
**For questions or change requests, reference this document and provide:**
- Section name
- Table/column affected
- Desired outcome
- Example use case

View File

@@ -1,571 +0,0 @@
# Deployment & Dev Server Migration Guide
Analysis of current `justfile` and required changes for the new directory structure.
---
## 🔍 Current Setup Analysis
### Current Dev Server (❌ INCORRECT)
```bash
# From justfile line 33
php -S 127.0.0.1:8000
```
**Problems:**
- Serves from project root (all files accessible via web)
- Exposes sensitive files: `storage/`, `tests/`, `vendor/`, config files
- Doesn't match production DocumentRoot configuration
- Security risk: `.env`, database files, source code all accessible
### Current Deployment (❌ INCORRECT)
```bash
# From justfile lines 56-75
rsync -vur --progress \
--exclude 'vendor' --exclude 'tests' --exclude '*.db' ... \
./ posterg:/var/www/html/
```
**Problems:**
- Deploys entire project to DocumentRoot
- Relies on exclusions to hide sensitive files (error-prone)
- Wrong structure: `/var/www/html/` should only contain public files
- Private files (`src/`, `config/`) are still in DocumentRoot
- Nginx serves from `/var/www/html/` exposing everything not excluded
---
## ✅ Required Changes
### 1. New Directory Structure on Server
**Before (current):**
```
/var/www/html/ # DocumentRoot ❌
├── index.php # Public
├── search.php # Public
├── admin/ # Public (protected by nginx)
├── assets/ # Public
├── inc/ # ❌ EXPOSED (config files!)
├── lib/ # ❌ EXPOSED (source code!)
├── database/ # ❌ EXPOSED (database files!)
└── vendor/ # ❌ (excluded but in wrong place)
```
**After (recommended):**
```
/var/www/posterg/ # Application root (private)
├── public/ # DocumentRoot ✅ (only this exposed)
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── src/ # ✅ PRIVATE
├── config/ # ✅ PRIVATE
├── database/ # ✅ PRIVATE
├── vendor/ # ✅ PRIVATE
├── var/ # ✅ PRIVATE (cache, logs)
└── lib/ # ✅ PRIVATE
```
---
## 📝 Updated Justfile
### Change 1: Dev Server
**Current:**
```just
[group('dev')]
serve:
@echo "🚀 Starting Post-ERG development server"
@echo "📍 Public site: http://localhost:8000"
@echo "📍 Admin panel: http://localhost:8000/admin/"
@php -S 127.0.0.1:8000
```
**New:**
```just
[group('dev')]
serve:
@echo "🚀 Starting Post-ERG development server"
@echo "========================================"
@echo ""
@echo "📍 Public site: http://localhost:8000"
@echo "📍 Admin panel: http://localhost:8000/admin/"
@echo ""
@echo "🔒 Serving from public/ directory (matches production)"
@echo ""
@if [ -d "vendor/php-live-reload" ]; then \
echo "✨ Live reload enabled - browser auto-refreshes on file save!"; \
else \
echo "💡 Tip: Run 'just setup' to enable live reload"; \
fi
@echo ""
@echo "Press Ctrl+C to stop"
@echo ""
@php -S 127.0.0.1:8000 -t public/
# Alternative: If you need router script for URL rewriting
[group('dev')]
serve-router:
@echo "🚀 Starting with router script (for clean URLs)"
@php -S 127.0.0.1:8000 -t public/ public/router.php
```
**Key change:** `-t public/` flag tells PHP server to use `public/` as DocumentRoot
---
### Change 2: Deployment Strategy
**Current (❌):**
```just
deploy:
rsync -vur --progress \
--exclude 'vendor' --exclude 'tests' ... \
./ posterg:/var/www/html/
```
**New (✅) - Two-Step Deployment:**
```just
[group('deploy')]
deploy:
@echo "📤 Deploying Post-ERG site"
@echo "=========================="
@echo ""
@echo "Step 1: Deploying application files..."
# Deploy entire application to private directory
rsync -vur --progress \
--exclude '.git*' \
--exclude '.jj' \
--exclude 'tests/' \
--exclude 'docs/' \
--exclude '*.md' \
--exclude 'justfile*' \
--exclude 'setup-dev.sh' \
--exclude 'migrate-structure.sh' \
--exclude 'database/test.db' \
--exclude 'database/backup_*' \
--exclude 'database/fixtures/' \
--exclude 'var/cache/*' \
--exclude 'var/logs/*' \
--exclude '.DS_Store' \
./ posterg:/var/www/posterg/
@echo ""
@echo "Step 2: Setting up directory structure..."
# Create necessary directories on server
ssh posterg "mkdir -p /var/www/posterg/var/{cache,logs,tmp} && \
chown -R www-data:posterg /var/www/posterg/var && \
chmod -R 775 /var/www/posterg/var"
@echo ""
@echo "Step 3: Setting permissions..."
# Set correct ownership and permissions
ssh posterg "cd /var/www/posterg && \
chown -R www-data:posterg . && \
find . -type d -exec chmod 755 {} \; && \
find . -type f -exec chmod 644 {} \; && \
chmod -R 775 var/ && \
chmod -R 775 database/ && \
chmod 660 database/*.db"
@echo ""
@echo "✅ Deployment complete!"
@echo ""
@echo "🔍 Verify deployment:"
@echo " • Public: https://posterg.erg.be/"
@echo " • Admin: https://posterg.erg.be/admin/"
@echo ""
@echo "📁 Server structure:"
@echo " • App root: /var/www/posterg/"
@echo " • DocumentRoot: /var/www/posterg/public/"
# Quick deploy - only public files (faster for frontend changes)
[group('deploy')]
deploy-public:
@echo "⚡ Quick deploy: public files only"
rsync -vur --progress \
--delete \
./public/ posterg:/var/www/posterg/public/
@echo "✅ Public files updated"
# Deploy only code (no assets)
[group('deploy')]
deploy-code:
@echo "⚡ Deploying PHP code only"
rsync -vur --progress \
--include='**.php' \
--include='**/' \
--exclude='*' \
./ posterg:/var/www/posterg/
@echo "✅ Code updated"
```
---
### Change 3: Database Deployment
**Current:**
```just
test-deploy:
ssh posterg "mkdir -p /var/www/html/database"
rsync -vur --progress ./storage/test.db posterg:/var/www/html/storage/test.db
```
**New:**
```just
[group('deploy')]
deploy-database:
@echo "📊 Deploying database..."
@echo "⚠️ This will overwrite the remote database!"
@read -p "Continue? [y/N] " -n 1 -r; \
echo; \
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
ssh posterg "mkdir -p /var/www/posterg/database" && \
rsync -vur --progress ./storage/test.db posterg:/var/www/posterg/storage/ && \
ssh posterg "chown www-data:posterg /var/www/posterg/storage/test.db && \
chmod 660 /var/www/posterg/storage/test.db" && \
echo "✅ Database deployed"; \
else \
echo "❌ Cancelled"; \
fi
# Backup remote database before deploying
[group('deploy')]
backup-remote-db:
@echo "💾 Backing up remote database..."
@ssh posterg "sqlite3 /var/www/posterg/storage/test.db .dump" > database/remote_backup_$(date +%Y%m%d_%H%M%S).sql
@echo "✅ Remote database backed up locally"
```
---
### Change 4: Updated Nginx Deployment
**Current nginx configuration probably points to:**
```nginx
root /var/www/html;
```
**Should change to:**
```nginx
root /var/www/posterg/public;
```
**Updated recipe:**
```just
[group('server')]
deploy-nginx:
@echo "🔧 Deploying nginx configuration..."
@echo ""
@echo "⚠️ IMPORTANT: Nginx config must point to /var/www/posterg/public"
@echo ""
# Check if nginx config has correct DocumentRoot
@if ! grep -q "/var/www/posterg/public" nginx/posterg.conf 2>/dev/null; then \
echo "❌ ERROR: nginx/posterg.conf doesn't contain '/var/www/posterg/public'"; \
echo " Update DocumentRoot before deploying!"; \
exit 1; \
fi
rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf
rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh
@echo "✅ Files uploaded to /tmp/ on server"
@echo ""
@echo "Next steps on the server:"
@echo " ssh posterg"
@echo " sudo bash /tmp/deploy-production.sh"
```
---
## 🔧 Nginx Configuration Changes
### Update `nginx/posterg.conf`
**Find and replace:**
```nginx
# OLD
root /var/www/html;
# NEW
root /var/www/posterg/public;
```
**Complete example:**
```nginx
server {
listen 80;
listen [::]:80;
server_name posterg.erg.be;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name posterg.erg.be;
# NEW DocumentRoot - only public directory
root /var/www/posterg/public;
index index.php index.html;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem;
# Logs
access_log /var/log/nginx/posterg_access.log;
error_log /var/log/nginx/posterg_error.log;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Deny access to sensitive files (should already be outside public/)
location ~ /\. {
deny all;
}
location ~ /storage/ {
deny all;
}
# Admin area - basic auth
location /admin/ {
auth_basic "Admin Access";
auth_basic_user_file /etc/nginx/.htpasswd;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
# IMPORTANT: Set correct SCRIPT_FILENAME
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# PHP files
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Static files
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Deny access to uploaded files execution
location ~ ^/uploads/.*\.php$ {
deny all;
}
}
```
---
## 🔄 Migration Steps
### Step 1: Local Development
```bash
# 1. Create new structure
mkdir -p public/{admin,assets}
mkdir -p src config var/{cache,logs,tmp}
# 2. Move public files
mv index.php search.php memoire.php public/
mv assets/* public/assets/
mv admin/* public/admin/
# 3. Move private files
mv inc/* config/ # or src/ depending on content
mv lib src/lib
# 4. Update paths in PHP files (see PATHS_UPDATE.md below)
# 5. Test local dev server
just serve
# Opens http://localhost:8000
# Verify that sensitive files return 404:
# http://localhost:8000/storage/test.db → 404
# http://localhost:8000/config/ → 404
# http://localhost:8000/src/ → 404
```
### Step 2: Update Nginx Config
```bash
# Edit nginx/posterg.conf
# Change: root /var/www/html;
# To: root /var/www/posterg/public;
# Validate locally
nginx -t -c nginx/posterg.conf
```
### Step 3: Deploy
```bash
# 1. Backup current production
ssh posterg "tar -czf /tmp/posterg-backup-$(date +%Y%m%d).tar.gz /var/www/html"
# 2. Create new directory structure
ssh posterg "mkdir -p /var/www/posterg"
# 3. Deploy application
just deploy
# 4. Deploy nginx config
just deploy-nginx
# 5. On server: activate new config
ssh posterg
sudo bash /tmp/deploy-production.sh
# 6. Reload nginx
sudo systemctl reload nginx
# 7. Test
just server-status
```
---
## 🧪 Testing Checklist
### Local Testing
- [ ] `just serve` starts on port 8000
- [ ] Public site loads: http://localhost:8000
- [ ] Admin loads: http://localhost:8000/admin/
- [ ] Assets load (CSS, images, JS)
- [ ] Database files NOT accessible via browser
- [ ] Config files NOT accessible via browser
- [ ] Tests still pass: `just test`
### Production Testing
- [ ] HTTPS works: https://posterg.erg.be/
- [ ] Admin login works
- [ ] Search functionality works
- [ ] Database connections work
- [ ] File uploads work (if applicable)
- [ ] Logs written to `/var/www/posterg/var/logs/`
- [ ] Sensitive URLs return 404:
- https://posterg.erg.be/storage/test.db
- https://posterg.erg.be/config/
- https://posterg.erg.be/src/
- https://posterg.erg.be/vendor/
---
## 📊 Path Changes Summary
| File Type | Current Path | New Path |
|-----------|-------------|----------|
| Public index | `/index.php` | `/public/index.php` |
| Admin panel | `/admin/` | `/public/admin/` |
| Assets | `/assets/` | `/public/assets/` |
| Config | `/inc/` | `/config/` or `/src/` |
| Libraries | `/lib/` | `/src/lib/` |
| Database | `/storage/` | `/storage/` (stays) |
| Vendor | `/vendor/` | `/vendor/` (stays) |
| Tests | `/tests/` | `/tests/` (stays) |
---
## 🔒 Security Improvements
### Before
- ❌ All files in DocumentRoot
- ❌ Relies on nginx deny rules
- ❌ One misconfiguration = full exposure
- ❌ Database accessible if nginx fails
### After
- ✅ Only `public/` in DocumentRoot
- ✅ Physical separation of public/private
- ✅ Nginx misconfiguration = site down (not exposed)
- ✅ Database physically unreachable via web
---
## 💡 Tips
### Router Script (for clean URLs)
Create `public/router.php` for development:
```php
<?php
// Development router script
if (php_sapi_name() === 'cli-server') {
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Serve static files directly
if (file_exists(__DIR__ . $path) && is_file(__DIR__ . $path)) {
return false;
}
// Route everything else to index.php
require __DIR__ . '/index.php';
}
```
### Environment Detection
In your code, detect environment:
```php
<?php
// config/bootstrap.php
define('IS_DEV', php_sapi_name() === 'cli-server');
define('APP_ROOT', dirname(__DIR__)); // /var/www/posterg
define('PUBLIC_ROOT', APP_ROOT . '/public');
```
### Use Relative Requires
```php
// Before (brittle)
require_once '/var/www/html/inc/config.php';
// After (portable)
require_once __DIR__ . '/../config/app.php';
// or
require_once APP_ROOT . '/config/app.php';
```
---
## 🆘 Troubleshooting
### Issue: 404 on all pages after deploy
**Cause:** Nginx DocumentRoot not updated
**Fix:** Check nginx config has `root /var/www/posterg/public;`
### Issue: PHP includes fail
**Cause:** Hardcoded paths or wrong APP_ROOT
**Fix:** Use `__DIR__` or define APP_ROOT constant
### Issue: Database connection fails
**Cause:** Path to database file wrong
**Fix:** Update path from `storage/test.db` to `../storage/test.db` (from public/)
### Issue: Can't write to cache/logs
**Cause:** Wrong permissions on var/ directory
**Fix:** `sudo chown -R www-data:posterg /var/www/posterg/var && chmod -R 775 /var/www/posterg/var`
---
## Next Steps
1. Create `PATHS_UPDATE.md` - document all PHP file path changes needed
2. Create `public/router.php` - for cleaner dev URLs
3. Update all `require`/`include` statements to use relative paths
4. Test migration on staging/local first
5. Schedule production deployment during low-traffic period

View File

@@ -1,215 +0,0 @@
# Deployment Steps
## First-Time Deployment (New Structure)
Since we're moving from `/var/www/html/` to `/var/www/posterg/`, follow these steps:
### 1. Setup Server Directory (ONE TIME)
```bash
just setup-server
```
This creates `/var/www/posterg/` with correct permissions:
- Owner: `www-data:posterg`
- Permissions: `775`
### 2. Deploy Application
```bash
just deploy
```
This deploys all files to `/var/www/posterg/`:
- `public/``/var/www/posterg/public/`
- `includes/``/var/www/posterg/includes/`
- `config/``/var/www/posterg/config/`
- `storage/``/var/www/posterg/storage/`
- `src/``/var/www/posterg/lib/`
### 3. Update Nginx Configuration
```bash
just deploy-nginx
```
This checks that nginx config has correct DocumentRoot and uploads it to server.
**IMPORTANT:** The nginx config must have:
```nginx
root /var/www/posterg/public;
```
If the check fails, edit `nginx/posterg.conf` first.
### 4. Apply Nginx Configuration on Server
```bash
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
### 5. Verify Deployment
```bash
just server-status
```
Check:
- https://posterg.erg.be/ (should work)
- https://posterg.erg.be/admin/ (should work)
- https://posterg.erg.be/storage/test.db (should 404 ✅)
---
## Subsequent Deployments
After the first deployment, you only need:
```bash
just deploy
```
That's it! The directory structure is already in place.
---
## Deploy Database
If you need to deploy the database:
```bash
just deploy-database
```
This will:
1. Upload `storage/test.db` to server
2. Set correct permissions
3. Warn before overwriting
---
## Troubleshooting
### Permission Denied on Deploy
**Error:**
```
mkdir "/var/www/posterg" failed: Permission denied (13)
```
**Solution:**
```bash
just setup-server
```
### Nginx 500 Error
**Cause:** Nginx DocumentRoot still pointing to old location
**Solution:**
1. Check nginx config has: `root /var/www/posterg/public;`
2. Redeploy nginx config:
```bash
just deploy-nginx
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
### Database Connection Errors
**Cause:** Database file permissions incorrect
**Solution:**
```bash
ssh posterg
cd /var/www/posterg
sudo chown www-data:posterg database/test.db
sudo chmod 660 database/test.db
```
### Admin 404
**Cause:** Nginx still using old `/formulaire/` location
**Solution:** Update `nginx/posterg.conf` to use `/admin/` location
---
## Rollback
If something goes wrong, rollback is easy:
### 1. Restore Old Directory
```bash
ssh posterg
sudo cp -r /var/www/html.backup /var/www/html # If you backed up
```
### 2. Restore Old Nginx Config
```bash
ssh posterg
sudo cp /etc/nginx/sites-available/posterg.backup /etc/nginx/sites-available/posterg
sudo systemctl reload nginx
```
### 3. Rollback Code with jj
```bash
jj log
jj edit <previous-change-id>
```
---
## Migration Checklist
- [ ] `just setup-server` - Create server directory
- [ ] `just deploy` - Deploy application
- [ ] `just deploy-nginx` - Update nginx config
- [ ] SSH to server and apply nginx config
- [ ] `sudo systemctl reload nginx`
- [ ] Verify site works: https://posterg.erg.be/
- [ ] Verify security: https://posterg.erg.be/storage/test.db → 404
- [ ] Test admin: https://posterg.erg.be/admin/
- [ ] Deploy database (if needed): `just deploy-database`
---
## Commands Reference
| Command | Purpose |
|---------|---------|
| `just setup-server` | Create `/var/www/posterg/` (first time only) |
| `just deploy` | Deploy application files |
| `just deploy-nginx` | Update nginx configuration |
| `just deploy-database` | Deploy database file |
| `just server-status` | Check server health |
| `just server-logs` | View server logs |
---
## Directory Structure on Server
```
/var/www/posterg/ # Application root (private)
├── public/ # DocumentRoot (nginx points here)
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── includes/ # Templates (private)
├── config/ # Configuration (private)
├── database/ # Database (private)
├── lib/ # PHP classes (private)
└── vendor/ # Dependencies (private)
```
**Nginx DocumentRoot:** `/var/www/posterg/public/`
Only the `public/` directory is accessible via web browser. Everything else is private.

View File

@@ -1,507 +0,0 @@
# Post-ERG Development Guide
Complete guide for developing the Post-ERG thesis management system.
## 🚀 Quick Start
### 1. Setup (One Time)
```bash
# Clone php-live-reload and setup directories
just setup
```
### 2. Start Development Server
```bash
just serve
```
This starts **one unified server** at:
- **Public site:** http://localhost:8000
- **Admin panel:** http://localhost:8000/admin/
**Live reload enabled** - your browser auto-refreshes when you save files!
### 3. Edit & Watch
Edit any PHP file and watch your browser automatically refresh! 🎉
---
## 📁 Project Structure
```
posterg-website/
├── index.php # Public homepage
├── memoire.php # Thesis detail page
├── search.php # Search page
├── *.php # Other public pages
├── admin/ # Admin panel
│ ├── index.php # Admin dashboard
│ ├── list.php # Thesis list
│ ├── edit.php # Edit thesis
│ └── formulaire.php # Add thesis
├── lib/ # Shared libraries
│ ├── Database.php # Database class
│ ├── RateLimit.php # Rate limiting
│ └── config.php # Configuration
├── inc/ # Page templates
│ ├── header.php # Site header
│ └── footer.php # Site footer
├── assets/ # Static files
│ ├── posterg.css # Main CSS
│ └── fonts/ # Custom fonts
├── database/ # Database
│ ├── schema.sql # Schema definition
│ ├── test.db # Test database
│ └── fixtures/ # Sample data
├── tests/ # Test suite
│ ├── Unit/ # Unit tests
│ ├── Integration/ # Integration tests
│ └── Security/ # Security tests
└── vendor/ # Third-party (gitignored)
└── php-live-reload/
```
---
## 🛠️ Development Workflow
### Starting Development
```bash
# Start the development server
just serve
# In your browser:
# - Public site: http://localhost:8000
# - Admin panel: http://localhost:8000/admin/
```
### Making Changes
1. **Edit PHP files** - Auto-refreshes browser
2. **Edit CSS** - Auto-refreshes browser
3. **Test changes** - See them instantly!
### Running Tests
```bash
# Run all tests
just test
# Run specific test suites
just test-unit # Unit tests
just test-integration # Integration tests
just test-security # Security tests
```
### Database Operations
```bash
# View database stats
just stats
# Open SQLite shell
just query
# Show specific thesis
just show 42
# Reset database
just reset-db
# Create with sample data
just fixtures
```
---
## 📝 Common Tasks
### Create a New Page
1. Create `newpage.php` in root
2. Add `require_once __DIR__ . '/src/Database.php';`
3. Include header: `include 'inc/header.php';`
4. Add your content
5. Include footer: `include 'inc/footer.php';`
Example:
```php
<?php
require_once __DIR__ . '/src/Database.php';
include 'inc/header.php';
?>
<section class="section">
<div class="container">
<h1 class="title">My New Page</h1>
<p>Content here...</p>
</div>
</section>
<?php include 'inc/footer.php'; ?>
```
### Add a Database Function
1. Edit `src/Database.php`
2. Add your method to the `Database` class
3. Write a test in `tests/Unit/DatabaseTest.php`
4. Run tests: `just test-unit`
### Update CSS
1. Edit `assets/posterg.css`
2. Browser auto-refreshes!
3. (Optional) Increment version in `inc/header.php`: `posterg.css?v=3`
### Add a Test
1. Choose location:
- `tests/Unit/` - For testing classes/functions
- `tests/Integration/` - For testing workflows
- `tests/Security/` - For testing security
2. Create test file (e.g., `tests/Unit/MyTest.php`)
3. Follow the template in `tests/README.md`
4. Add to `tests/run-tests.php`
5. Run: `just test`
---
## 🧪 Testing
### Test Database
Development uses `storage/test.db` (gitignored).
**Create test database:**
```bash
just init-db
```
**Populate with sample data:**
```bash
just fixtures
```
**Deploy test database to server:**
```bash
just deploy-test-db
```
**Reset everything:**
```bash
just reset-db
```
### Writing Tests
See `tests/README.md` for complete testing guide.
**Quick example:**
```php
<?php
require_once __DIR__ . '/../../src/Database.php';
echo "My Test\n";
echo "=======\n\n";
try {
$db = Database::getInstance();
echo "✓ PASS: Test passed\n";
return true;
} catch (Exception $e) {
echo "❌ FAIL: " . $e->getMessage() . "\n";
return false;
}
```
---
## 🚀 Deployment
### Deploy Everything
```bash
just deploy
```
This deploys:
- Public site (root PHP files)
- Admin panel (`admin/`)
- Shared libraries (`src/`)
**Note:** `vendor/` is automatically excluded from deployment.
### Deploy Selectively
```bash
# Deploy only the code
just deploy
# Deploy test database
just deploy-test-db
# Deploy nginx config
just deploy-nginx
# Deploy admin tools
just deploy-admin-tools
```
---
## 🔧 Justfile Commands
### Development
| Command | Description |
|---------|-------------|
| `just setup` | Setup development environment (one-time) |
| `just serve` | Start development server with live reload |
| `just stop` | Stop development server |
| `just logs` | View development logs |
### Testing
| Command | Description |
|---------|-------------|
| `just test` | Run all tests |
| `just test-unit` | Run unit tests |
| `just test-integration` | Run integration tests |
| `just test-security` | Run security tests |
| `just syntax` | Check PHP syntax |
### Database
| Command | Description |
|---------|-------------|
| `just init-db` | Create test database |
| `just reset-db` | Reset test database |
| `just query` | Open SQLite shell |
| `just show <id>` | Show thesis by ID |
| `just backup` | Backup database |
| `just fixtures` | Create sample data |
| `just deploy-test-db` | Deploy test database to server |
### Statistics
| Command | Description |
|---------|-------------|
| `just stats` | Show database statistics |
| `just recent` | Show recent theses |
### Deployment
| Command | Description |
|---------|-------------|
| `just deploy` | Deploy complete site |
| `just deploy-nginx` | Deploy nginx configuration |
| `just deploy-admin-tools` | Deploy admin user management |
### Server
| Command | Description |
|---------|-------------|
| `just server-logs` | View server logs |
| `just server-status` | Check server status |
### Utilities
| Command | Description |
|---------|-------------|
| `just clean` | Clean up dev files |
| `just setup-dirs` | Create data directories |
---
## 💡 Tips & Tricks
### Live Reload
The `just serve` command uses php-live-reload to automatically refresh your browser when you save files.
**What triggers refresh:**
- Saving any `.php` file
- Saving any file in the project
**How it works:**
- WebSocket connection monitors file changes
- Browser receives reload signal
- Page refreshes automatically
**No browser extension needed!**
### Multiple Browser Windows
Open multiple browser windows/tabs - they all get live reload!
```
http://localhost:8000/ # Public site
http://localhost:8000/admin/ # Admin panel
http://localhost:8000/memoire.php?id=13 # Specific thesis
```
All will auto-refresh when you save files! ✨
### Using a Real Test Database
The test database (`storage/test.db`) is gitignored. To share test data:
```bash
# Create fixtures
just fixtures
# Commit the fixtures generator
git add database/fixtures/
git commit -m "Update test fixtures"
```
Others can recreate with: `just fixtures`
### Debugging
**Check error logs:**
```bash
just logs
```
**Or directly:**
```bash
tail -f error.log
```
**PHP errors in browser:**
Edit your PHP file temporarily:
```php
ini_set('display_errors', 1);
error_reporting(E_ALL);
```
**Database issues:**
```bash
# Check database exists
ls -lh database/test.db
# Open database shell
just query
# Check tables
sqlite> .tables
# Show schema
sqlite> .schema theses
```
---
## 🔍 Troubleshooting
### Server won't start
**Port already in use:**
```bash
just stop
# Or manually:
pkill -f "php -S 127.0.0.1:8000"
```
**php-live-reload missing:**
```bash
just setup
```
### Live reload not working
**Check vendor directory:**
```bash
ls -la vendor/php-live-reload/
```
**Reinstall:**
```bash
rm -rf vendor/php-live-reload
just setup
```
### Database errors
**Database not found:**
```bash
just init-db
```
**Permissions error:**
```bash
chmod 644 database/test.db
```
**Schema errors:**
```bash
just reset-db
```
### Tests failing
**Run individual test:**
```bash
php tests/Unit/DatabaseTest.php
```
**Check database:**
```bash
just stats
```
**Reset database:**
```bash
just reset-db
just fixtures
just test
```
---
## 📚 Further Reading
- [Test Documentation](../tests/README.md)
- [Database Specification](../storage/DATABASE_SPECIFICATION.md)
- [Migration Guide](../MIGRATION_GUIDE.md)
- [Deployment Guide](../nginx/DEPLOYMENT_COMPLETE.md)
---
## ✨ Quick Reference
**Start developing:**
```bash
just setup # One time
just serve # Start server
```
**Test your changes:**
```bash
just test # Run tests
just stats # Check database
```
**Deploy to production:**
```bash
just deploy
```
That's it! Happy coding! 🚀

View File

@@ -1,246 +0,0 @@
# Recommended Directory Structure
Based on the **Standard PHP Package Skeleton** (researched by Paul M. Jones from thousands of GitHub projects).
## Directory Layout
```
posterg-website/
├── public/ # DocumentRoot - publicly accessible files
│ ├── index.php # Front controller
│ ├── assets/ # Public assets (CSS, JS, images)
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── .htaccess # Apache/nginx rules
├── src/ # Application source code (private)
│ ├── Controller/ # Controllers
│ ├── Model/ # Models
│ ├── View/ # Views/templates
│ ├── Service/ # Business logic services
│ ├── Repository/ # Data access layer
│ └── Middleware/ # Middleware components
├── config/ # Configuration files (private)
│ ├── app.php
│ ├── database.php
│ ├── routes.php
│ └── .env.example # Environment variables template
├── database/ # Database-related files
│ ├── migrations/ # Database migrations
│ ├── seeds/ # Database seeders
│ └── schema.sql # Database schema
├── tests/ # Unit and integration tests
│ ├── Unit/
│ ├── Integration/
│ └── bootstrap.php
├── vendor/ # Third-party dependencies (Composer)
│ └── autoload.php
├── bin/ # Executable scripts
│ └── console # CLI commands
├── var/ # Variable/temporary files (private)
│ ├── cache/ # Application cache
│ ├── logs/ # Log files
│ └── tmp/ # Temporary files
├── docs/ # Documentation
│ └── *.md
├── scripts/ # Build/deployment scripts
│ └── deploy.sh
├── resources/ # Non-PHP resources (private)
│ ├── views/ # Template files
│ ├── lang/ # Translations
│ └── emails/ # Email templates
├── lib/ # Internal libraries (if not using src/)
├── .git/ # Git repository
├── .gitignore
├── composer.json # Composer dependencies
├── composer.lock
├── phpunit.xml # PHPUnit configuration
├── README.md # Project documentation
└── LICENSE # License file
```
## Directory Purposes
### **public/** (PUBLIC - DocumentRoot points here)
- **Only directory accessible via web browser**
- Contains: front controller (index.php), assets (CSS/JS/images)
- Web server DocumentRoot should point to this directory
- Security: No sensitive files here
### **src/** (PRIVATE)
- Application source code
- All classes following PSR-4 autoloading
- Organized by responsibility (Controller, Model, Service, etc.)
- Not accessible from the web
### **config/** (PRIVATE)
- Configuration files
- Database credentials, API keys, app settings
- `.env` file for environment-specific configuration
- Never committed sensitive values (use `.env.example`)
### **database/** (PRIVATE)
- Database migrations, seeds, schema definitions
- Version-controlled database structure
### **tests/** (PRIVATE)
- PHPUnit tests
- Organized by test type (Unit, Integration, Functional)
- Mirror the `src/` structure
### **vendor/** (PRIVATE)
- Composer dependencies
- Auto-generated, excluded from version control
- Contains `autoload.php` for autoloading
### **bin/** (PRIVATE)
- Executable scripts and CLI commands
- Make scripts executable: `chmod +x bin/*`
### **var/** (PRIVATE)
- Runtime-generated files
- cache/, logs/, tmp/
- Typically .gitignored (except .gitkeep files)
- Needs write permissions
### **docs/** (PRIVATE)
- Project documentation
- API docs, guides, architecture decisions
### **scripts/** (PRIVATE)
- Build, deployment, maintenance scripts
- Not part of the application runtime
### **resources/** (PRIVATE)
- Non-PHP application resources
- Templates, translations, email layouts
## Migration from Current Structure
Your current structure → Recommended structure:
```
Current → Recommended
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
index.php → public/index.php
memoire.php → public/memoire.php OR src/Controller/
search.php → public/search.php OR src/Controller/
assets/ → public/assets/
inc/ → src/ OR config/
admin/ → src/Admin/ OR public/admin/
database/ → database/ (keep as-is)
tests/ → tests/ (keep as-is)
vendor/ → vendor/ (keep as-is)
lib/ → lib/ OR src/
docs/ → docs/ (keep as-is)
scripts/ → scripts/ (keep as-is)
nginx/ → scripts/nginx/ OR config/nginx/
```
## Security Best Practices
1. **DocumentRoot = public/**
- Configure web server to serve only from `public/`
- All other directories are above DocumentRoot
2. **Sensitive Files**
- Keep `.env`, config files outside `public/`
- Never commit passwords, API keys
- Use `.env.example` for templates
3. **Permissions**
```bash
# Private directories (not writable by web server)
chmod 755 src/ config/ database/ tests/
# Writable by web server
chmod 775 var/cache/ var/logs/ var/tmp/
```
4. **.gitignore**
```
/vendor/
/var/cache/*
/var/logs/*
/var/tmp/*
/.env
composer.lock
```
## Web Server Configuration
### Nginx
```nginx
server {
root /path/to/posterg-website/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
}
```
### Apache (.htaccess in public/)
```apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
</IfModule>
```
## Composer Configuration
Update `composer.json` to use PSR-4 autoloading:
```json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
```
## Next Steps
1. Create `public/` directory
2. Move web-accessible files to `public/`
3. Organize classes into `src/` with namespaces
4. Move configuration to `config/`
5. Update web server DocumentRoot
6. Update paths in application code
7. Run `composer dump-autoload`
8. Test the application
## References
- [Standard PHP Package Skeleton](https://github.com/php-pds/skeleton)
- [PSR-4 Autoloading](https://www.php-fig.org/psr/psr-4/)
- [Composer Documentation](https://getcomposer.org/doc/)

View File

@@ -1,297 +0,0 @@
# Live Reload Setup
Guide to setting up and using live reload for Post-ERG development.
## 🎯 What is Live Reload?
Live reload automatically refreshes your browser when you save files during development. No need to manually hit refresh!
## ✨ How It Works
1. **JavaScript** in the page polls the server for file changes
2. **PHP backend** checks file modification times
3. **Browser** automatically refreshes when changes detected
**No browser extension needed!**
---
## 🚀 Setup (One Time)
```bash
just setup
```
This clones `php-live-reload` into `vendor/php-live-reload/` (which is gitignored).
---
## 🏃 Using Live Reload
### Start Server
```bash
just serve
```
Output:
```
🚀 Starting Post-ERG development server
========================================
📍 Public site: http://localhost:8000
📍 Admin panel: http://localhost:8000/admin/
✨ Live reload enabled - browser auto-refreshes on file save!
Press Ctrl+C to stop
```
### Edit Files
1. Open http://localhost:8000 in your browser
2. Edit any PHP, CSS, or JS file
3. Save the file
4. **Browser automatically refreshes!**
### What Triggers Reload
- Saving `.php` files
- Saving `.css` files
- Saving `.js` files
- Any file change in the project
---
## 🔧 How It's Integrated
### In `inc/header.php`
Live reload script is conditionally included only during development:
```php
<?php if (getenv('PHP_ENV') === 'development' || php_sapi_name() === 'cli-server'): ?>
<!-- Live reload for development -->
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
<?php endif; ?>
```
**Result:**
- ✅ Included when using `php -S` (development server)
- ❌ NOT included in production (nginx/apache)
- ❌ NOT deployed to server (vendor/ is gitignored)
### Detection Logic
```php
php_sapi_name() === 'cli-server' // True when using PHP dev server
```
This means live reload is automatically enabled/disabled based on environment!
---
## 📁 File Structure
```
vendor/php-live-reload/
├── php-live-reload/
│ ├── live-reload.js # JavaScript client
│ ├── live-reload.php # PHP backend (checks files)
│ └── config.php # Configuration
└── README.md
```
---
## 🧪 Testing Live Reload
### Test It Works
1. Start server: `just serve`
2. Open http://localhost:8000
3. Open browser console (F12)
4. Edit `index.php` and save
5. Watch browser auto-refresh!
You'll see in console:
```javascript
GET /vendor/php-live-reload/php-live-reload/live-reload.php
change detected
```
---
## ⚙️ Configuration
### Polling Interval
Default: Checks every ~1-2 seconds
To change, edit `vendor/php-live-reload/php-live-reload/config.php`:
```php
define('MIN_DELAY', 1000); // Minimum milliseconds between checks
```
### File Watching
By default, watches all files in project directory.
To exclude paths, edit config:
```php
$exclude = [
'vendor',
'.git',
'node_modules',
'cache'
];
```
---
## 🐛 Troubleshooting
### Live Reload Not Working
**1. Check vendor directory exists:**
```bash
ls -la vendor/php-live-reload/
```
If missing:
```bash
just setup
```
**2. Check script is included in page:**
```bash
curl -s http://localhost:8000/ | grep live-reload
```
Should show:
```html
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
```
**3. Check endpoint is accessible:**
```bash
curl http://localhost:8000/vendor/php-live-reload/php-live-reload/live-reload.php
```
Should return JSON:
```json
{"time": 10, "changed": false}
```
**4. Check browser console for errors**
Open browser console (F12) and look for:
- WebSocket errors
- Network errors to `/vendor/php-live-reload/`
### Script Not Loading
**Make sure you're using dev server:**
```bash
just serve
```
NOT production (nginx/apache).
**Check PHP detection:**
```bash
php -r "echo php_sapi_name();"
```
When using `just serve`, should output: `cli-server`
### Changes Not Detected
**Check polling is working:**
Open browser console, you should see repeated requests to:
```
/vendor/php-live-reload/php-live-reload/live-reload.php
```
If not, check JavaScript loaded:
```bash
curl -I http://localhost:8000/vendor/php-live-reload/php-live-reload/live-reload.js
```
Should return `200 OK`.
---
## 🚫 Production Behavior
### Automatically Disabled
Live reload is **automatically disabled** in production because:
1. **`php_sapi_name()` check**: Only true with PHP dev server
2. **vendor/ gitignored**: Not deployed to server
3. **nginx serves files**: Different SAPI, condition false
### Verification
On production server:
```bash
curl https://posterg.erg.be/ | grep live-reload
```
Should return nothing (script not included).
---
## 💡 Tips
### Multiple Browser Windows
Open multiple tabs/windows - they all get live reload!
```
http://localhost:8000/ # Homepage
http://localhost:8000/admin/ # Admin panel
http://localhost:8000/memoire.php?id=13 # Thesis page
```
All will auto-refresh on file changes.
### Faster Development
With live reload:
1. ✅ Edit code
2. ✅ Save
3. ✅ Browser refreshes
4. ✅ See changes instantly!
No more:
1. ❌ Edit code
2. ❌ Save
3. ❌ Switch to browser
4. ❌ Hit F5
5. ❌ Switch back to editor
**Saves you seconds on every change!**
---
## 📚 More Information
- GitHub: https://github.com/ryantate13/php-live-reload
- Alternative: https://github.com/guard/guard-livereload
---
## ✅ Quick Reference
| Command | Description |
|---------|-------------|
| `just setup` | Install live reload (one time) |
| `just serve` | Start server with live reload |
| `just stop` | Stop server |
**To use:** `just serve` and edit files - browser auto-refreshes! ✨

View File

@@ -1,357 +0,0 @@
# Migration from YAML to SQLite
## Overview
The Post-ERG thesis submission form has been completely overhauled to use a SQLite database instead of flat YAML files. This provides better data integrity, querying capabilities, and prepares the system for a full-featured web application.
## What Changed
### Database Implementation
**Before:** Form data was saved as individual YAML files in `data/yaml/`, with file uploads scattered in `data/content/` and `data/cover/`.
**After:** All thesis data is now stored in a relational SQLite database (`../db/posterg.db`) with proper normalization and foreign key relationships.
### New Architecture
```
Form Submission Flow:
1. User fills out enhanced form (index.php)
2. Form validates input and begins database transaction
3. Creates/links: author, thesis, supervisors, keywords, languages, formats
4. Uploads files with random names for security
5. Records file metadata in database
6. Commits transaction (all-or-nothing)
7. Redirects to confirmation page showing database data
```
### Database Schema Highlights
- **19 tables** including junction tables and views
- **Normalized structure** (3rd Normal Form)
- **Automatic timestamps** via triggers
- **Cascade deletes** for referential integrity
- **Predefined lookup tables** for orientations, AP programs, finalities, etc.
- **Views** for simplified querying (v_theses_full, v_theses_public)
## New Files
### `Database.php`
Database helper class providing:
- PDO connection with error handling
- Transaction management
- Find-or-create methods for entities
- Prepared statement helpers
- Lookup methods for all reference data
**Key Methods:**
```php
$db = new Database();
$authorId = $db->findOrCreateAuthor($name, $email);
$keywordId = $db->findOrCreateKeyword($keyword);
$orientations = $db->getAllOrientations();
$thesis = $db->getThesis($id);
```
## Modified Files
### `index.php`
**Enhancements:**
- Dynamically loads form options from database
- Added required fields per schema:
- Subtitle (optional)
- Synopsis (~200 words, required)
- Finality (Approfondi/Enseignement/Spécialisé)
- Languages (multiple selection with checkboxes)
- Formats (multiple selection with checkboxes)
- Better form organization with sections
- Improved accessibility (proper labels, IDs)
**New Form Fields:**
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| Subtitle | Text | No | New field |
| Synopsis | Textarea | Yes | ~200 words |
| Finality | Select | Yes | From finality_types table |
| Languages | Checkboxes | Yes | Multiple selection |
| Formats | Checkboxes | No | Multiple selection |
### `formulaire.php`
**Complete rewrite** with:
1. **Transaction-Based Processing:**
- `BEGIN TRANSACTION` at start
- All insertions in single transaction
- `COMMIT` on success or `ROLLBACK` on error
- Ensures data consistency
2. **Prepared Statements:**
- All SQL queries use PDO prepared statements
- Protection against SQL injection
- Parameter binding for all user input
3. **Entity Creation:**
- Finds or creates authors (by name)
- Finds or creates supervisors (by name)
- Finds or creates keywords (by text)
- Links all entities via junction tables
4. **Identifier Generation:**
- Format: `YYYY-NNN` (e.g., "2026-001")
- Automatically increments per year
- Unique constraint in database
5. **File Handling:**
- Random cryptographic filenames (32 hex chars)
- Organized by year and identifier: `data/theses/YYYY/YYYY-NNN/`
- Cover images separate: `data/covers/`
- Metadata stored in `thesis_files` table
6. **Validation:**
- Year range: 2000 to current year + 1
- Max 10 keywords enforced
- At least one language required
- URL format validation
- File type and size validation
### `thanks.php`
**Complete redesign:**
- Reads from database using thesis ID
- Displays data from `v_theses_full` view
- Shows all relationships: authors, supervisors, keywords, languages, formats
- Lists uploaded files with metadata (type, size, date)
- Responsive CSS grid layout
- Publication status indicator
**Security:**
- Validates thesis ID (integer only)
- Uses prepared statements
- No path traversal vulnerability
- Error messages don't expose system details
## Database Files
### `../db/posterg.db`
Initialized SQLite database with:
- 19 tables (11 core, 5 junction, 3 reference)
- 2 views (v_theses_full, v_theses_public)
- Predefined data:
- 15 orientations
- 4 AP programs
- 3 finality types
- 2 languages (French, English)
- 7 format types
- 3 access types
- 4 static pages
### Schema Documentation
See `../db/README.md` and `../db/SETUP.md` for complete documentation.
## Security Improvements Retained
All security improvements from the previous commit are preserved:
✅ CSRF protection with session tokens
✅ Input validation and sanitization
✅ Prepared statements (SQL injection protection)
✅ Random filenames for uploads
✅ File type and size validation
✅ MIME type checking
✅ Error logging without exposing paths
✅ Path traversal protection
## Data Mapping
### YAML to Database Mapping
| Old YAML Field | New Database Location | Notes |
|----------------|----------------------|-------|
| `auteurice` | `authors.name` | Normalized, reusable |
| `email` | `authors.email` | Now in authors table |
| `année` | `theses.year` | Integer field |
| `titre` | `theses.title` | Required |
| - | `theses.subtitle` | New field |
| `description` | `theses.synopsis` | Renamed for clarity |
| `problématique` | (not yet used) | Can be added to schema |
| `orientation` | `theses.orientation_id` | Foreign key to orientations |
| `ap` | `theses.ap_program_id` | Foreign key to ap_programs |
| - | `theses.finality_id` | New field (required) |
| `promoteurice` | `supervisors.name` + `thesis_supervisors` | Many-to-many |
| `tag` | `keywords.keyword` + `thesis_keywords` | Many-to-many, max 10 |
| `lien` | `theses.baiu_link` | URL validation |
| `files` | `thesis_files` table | Full metadata |
| `couverture` | (stored as file, not in DB yet) | Could add cover_path column |
## Migration Path for Existing Data
If you have existing YAML files to import:
1. **Parse YAML files:**
```php
$yamlFiles = glob('data/yaml/*.yaml');
foreach ($yamlFiles as $file) {
$data = Yaml::parseFile($file);
// ...
}
```
2. **Insert into database:**
```php
$db->beginTransaction();
try {
$authorId = $db->findOrCreateAuthor($data['auteurice'], $data['email']);
// Insert thesis
// Link relationships
$db->commit();
} catch (Exception $e) {
$db->rollback();
}
```
3. **Verify data:**
```sql
SELECT COUNT(*) FROM theses;
SELECT * FROM v_theses_full LIMIT 5;
```
## Testing Checklist
Before production deployment:
- [ ] Form loads without errors
- [ ] All dropdown options populate from database
- [ ] Form submission creates thesis record
- [ ] Author is created or found correctly
- [ ] Supervisors linked properly
- [ ] Keywords created and linked (test max 10)
- [ ] Languages required (test validation)
- [ ] Formats optional (test multiple selection)
- [ ] Files upload successfully
- [ ] File metadata recorded in database
- [ ] Thanks page displays all data correctly
- [ ] Transaction rollback works on error
- [ ] CSRF token validated
- [ ] Invalid data rejected (year, URL, etc.)
## Known Limitations
1. **No cover_path column:** Cover images uploaded but path not stored in `theses` table (can be added)
2. **No problématique field:** Old field not yet in schema (can be added to `theses.remarks` or new column)
3. **File type detection:** Basic (by extension), could be enhanced
4. **No duplicate detection:** Same thesis can be submitted multiple times
5. **No edit capability:** Once submitted, no UI to edit (admin interface needed)
## Next Steps
1. **Initialize production database:**
```bash
cd /path/to/production/db
sqlite3 posterg.db < schema.sql
```
2. **Set permissions:**
```bash
chmod 644 posterg.db
chown www-data:www-data posterg.db
```
3. **Test form submission:**
- Submit test thesis
- Verify all fields saved
- Check file uploads
- Test thanks page
4. **Import existing data:**
- Create migration script
- Parse old YAML files
- Bulk insert into database
- Verify integrity
5. **Build admin interface:**
- CRUD operations for theses
- User management
- Approval workflow
- Bulk operations
6. **Build public website:**
- Search and filter theses
- Respect access controls
- Display thesis details
- Static pages management
## Compatibility Notes
### PHP Requirements
- PHP 7.4+ (tested on PHP 8.x)
- PDO extension with SQLite support
- Composer for Symfony YAML (still used for potential migration)
### Database
- SQLite 3.8.0+
- File-based database (no server needed)
- Single file: `db/posterg.db`
### Dependencies
```json
{
"require": {
"symfony/yaml": "^6.2",
"behat/transliterator": "^1.5"
}
}
```
Note: YAML library retained for potential data migration from old files.
## Backup Strategy
SQLite database is a single file - easy to backup:
```bash
# Simple copy
cp db/posterg.db db/backups/posterg_$(date +%Y%m%d).db
# SQL dump (portable)
sqlite3 db/posterg.db .dump > backups/posterg_$(date +%Y%m%d).sql
# Compressed backup
tar -czf backups/posterg_$(date +%Y%m%d).tar.gz db/posterg.db data/
```
Set up automated daily backups via cron.
## Performance Considerations
- **Indexes:** All critical foreign keys and search fields indexed
- **Views:** Pre-computed joins for common queries
- **Transactions:** Ensure atomicity without locking issues
- **File I/O:** Random filenames prevent directory listing overhead
For large datasets (1000+ theses):
- Consider WAL mode: `PRAGMA journal_mode=WAL;`
- Optimize with `ANALYZE;` periodically
- Monitor database size and `VACUUM` if needed
## Rollback Plan
If issues arise, you can roll back to YAML-based system:
1. Use previous jj commit: `jj checkout <commit-id>`
2. Old YAML files in `data/yaml/` still intact
3. Database changes don't affect old YAML code
4. Can run both systems in parallel during transition
## Support
For questions or issues:
- Schema documentation: `db/README.md`
- Setup guide: `db/SETUP.md`
- Security details: `SECURITY.md`
- Technical specs: `db/posterg_fiche-technique.md`
---
**Migration completed:** 2026-01-27
**Database version:** 1.0
**Form version:** 2.0 (SQLite)

View File

@@ -1,381 +0,0 @@
# Migration Checklist - Public Directory Structure
Quick reference for migrating from current flat structure to secure public/ structure.
## 📋 Summary of Required Changes
### 1. Dev Server (justfile) ⚡
**Line 51** - Change from:
```just
@php -S 127.0.0.1:8000
```
To:
```just
@php -S 127.0.0.1:8000 -t public/
```
### 2. Deployment (justfile) 📤
**Lines 56-75** - Replace entire `deploy` recipe with two-step deployment:
- Deploy app to `/var/www/posterg/` (not `/var/www/html/`)
- Nginx serves from `/var/www/posterg/public/`
See `DEPLOYMENT_MIGRATION.md` for complete updated recipe.
### 3. Nginx Configuration 🔧
**nginx/posterg.conf line 14** - Change from:
```nginx
root /var/www/html;
```
To:
```nginx
root /var/www/posterg/public;
```
**Also update admin location** (line 58) - Change from:
```nginx
location ^~ /formulaire/ {
```
To:
```nginx
location ^~ /admin/ {
```
(Since you're moving admin/ to public/admin/)
---
## 🚀 Quick Migration (Local Dev)
```bash
# 1. Update justfile serve command
sed -i 's/@php -S 127.0.0.1:8000/@php -S 127.0.0.1:8000 -t public\//' justfile
# 2. Test new dev server
just serve
# Visit http://localhost:8000
# Verify http://localhost:8000/storage/test.db returns 404
# 3. If it works, you're ready for production migration
```
---
## 📁 File Movements (Do This After Testing)
```bash
# Create new structure
mkdir -p public/{admin,assets}
mkdir -p src config var/{cache,logs,tmp}
# Move public files
mv index.php search.php memoire.php public/
mv admin/* public/admin/ && rmdir admin
mv assets/* public/assets/ && rmdir assets
# Move private files
mv inc/* config/ && rmdir inc
# OR if inc/ contains classes:
# mv inc/* src/ && rmdir inc
# Keep these as-is
# database/ (already private)
# vendor/ (already private)
# tests/ (already private)
# lib/ (decide if it goes to src/lib or stays)
```
---
## ⚠️ Critical Changes Required
### In nginx/posterg.conf:
1. **Line 14** - DocumentRoot
```nginx
# BEFORE
root /var/www/html;
# AFTER
root /var/www/posterg/public;
```
2. **Line 58-76** - Admin location (already at `/formulaire/`, might want `/admin/`)
```nginx
# BEFORE
location ^~ /formulaire/ {
auth_basic "Admin Access - Post-ERG";
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
# ... rest of config
}
# AFTER (if renaming to /admin/)
location ^~ /admin/ {
auth_basic "Admin Access - Post-ERG";
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
# ... rest of config
}
```
3. **Remove/update deny rules** (lines 48-60) - These become redundant!
```nginx
# BEFORE - needed because everything in DocumentRoot
location ^~ /storage/ { deny all; }
location ^~ /shared/ { deny all; }
location ^~ /data/ { deny all; }
# AFTER - can remove! They're already outside public/
# But keep as defense-in-depth:
location ^~ /storage/ { deny all; } # Will never match, but safe
```
### In justfile:
**Complete replacement for lines 40-76:**
```just
[group('dev')]
serve:
@echo "🚀 Starting Post-ERG development server"
@echo "========================================"
@echo ""
@echo "📍 Public site: http://localhost:8000"
@echo "📍 Admin panel: http://localhost:8000/admin/"
@echo ""
@echo "🔒 Serving from public/ directory (matches production)"
@if [ -d "vendor/php-live-reload" ]; then \
echo "✨ Live reload enabled"; \
else \
echo "💡 Tip: Run 'just setup' to enable live reload"; \
fi
@echo ""
@echo "Press Ctrl+C to stop"
@echo ""
@php -S 127.0.0.1:8000 -t public/
[group('deploy')]
deploy:
@echo "📤 Deploying Post-ERG complete site"
@echo "===================================="
@echo ""
@echo "Deploying to /var/www/posterg/..."
rsync -vur --progress \
--chown="www-data:posterg" \
--exclude 'vendor' \
--exclude 'tests' \
--exclude 'test.db' \
--exclude '*.md' \
--exclude '.git*' \
--exclude '.jj' \
--exclude '.DS_Store' \
--exclude 'justfile*' \
--exclude 'setup-dev.sh' \
--exclude 'database/backup_*' \
--exclude 'database/fixtures' \
--exclude 'var/cache/*' \
--exclude 'var/logs/*' \
./ posterg:/var/www/posterg/
@echo ""
@echo "Setting up directories and permissions..."
ssh posterg "cd /var/www/posterg && \
mkdir -p var/{cache,logs,tmp} && \
chown -R www-data:posterg var/ database/ && \
chmod -R 775 var/ database/ && \
chmod 660 database/*.db"
@echo ""
@echo "✅ Deployment complete!"
@echo ""
@echo "🔍 Verify:"
@echo " • Public: https://posterg.erg.be/"
@echo " • Admin: https://posterg.erg.be/admin/"
[group('deploy')]
test-deploy:
@echo "⚠️ Deploying test database"
ssh posterg "mkdir -p /var/www/posterg/database"
rsync -vur --progress ./storage/test.db posterg:/var/www/posterg/storage/
ssh posterg "chown www-data:posterg /var/www/posterg/storage/test.db && \
chmod 660 /var/www/posterg/storage/test.db"
@echo "✅ Test database deployed"
```
---
## 🧪 Testing Steps
### 1. Test Local Dev Server
```bash
# Start server with new -t public/ flag
just serve
# In another terminal:
curl http://localhost:8000/ # ✅ Should work
curl http://localhost:8000/admin/ # ✅ Should work (after moving)
curl http://localhost:8000/storage/test.db # ❌ Should 404
curl http://localhost:8000/config/ # ❌ Should 404
curl http://localhost:8000/vendor/ # ❌ Should 404
```
### 2. Test After File Migration
```bash
# After moving files to public/
just serve
# Test again
curl http://localhost:8000/ # ✅ index.php serves
curl http://localhost:8000/search.php # ✅ works
curl http://localhost:8000/admin/ # ✅ works
curl http://localhost:8000/assets/css/style.css # ✅ works
# Verify old paths don't work
curl http://localhost:8000/../storage/test.db # ❌ 404
curl http://localhost:8000/../config/ # ❌ 404
```
### 3. Test Production Deployment
```bash
# After deploying to server
just server-status
# Manual checks
curl -I https://posterg.erg.be/
curl -I https://posterg.erg.be/admin/
curl -I https://posterg.erg.be/storage/test.db # Must be 404!
```
---
## 📝 PHP Path Updates Needed
After moving to public/, update PHP includes:
**Before (from root):**
```php
<?php
require_once 'inc/config.php';
require_once 'lib/Database.php';
require_once 'database/test.db';
```
**After (from public/):**
```php
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../src/lib/Database.php';
$db = new PDO('sqlite:' . __DIR__ . '/../storage/test.db');
```
**Or use a bootstrap:**
```php
<?php
// public/index.php
require_once __DIR__ . '/../config/bootstrap.php';
// config/bootstrap.php
define('APP_ROOT', dirname(__DIR__));
define('PUBLIC_ROOT', APP_ROOT . '/public');
define('DATABASE_PATH', APP_ROOT . '/storage/test.db');
require_once APP_ROOT . '/vendor/autoload.php';
```
---
## 🔍 What to Check in Your Code
Run these searches to find hardcoded paths:
```bash
# Find absolute paths
grep -r "/var/www/html" ./*.php
grep -r "/var/www/html" ./admin/*.php
# Find relative paths that need updating
grep -r "require.*inc/" ./*.php
grep -r "require.*lib/" ./*.php
grep -r "require.*database/" ./*.php
# Find database connections
grep -r "\.db" ./*.php
# Find asset references
grep -r "src=\"assets/" ./*.php
grep -r "href=\"assets/" ./*.php
```
---
## ⚡ Quick Start (Minimal Changes First)
If you want to test with minimal changes:
**1. Only change dev server:**
```bash
# Edit justfile line 51
@php -S 127.0.0.1:8000 -t public/
# Create public/ with symlinks (temporary test)
mkdir public
ln -s ../index.php public/index.php
ln -s ../search.php public/search.php
ln -s ../admin public/admin
ln -s ../assets public/assets
# Test
just serve
```
**2. If it works, do full migration**
---
## 🆘 Rollback Plan
If production deployment fails:
```bash
# On server
sudo systemctl stop nginx
# Restore old config
sudo cp /etc/nginx/sites-available/posterg.backup /etc/nginx/sites-available/posterg
sudo nginx -t
sudo systemctl start nginx
# Or restore files
sudo rm -rf /var/www/posterg
sudo mv /var/www/html.backup /var/www/html
```
**Always backup before deploying:**
```bash
# Before migration
ssh posterg "sudo cp -r /var/www/html /var/www/html.backup"
ssh posterg "sudo cp /etc/nginx/sites-available/posterg /etc/nginx/sites-available/posterg.backup"
```
---
## 📊 Benefits After Migration
| Before | After |
|--------|-------|
| ❌ All files in DocumentRoot | ✅ Only public/ in DocumentRoot |
| ❌ Database accessible if nginx misconfigured | ✅ Database physically unreachable |
| ❌ Config files one deny rule away | ✅ Config files outside web root |
| ❌ 20+ deny/exclude rules needed | ✅ Physical separation, minimal rules |
| ❌ Dev server exposes everything | ✅ Dev matches production security |
---
## Next: Check Your Current Paths
Run this to see what paths need updating:
```bash
# Find all require/include statements
find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*" \
-exec grep -H "require\|include" {} \; > paths-audit.txt
# Review paths-audit.txt and update paths
cat paths-audit.txt
```
See **DEPLOYMENT_MIGRATION.md** for complete implementation details.

View File

@@ -1,214 +0,0 @@
# ✅ Migration to public/ Directory Structure - COMPLETE
## 📊 Summary of Changes
### Directory Structure Created
```
posterg-website/
├── config/ # ✅ NEW - Configuration files
│ └── bootstrap.php # Central path management
├── public/ # ✅ NEW - DocumentRoot (web-accessible)
│ ├── admin/ # Moved from /admin/
│ ├── assets/ # Moved from /assets/
│ ├── index.php # Moved from /index.php
│ ├── search.php # Moved from /search.php
│ └── memoire.php # Moved from /memoire.php
├── resources/ # ✅ NEW - Application resources
│ └── views/ # Moved from /inc/
│ ├── header.php
│ └── footer.php
├── var/ # ✅ NEW - Runtime files
│ ├── cache/
│ ├── logs/
│ └── tmp/
├── database/ # ✅ KEPT - Now private
├── lib/ # ✅ KEPT - Now private
├── vendor/ # ✅ KEPT - Now private
└── tests/ # ✅ KEPT - Now private
```
### Files Modified
**1. config/bootstrap.php** (NEW)
- Central path configuration
- Defines APP_ROOT, PUBLIC_ROOT, DATABASE_PATH, etc.
- Helper functions: view(), getDatabase()
- Environment detection (dev vs production)
- Error handling configuration
**2. public/*.php** (3 files updated)
- index.php: Uses bootstrap, updated require paths
- search.php: Uses bootstrap, updated require paths
- memoire.php: Uses bootstrap, updated require paths
- All now use view() helper for header/footer
**3. public/admin/*.php** (7 files updated)
- add.php, edit.php, formulaire.php, import.php
- index.php, publish.php, thanks.php
- All updated to use ../../ paths for lib access
- Bootstrap added where needed
**4. justfile** (Updated)
- Dev server: `php -S 127.0.0.1:8000 -t public/`
- Deploy: Now deploys to `/var/www/posterg/`
- Database deploy: Updated paths to `/var/www/posterg/`
- Nginx deploy: Checks for correct DocumentRoot
**5. nginx/posterg.conf** (Updated)
- DocumentRoot: `/var/www/html``/var/www/posterg/public`
- Admin location: `/formulaire/``/admin/`
**6. .gitignore** (Updated)
- Added var/ directory patterns
- Keeps .gitkeep files, ignores contents
### Security Improvements
**Before:**
- ❌ All files in DocumentRoot (/var/www/html/)
- ❌ Database accessible at /storage/test.db
- ❌ Config files accessible
- ❌ Dev server exposed everything
- ❌ Relied on nginx deny rules
**After:**
- ✅ Only public/ in DocumentRoot
- ✅ Database physically outside web root
- ✅ Config files physically private
- ✅ Dev server matches production security
- ✅ Physical separation = secure by default
## 🧪 Testing
### Local Development
```bash
# Start dev server
just serve
# Test in browser:
# - http://localhost:8000/ → Should work
# - http://localhost:8000/admin/ → Should work
# - http://localhost:8000/storage/test.db → Should 404 ✅
# - http://localhost:8000/config/ → Should 404 ✅
# - http://localhost:8000/../storage/test.db → Should 404 ✅
```
### Security Verification
```bash
# These should all return 404:
curl http://localhost:8000/storage/test.db
curl http://localhost:8000/config/bootstrap.php
curl http://localhost:8000/vendor/autoload.php
curl http://localhost:8000/../storage/test.db
curl http://localhost:8000/lib/Database.php
```
### Production Deployment
**BEFORE deploying to production:**
1. **Update nginx config on server:**
```bash
# Edit /etc/nginx/sites-available/posterg
# Change: root /var/www/html;
# To: root /var/www/posterg/public;
```
2. **Create new directory on server:**
```bash
ssh posterg "sudo mkdir -p /var/www/posterg"
```
3. **Deploy application:**
```bash
just deploy
```
4. **Deploy nginx config:**
```bash
just deploy-nginx
# Then on server:
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
5. **Verify:**
```bash
just server-status
curl -I https://posterg.erg.be/
curl -I https://posterg.erg.be/admin/
curl -I https://posterg.erg.be/storage/test.db # Must 404!
```
## 📝 Path Reference
### From public/*.php files:
```php
<?php
require_once __DIR__ . '/../config/bootstrap.php'; // Bootstrap
require_once LIB_ROOT . '/Database.php'; // Library
$db = getDatabase(); // Database
view('header.php', ['pageTitle' => 'Title']); // Template
```
### From public/admin/*.php files:
```php
<?php
require_once __DIR__ . '/../../config/bootstrap.php'; // Bootstrap
require_once LIB_ROOT . '/Database.php'; // Library
```
### Available Constants (from bootstrap):
- `APP_ROOT` - /path/to/posterg-website
- `PUBLIC_ROOT` - /path/to/posterg-website/public
- `CONFIG_ROOT` - /path/to/posterg-website/config
- `DATABASE_ROOT` - /path/to/posterg-website/database
- `DATABASE_PATH` - /path/to/posterg-website/storage/test.db
- `RESOURCES_ROOT` - /path/to/posterg-website/resources
- `LIB_ROOT` - /path/to/posterg-website/lib
- `VAR_ROOT` - /path/to/posterg-website/var
- `CACHE_ROOT` - /path/to/posterg-website/var/cache
- `LOGS_ROOT` - /path/to/posterg-website/var/logs
- `VIEWS_ROOT` - /path/to/posterg-website/resources/views
## 🎯 Next Steps
1. ✅ Migration complete - verify locally
2. ⏭️ Test dev server: `just serve`
3. ⏭️ Test all pages work correctly
4. ⏭️ Update nginx config on production server
5. ⏭️ Deploy to production: `just deploy`
6. ⏭️ Deploy nginx config: `just deploy-nginx`
7. ⏭️ Verify production deployment
## 🔄 Rollback (if needed)
If something goes wrong, jj makes it easy:
```bash
# View history
jj log
# Go back to previous state
jj edit <previous-change-id>
# Or abandon current changes
jj abandon @
```
## 📚 Documentation
See also:
- `DIRECTORY_STRUCTURE.md` - Full structure reference
- `DEPLOYMENT_MIGRATION.md` - Detailed migration guide
- `MIGRATION_CHECKLIST.md` - Quick checklist
## ✨ Benefits Achieved
1. **Security**: Private files physically separated from public
2. **Standards**: Follows PHP-FIG and Standard PHP Package Skeleton
3. **Development**: Dev server matches production security
4. **Maintainability**: Clear separation of concerns
5. **Portability**: Path constants make relocation easy
6. **Best Practices**: Industry-standard directory structure

View File

@@ -1,476 +0,0 @@
# Repository Restructure Migration Guide
This guide explains how to migrate the Post-ERG repository to a standard idiomatic PHP structure.
## 📋 Overview
### Current Structure
```
posterg-website/
├── apps/
│ ├── public/ # Public website
│ └── admin/ # Admin panel
├── shared/ # Shared PHP libraries
└── database/ # Database files
```
### New Structure (Standard PHP)
```
posterg-website/
├── index.php # Public root
├── *.php # Public PHP files
├── admin/ # Admin panel
├── lib/ # Shared libraries (renamed from shared/)
├── inc/ # Templates (header/footer)
├── assets/ # Static files
├── database/ # Database files
└── vendor/ # Third-party code (gitignored)
```
---
## 🎯 Benefits
**Standard PHP structure** - Follows community conventions
**Flatter hierarchy** - Easier navigation
**Simpler paths** - Less `../../` in code
**Cleaner deployment** - Single rsync command
**Live reload** - Auto-refresh during development
---
## 🚀 Migration Steps
### Step 1: Review the Plan
Read the restructure plan:
```bash
cat docs/RESTRUCTURE_PLAN.md
```
### Step 2: Commit Current State
**Important:** Commit all your current work first!
```bash
git add -A
git commit -m "Pre-migration checkpoint"
```
### Step 3: Run Migration Script
```bash
./migrate-structure.sh
```
This will:
- Move `apps/public/*` to root
- Move `apps/admin/` to `admin/`
- Rename `shared/` to `src/`
- Update all `require` paths automatically
- Remove `apps/` directory
- Update `.gitignore`
### Step 4: Test Locally
```bash
# Setup development environment
just setup-dev
# Test public site (with live reload!)
just serve-public
# Test admin panel
just serve-admin
```
Visit:
- http://localhost:8000 - Public site
- http://localhost:3000 - Admin panel
### Step 5: Verify Changes
```bash
# Check syntax
just check-public
# Run database tests
just stats-public
# View structure
tree -L 2 -I 'node_modules|.git|vendor'
```
### Step 6: Update Justfile
```bash
# Backup old justfile
mv justfile justfile.old
# Use new justfile
mv justfile.new justfile
```
### Step 7: Commit Migration
```bash
git add -A
git commit -m "Restructure to idiomatic PHP layout
- Moved apps/public to root
- Moved apps/admin to admin/
- Renamed shared/ to lib/
- Added php-live-reload for local dev
- Updated all require paths
- Simplified deployment"
```
### Step 8: Deploy to Production
```bash
# Deploy everything
just deploy
# Or deploy separately
just deploy-public
just deploy-admin
```
---
## 📝 What Changed
### File Movements
| Old Path | New Path |
|----------|----------|
| `apps/public/index.php` | `index.php` |
| `apps/public/memoire.php` | `memoire.php` |
| `apps/public/assets/` | `assets/` |
| `apps/public/inc/` | `inc/` |
| `apps/admin/` | `admin/` |
| `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `src/config.php` |
| `shared/cache/` | `src/cache/` |
### Path Updates
All PHP files automatically updated:
**Root files:**
```php
// Before
require_once __DIR__ . '/shared/Database.php';
// After
require_once __DIR__ . '/lib/Database.php';
```
**Admin files:**
```php
// Before
require_once __DIR__ . '/../../shared/Database.php';
// After
require_once __DIR__ . '/../lib/Database.php';
```
**Config file:**
```php
// Before
define('DB_ROOT', __DIR__ . '/..');
// After (stays the same)
define('DB_ROOT', __DIR__ . '/..');
```
### Justfile Changes
**Before:**
```bash
just serve-public # Basic PHP server
```
**After:**
```bash
just setup-dev # One-time: Install php-live-reload
just serve-public # PHP server with live reload!
```
**Before:**
```bash
# Deploy in multiple steps
rsync apps/public/ posterg:/var/www/html/
rsync apps/admin/ posterg:/var/www/html/formulaire/
rsync shared/ posterg:/var/www/html/shared/
```
**After:**
```bash
# Deploy in one command
just deploy
```
---
## 🔄 PHP Live Reload
### What It Does
Automatically refreshes your browser when you save PHP files!
### Setup (One Time)
```bash
just setup-dev
```
This clones https://github.com/ryantate13/php-live-reload to `vendor/php-live-reload/` (gitignored).
### Usage
```bash
# Start with live reload
just serve-public # or serve-admin
# Edit PHP files
# Browser auto-refreshes on save! 🎉
```
### How It Works
- Monitors PHP files for changes
- Sends reload signal via WebSocket
- Browser reloads automatically
- No browser extension needed
- Only for local development
- Never deployed to production
---
## 🧪 Testing
### Before Deploying
1. **Syntax check:**
```bash
just check-public
```
2. **Test database:**
```bash
just stats-public
```
3. **Test public site:**
```bash
just serve-public
# Visit http://localhost:8000
```
4. **Test admin:**
```bash
just serve-admin
# Visit http://localhost:3000
```
5. **Check file permissions:**
```bash
ls -la | head -n 20
ls -la admin/ | head -n 20
ls -la lib/
```
### After Deploying
1. **Test public site:**
```bash
curl -I https://posterg.erg.be/
```
2. **Test CSS loading:**
```bash
curl -I https://posterg.erg.be/assets/posterg.css
```
3. **Test admin:**
```bash
curl -I https://posterg.erg.be/admin/
```
---
## 🔙 Rollback (If Needed)
If something goes wrong:
```bash
# Revert the migration
git reset --hard HEAD~1
# Or restore from backup
git checkout HEAD~1 -- .
```
---
## 📚 New Directory Structure
```
posterg-website/
├── index.php # Public site root
├── memoire.php # Thesis detail page
├── search.php # Search page
├── apropos.php # About page
├── contact.php # Contact page
├── licences.php # Licenses page
├── test_db.php # Database test script
├── assets/ # Static files
│ ├── posterg.css # Main CSS
│ ├── normalize.css # CSS reset
│ ├── fonts/ # Custom fonts
│ └── icons.svg # Icon set
├── inc/ # Page templates
│ ├── header.php # Site header
│ └── footer.php # Site footer
├── lib/ # Shared libraries (was shared/)
│ ├── Database.php # Database class
│ ├── RateLimit.php # Rate limiting
│ ├── config.php # Configuration
│ └── cache/ # Cache files
├── admin/ # Admin panel (was apps/admin/)
│ ├── index.php # Admin dashboard
│ ├── list.php # Thesis list
│ ├── edit.php # Edit thesis
│ ├── formulaire.php # Add thesis
│ ├── import.php # Import tool
│ └── data/ # Upload directories
├── database/ # Database files & schema
│ ├── schema.sql # Database schema
│ ├── test.db # Test database
│ ├── fixtures/ # Test data generators
│ └── *.md # Documentation
├── vendor/ # Third-party code (gitignored)
│ └── php-live-reload/ # Live reload tool
├── nginx/ # Server configuration
├── docs/ # Documentation
├── tests/ # Tests (future)
├── justfile # Task runner
├── .gitignore # Git ignore rules
└── README.md # Project readme
```
---
## ❓ FAQ
### Q: Will the migration break the deployed site?
**A:** No. The migration only affects your local repository. Deploy only after testing locally.
### Q: Do I need to update the database?
**A:** No. The database structure doesn't change. Only file paths change.
### Q: What about nginx configuration?
**A:** Nginx config doesn't need to change. It still serves from `/var/www/html/`.
### Q: Will git history be preserved?
**A:** Yes. Git tracks file movements. Use `git log --follow filename.php` to see history.
### Q: Can I undo the migration?
**A:** Yes. Use `git reset --hard HEAD~1` before committing.
### Q: Does php-live-reload work on all platforms?
**A:** Yes. Works on Linux, macOS, and Windows (with PHP installed).
### Q: Will php-live-reload be deployed?
**A:** No. It's in `vendor/` which is gitignored and excluded from deployment.
---
## 🆘 Troubleshooting
### Migration script fails
**Check you're in the repo root:**
```bash
pwd
ls -la apps shared
```
**Make script executable:**
```bash
chmod +x migrate-structure.sh
```
### PHP can't find lib/ files
**Check paths were updated:**
```bash
grep -r "require.*lib/" . --include="*.php" | head -n 5
```
**Manually fix a file:**
```bash
# Edit the file and change:
require_once __DIR__ . '/shared/Database.php';
# To:
require_once __DIR__ . '/lib/Database.php';
```
### Live reload doesn't work
**Check vendor directory:**
```bash
ls -la vendor/php-live-reload/
```
**Re-run setup:**
```bash
just setup-dev
```
### Site works locally but not on server
**Check file permissions on server:**
```bash
ssh posterg "ls -la /var/www/html/ | head -n 20"
```
**Re-run deployment:**
```bash
just deploy
```
---
## 📞 Need Help?
1. **Check the plan:** `cat docs/RESTRUCTURE_PLAN.md`
2. **Review justfile:** `cat justfile`
3. **Check git status:** `git status`
4. **Test locally first:** `just serve-public`
---
**Ready to migrate?**
```bash
./migrate-structure.sh
```
Good luck! 🚀

View File

@@ -1,206 +0,0 @@
# Database Quick Reference
Quick lookup for the Post-ERG database schema.
## 📊 Table Summary
| Table | Type | Records | Description |
|-------|------|---------|-------------|
| `theses` | Core | ~500/year | Main thesis records |
| `authors` | Core | ~600 | Student/author info |
| `supervisors` | Core | ~50 | Thesis supervisors |
| `thesis_authors` | Junction | ~550/year | Thesis ↔ Authors |
| `thesis_supervisors` | Junction | ~600/year | Thesis ↔ Supervisors |
| `thesis_languages` | Junction | ~550/year | Thesis ↔ Languages |
| `thesis_formats` | Junction | ~700/year | Thesis ↔ Formats |
| `thesis_keywords` | Junction | ~3000/year | Thesis ↔ Keywords |
| `thesis_files` | Support | ~800/year | File attachments |
| `orientations` | Lookup | 15 | Art orientations |
| `ap_programs` | Lookup | 4 | Workshop programs |
| `finality_types` | Lookup | 3 | Master finalities |
| `languages` | Lookup | 2+ | Languages |
| `format_types` | Lookup | 7 | Media formats |
| `access_types` | Lookup | 3 | Access levels |
| `license_types` | Lookup | ~10 | Creative Commons |
| `keywords` | Lookup | ~500+ | Tag system |
| `pages` | Support | 4 | Static pages |
## 🔑 Key Relationships
```
theses ──┬── 1:N ──► thesis_authors ──► N:1 ── authors
├── 1:N ──► thesis_supervisors ──► N:1 ── supervisors
├── 1:N ──► thesis_keywords ──► N:1 ── keywords
├── 1:N ──► thesis_languages ──► N:1 ── languages
├── 1:N ──► thesis_formats ──► N:1 ── format_types
├── 1:N ──► thesis_files
├── N:1 ──► orientations
├── N:1 ──► ap_programs
├── N:1 ──► finality_types
├── N:1 ──► access_types
└── N:1 ──► license_types
```
## 📝 Core Fields Reference
### `theses` (Main Table)
**Identity:**
- `id` - Primary key
- `identifier` - Human-readable ID (e.g., "2025-002")
**Basic Info:**
- `title` - Thesis title (required)
- `subtitle` - Optional subtitle
- `year` - Academic year (required)
- `is_doctoral` - TFE (0) or Doctoral (1)
**Academic:**
- `orientation_id` - Art orientation
- `ap_program_id` - Workshop program
- `finality_id` - Master finality type
**Content:**
- `synopsis` - ~200 word summary
- `context_note` - Jury note (max 150 words)
- `duration_minutes` - For audio/video
- `duration_pages` - For written works
**Access:**
- `access_type_id` - Public/Internal/Restricted
- `license_id` - Creative Commons, etc.
**Workflow:**
- `submitted_at` - Student submission
- `defense_date` - Defense date
- `is_published` - Public visibility
- `published_at` - Publication date
- `jury_points` - Grade (0-20)
## 🏷️ Lookup Values
### Orientations (15)
Arts Numériques, Dessin, Cinéma d'animation, Installation-Performance, Peinture, Photographie, Sculpture, Vidéographie, Graphisme, Typographie, Design Numérique, Illustration, Bande-Dessinée, Sérigraphie, Gravure
### AP Programs (4)
- Narration Spéculative
- Design et Politique du Multiple (DPM)
- Atelier Pratiques Situées (APS)
- Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes (LIENS)
### Finality Types (3)
Approfondi, Enseignement, Spécialisé
### Languages (2+)
Français, Anglais
### Format Types (7)
Site web, Audio, Vidéo, Performance, Objet éditorial, Installation, Autre
### Access Types (3)
- **Libre**: Full access online + library
- **Interne**: Library only, note online
- **Interdit**: No access, note only
## 🔍 Common Queries
### Get Published Theses
```sql
SELECT * FROM v_theses_public ORDER BY year DESC;
```
### Get Thesis by ID
```sql
SELECT * FROM v_theses_full WHERE id = ?;
```
### Search by Title
```sql
SELECT * FROM v_theses_public
WHERE title LIKE '%keyword%'
ORDER BY year DESC;
```
### Filter by Year
```sql
SELECT * FROM v_theses_public
WHERE year = 2025
ORDER BY title;
```
### Filter by Orientation
```sql
SELECT t.* FROM theses t
JOIN orientations o ON t.orientation_id = o.id
WHERE o.name = 'Arts Numériques'
AND t.is_published = 1;
```
### Get Author's Theses
```sql
SELECT t.* 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 '%name%'
AND t.is_published = 1;
```
### Get Keywords for Thesis
```sql
SELECT k.keyword FROM keywords k
JOIN thesis_keywords tk ON k.id = tk.keyword_id
WHERE tk.thesis_id = ?;
```
### Count by Year
```sql
SELECT year, COUNT(*) as count
FROM theses
WHERE is_published = 1
GROUP BY year
ORDER BY year DESC;
```
## 📌 Important Constraints
- **Unique:** `theses.identifier`, `authors.email`, all lookup table names
- **Required:** `theses.title`, `theses.year`, at least one author
- **Max:** 10 keywords per thesis
- **Range:** `jury_points` 0.00 - 20.00
- **Cascade:** All junction tables DELETE CASCADE
## 🎯 Views
### `v_theses_full`
Complete thesis data with all relationships (comma-separated).
### `v_theses_public`
Only published theses (`is_published = 1`).
## 🔧 Making Changes
**Format for change requests:**
```
Table: [table_name]
Change: [add/modify/remove]
Column: [column_name]
Type: [data_type]
Reason: [why needed]
Example: [sample data]
```
**Example:**
```
Table: theses
Change: add
Column: external_url
Type: TEXT
Reason: Link to external project website
Example: https://example.com/project
```
## 📚 Full Documentation
See `DATABASE_SPECIFICATION.md` for complete details.

View File

@@ -1,345 +0,0 @@
# Secure Search Implementation - Complete
## ✅ Implementation Complete
The search feature has been implemented with **production-grade security** including comprehensive input validation, wildcard injection prevention, rate limiting, and pagination controls.
---
## Quick Start
### 1. Test Database Setup
```bash
cd /home/padlock/dev/posterg-website/front-backend
php create_test_db.php
```
### 2. Run Tests
```bash
# Functional tests
php test_search.php
# Security tests
php test_security_updated.php
# Rate limiting tests
php test_rate_limit.php
```
### 3. Access Search Page
Navigate to: `search.php`
---
## Security Features
### 🔒 Protection Against:
| Threat | Protection | Status |
|--------|-----------|--------|
| SQL Injection | Prepared statements | ✅ SECURE |
| XSS Attacks | Output escaping | ✅ SECURE |
| Wildcard Injection | LIKE escaping | ✅ SECURE |
| DoS (Long Input) | Length validation | ✅ SECURE |
| DoS (Rate Abuse) | 30 req/min limit | ✅ SECURE |
| Invalid Data | Range validation | ✅ SECURE |
| Pagination Abuse | Max 100/page | ✅ SECURE |
---
## Configuration
### Rate Limiting
**Location**: `search.php` line 8
```php
$rateLimit = new RateLimit(30, 60); // 30 requests per minute
```
**Adjust as needed:**
- More strict: `new RateLimit(10, 60)` - 10 req/min
- More lenient: `new RateLimit(60, 60)` - 60 req/min
- Hourly limit: `new RateLimit(100, 3600)` - 100 req/hour
### Pagination
**Default**: 20 results per page (max 100)
**User control**:
- `?per_page=50` - Get 50 results
- `?per_page=200` - Capped at 100
---
## Searchable Fields
Users can search across:
1. **Full-text query** - title, subtitle, synopsis, authors, supervisors, keywords
2. **Year** - Specific year (1900-2100)
3. **Orientation** - Arts Numériques, Peinture, Graphisme, etc.
4. **AP Program** - Narration Spéculative, DPM, APS, LIENS
5. **Finality** - Approfondi, Enseignement, Spécialisé
6. **Format** - Site web, Vidéo, Installation, etc.
7. **Language** - Français, Anglais
8. **Keywords** - Any keyword from published theses
9. **Type** - TFE or Doctoral theses
---
## Files Overview
### Core Files
- **Database.php** - Secure database class with validation
- **RateLimit.php** - Rate limiting system
- **search.php** - Search interface page
### Test Files
- **create_test_db.php** - Generate test database
- **test_search.php** - Functional tests
- **test_security_updated.php** - Security validation
- **test_rate_limit.php** - Rate limit tests
### Documentation
- **SEARCH_FEATURE.md** - Feature documentation
- **SECURITY_ANALYSIS.md** - Security analysis
- **SECURITY_IMPLEMENTATION.md** - Implementation details
- **README_SECURE_SEARCH.md** - This file
---
## Test Results Summary
### ✅ All Tests Passing
**Security Tests** (test_security_updated.php):
```
✅ Wildcard injection prevented
✅ Long input rejected (max 200 chars)
✅ Invalid year rejected (1900-2100)
✅ SQL injection prevented
✅ Pagination limited to 100
✅ Negative offsets handled
✅ Normal searches work correctly
```
**Rate Limiting Tests** (test_rate_limit.php):
```
✅ First 5 requests allowed
✅ 6th request blocked
✅ Remaining count accurate
✅ Reset time calculated
✅ Headers sent correctly
✅ Cleanup works
```
**Functional Tests** (test_search.php):
```
✅ All theses retrieved (6 found)
✅ Full-text search works
✅ Year filter works
✅ Orientation filter works
✅ AP program filter works
✅ Keyword search works
✅ Combined filters work
✅ Pagination works
```
---
## Example Searches
### Basic Search
```
search.php?query=urbain
→ Finds "Espaces Urbains et Narration Collective"
```
### Year Filter
```
search.php?year=2024
→ Finds 3 theses from 2024
```
### Combined Filters
```
search.php?query=performance&year=2024&orientation=Installation-Performance
→ Finds specific theses matching all criteria
```
### Pagination
```
search.php?year=2024&page=2&per_page=50
→ Second page, 50 results per page
```
---
## Security Highlights
### Input Validation
**Before (Vulnerable)**:
```php
$bindings[':query'] = '%' . $params['query'] . '%';
// User input "%" → matches EVERYTHING
```
**After (Secure)**:
```php
$validated = $this->escapeLikeString($params['query']);
$bindings[':query'] = '%' . $validated . '%';
// User input "%" → escapes to "\%" → matches literal %
// SQL: LIKE :query ESCAPE '\'
```
### Rate Limiting Flow
```
Request → RateLimit::check()
Allowed? ───No──→ HTTP 429 + Error page
Yes
Process search → Return results
Send X-RateLimit-* headers
```
---
## Production Deployment
### Pre-deployment Checklist
- [x] All tests passing
- [x] Security validated
- [x] Rate limiting configured
- [x] Cache directory created (755)
- [x] Error handling in place
- [x] Documentation complete
### Server Requirements
- [ ] PHP 7.4+ with PDO SQLite
- [ ] Write permissions on cache/ directory
- [ ] HTTPS enabled (recommended)
- [ ] Error logging configured
### Post-deployment
1. Monitor `error.log` for issues
2. Check rate limit cache growth
3. Analyze search patterns
4. Adjust rate limits if needed
---
## Troubleshooting
### Rate Limiting Not Working
**Check**:
```bash
# Cache directory exists and is writable
ls -la cache/rate_limit
# Should show: drwxr-xr-x
```
**Fix**:
```bash
mkdir -p cache/rate_limit
chmod 755 cache/rate_limit
```
### Search Returns No Results
**Check**:
1. Database exists: `ls ../formulaire/test.db`
2. Database has data: `php test_search.php`
3. Theses are published: `is_published = 1`
### Validation Errors
If users see "Search query too long":
- Current limit: 200 characters
- Adjust in `Database.php``validateSearchParams()`
---
## Performance Notes
### Optimized For
- SQLite full-text search across multiple fields
- Efficient LIKE queries with proper escaping
- Indexed columns (year, published, orientation, AP)
- Limited result sets (max 100/page)
### Benchmarks (6 theses in test DB)
- Simple search: < 1ms
- Complex multi-filter: < 2ms
- Rate limit check: < 0.1ms
### Scaling Considerations
- **100-1000 theses**: Current implementation excellent
- **1000-10000 theses**: Consider full-text search engine
- **10000+ theses**: Elasticsearch recommended
---
## Maintenance
### Daily
- Monitor error logs for unusual patterns
### Weekly
- Check rate limit violations
- Review search analytics
### Monthly
- Run security tests
- Update validation rules if needed
- Clean old cache files (automatic)
---
## Support & Documentation
### Documentation Files
1. **SEARCH_FEATURE.md** - User-facing feature docs
2. **SECURITY_ANALYSIS.md** - Threat analysis and mitigations
3. **SECURITY_IMPLEMENTATION.md** - Technical implementation
4. **README_SECURE_SEARCH.md** - This overview
### Code Documentation
- All methods have PHPDoc comments
- Inline comments explain security measures
- Test files demonstrate usage
---
## Summary
**Feature Complete**: Full search with advanced filtering
**Security Hardened**: Production-grade protection
**Well Tested**: 100% test coverage
**Documented**: Comprehensive documentation
**Performance**: Optimized queries and caching
**Maintainable**: Clear code structure
**Ready for production deployment!**
---
## Credits
Implementation includes:
- Secure parameterized queries (PDO)
- OWASP Top 10 protections
- Rate limiting best practices
- Input validation standards
- RESTful search API design
Generated: 2026-01-28
Status: ✅ Production Ready

View File

@@ -1,534 +0,0 @@
# Repository Structure Analysis
## Current Structure
```
posterg-website/ (monorepo root)
├── front-backend/ Public-facing site
│ ├── index.php Browse theses
│ ├── search.php Search feature
│ ├── memoire.php View individual thesis
│ ├── Database.php DB connection (reads ../formulaire/test.db)
│ ├── RateLimit.php Rate limiting
│ ├── tests/ Tests (Unit/Integration/Security)
│ ├── assets/ CSS, images
│ └── inc/ Header/footer templates
├── formulaire/ Submission system (admin)
│ ├── index.php List submissions
│ ├── formulaire.php Submission form
│ ├── edit.php Edit submissions
│ ├── Database.php DB connection (different implementation!)
│ ├── assets/ CSS, images
│ └── data/ Upload storage
├── db/ Shared database schemas
│ ├── schema.sql Database structure
│ ├── posterg.db Production database
│ └── README.md Documentation
├── justfile Deployment recipes
└── README.md
```
## Deployment Model
```bash
# front-backend → /var/www/html/ (root domain)
rsync ./front-backend/ server:/var/www/html/
# formulaire → /var/www/html/formulaire/ (subdomain or /formulaire path)
rsync ./formulaire/ server:/var/www/html/formulaire/
```
**URL Structure:**
- Public site: `https://posterg.example.com/`
- Admin/submission: `https://posterg.example.com/formulaire/`
## Issues with Current Structure
### ❌ Problems
1. **Inconsistent Database Access**
- `front-backend/Database.php` points to `../formulaire/test.db`
- `formulaire/Database.php` points to `../db/posterg.db` (production)
- Different implementations, different paths
2. **Code Duplication**
- Two separate `Database.php` files with different logic
- No shared code between front-backend and formulaire
3. **Confusing Dependencies**
- front-backend depends on formulaire for database location
- Circular/unclear relationship
4. **Test Database Location**
- Currently in `formulaire/test.db`
- Should be in `db/` with schema
---
## Option Analysis
### Option A: Keep Monorepo, Improve Organization ⭐ RECOMMENDED
```
posterg-website/
├── apps/ Application code
│ ├── public/ Public-facing site (was front-backend)
│ │ ├── index.php
│ │ ├── search.php
│ │ ├── memoire.php
│ │ ├── assets/
│ │ ├── inc/
│ │ └── tests/
│ │
│ └── admin/ Submission system (was formulaire)
│ ├── index.php
│ ├── formulaire.php
│ ├── edit.php
│ ├── assets/
│ └── data/
├── shared/ Shared code
│ ├── Database.php Single DB class used by both apps
│ └── RateLimit.php Shared utilities
├── database/ Database files (was db/)
│ ├── schema.sql
│ ├── posterg.db Production database
│ ├── test.db Test database
│ └── README.md
├── justfile Deployment
├── composer.json Dependencies
└── README.md
```
**Pros:**
- ✅ Clear separation: public vs admin
- ✅ Shared code in one place (DRY)
- ✅ Single source of truth for database
- ✅ Professional naming (apps/, shared/, database/)
- ✅ Easy to add more apps later
**Cons:**
- ⚠️ Requires refactoring paths in all files
- ⚠️ Need to update deployment scripts
**Migration Effort:** Medium (2-3 hours)
---
### Option B: Promote front-backend to Root
```
posterg-website/ (IS the public site)
├── index.php Public site files at root
├── search.php
├── memoire.php
├── Database.php
├── RateLimit.php
├── assets/
├── inc/
├── tests/
├── formulaire/ Keep admin as subfolder
│ ├── index.php
│ ├── formulaire.php
│ └── ...
└── db/ Shared database
├── schema.sql
└── posterg.db
```
**Pros:**
- ✅ Simpler paths for main application
- ✅ Less nesting
- ✅ Minimal refactoring needed
**Cons:**
- ❌ Root directory becomes cluttered
- ❌ Mixed responsibilities (public site + monorepo management)
- ❌ Still have duplicate Database.php files
- ❌ Harder to add new applications
- ❌ Tests mixed with application code at root
**Migration Effort:** Low (30 mins)
---
### Option C: Flatten Everything
```
posterg-website/
├── public/ Public site (was front-backend)
├── admin/ Submission (was formulaire)
├── database/ Schemas (was db)
├── shared/ Shared code
├── tests/ All tests
├── composer.json
└── justfile
```
**Pros:**
- ✅ Very clean root directory
- ✅ Professional structure
- ✅ Clear naming
**Cons:**
- ❌ Deployment scripts need updates
- ❌ All tests in one place (less clear ownership)
**Migration Effort:** Medium (2 hours)
---
### Option D: Keep Current Structure, Fix Issues
```
posterg-website/
├── front-backend/ Keep as-is
│ └── (no Database.php here)
├── formulaire/ Keep as-is
│ └── (no Database.php here)
├── shared/ NEW: Shared code
│ ├── Database.php Single database class
│ └── RateLimit.php
└── db/ Keep as-is
├── schema.sql
├── posterg.db
└── test.db
```
**Pros:**
- ✅ Minimal changes
- ✅ Keeps familiar structure
- ✅ Fixes code duplication
**Cons:**
- ⚠️ Still nested (front-backend, formulaire)
- ⚠️ Names not professional (what does "front-backend" mean?)
**Migration Effort:** Low (1 hour)
---
## Detailed Recommendation: Option A
### Why Option A is Best
1. **Scalability** - Easy to add new apps:
- `apps/api/` for REST API
- `apps/import/` for batch imports
- Each app is independent
2. **Professional** - Industry standard structure:
- Clear naming (`public`, `admin`, `shared`)
- Other developers understand immediately
- Matches Laravel, Symfony conventions
3. **Maintainability**:
- Single Database.php used by all apps
- Shared utilities (RateLimit) in one place
- Tests stay with their applications
4. **Deployment Clarity**:
```just
deploy-public:
rsync apps/public/ server:/var/www/html/
deploy-admin:
rsync apps/admin/ server:/var/www/html/formulaire/
deploy-shared:
rsync shared/ server:/var/www/html/shared/
```
### Proposed Structure Details
```
posterg-website/
├── apps/ Applications
│ ├── public/ Public-facing site
│ │ ├── index.php Homepage (browse theses)
│ │ ├── search.php Search interface
│ │ ├── memoire.php Individual thesis view
│ │ ├── apropos.php, contact.php, licences.php
│ │ ├── assets/ Public CSS, images
│ │ ├── inc/ Templates (header, footer)
│ │ └── tests/ Public site tests
│ │ ├── Unit/
│ │ ├── Integration/
│ │ ├── Security/
│ │ └── Fixtures/
│ │
│ └── admin/ Admin/submission system
│ ├── index.php Dashboard (list theses)
│ ├── formulaire.php Submission form
│ ├── edit.php Edit submission
│ ├── import.php Bulk import
│ ├── list.php List view
│ ├── assets/ Admin CSS
│ ├── data/ File uploads
│ └── tests/ Admin tests (optional)
├── shared/ Shared code (library)
│ ├── Database.php Single database class
│ ├── RateLimit.php Rate limiting
│ └── (future shared utilities)
├── database/ Database files & schemas
│ ├── schema.sql Database structure
│ ├── migrations/ Migration scripts (future)
│ ├── fixtures/ Test data
│ │ └── CreateTestDatabase.php
│ ├── posterg.db Production database (gitignored)
│ └── test.db Test database (gitignored)
├── config/ Configuration (optional)
│ ├── database.php DB connection settings
│ └── paths.php Path constants
├── .gitignore Git exclusions
├── justfile Deployment recipes
├── composer.json Dependencies
├── run-tests.php Test runner
└── README.md Documentation
```
### Migration Steps for Option A
1. **Create new structure** (no breaking changes yet):
```bash
mkdir -p apps/{public,admin}
mkdir -p shared
mkdir -p database/fixtures
```
2. **Move applications**:
```bash
mv front-backend/* apps/public/
mv formulaire/* apps/admin/
rmdir front-backend formulaire
```
3. **Create shared Database.php**:
- Merge best parts of both Database.php files
- Make database path configurable
- Use production db by default
4. **Move shared utilities**:
```bash
mv apps/public/RateLimit.php shared/
```
5. **Reorganize database**:
```bash
mv db database
mv apps/public/tests/Fixtures/CreateTestDatabase.php database/fixtures/
```
6. **Update all `require_once` paths**:
```php
// In apps/public/index.php
require_once __DIR__ . '/../../shared/Database.php';
require_once __DIR__ . '/../../shared/RateLimit.php';
// In apps/admin/index.php
require_once __DIR__ . '/../../shared/Database.php';
```
7. **Update Database.php to use config**:
```php
class Database {
private function __construct() {
// Determine database path
$dbPath = $this->getDatabasePath();
$this->pdo = new PDO('sqlite:' . $dbPath);
// ...
}
private function getDatabasePath() {
// Check environment
if (file_exists(__DIR__ . '/../storage/test.db')) {
return __DIR__ . '/../storage/test.db';
}
return __DIR__ . '/../storage/posterg.db';
}
}
```
8. **Update justfile**:
```just
[group('deploy')]
deploy-public:
rsync -vur --progress \
--exclude 'tests/' \
--exclude '*.md' \
apps/public/ posterg:/var/www/html/
rsync -vur shared/ posterg:/var/www/html/shared/
[group('deploy')]
deploy-admin:
rsync -vur --progress \
--exclude 'tests/' \
apps/admin/ posterg:/var/www/html/formulaire/
[group('deploy')]
deploy: deploy-public deploy-admin
```
9. **Update .gitignore**:
```
/storage/*.db
/apps/*/cache/
/shared/cache/
*.log
```
10. **Test everything**:
```bash
php run-tests.php
```
---
## Alternative: Quick Fix (Option D)
If you don't want major refactoring right now:
### Minimal Changes to Current Structure
1. **Create shared/ directory**:
```bash
mkdir shared
```
2. **Create unified Database.php in shared/**:
```php
// shared/Database.php - works for both apps
class Database {
private function __construct() {
// Smart path detection
if (file_exists(__DIR__ . '/../db/test.db')) {
$dbPath = __DIR__ . '/../db/test.db';
} else {
$dbPath = __DIR__ . '/../db/posterg.db';
}
$this->pdo = new PDO('sqlite:' . $dbPath);
// ... rest of implementation
}
}
```
3. **Move RateLimit.php to shared/**:
```bash
mv front-backend/RateLimit.php shared/
```
4. **Update both apps to use shared/**:
```php
// In front-backend/index.php
require_once __DIR__ . '/../shared/Database.php';
// In formulaire/index.php
require_once __DIR__ . '/../shared/Database.php';
```
5. **Delete duplicate Database.php files**:
```bash
rm front-backend/Database.php
rm formulaire/Database.php
```
**Result:**
```
posterg-website/
├── front-backend/ (uses shared/)
├── formulaire/ (uses shared/)
├── shared/ NEW: shared code
│ ├── Database.php
│ └── RateLimit.php
└── db/ (existing)
```
---
## Comparison Matrix
| Criteria | Option A (Restructure) | Option B (Root) | Option C (Flatten) | Option D (Quick Fix) | Current |
|----------|----------------------|-----------------|-------------------|---------------------|---------|
| **Professional** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| **Scalable** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| **Clear Separation** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| **Ease of Migration** | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | N/A |
| **Code Reuse** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ |
| **Maintainability** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
---
## Final Recommendation
### Short Term: Option D (Quick Fix)
- Takes 1 hour
- Fixes code duplication immediately
- Minimal risk
- Keeps familiar structure
### Long Term: Option A (Full Restructure)
- Plan for when you have 2-3 hours
- Professional, scalable structure
- Industry standard conventions
- Future-proof
### Do NOT: Option B (Promote to Root)
- Creates more problems than it solves
- Clutters root directory
- Doesn't fix core issues
---
## Questions to Consider
1. **How often do you modify both apps?**
- Often → Option A (shared code helps)
- Rarely → Option D is fine
2. **Will you add more applications?**
- Yes (API, mobile backend, etc.) → Option A
- No → Option D
3. **Team size?**
- Solo → Option D ok
- Team → Option A for clarity
4. **Timeline?**
- Urgent → Option D
- Can wait → Option A
---
## My Recommendation
**Start with Option D (Quick Fix), migrate to Option A later.**
**Immediate (30 mins):**
1. Create `shared/` directory
2. Move Database.php and RateLimit.php to shared/
3. Update both apps to use shared/
4. Test and deploy
**When you have time (Option A):**
- Better names (`apps/public/`, `apps/admin/`)
- Professional structure
- Industry conventions
Would you like me to help implement either approach?

View File

@@ -1,135 +0,0 @@
# Repository Restructure Plan
## Current Structure
```
posterg-website/
├── apps/
│ ├── public/ # Public website
│ └── admin/ # Admin panel
├── shared/ # Shared PHP libraries
├── database/ # Database files
├── nginx/ # Server config
└── docs/ # Documentation
```
## Proposed Structure (Idiomatic PHP)
```
posterg-website/
├── index.php # Public website root
├── memoire.php
├── search.php
├── apropos.php
├── contact.php
├── licences.php
├── assets/ # Static files (CSS, JS, images)
│ ├── posterg.css
│ ├── normalize.css
│ └── fonts/
├── inc/ # Page templates/partials
│ ├── header.php
│ └── footer.php
├── lib/ # Shared libraries (renamed from shared/)
│ ├── Database.php
│ ├── RateLimit.php
│ ├── config.php
│ └── cache/
├── admin/ # Admin panel (from apps/admin/)
│ ├── index.php
│ ├── list.php
│ ├── edit.php
│ └── ...
├── database/ # Database files & schema
│ ├── schema.sql
│ ├── test.db
│ └── ...
├── vendor/ # Third-party dependencies (gitignored)
│ └── php-live-reload/ # Local dev only
├── nginx/ # Server configuration
├── docs/ # Documentation
├── tests/ # Tests (future)
└── .gitignore # Updated
```
## Benefits
**Standard PHP structure** - Follows common conventions
**Flatter hierarchy** - Easier to navigate
**Clear separation** - lib/ for code, inc/ for templates, assets/ for static files
**Simpler paths** - Less `../../` in require statements
**Vendor folder** - Standard for third-party code
## Migration Steps
### 1. Move public to root
```bash
mv apps/public/* .
```
### 2. Move admin
```bash
mv apps/admin admin/
rm -rf apps/
```
### 3. Rename shared to lib
```bash
mv shared lib/
```
### 4. Update all require paths
- `require_once __DIR__ . '/shared/Database.php'``require_once __DIR__ . '/lib/Database.php'`
- `require_once __DIR__ . '/../shared/Database.php'``require_once __DIR__ . '/../lib/Database.php'`
### 5. Setup vendor for local dev
```bash
mkdir -p vendor/
echo "vendor/" >> .gitignore
```
## Path Changes Summary
| Old Path | New Path |
|----------|----------|
| `apps/public/index.php` | `index.php` |
| `apps/public/inc/header.php` | `inc/header.php` |
| `apps/public/assets/posterg.css` | `assets/posterg.css` |
| `apps/admin/index.php` | `admin/index.php` |
| `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `src/config.php` |
| `shared/cache/` | `src/cache/` |
## Deployment Changes
### Before
```
rsync apps/public/ posterg:/var/www/html/
rsync apps/admin/ posterg:/var/www/html/formulaire/
rsync shared/ posterg:/var/www/html/shared/
```
### After
```
rsync --exclude 'vendor' --exclude 'tests' . posterg:/var/www/html/
```
Much simpler!
## PHP Live Reload Setup
### For Local Development Only
1. Clone to `vendor/php-live-reload/` (gitignored)
2. Include in local dev server
3. Auto-refresh browser on file changes
4. Never deployed to production
### Usage
```bash
# Setup (one time)
just setup-dev
# Start dev server with live reload
just serve-public # or serve-admin
```

View File

@@ -1,163 +0,0 @@
# 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.

View File

@@ -1,406 +0,0 @@
# 🔒 Security Vulnerability Analysis — posterg-website
> **Date:** 2026-02-08
> **Scope:** Full static code review of all PHP, nginx config, and deployment files.
> **Analyst:** Claude Code
---
## Good Practices Already in Place
- ✅ SQL injection: all queries use PDO prepared statements consistently
- ✅ XSS output: `htmlspecialchars()` applied on all user-controlled output
- ✅ CSRF: tokens generated with `bin2hex(random_bytes(32))` and validated with `hash_equals()` (timing-safe) on all state-changing forms
- ✅ File upload: MIME type validated with `finfo` for cover images and thesis files
- ✅ Input validation: year, IDs, and pagination values cast to integers
- ✅ LIKE wildcard escaping implemented in public search (`Database::escapeLikeString`)
---
## 🔴 CRITICAL
### 1. Admin HTTP Basic Auth Over Plain HTTP (No TLS/HTTPS)
**File:** `nginx/posterg.conf`
The nginx config listens only on port 80. HTTP Basic Authentication Base64-encodes
credentials but **does not encrypt them**. Anyone intercepting network traffic can
decode the admin password trivially (`echo "dXNlcjpwYXNz" | base64 -d`).
```nginx
listen 80 default_server; # ← No HTTPS configured at all
auth_basic "Admin Access - Post-ERG";
auth_basic_user_file /etc/nginx/.htpasswd-posterg;
```
**Fix:** Add a TLS/HTTPS server block (e.g., Let's Encrypt via certbot) and redirect
all port 80 traffic to HTTPS:
```nginx
server {
listen 80;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem;
# ... rest of config
}
```
---
### 2. No PHP-Level Authentication in Admin Panel
**Files:** `public/admin/index.php`, `add.php`, `edit.php`, `import.php`, `thanks.php`
Every admin file calls `session_start()` only to generate a CSRF token. There is
**zero check that the user is authenticated in PHP**. If nginx is misconfigured,
restarted, or the app is accessed directly without nginx in front (e.g., via the
PHP CLI dev server), all admin functionality is fully open.
```php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// ← No authentication check whatsoever
```
**Fix:** Implement a PHP login system with session-based authentication and add an
auth guard at the top of every admin page:
```php
// public/admin/auth.php
session_start();
if (empty($_SESSION['admin_authenticated'])) {
header('Location: /admin/login.php');
exit;
}
```
---
## 🟠 HIGH
### 3. Uploaded Files Stored Inside the Webroot
**File:** `public/admin/actions/formulaire.php` (lines ~127131)
Thesis files and cover images are stored *inside* the public web directory:
```php
$uploadBaseDir = __DIR__ . "/data/theses/{$annee}/{$identifier}/";
$coverDir = __DIR__ . "/data/covers/";
// Resolves to: /var/www/posterg/public/admin/actions/data/...
```
This means uploaded PDFs, ZIPs, MP4s, and images sit in a potentially web-accessible
path. The nginx rule `location ^~ /data/ { deny all; }` only blocks the URL path
`/data/…` from the server root — it does **not** block `/admin/actions/data/…`.
**Fix:** Store uploads outside the webroot and serve through a controller:
```php
$uploadBaseDir = '/var/www/posterg/storage/theses/' . $annee . '/' . $identifier . '/';
$coverDir = '/var/www/posterg/storage/covers/';
```
---
### 4. File Path Mismatch — Media Files Cannot Be Served to Public
**Files:** `formulaire.php` (stores), `memoire.php` and `search.php` (reference)
Files are stored at `public/admin/actions/data/theses/YEAR/ID/file.ext` but recorded
in the database as `data/theses/YEAR/ID/file.ext`. When a browser loads `/memoire.php`,
relative links resolve to `/data/theses/…`, which nginx blocks:
```php
// memoire.php — tries to embed a URL that nginx denies
<embed src="<?= htmlspecialchars($file['file_path']); ?>" type="application/pdf">
```
```nginx
location ^~ /data/ {
deny all; # ← This kills the public file references
}
```
This is both a security misconfiguration and a **functional bug** (files never display
on the public site).
**Fix:** Use a consistent, intentionally accessible path (e.g., `/var/www/posterg/storage/`)
and serve files through a dedicated PHP endpoint that validates access rights.
---
### 5. Rate Limiter Vulnerable to IP Spoofing
**File:** `src/RateLimit.php` — method `getClientIdentifier()`
```php
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // ← Fully attacker-controlled
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP']; // ← Also attacker-controlled
}
```
Any attacker can set `X-Forwarded-For: 1.2.3.4` to a new IP on every request,
completely bypassing the rate limiter. The `/search` endpoint has no other
brute-force protection.
**Fix:** Use `$_SERVER['REMOTE_ADDR']` only. If behind a trusted reverse proxy,
whitelist the proxy and extract the last trusted IP from the forwarded header:
```php
private function getClientIdentifier(): string {
// Only trust REMOTE_ADDR unless explicitly behind a known proxy
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
```
---
### 6. `.htaccess` Security Controls Silently Ignored by nginx
**File:** `public/admin/.htaccess`
This file contains X-Frame-Options, CSP, X-Content-Type-Options, `Options -Indexes`,
and file-protection rules — all **Apache-specific directives**. The production server
runs nginx, which never reads `.htaccess`. None of these controls are active.
```apache
Header always set Content-Security-Policy "..." # ← Dead letter on nginx
Options -Indexes # ← Never applied
<FilesMatch "(error\.log)$"> # ← nginx doesn't parse this
Require all denied
```
**Fix:** Move all security rules into the nginx `server` block directly.
---
## 🟡 MEDIUM
### 7. LIKE Wildcard Injection in Admin Search
**File:** `public/admin/index.php` (lines ~3842)
```php
$searchParam = "%$searchQuery%"; // ← % and _ characters not escaped
$params[] = $searchParam;
```
While SQL injection is prevented by prepared statements, unescaped `%` and `_`
trigger unintended SQL wildcard matching (e.g., `%` matches every row, `_` matches
any single character). The public `Database::searchTheses()` already does this
correctly — the admin search does not.
**Fix:**
```php
$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $searchQuery);
$searchParam = "%" . $escaped . "%";
```
---
### 8. Session Cookies Not Hardened
**Files:** All admin PHP files using `session_start()`
No secure cookie parameters are configured before `session_start()`:
- No `Secure` flag → cookies sent over plain HTTP
- No `HttpOnly` flag → accessible to JavaScript (XSS cookie theft)
- No `SameSite` → vulnerable to cross-site request forgery via cookies
**Fix:** Add before every `session_start()`:
```php
session_set_cookie_params([
'lifetime' => 0,
'path' => '/admin',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
]);
session_start();
```
---
### 9. Error Log in Potentially Web-Accessible Path
**File:** `public/admin/actions/formulaire.php`
```php
ini_set('error_log', 'error.log'); // Relative path → public/admin/actions/error.log
```
The nginx config denies `.md`, `.txt`, `.sql`, `.sh`, `.json` files — but **not
`.log` files**. The `.htaccess` rule blocking `error.log` is ignored by nginx (see #6).
Error logs can contain PHP stack traces, internal file paths, and database details.
**Fix:** Use an absolute path outside the webroot:
```php
ini_set('error_log', '/var/log/posterg/error.log');
```
---
### 10. External CDN Stylesheet Without Subresource Integrity (SRI)
**File:** `templates/admin/head.php`
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
```
If the CDN is compromised, attackers can inject malicious CSS into the admin panel.
CSS-based data exfiltration (capturing form inputs via attribute selectors) is a
known attack vector.
**Fix:** Pin the resource with an integrity hash:
```html
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
integrity="sha256-<computed-hash>"
crossorigin="anonymous">
```
Generate the hash: `curl -s https://cdn.jsdelivr.net/npm/water.css@2/out/water.css | openssl dgst -sha256 -binary | base64`
---
### 11. Missing Content Security Policy on Public Pages
**File:** `nginx/posterg.conf`
The nginx config sets `X-Frame-Options`, `X-Content-Type-Options`, etc., but defines
**no `Content-Security-Policy`** header for public routes. This leaves the public
site without a last line of defence if any output escaping is ever missed.
**Fix:** Add to the main `server` block in nginx:
```nginx
add_header Content-Security-Policy
"default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none';"
always;
```
---
### 12. CSV Import Without Server-Side MIME Validation
**File:** `public/admin/import.php`
The cover image upload validates MIME type with `finfo`, but the CSV upload does not:
```php
// Only HTML-side restriction — trivially bypassed:
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
```
**Fix:**
```php
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['csv_file']['tmp_name']);
if (!in_array($mime, ['text/plain', 'text/csv', 'application/csv'])) {
throw new Exception("Type de fichier invalide. Seuls les fichiers CSV sont acceptés.");
}
```
---
## 🔵 LOW
### 13. Deprecated `X-XSS-Protection` Header
**Files:** `nginx/posterg.conf`, `public/admin/.htaccess`
`X-XSS-Protection "1; mode=block"` has been removed from Chrome and is ignored by
Firefox. In some legacy browsers it can actually *introduce* reflected-XSS
vulnerabilities by blocking legitimate content.
**Fix:** Remove the header. Rely on a properly configured CSP instead.
---
### 14. Missing `rel="noreferrer"` on External Links
**File:** `public/admin/thanks.php`
```php
<a href="..." target="_blank" rel="noopener">
```
`noopener` prevents `window.opener` hijacking, but `noreferrer` is also needed to
prevent the internal admin URL from leaking in the HTTP `Referer` header to external
sites.
**Fix:** Use `rel="noopener noreferrer"` on all `target="_blank"` links.
---
### 15. Unescaped Integer Outputs (Cosmetic / Defence in Depth)
**Files:** `public/index.php`, `public/search.php`
```php
<?= $item["id"] ?> // integer from DB
<?= $year; ?> // integer from DB
<?= $totalItems; ?> // integer from COUNT()
```
Safe in practice (integers cannot carry XSS payloads), but inconsistent with the
rest of the codebase which uses `htmlspecialchars()` everywhere.
**Fix:** Apply explicit integer cast for clarity: `<?= (int) $item['id'] ?>`.
---
### 16. Redundant `DATABASE_PATH` Constant
**File:** `config/bootstrap.php`
```php
define('DATABASE_PATH', APP_ROOT . '/storage/test.db');
```
This constant is never used anywhere. `Database.php` uses `getDatabasePath()` from
`src/config.php`. The duplicate creates confusion about which configuration is
authoritative and could lead to future bugs if someone uses the wrong one.
**Fix:** Remove the `DATABASE_PATH` define from `bootstrap.php`.
---
## Summary Table
| # | Issue | Severity | File(s) |
|---|-------|----------|---------|
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | nginx/posterg.conf |
| 2 | No PHP-level authentication in admin | 🔴 CRITICAL | public/admin/\*.php |
| 3 | Uploaded files stored inside webroot | 🟠 HIGH | admin/actions/formulaire.php |
| 4 | File path mismatch — media broken & insecure | 🟠 HIGH | formulaire.php, memoire.php |
| 5 | Rate limiter bypassed by IP spoofing | 🟠 HIGH | lib/RateLimit.php |
| 6 | .htaccess rules ignored by nginx | 🟠 HIGH | public/admin/.htaccess |
| 7 | LIKE wildcard injection in admin search | 🟡 MEDIUM | public/admin/index.php |
| 8 | Session cookies not hardened | 🟡 MEDIUM | All admin PHP files |
| 9 | error.log in web-accessible path | 🟡 MEDIUM | admin/actions/formulaire.php |
| 10 | CDN stylesheet without SRI | 🟡 MEDIUM | admin/inc/head.php |
| 11 | Missing CSP on public pages | 🟡 MEDIUM | nginx/posterg.conf |
| 12 | CSV upload missing MIME validation | 🟡 MEDIUM | admin/import.php |
| 13 | Deprecated X-XSS-Protection header | 🔵 LOW | nginx/posterg.conf |
| 14 | Missing rel="noreferrer" on external links | 🔵 LOW | admin/thanks.php |
| 15 | Unescaped integers (defence in depth) | 🔵 LOW | index.php, search.php |
| 16 | Redundant DATABASE_PATH constant | 🔵 LOW | config/bootstrap.php |
---
*End of report.*

View File

@@ -1,350 +0,0 @@
# Security Implementation - Production Ready
## Overview
The search system has been hardened with comprehensive security measures and is now **production-ready**.
## Security Features Implemented
### ✅ 1. SQL Injection Protection
- **Method**: PDO prepared statements with parameter binding
- **Status**: ✅ SECURE
- **Test Result**: All injection attempts treated as literal strings
- **Coverage**: All database queries
### ✅ 2. XSS (Cross-Site Scripting) Protection
- **Method**: `htmlspecialchars()` on all output
- **Status**: ✅ SECURE
- **Coverage**: All user-generated content display
### ✅ 3. Wildcard Injection Prevention
- **Method**: Escape LIKE wildcards (`%`, `_`) before queries
- **Implementation**: `escapeLikeString()` private method
- **SQL**: Uses `ESCAPE '\\'` clause in all LIKE queries
- **Status**: ✅ SECURE
- **Test Result**: Searching for `%` returns 0 results instead of all records
**Example:**
```php
// User input: "%"
// Before: '%' . $query . '%' → "%%%" (matches everything)
// After: '%' . escapeLikeString($query) . '%' → "%\%%" (matches literal %)
```
### ✅ 4. Input Length Validation
- **Limits**:
- Query: 200 characters max
- Orientation/AP/Finality: 100 characters max
- Keywords/Formats: 100 characters max
- Languages: 50 characters max
- **Status**: ✅ SECURE
- **Test Result**: 4000-character input rejected with error message
### ✅ 5. Year Range Validation
- **Allowed Range**: 1900-2100
- **Status**: ✅ SECURE
- **Test Result**: Year 999999 rejected with "Invalid year" error
### ✅ 6. Pagination Limits
- **Maximum per page**: 100 results
- **Minimum per page**: 1 result
- **Offset validation**: Non-negative values only
- **Status**: ✅ SECURE
- **Test Result**: Request for 500 results limited to 100
### ✅ 7. Rate Limiting (NEW)
- **Limit**: 30 requests per minute per IP address
- **Method**: File-based tracking
- **HTTP Status**: 429 Too Many Requests when exceeded
- **Headers Sent**:
- `X-RateLimit-Limit: 30`
- `X-RateLimit-Remaining: N`
- `X-RateLimit-Reset: timestamp`
- `Retry-After: seconds`
- **Status**: ✅ SECURE
- **Test Result**: All tests pass, 6th request blocked correctly
**Features:**
- Automatic cleanup of old rate limit files
- Per-IP tracking (handles X-Forwarded-For for proxies)
- Graceful error message in French
- 1% chance of cleanup on each request (low overhead)
---
## Files Modified/Created
### Modified Files
1. **Database.php** - Enhanced with security features:
- Added `escapeLikeString()` - Escape SQL LIKE wildcards
- Added `validateSearchParams()` - Comprehensive input validation
- Updated `searchTheses()` - Secure implementation with validation
- Updated `countSearchResults()` - Secure implementation with validation
2. **search.php** - Added rate limiting and error handling:
- Rate limiting check at the beginning
- Rate limit headers sent on all responses
- Validation error display
- 429 error page for rate limit exceeded
3. **inc/header.php** - Added search navigation link
### New Files Created
1. **RateLimit.php** - Rate limiting class:
- File-based request tracking
- Configurable limits and time windows
- Automatic cleanup
- HTTP header support
2. **create_test_db.php** - Test database generator
3. **test_search.php** - Functional tests
4. **test_security_updated.php** - Security validation tests
5. **test_rate_limit.php** - Rate limiting tests
6. **SECURITY_ANALYSIS.md** - Detailed security analysis
7. **SECURITY_IMPLEMENTATION.md** - This file
8. **SEARCH_FEATURE.md** - Feature documentation
---
## Test Results
### Security Tests: ✅ ALL PASSED
```
✅ SECURE from SQL Injection (prepared statements)
✅ SECURE from wildcard injection (escaped)
✅ SECURE from DoS via long inputs (length validation)
✅ SECURE from invalid year values (range validation)
✅ SECURE from excessive pagination (max 100 per page)
✅ SECURE from negative offsets (validated)
```
### Rate Limiting Tests: ✅ ALL PASSED
```
✅ Rate limiting works correctly
✅ Requests are tracked per client
✅ Limits are enforced
✅ Reset time is calculated
✅ Headers are sent
✅ Cleanup removes old files
```
### Functional Tests: ✅ ALL PASSED
- Full-text search: Working
- Year filtering: Working
- Orientation filtering: Working
- AP program filtering: Working
- Keyword search: Working
- Combined filters: Working
- Pagination: Working
---
## Configuration
### Rate Limiting
Current settings in `search.php`:
```php
$rateLimit = new RateLimit(30, 60); // 30 requests per minute
```
To adjust:
```php
// More restrictive (10 requests per minute)
$rateLimit = new RateLimit(10, 60);
// More permissive (60 requests per minute)
$rateLimit = new RateLimit(60, 60);
// Different time window (100 requests per hour)
$rateLimit = new RateLimit(100, 3600);
```
### Pagination
Current setting in Database.php:
```php
$limit = max(1, min(100, intval($limit))); // Max 100 per page
```
Default in search.php:
```php
$itemsPerPage = min(100, isset($_GET['per_page']) ? intval($_GET['per_page']) : 20);
```
Users can request different page sizes:
- `search.php?per_page=50` - 50 results per page
- `search.php?per_page=1000` - Capped at 100
---
## Security Headers
Consider adding these to production (in header.php or .htaccess):
```php
// Content Security Policy
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' cdn.jsdelivr.net;");
// Prevent MIME sniffing
header("X-Content-Type-Options: nosniff");
// Prevent clickjacking
header("X-Frame-Options: DENY");
// XSS Protection
header("X-XSS-Protection: 1; mode=block");
// Referrer Policy
header("Referrer-Policy: strict-origin-when-cross-origin");
```
---
## Production Checklist
- [x] SQL injection protection
- [x] XSS protection
- [x] Wildcard injection protection
- [x] Input length validation
- [x] Input range validation
- [x] Rate limiting
- [x] Pagination limits
- [x] Error handling
- [x] Security testing
- [ ] HTTPS enabled (server configuration)
- [ ] Security headers added (recommended)
- [ ] Database backups configured
- [ ] Error log monitoring setup
- [ ] Rate limit cache directory permissions set (755)
---
## Error Handling
### User-Facing Errors
1. **Rate Limit Exceeded** (429):
```
Trop de requêtes
Vous avez dépassé la limite de 30 recherches par minute.
Veuillez réessayer dans X secondes.
```
2. **Validation Error** (400):
```
Erreur de validation : Search query too long (max 200 characters)
```
3. **Database Error** (500):
```
Une erreur est survenue lors de la recherche.
```
### Error Logging
All errors are logged to `error.log`:
- Database connection failures
- Search validation errors
- Unexpected exceptions
- Rate limit violations (can be enabled)
---
## Performance Considerations
### Database Indexes
Ensure these indexes exist (from schema.sql):
- `idx_theses_year` - Year filtering
- `idx_theses_published` - Published filter
- `idx_theses_orientation` - Orientation filtering
- `idx_theses_ap_program` - AP program filtering
- `idx_thesis_keywords_thesis` - Keyword searches
### Rate Limit Cache
- Location: `front-backend/cache/rate_limit/`
- File per IP: `{md5_hash}.json`
- Automatic cleanup: Old files removed after 24h
- Permissions: Ensure directory is writable (755)
---
## Monitoring Recommendations
### Metrics to Track
1. **Search patterns**:
- Most searched terms
- Filter combinations used
- Peak search times
2. **Rate limiting**:
- Number of 429 errors
- IPs hitting rate limits
- Potential abuse patterns
3. **Performance**:
- Search query duration
- Database response time
- Cache file growth
### Log Analysis
Monitor `error.log` for:
- `Search validation error:` - Invalid inputs
- `Error in search:` - Database issues
- `Suspicious search pattern from` - Potential attacks (can be enabled)
---
## Maintenance
### Weekly Tasks
- Review error logs
- Check rate limit violations
- Monitor disk usage of cache directory
### Monthly Tasks
- Analyze search patterns
- Review and update security measures
- Test backup restoration
### As Needed
- Adjust rate limits based on usage
- Update input validation rules
- Optimize slow queries
---
## Summary
The search system is now **production-ready** with:
**Comprehensive Security**: All major attack vectors covered
**Rate Limiting**: Prevents abuse and DoS attacks
**Input Validation**: All user inputs sanitized and validated
**Error Handling**: Graceful degradation with user-friendly messages
**Testing**: Full test coverage with passing results
**Documentation**: Complete implementation and security docs
**Risk Level**: LOW - Suitable for production deployment
**Next Steps**:
1. Enable HTTPS on production server
2. Add security headers
3. Configure error log monitoring
4. Set up database backups
5. Monitor search usage patterns

View File

@@ -1,111 +0,0 @@
# Server Setup
## One-time setup (before first deploy)
Run the setup script on the server. It creates `/var/www/posterg`, sets the
correct ownership/permissions, and adds the deploy user to the `posterg` group:
```bash
just setup-server
```
What the script does:
- Creates the `posterg` group if it doesn't exist
- Adds both the SSH user (read from `~/.ssh/config` via `ssh -G posterg`) and `www-data` to `posterg`
- Creates `/var/www/posterg` owned by `www-data:posterg`
- Sets all directories to **2775** (`rwxrws r-x`) — the setgid bit ensures
new files/dirs inherit the `posterg` group, which is required for
`rsync --chown=www-data:posterg` to succeed
- Sets files to **664**
- Sets `storage/` to **2775**, database files to **660**
> **Important:** After running `setup-server`, log out and back in on the server
> (or run `newgrp posterg`) so the new group membership is active before deploying.
### Why setgid (2775) on directories?
rsync uses `--chown=www-data:posterg` to set ownership on transferred files.
For this to work, the receiving process (running as `padlock`) must have write
permission on every target directory. Without the setgid bit:
- Newly created subdirectories inherit `padlock`'s primary group
- `www-data` (nginx/php-fpm) can't write to them → 403 errors
- `padlock` can't write to dirs owned by `www-data` → rsync Permission denied
With `2775 + group=posterg`:
- Both `padlock` and `www-data` are in `posterg` → both can write
- New subdirs automatically get `posterg` as their group
- rsync can create files and directories without errors
## Deploying the application
```bash
# Push all app files
just deploy
# Push initial database (aborts if remote DB already exists)
just deploy-db
```
## Applying the nginx config
```bash
just deploy-nginx
```
Uploads `nginx/posterg.conf` to the server, runs `scripts/deploy-server.sh`
(which installs it into `/etc/nginx/sites-available/posterg`), then reloads nginx.
## Managing admin users
```bash
ssh posterg "sudo bash /var/www/posterg/scripts/manage-admin-users.sh"
```
Interactive menu for adding, changing, and deleting htpasswd entries at
`/etc/nginx/.htpasswd-posterg`.
## Troubleshooting
### rsync: Permission denied on mkdir or mkstemp
The remote directory permissions are wrong. Run:
```bash
just setup-server
```
Then log out/in on the server and retry `just deploy`.
If you need to fix it manually (replace `youruser` with your remote username):
```bash
ssh posterg
sudo DEPLOY_USER=youruser bash /tmp/setup-server.sh
```
Or directly:
```bash
ssh posterg
sudo chown -R www-data:posterg /var/www/posterg
sudo find /var/www/posterg -type d -exec chmod 2775 {} \;
sudo find /var/www/posterg -type f -exec chmod 664 {} \;
sudo usermod -aG posterg youruser
```
### Nginx 403 Forbidden
```bash
ssh posterg
sudo find /var/www/posterg -type d -exec chmod 2775 {} \;
sudo find /var/www/posterg -type f -exec chmod 664 {} \;
sudo chmod 660 /var/www/posterg/storage/*.db
```
### Database permission error
```bash
ssh posterg
sudo chown www-data:posterg /var/www/posterg/storage/posterg.db
sudo chmod 660 /var/www/posterg/storage/posterg.db
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +0,0 @@
# Website Structure Simplification
## Problem Identified
The initial migration used the **Standard PHP Package Skeleton**, which is designed for **reusable PHP packages/libraries** (like Composer packages), not for websites.
This resulted in:
- ❌ Overcomplicated structure (`var/`, `resources/`, complex bootstrap)
- ❌ Unused directories (`var/cache/`, `var/logs/`, `var/tmp/`)
- ❌ Package-oriented naming (`resources/views/`)
- ❌ Unnecessary constants and helper functions
## Solution: Simplified for Website
### Removed
1. **`var/` directory** - Completely unused, only needed for complex applications with:
- Custom caching systems
- Application-level logging
- Temporary file processing
2. **`resources/` directory** - Package-oriented name, renamed to simple `includes/`
3. **Complex bootstrap** - Removed unused constants:
- `VAR_ROOT`, `CACHE_ROOT`, `LOGS_ROOT`
- `PUBLIC_ROOT`, `CONFIG_ROOT`, `RESOURCES_ROOT`
- `view()` helper function
### Simplified Structure
```
posterg-website/
├── public/ # DocumentRoot (web-accessible) ✅
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── includes/ # Simple template includes ✅
│ ├── header.php
│ └── footer.php
├── config/ # Minimal configuration ✅
│ └── bootstrap.php (simplified)
├── database/ # Database files (private)
│ └── test.db
├── lib/ # PHP classes (private)
│ ├── Database.php
│ ├── RateLimit.php
│ └── cache/rate_limit/ (used by RateLimit)
├── vendor/ # Composer dependencies (private)
└── tests/ # Tests (private)
```
### Simplified config/bootstrap.php
**Before:** 66 lines with many unused constants
**After:** 33 lines with only essentials
```php
<?php
// Define application root
define('APP_ROOT', dirname(__DIR__));
// Database path
define('DATABASE_PATH', APP_ROOT . '/storage/test.db');
// Error reporting (dev vs production)
if (php_sapi_name() === 'cli-server') {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
error_reporting(E_ALL);
ini_set('display_errors', '0');
}
// Simple helper for templates
function include_template($name) {
include APP_ROOT . '/includes/' . $name;
}
// Composer autoload
if (file_exists(APP_ROOT . '/vendor/autoload.php')) {
require_once APP_ROOT . '/vendor/autoload.php';
}
```
### Simplified PHP Files
**Before:**
```php
require_once __DIR__ . '/../config/bootstrap.php';
require_once LIB_ROOT . '/Database.php';
view('header.php', ['pageTitle' => $title]);
```
**After:**
```php
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/lib/Database.php';
include APP_ROOT . '/includes/header.php';
```
## Comparison: Package vs Website
| Feature | PHP Package | PHP Website (this project) |
|---------|-------------|----------------------------|
| Purpose | Reusable library | Single website |
| Structure | Complex (PSR-4, namespaces) | Simple (includes, classes) |
| Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `src/` |
| Autoloading | PSR-4 namespaces | Simple require statements |
| Config | Complex bootstrap with many constants | Minimal bootstrap |
| Caching | `var/cache/` with framework | Simple file-based if needed |
| Logging | `var/logs/` with logger | PHP error_log |
## Benefits of Simplification
### Before (Package-oriented)
- ❌ 66-line bootstrap file
- ❌ 10+ unused constants
-`resources/views/` (confusing name)
-`var/` directory (completely unused)
- ❌ Helper functions for simple includes
- ❌ Over-engineered for a simple website
### After (Website-focused)
- ✅ 33-line bootstrap file (50% smaller)
- ✅ 2 essential constants (APP_ROOT, DATABASE_PATH)
-`includes/` (clear, simple name)
- ✅ No unused directories
- ✅ Standard PHP `include` statements
- ✅ Appropriate for a PHP website
### Security (Unchanged)
- ✅ Still uses `public/` as DocumentRoot
- ✅ Database still outside web root
- ✅ Config still private
- ✅ All security improvements retained
## Testing
All PHP files pass syntax check:
```bash
$ php -l config/bootstrap.php # OK
$ php -l public/index.php # OK
$ php -l public/search.php # OK
$ php -l public/memoire.php # OK
$ php -l public/admin/index.php # OK
```
Start dev server:
```bash
$ just serve
```
## When You WOULD Need var/
You would need a `var/` directory if you were building:
- A framework (Laravel, Symfony)
- A CMS (WordPress, Drupal)
- An application with:
- Template compilation/caching
- Session storage
- File upload processing
- Application-level logging
- Queue systems
For your thesis website: **Not needed**
## Conclusion
- ✅ Structure is now appropriate for a **PHP website**
- ✅ Removed package-oriented complexity
- ✅ Kept all security improvements (public/ directory)
- ✅ Simpler, cleaner, easier to maintain
- ✅ Still follows best practices (just the right ones)
The core improvement (public/ directory for security) remains intact, but now with a structure that fits a website, not a reusable package.

View File

@@ -1,299 +0,0 @@
# Test Centralization Summary
All tests have been centralized into the `tests/` directory following standard testing conventions.
## 📁 New Test Structure
```
tests/
├── run-tests.php # Main test runner
├── README.md # Test documentation
├── Unit/ # Unit tests
│ ├── DatabaseTest.php # Database connection & queries
│ └── RateLimitTest.php # Rate limiting functionality
├── Integration/ # Integration tests
│ └── SearchTest.php # Search functionality
└── Security/ # Security tests
└── SecurityTest.php # SQL injection & XSS protection
```
## ✅ What Was Done
### 1. Created Test Directory Structure
- `tests/Unit/` - Tests for individual components
- `tests/Integration/` - Tests for feature workflows
- `tests/Security/` - Tests for security vulnerabilities
### 2. Moved & Created Tests
**Before:**
- `test_db.php` (root) - Basic database test
- `run-tests.php` (root) - Old test runner
**After:**
- `tests/Unit/DatabaseTest.php` - Comprehensive database testing
- `tests/Unit/RateLimitTest.php` - Rate limit testing
- `tests/Integration/SearchTest.php` - Search functionality testing
- `tests/Security/SecurityTest.php` - Security testing
- `tests/run-tests.php` - New unified test runner
- `tests/README.md` - Complete test documentation
### 3. Updated Justfile
**New Commands:**
```bash
just test # Run all tests
just test-unit # Run unit tests only
just test-integration # Run integration tests only
just test-security # Run security tests only
just syntax # Check PHP syntax
```
**Removed:**
- Old scattered test commands
- Duplicate test logic
### 4. Removed Old Files
- ✅ Deleted `test_db.php` from root
- ✅ Deleted `run-tests.php` from root
---
## 🚀 Running Tests
### Run All Tests (Recommended)
```bash
just test
```
Output:
```
╔════════════════════════════════════════════╗
║ Post-ERG Test Suite ║
╚════════════════════════════════════════════╝
┌─────────────────────────────────────────┐
│ Database (Unit) │
└─────────────────────────────────────────┘
✓ PASS: Database connection successful
✓ PASS: Found 16 published theses
...
✅ TEST PASSED
...
╔════════════════════════════════════════════╗
║ Test Summary ║
╠════════════════════════════════════════════╣
║ Total: 4 ║
║ Passed: 4 ✅ ║
║ Failed: 0 ║
╚════════════════════════════════════════════╝
✅ All tests passed!
```
### Run Specific Test Suites
```bash
# Unit tests only
just test-unit
# Integration tests only
just test-integration
# Security tests only
just test-security
# Syntax check only
just syntax
```
### Run Individual Tests
```bash
# Database test
php tests/Unit/DatabaseTest.php
# Search test
php tests/Integration/SearchTest.php
# Security test
php tests/Security/SecurityTest.php
# Rate limit test
php tests/Unit/RateLimitTest.php
```
---
## ✅ Test Coverage
### Unit Tests (2)
**DatabaseTest.php** - 4 assertions
- ✅ Database connection
- ✅ Count published theses
- ✅ Get published theses
- ✅ Get single thesis by ID
**RateLimitTest.php** - 5 assertions
- ✅ RateLimit initialization
- ✅ check() method returns boolean
- ✅ sendHeaders() executes
- ✅ getResetTime() returns valid value
- ✅ cleanup() executes
### Integration Tests (1)
**SearchTest.php** - 3 assertions
- ✅ Empty search query handling
- ✅ Search for specific terms
- ✅ Special characters in search
### Security Tests (1)
**SecurityTest.php** - 3 test groups
- ✅ SQL injection protection (4 injection attempts blocked)
- ✅ Invalid ID rejection (4 invalid IDs rejected)
- ✅ XSS protection verification
**Total: 4 test files, 15 assertions**
---
## 📝 Test Results
All tests passing:
```
✅ Database (Unit) - PASSED
✅ Rate Limit (Unit) - PASSED
✅ Search (Integration) - PASSED
✅ Security - PASSED
Total: 4
Passed: 4 ✅
Failed: 0
```
---
## 🎯 Benefits
### Before Centralization
- ❌ Tests scattered in root directory
- ❌ No clear organization
- ❌ Hard to run specific test types
- ❌ No test documentation
- ❌ Inconsistent test format
### After Centralization
- ✅ All tests in `tests/` directory
- ✅ Clear organization (Unit/Integration/Security)
- ✅ Easy to run any combination
- ✅ Comprehensive test documentation
- ✅ Consistent test format and output
- ✅ Single test runner
- ✅ Beautiful formatted output
---
## 📚 Writing New Tests
### 1. Choose Test Type
- **Unit Test** → `tests/Unit/` - Tests single functions/classes
- **Integration Test** → `tests/Integration/` - Tests feature workflows
- **Security Test** → `tests/Security/` - Tests security measures
### 2. Use Template
```php
<?php
/**
* Test Name
* Description
*/
require_once __DIR__ . '/../../lib/YourClass.php';
echo "Test Name\n";
echo "=========\n\n";
try {
echo "Test 1: Description\n";
// ... test code ...
echo "✓ PASS: Test passed\n\n";
echo "✅ All tests passed!\n";
return true;
} catch (Exception $e) {
echo "❌ FAIL: " . $e->getMessage() . "\n";
return false;
}
```
### 3. Add to Test Runner
Edit `tests/run-tests.php` and add your test to the `$testFiles` array:
```php
['name' => 'Your Test Name', 'path' => __DIR__ . '/Unit/YourTest.php'],
```
### 4. Run It
```bash
just test
```
---
## 🔄 CI/CD Integration (Future)
Tests are ready for CI/CD integration:
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Run tests
run: php tests/run-tests.php
```
---
## 📖 Related Documentation
- [Test README](../tests/README.md) - Complete test documentation
- [Database Specification](../storage/DATABASE_SPECIFICATION.md)
- [Security Documentation](SECURITY.md)
---
## ✨ Quick Reference
| Command | Description |
|---------|-------------|
| `just test` | Run all tests |
| `just test-unit` | Unit tests only |
| `just test-integration` | Integration tests only |
| `just test-security` | Security tests only |
| `just syntax` | Check PHP syntax |
| `php tests/run-tests.php` | Run test runner directly |
---
**All tests centralized and passing!**

View File

@@ -1,101 +0,0 @@
# 🔒 Security TODO — posterg-website
> Based on `docs/SECURITY_ANALYSIS.md` (2026-02-08).
> Tracks resolution status for all identified vulnerabilities.
---
## ✅ Resolved
### Infrastructure / Deployment
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | **TLS is terminated upstream by the reverse proxy** (outside nginx). nginx.conf does not need to handle TLS directly. |
| 3 | Uploaded files stored inside the webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. `formulaire.php` updated. |
| 4 | File path mismatch — media files broken and insecure | 🟠 HIGH | DB paths are now storage-relative (`theses/YEAR/ID/file`, `covers/file`). New controller `public/media.php` serves files safely. `memoire.php` and `search.php` updated to use `/media.php?path=…`. Cover recording in `formulaire.php` fixed (was never inserted into DB). |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `src/RateLimit.php` `getClientIdentifier()` now uses `REMOTE_ADDR` only. |
| 6 | `.htaccess` security rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf` (CSP to `/admin/` block, `.log` denial, `autoindex off`). See `nginx/HTACCESS_TO_NGINX.md`. |
| 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | **Removed** from `nginx/posterg.conf`; see `nginx/SECURITY_HEADERS.md` for rationale. |
---
### Frontend / Assets
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 10 | CDN stylesheet (water.css) without Subresource Integrity | 🟡 MEDIUM | **CDN will not be used in production.** The stylesheet will be self-hosted, eliminating the supply-chain risk entirely. |
---
### Frontend
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 14 | Missing `rel="noreferrer"` on external `target="_blank"` links | 🔵 LOW | `rel="noopener noreferrer"` applied in `public/admin/thanks.php`. |
---
### Code Quality / Defence in Depth
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 15 | Unescaped integer outputs (inconsistency) | 🔵 LOW | Explicit `(int)` casts added in `public/index.php` and `public/search.php`. |
| 16 | Redundant `DATABASE_PATH` constant never used | 🔵 LOW | `define('DATABASE_PATH', …)` removed from `config/bootstrap.php`. |
---
### Admin Panel — Authentication & Sessions
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 2 | No PHP-level authentication in admin panel | 🔴 CRITICAL | `src/AdminAuth.php` implements a session guard with `password_verify` + `session_regenerate_id`. All admin PHP files now call `AdminAuth::requireLogin()` instead of bare `session_start()`. Credentials stored in gitignored `config/admin_credentials.php` (define `ADMIN_PASSWORD_HASH`). No-op when constant is absent (dev / cli-server). Also resolves item #8 (session cookie hardening via `session_set_cookie_params` before `session_start`). See `nginx/PHP_AUTH_LAYER.md`. |
| 8 | Session cookies not hardened (`Secure`, `HttpOnly`, `SameSite` missing) | 🟡 MEDIUM | **Resolved as part of item #2.** `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0` before every `session_start()`. |
---
## 🔧 In Progress
### Database / Input Handling
| # | Issue | Severity | Status |
|---|-------|----------|--------|
| 7 | LIKE wildcard injection in admin search (`public/admin/index.php`) | 🟡 MEDIUM | **Needs standardisation of DB input sanitisation across the whole codebase.** The public `Database::searchTheses()` already escapes `%` and `_` correctly; the same pattern must be applied to admin search and any other raw LIKE queries. |
---
## ❌ Not Yet Implemented
### Infrastructure / Deployment
| # | Issue | Severity | Files |
|---|-------|----------|-------|
| 11 | Missing Content-Security-Policy on public pages | 🟡 MEDIUM | `nginx/posterg.conf` → add `Content-Security-Policy` header to main server block (admin already has CSP via item #6) |
---
### Admin Panel — Error Logging
| # | Issue | Severity | Files |
|---|-------|----------|-------|
| 9 | `error.log` written to a web-accessible path | 🟡 MEDIUM | `public/admin/actions/formulaire.php` → use absolute path outside webroot |
---
### File Import
| # | Issue | Severity | Files |
|---|-------|----------|-------|
| 12 | CSV import missing server-side MIME validation | 🟡 MEDIUM | `public/admin/import.php` → add `finfo` MIME check |
---
## Priority Order for Remaining Work
1. 🔴 **CRITICAL** — ✅ All done (items 12)
2. 🟡 **MEDIUM** — Items 7, 9, 11, 12 (sanitisation standardisation, error log, CSP, CSV MIME) — item 8 resolved with item 2
3. 🔵 **LOW** — ✅ All done (items 1316)
---
*Last updated: 2026-02-08*

62
docs/TODO.md Normal file
View File

@@ -0,0 +1,62 @@
# TODO — Docs Deduplication & Standardization
## Analysis
- 36 markdown files, many heavily duplicated across topic clusters
- Filenames inconsistent (SCREAMING_CASE, Title-Case, lowercase)
- Historical migration docs scattered across 10+ files
- Security docs split across 5 files with overlapping content
- Database docs duplicated across 4 files (~72KB → ~20KB)
## Plan
### Consolidate into authoritative docs
- [x] `database.md` — merge DATABASE_SPECIFICATION + QUICK_SCHEMA_REFERENCE + DATABASE_CONFIG + SETUP.md (schema & config sections)
- [x] `deployment.md` — merge SERVER_SETUP + COMPLETE_DEPLOYMENT_GUIDE + DEPLOYMENT_STEPS
- [x] `security.md` — merge SECURITY_ANALYSIS + TODO.SECURITY
- [x] `development.md` — merge DEVELOPMENT_GUIDE + LIVE_RELOAD_SETUP + TEST_CENTRALIZATION
- [x] `migration-history.md` — consolidate all past migration docs into one reference
### Rename to lowercase kebab-case (standard convention)
- [x] SEARCH_FEATURE.md → search.md
- [x] IMPORT.md → import.md
- [x] ORM_ASSESSMENT.md → orm-assessment.md
- [x] REFACTORING_RECOMMENDATIONS.md → refactoring.md
- [x] CSS_CLEANUP.md → css.md
- [x] posterg_fiche-technique.md → spec-sheet.md
- [x] TESTING_BEST_PRACTICES.md → testing.md
- [x] ANALYSIS_PHP_VS_FLASK.md → php-vs-flask.md
### Remove (superseded / not docs)
- [x] Context.md (58KB research notes)
- [x] chat-export-2026-04-02.md (chat log)
- [x] SECURITY.md (pre-SQLite, superseded by security.md)
- [x] SECURITY_IMPLEMENTATION.md (search security → covered by search.md + security.md)
- [x] README_SECURE_SEARCH.md (duplicate of SECURITY_IMPLEMENTATION)
- [x] SETUP.md (35KB, 90% duplicated → merged into database.md)
- [x] DATABASE_CONFIG.md (merged into database.md)
- [x] DATABASE_SPECIFICATION.md (merged into database.md)
- [x] QUICK_SCHEMA_REFERENCE.md (merged into database.md)
- [x] SERVER_SETUP.md (merged into deployment.md)
- [x] COMPLETE_DEPLOYMENT_GUIDE.md (merged into deployment.md)
- [x] DEPLOYMENT_STEPS.md (merged into deployment.md)
- [x] DEVELOPMENT_GUIDE.md (merged into development.md)
- [x] LIVE_RELOAD_SETUP.md (merged into development.md)
- [x] TEST_CENTRALIZATION.md (merged into development.md)
- [x] SECURITY_ANALYSIS.md (merged into security.md)
- [x] TODO.SECURITY.md (merged into security.md)
- [x] MIGRATION.md (merged into migration-history.md)
- [x] MIGRATION_GUIDE.md (merged into migration-history.md)
- [x] MIGRATION_CHECKLIST.md (merged into migration-history.md)
- [x] MIGRATION_COMPLETE.md (merged into migration-history.md)
- [x] DEPLOYMENT_MIGRATION.md (merged into migration-history.md)
- [x] RESTRUCTURE_PLAN.md (merged into migration-history.md)
- [x] DIRECTORY_STRUCTURE.md (merged into migration-history.md)
- [x] SIMPLIFICATION.md (merged into migration-history.md)
- [x] REPOSITORY_STRUCTURE_ANALYSIS.md (merged into migration-history.md)
- [x] Analysis.md (merged into migration-history.md)
- [x] assessments.md (merged into migration-history.md)
### Final verification
- [x] Cross-references updated (search.md self-reference fixed)
- [x] No unique content lost (all information preserved in consolidated files)
- [ ] jj commit

File diff suppressed because it is too large Load Diff

View File

@@ -1,167 +0,0 @@
### User
for a low traffic website, using a csv vs a sqlite or a nosql db, which would make sense ? it would be a php website.
### Assistant
Short answer:**use SQLite**for almost all low-traffic PHP sites unless your data is literally a tiny, read-only list best kept as a CSV. CSVs are fragile; NoSQL is usually overkill and adds operational cost.
Heres a practical breakdown to help you choose.
# When to pick each
**SQLite (recommended)**
- Best for low-traffic sites with structured data (users, posts, settings, relations).
- ACID transactions, SQL queries, indexes, easy backups (single file).
- Very simple to use from PHP via PDO; no separate server to manage.
- Handles many concurrent reads well; writes are serialized but fine for low write volume. Enable WAL for better concurrency.
- Put the`.sqlite`file outside webroot and set tight file permissions.
**CSV**
- Accept if data is: tiny, mostly read-only, and never queried/joined (e.g., a static list of countries).
- Pros: human-readable, editable in Excel.
- Cons: no indexes, no transactions, parsing edge cases, concurrency problems, security issues if stored in webroot.
- If you use CSV writes, you must handle locking and validation yourself.
**NoSQL (MongoDB, CouchDB, etc.)**
- Use only if you already need document DB features (flexible schema, distributed clustering) or will scale horizontally soon.
- Adds server/process to manage, drivers, extra operational complexity — usually overkill for low traffic.
**MySQL/Postgres**
- Good if you expect to scale or need multi-user DB server now; slightly more operational overhead than SQLite but easier to scale later.
# Practical considerations
- **Concurrency:**SQLite serializes writes. For low traffic or mostly-read sites this is fine. Turn on WAL mode (`PRAGMA journal_mode = WAL`) to improve concurrency.
- **Backups:**SQLite = copy the file (use`VACUUM`or use`sqlite3`online backup API). CSV = copy file but risk partial writes. NoSQL = backup routines/replication needed.
- **Security:**Never keep your DB/CSV in webroot. Correct file permissions. Use prepared statements to avoid injection.
- **Querying & indexing:**CSV has none — searching/joins are O(n). SQLite supports indexes and complex queries.
- **Migrations:**SQLite supports schema migration (use a simple migration script). CSV makes schema evolution painful.
- **Tooling:**PHP has native PDO for SQLite. Many tools can open SQLite files. CSV editing via spreadsheets is easy but risky.
# Minimal PHP examples
**SQLite (PDO) — recommended**
```
PHP// open$pdo = new PDO('sqlite:/path/outside_webroot/app.db');$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);// create$pdo->exec("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT, created_at INTEGER)");// insert (prepared)$stmt = $pdo->prepare('INSERT INTO items (name, created_at) VALUES (:name, :ts)');$stmt->execute([':name' => 'Example', ':ts' => time()]);// queryforeach ($pdo->query('SELECT * FROM items ORDER BY id DESC') as $row) { var_dump($row);}
```
Tip: after creating DB run`PRAGMA journal_mode = WAL;`once to set WAL mode if you expect concurrent reads.
**CSV (with locking) — only for very simple data**
```
PHP$csvFile = '/path/outside_webroot/data.csv';// readif (($h = fopen($csvFile, 'r')) !== false) { while (($row = fgetcsv($h)) !== false) { // process $row array } fclose($h);}// append safelyif (($h = fopen($csvFile, 'a')) !== false) { if (flock($h, LOCK_EX)) { fputcsv($h, ['id', 'value', time()]); fflush($h); flock($h, LOCK_UN); } fclose($h);}
```
# Rule of thumb
- If you need queries, joins, indexes, transactions →**SQLite**.
- If its a single tiny static table you occasionally open in Excel →**CSV**.
- If you need scaling, replication, distributed features, or already use NoSQL → consider**NoSQL**(but expect extra ops work).
If you want, I can:
- give a small migration plan from CSV → SQLite, or
- provide a tiny PDO wrapper and migration scripts for your PHP project.
Which would you like next?
### User
this is basically to make a libary for theses of a school. how could I implement a export import of the sqlite to csv ?
### Assistant
Nice — good fit for SQLite. Below are ready-to-use options (PHP functions + CLI commands) to**export**and**import**CSVs safely and efficiently for your school theses library. I include streaming (no huge memory use), header handling, transactions, column mapping, basic validation, and tips for Excel compatibility.
# Quick notes before code
- Put DB file outside webroot and set tight permissions.
- Always back up DB before imports.
- Use transactions for imports for speed and atomicity.
- For Excel, prepend a UTF-8 BOM (`\xEF\xBB\xBF`) when exporting so Excel recognizes UTF-8.
- For large imports, process in batches and use prepared statements.
- Validate rows (required columns, types, length) to avoid corrupting DB.
# 1) Export a table to CSV (streaming, low memory)
```
PHP<?phpfunction exportTableToCsv(PDO $pdo, string $table, string $csvPath, bool $includeHeader = true, string $bom = "\xEF\xBB\xBF") { $stmt = $pdo->prepare("SELECT * FROM \"$table\""); $stmt->execute(); $out = fopen($csvPath, 'w'); if ($out === false) { throw new RuntimeException("Cannot open CSV file for writing: $csvPath"); } // Write BOM for Excel/Windows compatibility (optional) if ($bom !== '') { fwrite($out, $bom); } // Write header if ($includeHeader) { $columnCount = $stmt->columnCount(); $headers = []; for ($i = 0; $i < $columnCount; $i++) { $meta = $stmt->getColumnMeta($i); $headers[] = $meta['name'] ?? "col$i"; } fputcsv($out, $headers); } // Stream rows while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { fputcsv($out, array_values($row)); } fclose($out);}
```
Usage:
```
PHP$pdo = new PDO('sqlite:/path/outside_webroot/library.db');$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);exportTableToCsv($pdo, 'theses', '/tmp/theses_export.csv');
```
# 2) Export an arbitrary query to CSV
```
PHPfunction exportQueryToCsv(PDO $pdo, string $sql, array $params, string $csvPath, bool $includeHeader = true, string $bom = "\xEF\xBB\xBF") { $stmt = $pdo->prepare($sql); $stmt->execute($params); $out = fopen($csvPath, 'w'); if ($out === false) throw new RuntimeException("Cannot open CSV file: $csvPath"); if ($bom !== '') fwrite($out, $bom); if ($includeHeader) { $columnCount = $stmt->columnCount(); $headers = []; for ($i = 0; $i < $columnCount; $i++) { $meta = $stmt->getColumnMeta($i); $headers[] = $meta['name'] ?? "col$i"; } fputcsv($out, $headers); } while ($row = $stmt->fetch(PDO::FETCH_NUM)) { fputcsv($out, $row); } fclose($out);}
```
# 3) Import CSV into a table (stream + prepared insert, with header mapping)
```
PHP<?phpfunction importCsvToTable(PDO $pdo, string $csvPath, string $table, array $columnMap = null, int $batchSize = 500, string $encoding = 'UTF-8') { if (!file_exists($csvPath)) { throw new RuntimeException("CSV file not found: $csvPath"); } $h = fopen($csvPath, 'r'); if ($h === false) throw new RuntimeException("Cannot open CSV: $csvPath"); // Read header row $header = fgetcsv($h); if ($header === false) { fclose($h); throw new RuntimeException("CSV is empty or unreadable"); } // Optional: strip BOM from first header cell $header[0] = preg_replace('/^\x{FEFF}/u', '', $header[0]); // Map incoming CSV columns to DB columns. // $columnMap can be: null (assume header names match table columns) // or an associative array: ['CSV Column Name' => 'db_column'] $map = []; if ($columnMap === null) { foreach ($header as $col) $map[$col] = $col; } else { foreach ($header as $col) { if (isset($columnMap[$col])) $map[$col] = $columnMap[$col]; else $map[$col] = null; // skip if not mapped } } // Determine DB columns we will insert (in order) $dbCols = array_values(array_filter($map, fn($v) => $v !== null)); if (count($dbCols) === 0) { fclose($h); throw new RuntimeException("No CSV columns map to DB columns."); } // Prepare insert statement $placeholders = implode(',', array_fill(0, count($dbCols), '?')); $colList = implode(',', array_map(fn($c) => "\"$c\"", $dbCols)); $insertSql = "INSERT INTO \"$table\" ($colList) VALUES ($placeholders)"; $insertStmt = $pdo->prepare($insertSql); // Read rows in batches inside a transaction $rowCount = 0; $batch = []; $pdo->beginTransaction(); try { while (($row = fgetcsv($h)) !== false) { $assoc = array_combine($header, $row); // Build values in dbCols order; do simple validation (you can extend) $values = []; foreach ($map as $csvCol => $dbCol) { if ($dbCol === null) continue; } foreach ($dbCols as $dbCol) { // find the CSV column that maps to this dbCol (reverse lookup) $csvKey = array_search($dbCol, $map, true); $values[] = $assoc[$csvKey] ?? null; } // Optional: basic validation example (skip blank required rows) $allEmpty = true; foreach ($values as $v) { if (trim((string)$v) !== '') { $allEmpty = false; break; } } if ($allEmpty) continue; $insertStmt->execute($values); $rowCount++; if ($rowCount % $batchSize === 0) { $pdo->commit(); $pdo->beginTransaction(); } } $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); fclose($h); throw $e; } fclose($h); return $rowCount;}
```
Usage with column mapping:
```
PHP$colMap = [ 'Title' => 'title', 'Author' => 'author', 'Year' => 'year', 'Supervisor' => 'supervisor'];$count = importCsvToTable($pdo, '/tmp/theses_import.csv', 'theses', $colMap);echo "Imported $count rows\n";
```
# 4) Tips & extras
**Detect table columns automatically**
```
PHPfunction getTableColumns(PDO $pdo, string $table): array { $cols = []; $stmt = $pdo->query("PRAGMA table_info(\"$table\")"); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { $cols[] = $col['name']; } return $cols;}
```
**Speed optimizations for large imports**
- Wrap inserts in a transaction (done above).
- Turn off synchronous temporarily for import:`PRAGMA synchronous = OFF;`then set back to`NORMAL`or`FULL`afterwards. (Be careful — power loss can cause corruption during import.)
- Use`PRAGMA journal_mode = WAL;`for better concurrency.
**CSV dialect**
- Use`fgetcsv`/`fputcsv`which handle quoting/commas.
- If you must support Excel-generated CSVs, handle`\r\n`line endings and possibly semicolon delimiters (region dependent).
**Conflict handling**
- If`theses`table has unique constraints (e.g.,`unique(identifier)`), you can use`INSERT OR REPLACE`or`INSERT OR IGNORE`depending on desired behavior:```
SQLINSERT OR REPLACE INTO "theses" (col1, col2) VALUES (?, ?)
```
**CSV created by CLI (SQLite)**
- If you have shell access, you can quickly export:```
Bashsqlite3 library.db -header -csv "SELECT * FROM theses;" > theses.csv
```
- Import via sqlite3 CLI (beware: requires exact columns & table prepared):```
Bashsqlite3 library.db.mode csv.import /path/to/theses.csv theses
```
# 5) Example end-to-end flow for your theses library
- Create`theses`schema in SQLite with fields:`id, title, author, year, department, supervisor, abstract, filepath, created_at`.
- Provide an admin page to**Export**: call`exportTableToCsv()`and stream the CSV to browser with correct headers and BOM.
- Provide an admin**Import**form (file upload). On upload:
- Save uploaded CSV to a temp folder outside webroot.
- Make a backup copy of DB.
- Run`importCsvToTable()`with a column map (allow admins to map CSV columns to DB fields if header names differ).
- Show import summary: rows imported, rows skipped, errors.
- For repeatable imports, keep a small import-log table to record timestamp, filename, rows imported, and admin user.
If you want, I can:
- produce a full single-file PHP admin script (upload UI + export button + import with mapping UI + summary) tailored to your`theses`schema, or
- give a tiny migration script to convert an existing CSV folder into the`theses`SQLite DB.
Which would you like me to generate now?

329
docs/database.md Normal file
View File

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

204
docs/deployment.md Normal file
View File

@@ -0,0 +1,204 @@
# Deployment
Server setup, deployment, and rollback procedures for Post-ERG.
---
## One-Time Server Setup
Run before first deploy:
```bash
just setup-server
```
This creates `/var/www/posterg/` with correct ownership/permissions:
- Owner: `www-data:posterg`
- Directories: **2775** (setgid — new files inherit `posterg` group)
- Files: **664**
- Database files: **660**
> **Important:** After running `setup-server`, log out and back in on the server (or `newgrp posterg`) so group membership is active before deploying.
### Why setgid (2775)?
rsync uses `--chown=www-data:posterg`. Both `padlock` and `www-data` must write to dirs. With `2775 + group=posterg`, new subdirs inherit the group automatically.
---
## Deploying
```bash
just deploy # Push all app files
just deploy-db # Push initial database (aborts if remote DB exists)
just deploy-nginx # Push + apply nginx config
```
### First-Time Deployment
Since we moved from `/var/www/html/` to `/var/www/posterg/`:
1. **Setup server directory** (one time):
```bash
just setup-server
```
2. **Deploy application**:
```bash
just deploy
```
Uploads to `/var/www/posterg/`, excludes tests/docs/vendor.
3. **Deploy nginx config**:
```bash
just deploy-nginx
ssh posterg
sudo bash /tmp/deploy-production.sh
sudo systemctl reload nginx
```
4. **Verify**:
```bash
just server-status
curl -I https://posterg.erg.be/ # 200 ✓
curl -I https://posterg.erg.be/admin/ # 200 ✓
curl -I https://posterg.erg.be/storage/ # 404 ✓
```
### Subsequent Deployments
```bash
just deploy
```
---
## Server Directory Structure
```
/var/www/posterg/ # Application root (private)
├── public/ # DocumentRoot (nginx points here)
│ ├── index.php
│ ├── search.php
│ ├── memoire.php
│ ├── admin/
│ └── assets/
├── includes/ # Templates (private)
├── config/ # Configuration (private)
├── storage/ # Database + uploads (private)
│ ├── posterg.db
│ └── theses/
├── src/ # PHP classes (private)
└── scripts/ # Admin tools (private)
```
**Nginx DocumentRoot:** `/var/www/posterg/public/`
Only `public/` is web-accessible. Everything else is physically private.
---
## Managing Admin Users
```bash
ssh posterg "sudo bash /var/www/posterg/scripts/manage-admin-users.sh"
```
Interactive menu for adding/changing/deleting htpasswd entries at `/etc/nginx/.htpasswd-posterg`.
---
## Security Verification
After every deploy, verify private files are inaccessible:
```bash
curl -I https://posterg.erg.be/storage/test.db # Must 404
curl -I https://posterg.erg.be/config/bootstrap.php # Must 404
curl -I https://posterg.erg.be/src/Database.php # Must 404
```
---
## Troubleshooting
### rsync Permission Denied
```bash
just setup-server # Fixes directory permissions
# Then log out/in on server and retry
```
Manual fix:
```bash
ssh posterg
sudo chown -R www-data:posterg /var/www/posterg
sudo find /var/www/posterg -type d -exec chmod 2775 {} \;
sudo find /var/www/posterg -type f -exec chmod 664 {} \;
sudo chmod 660 /var/www/posterg/storage/*.db
```
### Nginx 403 Forbidden
```bash
ssh posterg
sudo find /var/www/posterg -type d -exec chmod 2775 {} \;
sudo find /var/www/posterg -type f -exec chmod 664 {} \;
sudo chmod 660 /var/www/posterg/storage/*.db
```
### Database Permission Error
```bash
ssh posterg
sudo chown www-data:posterg /var/www/posterg/storage/posterg.db
sudo chmod 660 /var/www/posterg/storage/posterg.db
```
### Nginx 500 / Site 404
Check nginx DocumentRoot:
```bash
ssh posterg "grep 'root ' /etc/nginx/sites-available/posterg"
# Should show: root /var/www/posterg/public;
```
### Admin 404
Nginx may still use old `/formulaire/` location. Update `nginx/posterg.conf` to use `/admin/`.
---
## Rollback
If something goes wrong:
```bash
# Restore old nginx config
ssh posterg
sudo cp /etc/nginx/sites-available/posterg.backup /etc/nginx/sites-available/posterg
sudo systemctl reload nginx
# Or restore old site (if backed up)
sudo rm -rf /var/www/posterg
sudo mv /var/www/html.backup /var/www/html
```
With jj:
```bash
jj log
jj edit <previous-change-id>
```
---
## Commands Reference
| Command | Purpose |
|---------|---------|
| `just setup-server` | Create `/var/www/posterg/` (first time only) |
| `just deploy` | Deploy application files |
| `just deploy-nginx` | Update nginx configuration |
| `just deploy-db` | Deploy database file |
| `just server-status` | Check server health |
| `just server-logs` | View server logs |

272
docs/development.md Normal file
View File

@@ -0,0 +1,272 @@
# Development Guide
Setup, workflow, testing, and live reload for Post-ERG development.
---
## Quick Start
```bash
just setup # One time: clone php-live-reload + setup directories
just serve # Start dev server at http://localhost:8000
```
One unified server serves both:
- **Public site:** http://localhost:8000
- **Admin panel:** http://localhost:8000/admin/
Live reload is enabled automatically — browser refreshes when you save files.
---
## Project Structure
```
posterg-website/
├── public/ # DocumentRoot (web-accessible)
│ ├── index.php # Homepage
│ ├── search.php # Search/répertoire
│ ├── memoire.php # Thesis detail
│ ├── admin/ # Admin panel
│ └── assets/ # CSS, fonts, images
├── includes/ # Template partials (header, footer)
├── config/ # Configuration (bootstrap.php)
├── src/ # PHP classes (Database, AdminAuth, RateLimit)
├── storage/ # Database + uploads (private)
├── database/ # Schema + migrations
├── tests/ # Test suite
├── nginx/ # Server configuration
├── scripts/ # Deployment/admin scripts
└── vendor/ # Third-party (gitignored, dev only)
```
---
## Development Workflow
### Starting Development
```bash
just serve
```
### Making Changes
1. Edit PHP/CSS files — browser auto-refreshes
2. Run tests: `just test`
3. Check syntax: `just syntax`
4. Deploy: `just deploy`
### Database Operations
```bash
just stats # View database stats
just query # Open SQLite shell
just show 42 # Show thesis by ID
just reset-db # Reset database
just fixtures # Create sample data
just backup # Backup database
```
---
## Live Reload
### What It Does
Automatically refreshes your browser when you save PHP/CSS/JS files. No browser extension needed.
### Setup (One Time)
```bash
just setup
```
Clones `php-live-reload` into `vendor/` (gitignored).
### How It Works
- Conditionally included in `header.php` when `php_sapi_name() === 'cli-server'`
- JavaScript polls server for file changes → browser refreshes
- **Never active in production** (different SAPI, vendor/ not deployed)
### Detection
```php
<?php if (php_sapi_name() === 'cli-server'): ?>
<script src="/vendor/php-live-reload/php-live-reload/live-reload.js"></script>
<?php endif; ?>
```
### Troubleshooting
```bash
ls -la vendor/php-live-reload/ # Check installed
just setup # Reinstall if missing
curl -s http://localhost:8000/ | grep live-reload # Verify script included
```
---
## Testing
### Test Structure
```
tests/
├── run-tests.php # Main test runner
├── Unit/ # Unit tests
│ ├── DatabaseTest.php
│ └── RateLimitTest.php
├── Integration/ # Integration tests
│ └── SearchTest.php
└── Security/ # Security tests
└── SecurityTest.php
```
### Running Tests
```bash
just test # Run all tests
just test-unit # Unit tests only
just test-integration # Integration tests only
just test-security # Security tests only
just syntax # Check PHP syntax
```
### Writing Tests
1. Choose type: Unit / Integration / Security
2. Create test file in appropriate `tests/` subdirectory
3. Follow template:
```php
<?php
require_once __DIR__ . '/../../src/Database.php';
echo "Test Name\n";
echo "=========\n\n";
try {
$db = Database::getInstance();
echo "✓ PASS: Test description\n";
return true;
} catch (Exception $e) {
echo "❌ FAIL: " . $e->getMessage() . "\n";
return false;
}
```
4. Add to `tests/run-tests.php` `$testFiles` array
5. Run: `just test`
---
## Common Tasks
### Create a New Page
```php
<?php
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/src/Database.php';
$db = App::boot();
include APP_ROOT . '/includes/header.php';
?>
<section class="section">
<div class="container">
<h1 class="title">New Page</h1>
</div>
</section>
<?php include APP_ROOT . '/includes/footer.php'; ?>
```
### Add a Database Method
1. Edit `src/Database.php`
2. Add method to the class
3. Write test in `tests/Unit/`
4. Run: `just test-unit`
### Update CSS
1. Edit `public/assets/posterg.css`
2. Browser auto-refreshes
3. Increment cache-bust in header: `posterg.css?v=N`
---
## Justfile Commands
### Development
| Command | Description |
|---------|-------------|
| `just setup` | Setup dev environment (one-time) |
| `just serve` | Start dev server with live reload |
| `just stop` | Stop dev server |
| `just logs` | View dev logs |
### Testing
| Command | Description |
|---------|-------------|
| `just test` | Run all tests |
| `just test-unit` | Unit tests only |
| `just test-integration` | Integration tests only |
| `just test-security` | Security tests only |
| `just syntax` | Check PHP syntax |
### Database
| Command | Description |
|---------|-------------|
| `just stats` | Database statistics |
| `just query` | Open SQLite shell |
| `just show <id>` | Show thesis by ID |
| `just reset-db` | Reset test database |
| `just fixtures` | Create sample data |
| `just backup` | Backup database |
### Deployment
| Command | Description |
|---------|-------------|
| `just deploy` | Deploy complete site |
| `just deploy-nginx` | Deploy nginx config |
| `just deploy-db` | Deploy database |
| `just server-status` | Check server health |
| `just server-logs` | View server logs |
---
## Debugging
```bash
just logs # View error logs
tail -f error.log # Direct log monitoring
# PHP errors in browser (temporary):
# Add to PHP file:
ini_set('display_errors', 1);
error_reporting(E_ALL);
# Database issues:
just stats # Check DB exists and has data
just query # Open SQLite shell
```
### Server Won't Start
```bash
just stop # Kill existing process
just setup # Reinstall php-live-reload
```
### Database Errors
```bash
just reset-db # Reset from schema
just fixtures # Repopulate with sample data
just test # Verify everything works
```

154
docs/migration-history.md Normal file
View File

@@ -0,0 +1,154 @@
# Migration History
Consolidated record of structural migrations performed on posterg-website.
---
## Migration 1: YAML → SQLite (2026-01)
### What Changed
Replaced flat YAML file storage with a normalized SQLite database.
**Before:** Form data saved as individual YAML files in `data/yaml/`, with file uploads in `data/content/` and `data/cover/`.
**After:** All thesis data stored in a relational SQLite database with 19 tables (11 core, 5 junction, 3 reference), 2 views, proper normalization (3NF), auto timestamps, cascade deletes.
### Key Changes
- `Database.php` — PDO wrapper with transaction management, find-or-create methods, lookup helpers
- `index.php` — Dynamically loads form options from DB; added subtitle, synopsis, finality, languages, formats
- `formulaire.php` — Complete rewrite: transaction-based processing, prepared statements, random cryptographic filenames
- `thanks.php` — Reads from DB using thesis ID, displays data from `v_theses_full` view
### YAML → Database Mapping
| YAML Field | Database Location |
|------------|-------------------|
| `auteurice` | `authors.name` |
| `email` | `authors.email` |
| `année` | `theses.year` |
| `titre` | `theses.title` |
| `description` | `theses.synopsis` |
| `orientation` | `theses.orientation_id` |
| `ap` | `theses.ap_program_id` |
| `promoteurice` | `supervisors.name` + `thesis_supervisors` |
| `tag` | `keywords.keyword` + `thesis_keywords` |
| `files` | `thesis_files` table |
### Data Migration Path
For importing existing YAML data:
1. Parse YAML files with `Symfony\Yaml\Yaml::parseFile()`
2. Insert into DB within transactions
3. Verify with `SELECT COUNT(*) FROM theses; SELECT * FROM v_theses_full LIMIT 5;`
---
## Migration 2: Repository Restructure (2026-02)
### What Changed
Restructured from `apps/public/` + `apps/admin/` + `shared/` layout to idiomatic PHP website layout.
**Before:**
```
posterg-website/
├── apps/public/ # Public website
├── apps/admin/ # Admin panel
├── shared/ # Shared PHP libraries
└── database/
```
**After (intermediate):**
```
posterg-website/
├── index.php # Public root
├── admin/ # Admin panel
├── lib/ # Shared libraries (was shared/)
├── inc/ # Templates (header/footer)
├── assets/ # Static files
├── database/
└── vendor/
```
### Key Changes
- Moved `apps/public/*` to root
- Moved `apps/admin/` to `admin/`
- Renamed `shared/` to `lib/`
- Updated all `require` paths
- Added php-live-reload to `vendor/`
---
## Migration 3: Public Directory Structure (2026-02)
### What Changed
Moved web-accessible files into `public/` subdirectory so only `public/` is the DocumentRoot.
**Before:** All files (including config, DB, source) in DocumentRoot — security relied on nginx deny rules.
**After:**
```
posterg-website/
├── public/ # DocumentRoot — only this exposed
│ ├── index.php
│ ├── search.php
│ ├── admin/
│ └── assets/
├── config/ # Private
├── includes/ # Private (was inc/)
├── src/ # Private (was lib/)
├── storage/ # Private (DB + uploads)
└── var/ # Private (cache, logs)
```
### Key Changes
- `config/bootstrap.php` — Central path management with constants (APP_ROOT, PUBLIC_ROOT, etc.)
- All public PHP files updated to use bootstrap and relative paths
- Dev server: `php -S 127.0.0.1:8000 -t public/`
- Deployment: rsync to `/var/www/posterg/` (not `/var/www/html/`)
- Nginx DocumentRoot: `/var/www/posterg/public`
### Security Impact
| Resource | Before | After |
|----------|--------|-------|
| Database | Accessible if nginx misconfigured | Physically outside web root |
| Config | One deny rule away | Physically private |
| Source code | Exposed | Physically private |
| Dev server | Exposed everything | Matches production security |
---
## Migration 4: Simplification (2026-02)
### What Changed
Removed package-oriented complexity from the `public/` migration.
- Removed `var/` directory (completely unused for this website)
- Renamed `resources/views/``includes/` (simpler, clearer)
- Simplified `config/bootstrap.php` from 66 → 33 lines
- Removed 10+ unused constants (VAR_ROOT, CACHE_ROOT, LOGS_ROOT, etc.)
- Replaced `view()` helper with standard PHP `include`
### Result
Only essential constants remain: `APP_ROOT`, `STORAGE_ROOT`, plus environment detection. Security unchanged (still uses `public/` as DocumentRoot).
---
## Rollback Notes
All migrations are tracked in jj. To view history:
```bash
jj log
jj edit <previous-change-id>
```
The YAML-based code was kept as a fallback during the SQLite migration. The old `/var/www/html/` structure was kept as a backup during the directory structure migration.

View File

@@ -8,7 +8,7 @@ The search feature allows users to search across theses using multiple criteria
### New Files ### New Files
1. **search.php** - Main search interface page 1. **search.php** - Main search interface page
2. **create_test_db.php** - Script to generate test database with sample data 2. **create_test_db.php** - Script to generate test database with sample data
3. **SEARCH_FEATURE.md** - This documentation file 3. **search.md** - This documentation file
### Modified Files ### Modified Files
1. **Database.php** - Added search methods: 1. **Database.php** - Added search methods:

82
docs/security.md Normal file
View File

@@ -0,0 +1,82 @@
# Security
Vulnerability analysis and resolution status for posterg-website.
> Based on security audit (2026-02-08). All items tracked below.
---
## Resolved
### Infrastructure / Deployment
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | TLS terminated upstream by reverse proxy. nginx.conf doesn't need to handle TLS directly. |
| 3 | Uploaded files stored inside webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. |
| 4 | File path mismatch — media broken & insecure | 🟠 HIGH | DB paths now storage-relative. New `public/media.php` serves files safely. `memoire.php` and `search.php` use `/media.php?path=…`. Cover recording fixed. |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `src/RateLimit.php` `getClientIdentifier()` uses `REMOTE_ADDR` only. |
| 6 | `.htaccess` rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf`. See `nginx/HTACCESS_TO_NGINX.md`. |
| 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | Removed from `nginx/posterg.conf`. |
### Frontend / Assets
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 10 | CDN stylesheet without SRI | 🟡 MEDIUM | CDN will not be used in production. Self-hosted, eliminating supply-chain risk. |
### Code Quality / Defence in Depth
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 14 | Missing `rel="noreferrer"` on external links | 🔵 LOW | `rel="noopener noreferrer"` applied in `public/admin/thanks.php`. |
| 15 | Unescaped integer outputs | 🔵 LOW | Explicit `(int)` casts added in `public/index.php` and `public/search.php`. |
| 16 | Redundant `DATABASE_PATH` constant | 🔵 LOW | Removed from `config/bootstrap.php`. |
### Admin Panel — Authentication & Sessions
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 2 | No PHP-level authentication in admin | 🔴 CRITICAL | `src/AdminAuth.php` implements session guard with `password_verify` + `session_regenerate_id`. All admin files call `AdminAuth::requireLogin()`. Credentials in gitignored `config/admin_credentials.php`. No-op when constant absent (dev/cli-server). |
| 8 | Session cookies not hardened | 🟡 MEDIUM | Resolved with #2. `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0`. |
---
## In Progress
| # | Issue | Severity | Status |
|---|-------|----------|--------|
| 7 | LIKE wildcard injection in admin search | 🟡 MEDIUM | Public `Database::searchTheses()` escapes `%` and `_` correctly. Same pattern must be applied to admin search and any other raw LIKE queries. |
---
## Not Yet Implemented
| # | Issue | Severity | Files |
|---|-------|----------|-------|
| 11 | Missing Content-Security-Policy on public pages | 🟡 MEDIUM | `nginx/posterg.conf` → add CSP header to main server block |
| 9 | `error.log` in web-accessible path | 🟡 MEDIUM | `public/admin/actions/formulaire.php` → use absolute path outside webroot |
| 12 | CSV import missing server-side MIME validation | 🟡 MEDIUM | `public/admin/import.php` → add `finfo` MIME check |
---
## Priority Order
1. ~~🔴 CRITICAL~~ — All done (items 12)
2. 🟡 **MEDIUM** — Items 7, 9, 11, 12 remaining
3. ~~🔵 LOW~~ — All done (items 1316)
---
## Good Practices Already in Place
- ✅ SQL injection: all queries use PDO prepared statements
- ✅ XSS output: `htmlspecialchars()` on all user-controlled output
- ✅ CSRF: tokens with `bin2hex(random_bytes(32))`, validated with `hash_equals()`
- ✅ File upload: MIME type validated with `finfo`
- ✅ Input validation: year, IDs, pagination cast to integers
- ✅ LIKE wildcard escaping in public search (`Database::escapeLikeString`)
---
*Last updated: 2026-02-08*