mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
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:
600
docs/Analysis.md
600
docs/Analysis.md
@@ -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.*
|
||||
@@ -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!** 🔒
|
||||
463
docs/Context.md
463
docs/Context.md
@@ -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 d’auteur De l’inscription de l’étudiant à l’ESA 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 l’ensemble 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 d’auteur entre l’étudiant et l’école et implique :
|
||||
|
||||
- pour l’étudiant : de mentionner l’année de création et le nom de l’école (ESA Saint-Luc Bruxelles) lorsqu’il utilisera ou diffusera ces travaux ;
|
||||
|
||||
- pour l’ESA Saint-Luc Bruxelles : de mentionner le nom de l’étudiant et l’année de création des travaux qu’elle souhaiterait diffuser, à des fins pédagogiques, artistiques ou promotionnelles uniquement, ce durant toute la durée de protection de ces travaux par le droit d’auteur. En aucun cas, l’école n’est 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ècedanslecadred’unconcoursauquellecursusdanslequel il est inscrit a participé avec le suivi d’un/de professeur(s), 50 % du montant de ce prix sera versé sur le compte de l’option, l’autre 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 s’informer 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é d’utilisation, 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 l’Open Source Initiative (OSI), la suite à des divergences philosophiques avec la Free Software Fundation (FSF) promouvant, quant à elle, les licences libres. L’OSI a créé une « Open Source Definition » (OSD) décrivant les 10 conditions requises pour qu’une 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 l’OSI 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 l’OSI, 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 d’exploitation, est un élément qui concerne le droit de propriété intellectuelle et qui encadre l’utilisation et la diffusion d’un produit. Le type de licence choisi par le propriétaire définit les conditions d’utilisation et de partage pour l’utilisateur, qui est tenu de les respecter.
|
||||
|
||||
Il existe différents types de licences : les licences propriétaires, qui n’autorisent qu’un usage privé de l’oeuvre par l’acqué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 d’utiliser l’oeuvre. Ces libertés sont la liberté d’utilisation 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 d’abord les licences permissives, définies comme « non-copyleft », qui ne contraignent l’utilisateur qu’à la seule obligation d’attribuer les portions de code sous licence à leurs développeurs d’origine, que ce soit dans le code qu’ils 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é d’utiliser, de copier, de modifier, de publier, de distribuer, de vendre et d’incorporer 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. C’est une des moins restrictives, mais qui permet de protéger les auteurs par rapport à l’inscription de leur nom dans les produits dérivés et qui les décharge de la responsabilité d’éventuels problèmes lors de l’utilisation 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 n’obligeant seulement à fournir une copie de la licence et la notice avec le code source et d’indiquer clairement le nom du développeur d’origine et que les fichiers ont été modifiés.
|
||||
|
||||
La licence Creative Commons BY, ou attribution en français, permet d’utiliser librement l’œuvre à condition de l’attribuer à l’auteur 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. C’est 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é d’utiliser le logiciel pour n’importe quel usage, l’accè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 s’applique à la création artistique. Elle autorise à copier, transformer ou diffuser une oeuvre librement, dans une visée commerciale ou non, à la seule condition qu’il soit toujours possible d’accéder à l’oeuvre 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 qu’elles 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 l’oeuvre 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 n’autorisent qu’une utilisation non commerciale du produit. Une autorisation commerciale du produit est interdite sauf si une demande d’autorisation est acceptée.
|
||||
|
||||
La licence Creative Commons BY-ND pour No Derivative Works n’autorise à copier, distribuer et utiliser que des versions non- modifiées de l’oeuvre. Il est possible de cumuler ces deux licences, ce qui donne la licence CC BY-NC-ND, qui autorisent l’utilisation et la redistribution de l’oeuvre 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 l’information, 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 l’expression « 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 l’on 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 d’une copie, l’acheteur peut utiliser, reproduire et redistribuer le programme autant de fois qu’il le désire. La vente de copies de programmes n’est 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. L’idée sous-jacente du mouvement libre n’est pas de s’approprier les programmes informatiques afin d’en faire payer l’usage 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 d’informaticiens. Lorsque l’on parle de « libre copie » et d’accès aux sources de logiciels, il s’agit souvent de « parties de programmes » disséminées à travers l’Internet 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 l’environnement 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 d’origine -> Ainsi se crée un cercle vertueux incitant à la multiplication d’apports de qualité.
|
||||
|
||||
Une des bases des logiciels libres et open-source est la contributions venant d’autres 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 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, encouragents le payement du développeur.
|
||||
|
||||
Il est important de noter que la formation des fondations se développe pour s’assurer qu’un ou des projets open-sources libres sous leurs tutelles ne deviennent propriétaire ou payant. C’est 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 d’un texte, de céder une partie des droits ‘naturels’ accordés à l’auteur.e à la création d’une œuvre de l’esprit (à savoir qu’il.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 à l’information. En effet depuis la démocratisation d’internet, une œuvre de l’esprit, peut être copiée, modifiée et diffusée pour un coût négligeable.
|
||||
|
||||
Les licences libres s’ancrent dans le mouvement de la culture libre. Celui-ci promeut la liberté de distribuer et de modifier des œuvres de l’esprit. Le but recherché est d’encourager de manière simple et licite la circulation des œuvres, l’échange et la créativité.
|
||||
|
||||
Cependant, aujourd’hui, la revente d’œuvres originales ou de copies constitue l’une des principales manières pour certains créateurs de se rémunérer. C’est un argument souvent invoqué par les personnes réticentes voire contre une distribution libre de l’information. 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é d’artiste/auteur.e doivent nécessairement générer leurs revenus par la vente de ces droits, que cela concerne la vente d’original de copie ou la cession de ces droits pour la diffusion. Sous ce régime, il n’est 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 d’auteur
|
||||
|
||||
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é d’architecture LOCI de l’UCL).
|
||||
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, l’ERG et certaines sections de l’ESA). 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 l’UCLouvain il existe aussi le logiciel Dial où l’on peut consulter numériquement les mémoires. Malheureusement certains étudiants restreignent l’accès à leurs mémoires, par peur du plagiat. Un des travail de Laurent est donc de réussir à convaincre les étudiants de l’intérêt d’avoir son mémoire bien indexé, disponible, et sur le fait que limiter l’accès n’est pas nécessairement une meilleure protection. Une des difficultés pour l’ERG 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 qu’ils existent, et quels thèmes ont été travaillés. On peut aussi, si c’est 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 d’aborder 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. À l’ERG, c’est Xavier Gorgol qui les amène, car l’école n’a pas la possibilité de les stocker.
|
||||
*Lancement de l’interview*
|
||||
|
||||
Aurélie : On travaille sur les licences libres, ce que ça veut dire… et la question qu’on s’est posée, et c’est pour ça qu’on a voulu vous rencontrer, c’est, si par exemple à l’erg ou dans une autre école on cherche à licencier son mémoire, de manière à le diffuser plus largement, est-ce que c’est 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 qu’il y a des contrats entre les écoles et vous ?
|
||||
|
||||
Laurent Leprince : Alors ici c’est un peu compliqué parce qu’on est à cheval sur tous les instituts Saint-Luc et l’UCLouvain, et c’est 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 d’architecture de l’UCLouvain, et puis les mémoires de l’erg et de l’esa, et l’esa c’est uniquement certaines sections. Il y a aussi d’autres instituts qui sont liés à la bibliothèque, donc Saint-Luc secondaire, où il n’y a pas de mémoire, et les cours du soir, les ateliers Saint-Luc, où il n’y en a pas non plus, sauf une section qui est l’ISRE, la formation en urbanisme mais là leurs mémoires sont conservés chez eux, donc ils ne sont pas ici. Au niveau de l’université, pour commencer, donc la formation en architecture de l’UCLouvain, jusqu’en 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 l’université. Ce qu’on a fait c’est qu’on a descendu en archives tous les mémoires papier, qui sont encore consultables, et Dial est accessible en ligne. À l’UCL, 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 qu’en architecture c’est 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 d’une 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 qu’en 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. J’en ai discuté avec des jobistes : « Pourquoi ? Est-ce que c’est vraiment dans ton intérêt ? « et il y en a qui ont dit « Je sais comment ça marche, je n’ai pas envie qu’on 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 d’ouvrir les accès, alors certes le travail n’est 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. C’est un logiciel qui moissonne toute sorte de ressources, les professeurs encodent des paragraphes et voient si ça se retrouve ailleurs. Et par exemple, à l’ULB ils utilisent ce type d’outils, et si par exemple l’étudiant bloque l’accès de son mémoire, ou donne un accès uniquement sur place, l’ULB n’a 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 d’avoir 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 l’accès n’est pas nécessairement une meilleure protection. Alors l’erg, l’esa, etc, ce ne sont que des documents papiers, c’est 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 c’est plus au début, le papier qu’on doit signer à la rentrée.
|
||||
|
||||
LL : Après le fait qu’un mémoire soit consultable ici ne veut pas nécessairement dire que l’étudiant autorise tout. Ici on fonctionne plus comme dépôt, c’est à dire que c’est plus un service, ce sont des mémoires papier qui sont consultables uniquement sur place, les mémoires ne sortent pas. Et alors qu’est-ce qu’il se passe ? On reçoit chaque année un certain nombre de mémoires de l’esa et de l’erg et puis nous on les encode dans le catalogue de la bibliothèque, qui est celui de l’UCLouvain, et ça par contre ça a été décidé dans le cadre d’une commission qui est la commission Art, qui est une commission qui lie la bibliothèque et les différents instituts, et c’est 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 qu’ils ne peuvent pas être consultés, si quelqu’un 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 . C’est parce qu’ici l’idée c’est plutôt de faire une sorte de dépôt, d’abord parce qu’à l’erg par exemple il n’y a pas la place pour, ça permet que les travaux soient disponibles pour l’ensemble des instituts Saint-Luc. En art l’approche est différente qu’en architecture, le mémoire est un objet, un livre d’artiste, au niveau de la mise en page, du type de reliure, etc., il y a tout un travail qui est fait qu’il n’y a pas toujours dans les mémoires d’architecture. 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 qu’ils peuvent trouver les mémoires ici pour s’en inspirer, montrer des bons ou des moins bons exemples, des choses comme ça. Et alors au niveau de l’erg, c’est Xavier Gorgol qui les amène. Alors parfois quand je vais là-bas, je vois Laurence Rassel qui me donne parfois des catalogues d’expos, des choses comme ça qu’elle a sur son bureau, et s’il y a des mémoires qui trainent elle les met. Parce que c’est plus l’idée que là-bas il n’y a pas la place pour les stocker, et que comme ça c’est accessible à tout le monde. À l’esa c’est 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 c’est déjà arrivé qu’un é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 n’est pas affichée, elle est en message privé, elle est visible par les bibliothécaires mais elle n’est pas disponible en consultation dans le catalogue. Mais donc ça nous permet de voir que tel mémoire n’a pas eu les 70%, et donc il est bien en bas. Mais je n’ai pas eu de cas où quelqu’un est venu en demandant de retirer son mémoire.
|
||||
|
||||
A : À l’esa vous disiez que c’est seulement certaines sections, ce sont lesquelles ?
|
||||
|
||||
LL : Alors il y a Bande dessinée, Création d’intérieur, Scénographie, en fait c’est les masters, ce sont les sections qui sont passées au niveau master.
|
||||
|
||||
Élodie : Est-ce qu’il y a des licences qui sont associées au dépôt des mémoires ou est-ce qu’il s’agit plutôt d’un contrat officieux ?
|
||||
|
||||
LL : Je ne sais pas du tout, parce qu’on est plus un lieu de stockage. Par exemple j’avais travaillé au secrétariat à l’ISRE, où il y a des mémoires, et là c’est un contrat entre l’institut 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 d’autres pas, donc ça dépend de l’étudiant, de s’il a pris le temps. Théophile : Et il n’y a pas cette discussion avec la bibliothèque ?
|
||||
|
||||
LL : Dans le sens de limiter l’accès ? Non dans le sens d’ouvrir ?
|
||||
|
||||
T : Oui, dans le sens d’ouvrir.
|
||||
|
||||
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 d’utiliser, d’étudier, de copier, de modifier et de distribuer.
|
||||
|
||||
LL : Nous on travaille plutôt dans l’optique d’un travail académique. On peut citer, mais on mentionne ses sources, et donc après ça peut être dans l’idée de « je vais distribuer mais dans le cadre de mon propre travail », et là c’est repris dans la bibliographie. Tandis que quand c’est penser à peut-être récupérer un chapitre ou des illustrations et les diffuser ou les intégrer dans un autre travail, c’est autre chose.
|
||||
|
||||
T : Après les licences peuvent demander qu’il y ait attribution, donc qu’il y ait quand même une citation de l’auteur. Ça marche peut-être plus pour des travaux de type artistique, comme vous disiez c’est un objet, c’est un tout, et il y a peut-être moyen de s’amuser avec ça. O : Selon vous, c’est mieux que ce soit libre et qu’il y ait pas trop d’encadrement comme à l’erg, plutôt qu’on leur laisse le choix et qu’au final les accès soient limités ?
|
||||
|
||||
LL : Je ne sais pas, après je ne sais pas comment ça se passe à l’erg, il y a peut-être un choix fait au moment du dépôt.
|
||||
|
||||
O : Mais j’ai l’impression que même nous, étudiants, on est pas vraiment au courant, même qu’il y a des mémoires ici à la bibliothèque, je pense que beaucoup l’apprennent par après.
|
||||
|
||||
LL : Ils s’en réjouissent ou pas ?
|
||||
|
||||
T : On ne sait pas, on devrait essayer de voir ça. O : En tout cas, on dirait qu’ils ne sont pas tant au courant de ça qu’il y a des mémoires ici.
|
||||
|
||||
LL : Par exemple je sais qu’il 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 j’ai vu ça.
|
||||
|
||||
LL : Ils ont demandé de pouvoir consulter tous les mémoires, et là on a remonté même ceux qui n’avaient 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 : C’est par la commission Art, ça a du être dans des PV, mais c’est plus pour des questions pédagogiques, c’est parce que le but ici c’est plus de mettre à disposition des exemples, et c’est un signal pour dire que c’est un travail de qualité.
|
||||
|
||||
A : J’ai encore une question, ici, il y a les mémoires d’architecture, de l’erg et de Saint-Luc, est-ce qu’il y a d’autres endroits où se trouvent ces mémoires ou c’est exclusif à la bibliothèque ?
|
||||
|
||||
LL : C’est exclusif à la bibliothèque mais le catalogue est commun avec l’UCLouvain. Mais comme les mémoires ne bougent pas, ils ne peuvent être consultés qu’ici. 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à c’est consultable seulement sur place. Après ça c’est une question d’infrastructures aussi, c’est des serveurs, c’est assez lourds, parce que ce sont des serveurs qui servent aussi pour les publications scientifiques, et puis c’est aussi des différences d’objets, il y a pas mal de mémoires artistiques, ça serait plus difficile de les mettre sur ce genre de plateforme, parce que c’est quand même assez formaté, c’est du pdf. En architecture, il y a parfois aussi plus de créativité, parce qu’en architecture c’est aussi différent, c’est un TFE en et sur l’architecture, et chez eux le projet prime, donc en fait, ils ont un TFE à faire, mais ça s’articule avec leur travail d’atelier 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 d’une thématique liée. Il y a aussi des évolutions par exemple à l’esa. Au début les mémoires de l’erg étaient beaucoup plus créatifs, à l’esa ils étaient très scolaires, moins objets, et maintenant il y a plus de recherche, par exemple avec des formats différents, etc. Mais à l’esa ça reste plus une réflexion sur, alors qu’à l’erg le mémoire est parfois un objet artistique aussi, même s’il est pensé, indexé, réfléchi, c’est 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, c’est à dire que ça sera accessible après un certain nombre d’années, par exemple en médecine, en pharmacie, chez les ingénieurs ça peut se justifier, par exemple s’il 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, l’entreprise 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 c’est qu’il n’y a pas de restrictions, interdit c’est quand rien du tout n’est accessible, restreint c’est restreint au réseau UCL, et mixte ça veut dire qu’il y a plusieurs documents et que tous les documents n’ont pas la même protection. Par exemple, mon travail théorique je le rends public mais les annexes, dans le cadre d’un travail avec une entreprise ou autre, je mets un embargo ou j’interdis l’accès. Par contre le titre du mémoire, le résumé, sont publics, et on retrouve les promoteurs... Libellule c’est le catalogue de la bibliothèque, il dessert l’UCLouvain, L’Unamur et L’Université Saint-Louis. Les mémoires sont répertoriés dedans. Ça vaut peut-être le coup d’aller voir comment ça se passe pour les mémoires au niveau des écoles d’art en France, ou du côté néerlandophone aussi. Après je sais qu’il y a aussi du coté des universités, l’ULiège par exemple, ont beaucoup développé tout ce qui est l’open-source etc.
|
||||
|
||||
A : J’ai encore une question, est-ce que c’est déjà arrivé, qu’une fois qu’un étudiant sorte de master, il finisse par publier son mémoire, avec un éditeur par exemple ?
|
||||
|
||||
LL : Je n’en ai pas connaissance, je sais qu’à l’esa, j’ai entendu parler d’étudiantes qui publiaient des articles, je sais qu’il y a un étudiant qui a fait un très bon travail sur les chalets norvégiens à l’esa, et lui a peut-être envie de faire une publication. Je sais qu’il 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 d’inventaire qui n’existe 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 s’abî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, j’ai 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, qu’ils sont archivés ? Et la bibliothèques existe depuis combien de temps ?
|
||||
|
||||
LL : Alors ici c’est depuis 2011, et avant c’était un centre de documentation à Saint-Luc, rue d’Irlande, depuis plus longtemps encore. Alors là ce sont les mémoires, ici ce sont d’autres 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 d’archives d’architectes, ce sont des architectes dont les familles ont fait don des archives, ça peut être des plans... Et c’est disponible ici pour les consulter. Par exemple Bastin, c’est un architecte qui a réalisé une aile du musée d’art 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%, c’est pour toutes les écoles ?
|
||||
|
||||
LL : Oui c’est pour tout le monde. Et il y a autre chose que je n’ai pas dit. C’est vrai que la limite des 70% continue à s’appliquer à l’université. Si l’étudiant décide que l’accès est libre, mais que le travail n’a pas atteint les 70%, l’université peut bloquer l’accès, peut décider que l’ouvrage n’est pas consultable. Par contre si l’étudiant interdit l’accès, l’université ne peut pas l’y obliger. Et ça c’est surtout pour qu’il n’y ait pas des travaux qui circulent, qui ne soient pas assez bons.
|
||||
|
||||
O : Est-ce que pour l’erg, qui fonctionne plus comme un dépôt, par exemple l’année passée avec le COVID, il y a des mémoires qui manquent, par qu’ils n’ont 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 qu’on reçoit et les PV de délibérations. Alors parfois, par exemple j’ai dit à un jobiste dont le mémoire n’était pas arrivé qu’il pouvait le ramener, il ne l’a pas fait, mais par contre si un étudiant vient en disant qu’on a pas reçu son mémoire et qu’il voudrait qu’il figure à la bibliothèque, on n’a aucun soucis.
|
||||
|
||||
T : Vous ne stockez que les versions papiers, c’est ce qui est requis par l’école si je comprends bien ?
|
||||
|
||||
LL : Oui.
|
||||
|
||||
T : Mais du coup si quelqu’un rendait un travail numérique ?
|
||||
|
||||
LL : Ça c’est un peu un problème, on a eu quelques fois le cas avec des travaux de l’erg, où le truc, c’est parfois juste une sorte de petite étiquette avec un code barre ou des choses comme ça, alors ça c’est un peu délicat.
|
||||
|
||||
T : Vous n’avez pas d’espace numérique ?
|
||||
|
||||
LL : Non, et en fait Dial est vraiment limité à l’UCLouvain. Donc souvent dans ce cas-là, ce que les étudiants font, c’est qu’ils 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 m’intéresse plus, je ne paye plus » et le travail disparaît. Des étudiants de l’UCLouvain avaient proposé une solution, je crois qu’il y a moyen d’archiver en ligne. On l’avait fait, parce qu’on 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 d’un serveur ou d’autres choses.
|
||||
|
||||
T : Est-ce qu’on peut voir dans le catalogue le nombre de mémoires qui ont été faits au format numérique ?
|
||||
|
||||
LL : Je ne pense pas, parfois c’est sous forme de podcast, c’est de l’audio, 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 c’est complexe. Parce que c’est vrai que tout a des fins pédagogiques, la base c’est que c’est à des fins pédagogiques. Donc tu parlais d’obligation tout à l’heure, on est plus dans l’idée d’archivage, parce qu’il n’y a pas la place à l’erg, et donc plutôt que de laisser ça dans un placard, ça vient ici, parce que c’est une manière de rendre les choses accessibles et consultables par les profs et les étudiants. C’est une autre finalité.
|
||||
|
||||
O : Et donc pour l’erg ça commence en 2005 ?
|
||||
|
||||
LL : Oui c’est ça. Et je pense qu’au tout début, il y avait énormément de mémoires ici, parce que la qualité ne suivait pas, mais petit à petit ça s’est relevé. Quand les étudiants de Renaud sont venus, ils ont récupérés une partie de ceux-là, parce qu’ils regardaient surtout l’objet. C’est compliqué d’avoir quelqu’un qui a une démarche scientifique et aussi artistique. Alors il y en a certains que l’on conserve un double, c’est un peu ce que tu disais, s’il y en a qui disparaissent. Et on ne les numérise pas nous.
|
||||
|
||||
T : J’ai vu qu’il y avait écrit « numériser » sur des cartons juste derrière, qu’est-ce que vous numérisez du coup ?
|
||||
|
||||
LL : Ça ce sont des VHS, et Aurélien les numérise.
|
||||
|
||||
A : J’ai encore des questions. Quand c’est un mémoire numérique, sous forme de site web par exemple, est-ce qu’ils sont répertoriés quelque part ?
|
||||
|
||||
LL : Ils sont d’office 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 n’est qu’un site on fournissait le lien, mais si un jour l’étudiant arrête de payer son nom de domaine, il n’y a plus rien. Après, je sais qu’on l’avais fait une fois, on avait cherché des solutions intermédiaires, on avait archivé en ligne. Mais c’est vraiment au cas par cas, il n’y a rien de prévu de manière automatique.
|
||||
|
||||
T : Oui, il faudrait voir alors.
|
||||
|
||||
LL : Oui, prévoir un serveur à l’erg.
|
||||
|
||||
O : Ou que la bibliothèque soit aussi un appui numérique. T : Je suis pas sûr qu’il 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 s’informer 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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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! 🚀
|
||||
@@ -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/)
|
||||
@@ -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! ✨
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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! 🚀
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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?
|
||||
@@ -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
|
||||
```
|
||||
163
docs/SECURITY.md
163
docs/SECURITY.md
@@ -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.
|
||||
@@ -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 ~127–131)
|
||||
|
||||
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 ~38–42)
|
||||
|
||||
```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.*
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
1375
docs/SETUP.md
1375
docs/SETUP.md
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -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!** ✅
|
||||
@@ -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 1–2)
|
||||
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 13–16)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-02-08*
|
||||
62
docs/TODO.md
Normal file
62
docs/TODO.md
Normal 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
|
||||
1098
docs/assessments.md
1098
docs/assessments.md
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
Here’s 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 it’s 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
329
docs/database.md
Normal 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 (0–20) |
|
||||
| `jury_note_added` | BOOLEAN | no | Jury context note flag |
|
||||
| `submitted_at` | DATETIME | no | Student submission |
|
||||
| `defense_date` | DATETIME | no | Defense date |
|
||||
| `published_at` | DATETIME | no | Publication date |
|
||||
| `is_published` | BOOLEAN | no | Publication status |
|
||||
| `baiu_link` | TEXT | no | Institutional repository link |
|
||||
| `created_at` | DATETIME | auto | Record creation |
|
||||
| `updated_at` | DATETIME | auto | Last update (trigger) |
|
||||
|
||||
**Indexes:** `idx_theses_year`, `idx_theses_published`, `idx_theses_identifier`, `idx_theses_orientation`, `idx_theses_ap_program`, `idx_theses_access_type`
|
||||
|
||||
### `authors`
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | INTEGER PK | Auto |
|
||||
| `name` | TEXT NOT NULL | Full name |
|
||||
| `email` | TEXT | Contact email (optional) |
|
||||
| `created_at` / `updated_at` | DATETIME | Auto timestamps |
|
||||
|
||||
**Index:** `idx_authors_email`
|
||||
|
||||
### `supervisors`
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | INTEGER PK | Auto |
|
||||
| `name` | TEXT NOT NULL | Full name |
|
||||
| `created_at` / `updated_at` | DATETIME | Auto timestamps |
|
||||
|
||||
### `thesis_files`
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | INTEGER PK | Auto |
|
||||
| `thesis_id` | INTEGER FK | → `theses` (CASCADE) |
|
||||
| `file_type` | TEXT | `main`, `annex`, `written_part`, `other` |
|
||||
| `file_path` | TEXT | Relative path |
|
||||
| `file_name` | TEXT | Original filename |
|
||||
| `file_size` | INTEGER | Size in bytes |
|
||||
| `mime_type` | TEXT | MIME type |
|
||||
| `description` | TEXT | Optional |
|
||||
| `uploaded_at` | DATETIME | Upload timestamp |
|
||||
|
||||
### `pages`
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | INTEGER PK | Auto |
|
||||
| `slug` | TEXT UNIQUE | URL identifier |
|
||||
| `title` | TEXT NOT NULL | Page title |
|
||||
| `content` | TEXT | Markdown/HTML |
|
||||
| `is_published` | BOOLEAN | Default 1 |
|
||||
| `created_at` / `updated_at` | DATETIME | Auto timestamps |
|
||||
|
||||
**Pre-loaded:** `charte`, `about`, `licenses`, `contact`
|
||||
|
||||
---
|
||||
|
||||
## Lookup Tables
|
||||
|
||||
### `orientations` (15 predefined)
|
||||
|
||||
Arts Numériques, Dessin, Cinéma d'animation, Installation-Performance, Peinture, Photographie, Sculpture, Vidéographie, Graphisme, Typographie, Design Numérique, Illustration, Bande-Dessinée, Sérigraphie, Gravure
|
||||
|
||||
### `ap_programs` (4)
|
||||
|
||||
| Code | Name |
|
||||
|------|------|
|
||||
| — | Narration Spéculative |
|
||||
| DPM | Design et Politique du Multiple |
|
||||
| APS | Atelier Pratiques Situées |
|
||||
| LIENS | Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes |
|
||||
|
||||
### `finality_types` (3)
|
||||
|
||||
Approfondi, Enseignement, Spécialisé
|
||||
|
||||
### `format_types` (7)
|
||||
|
||||
Site web, Audio, Vidéo, Performance, Objet éditorial, Installation, Autre
|
||||
|
||||
### `access_types` (3)
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| Libre | Full access online + library |
|
||||
| Interne | Physical only; note online |
|
||||
| Interdit | No access; note only |
|
||||
|
||||
**Business rule:** Access can only be restricted (Libre → Interne → Interdit), never opened.
|
||||
|
||||
### `languages`
|
||||
|
||||
Français, Anglais (expandable)
|
||||
|
||||
### `keywords`
|
||||
|
||||
Dynamic, grows organically. Max 10 per thesis (application-enforced).
|
||||
|
||||
---
|
||||
|
||||
## Junction Tables
|
||||
|
||||
All use composite PKs (`thesis_id`, `*_id`) with `ON DELETE CASCADE`.
|
||||
|
||||
| Table | Links | Order column |
|
||||
|-------|-------|-------------|
|
||||
| `thesis_authors` | theses ↔ authors | `author_order` |
|
||||
| `thesis_supervisors` | theses ↔ supervisors | `supervisor_order` |
|
||||
| `thesis_keywords` | theses ↔ keywords | — |
|
||||
| `thesis_languages` | theses ↔ languages | — |
|
||||
| `thesis_formats` | theses ↔ format_types | — |
|
||||
|
||||
**Indexes on junction tables:** `idx_thesis_keywords_thesis`, `idx_thesis_keywords_keyword` (and equivalents for authors)
|
||||
|
||||
---
|
||||
|
||||
## Views
|
||||
|
||||
### `v_theses_full` — Admin view
|
||||
|
||||
All theses with joined relationships (GROUP_CONCAT for authors, supervisors, keywords, languages, formats, plus human-readable names for orientation, AP, finality, access type, license).
|
||||
|
||||
### `v_theses_public` — Public view
|
||||
|
||||
Same as `v_theses_full` filtered to `is_published = 1`. Unpublished theses never exposed.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Features
|
||||
|
||||
- **Auto-increment IDs:** All PKs use `AUTOINCREMENT`
|
||||
- **Auto timestamps:** `created_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
204
docs/deployment.md
Normal 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
272
docs/development.md
Normal 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
154
docs/migration-history.md
Normal 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.
|
||||
@@ -8,7 +8,7 @@ The search feature allows users to search across theses using multiple criteria
|
||||
### New Files
|
||||
1. **search.php** - Main search interface page
|
||||
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
|
||||
1. **Database.php** - Added search methods:
|
||||
82
docs/security.md
Normal file
82
docs/security.md
Normal 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 1–2)
|
||||
2. 🟡 **MEDIUM** — Items 7, 9, 11, 12 remaining
|
||||
3. ~~🔵 LOW~~ — All done (items 13–16)
|
||||
|
||||
---
|
||||
|
||||
## 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*
|
||||
Reference in New Issue
Block a user