From e789c286de1b20b63b7f53b40ac9cc9a9b182f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Gervreau-Mercier?= Date: Fri, 6 Feb 2026 11:33:20 +0100 Subject: [PATCH] Refactor admin panel and add migration documentation - Add comprehensive migration guides (DEPLOYMENT_MIGRATION.md, DIRECTORY_STRUCTURE.md, MIGRATION_CHECKLIST.md) - Refactor admin panel: split add.php, create reusable header/footer - Update styles: admin.css, common.css, main.css - Improve public pages: index.php, memoire.php - Reorganize database documentation into database/docs/ - Update .gitignore and justfile This prepares for migration to public/ directory structure --- .gitignore | 20 +- DEPLOYMENT_MIGRATION.md | 571 ++++++++++++++++++ DIRECTORY_STRUCTURE.md | 246 ++++++++ MIGRATION_CHECKLIST.md | 381 ++++++++++++ admin/add.php | 246 ++++++++ admin/import.php | 148 ++--- admin/inc/footer.php | 6 + admin/inc/head.php | 22 + admin/index.php | 523 ++++++++-------- admin/list.php | 451 -------------- assets/admin.css | 180 ++++++ assets/common.css | 184 +++--- assets/main.css | 101 ++++ database/{ => docs}/DATABASE_SPECIFICATION.md | 0 database/{ => docs}/QUICK_SCHEMA_REFERENCE.md | 0 database/{ => docs}/SETUP.md | 0 .../{ => docs}/posterg_fiche-technique.md | 0 inc/footer.php | 13 +- inc/header.php | 5 +- index.php | 125 ++-- justfile | 23 +- .../ad921d60486366258809553a3db49a4a.json | 2 +- .../f528764d624db129b32c21fbca0cb8d6.json | 2 +- memoire.php | 241 ++++---- 24 files changed, 2365 insertions(+), 1125 deletions(-) create mode 100644 DEPLOYMENT_MIGRATION.md create mode 100644 DIRECTORY_STRUCTURE.md create mode 100644 MIGRATION_CHECKLIST.md create mode 100644 admin/add.php create mode 100644 admin/inc/footer.php create mode 100644 admin/inc/head.php delete mode 100644 admin/list.php rename database/{ => docs}/DATABASE_SPECIFICATION.md (100%) rename database/{ => docs}/QUICK_SCHEMA_REFERENCE.md (100%) rename database/{ => docs}/SETUP.md (100%) rename database/{ => docs}/posterg_fiche-technique.md (100%) diff --git a/.gitignore b/.gitignore index e7c979f..43b07d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,13 @@ +# Vendor directory (third-party code) vendor/ compose.lock ### Test databases ### -formulaire/test.db -db/test.db - -### Data et Mémoire### -formulaire/data/yaml/* -formulaire/data/content/* -formulaire/data/cover/* -formulaire/data/theses/* -formulaire/data/covers/* - -front-backend/data/yaml/* -front-backend/data/content/* -front-backend/data/cover/* +database/test.db ### Logs ### formulaire/error.log - - -# Vendor directory (third-party code) -vendor/ +lib/cache/rate_limit/ # OS files .DS_Store diff --git a/DEPLOYMENT_MIGRATION.md b/DEPLOYMENT_MIGRATION.md new file mode 100644 index 0000000..1442a4a --- /dev/null +++ b/DEPLOYMENT_MIGRATION.md @@ -0,0 +1,571 @@ +# 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: `database/`, `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 ./database/test.db posterg:/var/www/html/database/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 ./database/test.db posterg:/var/www/posterg/database/ && \ + ssh posterg "chown www-data:posterg /var/www/posterg/database/test.db && \ + chmod 660 /var/www/posterg/database/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/database/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 ~ /database/ { + 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/database/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/database/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 | `/database/` | `/database/` (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 + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^ index.php [L] + +``` + +## 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/) diff --git a/MIGRATION_CHECKLIST.md b/MIGRATION_CHECKLIST.md new file mode 100644 index 0000000..8a9d1a5 --- /dev/null +++ b/MIGRATION_CHECKLIST.md @@ -0,0 +1,381 @@ +# 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/database/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 ^~ /database/ { 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 ^~ /database/ { 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 ./database/test.db posterg:/var/www/posterg/database/ + ssh posterg "chown www-data:posterg /var/www/posterg/database/test.db && \ + chmod 660 /var/www/posterg/database/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/database/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/../database/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/database/test.db # Must be 404! +``` + +--- + +## 📝 PHP Path Updates Needed + +After moving to public/, update PHP includes: + +**Before (from root):** +```php + paths-audit.txt + +# Review paths-audit.txt and update paths +cat paths-audit.txt +``` + +See **DEPLOYMENT_MIGRATION.md** for complete implementation details. diff --git a/admin/add.php b/admin/add.php new file mode 100644 index 0000000..a639354 --- /dev/null +++ b/admin/add.php @@ -0,0 +1,246 @@ +getAllOrientations(); + $apPrograms = $db->getAllAPPrograms(); + $finalityTypes = $db->getAllFinalityTypes(); + $languages = $db->getAllLanguages(); + $formatTypes = $db->getAllFormatTypes(); +} catch (Exception $e) { + error_log("Failed to load form data: " . $e->getMessage()); + die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard."); +} + +// Get error message and preserved form data from session (if redirected back from error) +$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null; +$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : []; + +// Clear session data after retrieving +unset($_SESSION["form_error"]); +unset($_SESSION["form_data"]); + +// Helper function to get old form value +function old($key, $default = "") +{ + global $formData; + return isset($formData[$key]) + ? htmlspecialchars($formData[$key]) + : $default; +} + +// Helper function to check if value was previously selected +function wasSelected($key, $value) +{ + global $formData; + if (!isset($formData[$key])) { + return false; + } + if (is_array($formData[$key])) { + return in_array($value, $formData[$key]); + } + return $formData[$key] == $value; +} +?> + +
+ +
+ ⚠️ Erreur: +
+ + +
+ + "> + + +
+ Informations de base + + " required> + + + "> + + " placeholder="" value="" required> +
+ + +
+ Informations académiques + + + + + + + + + + + "> +
+ + + +
+ À propos du TFE + + " required> + + + "> + + + + + +
    + +
  • + +
  • + +
+ + +
    + +
  • + +
  • + +
+ + + "> + Séparez les mots-clés par des virgules. Maximum 10 mots-clés. + + "> + Indiquez la durée (en minutes) ou le nombre de pages de votre TFE. + + "> +
+ + +
+ Fichiers + + Formats acceptés : JPG, PNG. Taille max : 10MB. + + + + Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB. + Si vous voulez importer un dossier, créez une archive ZIP. + +
+ +
+ +
+
+ + \ No newline at end of file diff --git a/admin/import.php b/admin/import.php index c47c052..6cbe0d3 100644 --- a/admin/import.php +++ b/admin/import.php @@ -11,6 +11,8 @@ if (empty($_SESSION['csrf_token'])) { require_once __DIR__ . '/../lib/Database.php'; +$pageTitle = "Import"; + $message = ''; $errors = []; $importedCount = 0; @@ -249,7 +251,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) { $db->commit(); $importedCount++; $importResults[] = "✓ Ligne $lineNumber: \"$title\" importé (ID: $thesisId)"; - } catch (Exception $e) { $db->rollback(); $skippedCount++; @@ -261,7 +262,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) { fclose($handle); $message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés."; - } catch (Exception $e) { $errors[] = $e->getMessage(); error_log("CSV import error: " . $e->getMessage()); @@ -272,95 +272,81 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } ?> - - - - - - Import CSV - Post-ERG - - - - - -
-

Import CSV - Post-ERG

- -
+// Import CSV - Post-ERG +//
+ //

Import CSV - Post-ERG

+ // + //
-
-

Importer des TFE depuis un fichier CSV

+
+

Importer des TFE depuis un fichier CSV

- -
- ⚠️ Erreurs: -
    - -
  • - -
-
- + +
+ ⚠️ Erreurs: +
    + +
  • + +
+
+ - -
- -
- + +
+ +
+ -
- + + -
- Sélectionner un fichier CSV +
+ Sélectionner un fichier CSV -

Format attendu:

-
    -
  • Colonnes: Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU
  • -
  • Les deux premières lignes seront ignorées (entête)
  • -
  • Séparateur: virgule
  • -
  • Encodage: UTF-8
  • -
+

Format attendu:

+
    +
  • Colonnes: Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU
  • +
  • Les deux premières lignes seront ignorées (entête)
  • +
  • Séparateur: virgule
  • +
  • Encodage: UTF-8
  • +
- - + + - -
- + +
+ - -

Résultats de l'import

-
-
-
- + +

Résultats de l'import

+
+
+
+ -
+
-

Notes importantes

-
    -
  • Codes orientation: SC (Sculpture), VI (Vidéographie), CA (Cinéma d'animation), IP (Installation-Performance), etc.
  • -
  • Codes AP: DPM, LIENS, APS (comme dans la base)
  • -
  • Auteurs multiples: Séparer par des virgules
  • -
  • Mots-clés: Maximum 10, séparés par des virgules
  • -
  • Formats: Séparer par des virgules
  • -
  • Les lignes avec erreurs seront ignorées et loggées
  • -
+

Notes importantes

+
    +
  • Codes orientation: SC (Sculpture), VI (Vidéographie), CA (Cinéma d'animation), IP (Installation-Performance), etc.
  • +
  • Codes AP: DPM, LIENS, APS (comme dans la base)
  • +
  • Auteurs multiples: Séparer par des virgules
  • +
  • Mots-clés: Maximum 10, séparés par des virgules
  • +
  • Formats: Séparer par des virgules
  • +
  • Les lignes avec erreurs seront ignorées et loggées
  • +
-

Exemple de fichier CSV

-

Voir: ../db/Database_TFE_test.csv

-
+

Exemple de fichier CSV

+

Voir: ../db/Database_TFE_test.csv

+
- - - + \ No newline at end of file diff --git a/admin/inc/footer.php b/admin/inc/footer.php new file mode 100644 index 0000000..5678470 --- /dev/null +++ b/admin/inc/footer.php @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/admin/inc/head.php b/admin/inc/head.php new file mode 100644 index 0000000..277ef7d --- /dev/null +++ b/admin/inc/head.php @@ -0,0 +1,22 @@ + + + + + + + + <? echo $pageTitle ?> + + + + + + + +
+

+ +
\ No newline at end of file diff --git a/admin/index.php b/admin/index.php index 9285ed2..df7f471 100644 --- a/admin/index.php +++ b/admin/index.php @@ -1,300 +1,285 @@ getPDO(); + + // Get filter parameters + $searchQuery = isset($_GET['search']) ? trim($_GET['search']) : ''; + $yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null; + $orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null; + + // Build query + $sql = "SELECT + t.id, t.identifier, t.title, t.subtitle, t.year, + o.name as orientation, + ap.name as ap_program, + GROUP_CONCAT(DISTINCT a.name) as authors, + t.submitted_at, + t.is_published + FROM theses t + LEFT JOIN orientations o ON t.orientation_id = o.id + LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id + LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id + LEFT JOIN authors a ON ta.author_id = a.id + WHERE 1=1"; + + $params = []; + + if ($searchQuery) { + $sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)"; + $searchParam = "%$searchQuery%"; + $params[] = $searchParam; + $params[] = $searchParam; + $params[] = $searchParam; + } + + if ($yearFilter) { + $sql .= " AND t.year = ?"; + $params[] = $yearFilter; + } + + if ($orientationFilter) { + $sql .= " AND t.orientation_id = ?"; + $params[] = $orientationFilter; + } + + $sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $theses = $stmt->fetchAll(); + + // Get unique years for filter + $yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC"); + $years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN); + + // Get orientations for filter $orientations = $db->getAllOrientations(); - $apPrograms = $db->getAllAPPrograms(); - $finalityTypes = $db->getAllFinalityTypes(); - $languages = $db->getAllLanguages(); - $formatTypes = $db->getAllFormatTypes(); } catch (Exception $e) { - error_log("Failed to load form data: " . $e->getMessage()); - die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard."); -} - -// Get error message and preserved form data from session (if redirected back from error) -$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null; -$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : []; - -// Clear session data after retrieving -unset($_SESSION["form_error"]); -unset($_SESSION["form_data"]); - -// Helper function to get old form value -function old($key, $default = "") -{ - global $formData; - return isset($formData[$key]) - ? htmlspecialchars($formData[$key]) - : $default; -} - -// Helper function to check if value was previously selected -function wasSelected($key, $value) -{ - global $formData; - if (!isset($formData[$key])) { - return false; - } - if (is_array($formData[$key])) { - return in_array($value, $formData[$key]); - } - return $formData[$key] == $value; + error_log("Error loading theses list: " . $e->getMessage()); + die("Erreur lors du chargement de la liste."); } ?> - - - - - - - Formulaire - - - - + + + +
+ +
+ ⚠️ Erreur: +
+ + + +
+ +
+ + + + + + +
+
+
+
TFE total
+
+
+
$t['is_published'])); ?>
+
Publiés
+
+
+
!$t['is_published'])); ?>
+
En attente
+
+
+ +
+
- - " required> + +
- - "> -
- -
- - " placeholder="" value="" required> -
- -

Informations académiques

- - -
- - + + + +
-
- - - -
- -
- - - -
- -
- - "> - -
- -

À propos du TFE

- - -
- - " required> - -
- -
- - "> -
- -
- - -
- -
- - -
- -
- - - +
+ + +
-
- - - - -
- -
- - "> - Séparez les mots-clés par des virgules. Maximum 10 mots-clés. -
- -
- - "> - Indiquez la durée (en minutes) ou le nombre de pages de votre TFE. -
- -
- - "> -
- -

Fichiers

- -
- - Formats acceptés : JPG, PNG. Taille max : 10MB. - -
- -
- - Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB. - Si vous voulez importer un dossier, créez une archive ZIP. - -
- -
- + + + Réinitialiser + -
+ - - + +

Aucun TFE trouvé.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDTitreAuteur(s)AnnéeOrientationAPStatutActions
+
+ +
+ +
+ + Publié + + En attente + + +
+ Voir + Éditer +
+ + + + + + + + + +
+
+
+ + - \ No newline at end of file + diff --git a/admin/list.php b/admin/list.php deleted file mode 100644 index 4be9941..0000000 --- a/admin/list.php +++ /dev/null @@ -1,451 +0,0 @@ -getPDO(); - - // Get filter parameters - $searchQuery = isset($_GET['search']) ? trim($_GET['search']) : ''; - $yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null; - $orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null; - - // Build query - $sql = "SELECT - t.id, t.identifier, t.title, t.subtitle, t.year, - o.name as orientation, - ap.name as ap_program, - GROUP_CONCAT(DISTINCT a.name) as authors, - t.submitted_at, - t.is_published - FROM theses t - LEFT JOIN orientations o ON t.orientation_id = o.id - LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id - LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id - LEFT JOIN authors a ON ta.author_id = a.id - WHERE 1=1"; - - $params = []; - - if ($searchQuery) { - $sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)"; - $searchParam = "%$searchQuery%"; - $params[] = $searchParam; - $params[] = $searchParam; - $params[] = $searchParam; - } - - if ($yearFilter) { - $sql .= " AND t.year = ?"; - $params[] = $yearFilter; - } - - if ($orientationFilter) { - $sql .= " AND t.orientation_id = ?"; - $params[] = $orientationFilter; - } - - $sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC"; - - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - $theses = $stmt->fetchAll(); - - // Get unique years for filter - $yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC"); - $years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN); - - // Get orientations for filter - $orientations = $db->getAllOrientations(); - -} catch (Exception $e) { - error_log("Error loading theses list: " . $e->getMessage()); - die("Erreur lors du chargement de la liste."); -} -?> - - - - - - Liste des TFE - Post-ERG - - - - - - - -
-

Liste des TFE

- -
- -
- -
- ⚠️ Erreur: -
- - - -
- -
- - - - - - -
-
-
-
TFE total
-
-
-
$t['is_published'])); ?>
-
Publiés
-
-
-
!$t['is_published'])); ?>
-
En attente
-
-
- -
-
-
- - -
- -
- - -
- -
- - -
- - - - Réinitialiser - -
-
- - -

Aucun TFE trouvé.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDTitreAuteur(s)AnnéeOrientationAPStatutActions
-
- -
- -
- - Publié - - En attente - - -
- Voir - Éditer -
- - - - - - - - - -
-
-
- -
- - - - diff --git a/assets/admin.css b/assets/admin.css index e69de29..da2f766 100644 --- a/assets/admin.css +++ b/assets/admin.css @@ -0,0 +1,180 @@ +main { + margin: 1.2rem 0; +} + +.filters { + background: #f5f5f5; + padding: 1rem; + margin-bottom: 2rem; + border-radius: 4px; +} + +.filters form { + display: flex; + gap: 1rem; + flex-wrap: wrap; + align-items: end; +} + +.filters fieldset { + margin: 0; + padding: 0; + border: none; + min-width: 200px; +} + +.thesis-table { + width: 100%; + border-collapse: collapse; +} + +.thesis-table th, +.thesis-table td { + padding: 0.75rem; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.thesis-table th { + background: #f0f0f0; + font-weight: bold; +} + +.thesis-table tr:hover { + background: #f9f9f9; +} + +.thesis-title { + font-weight: bold; +} + +.thesis-subtitle { + font-style: italic; + color: #666; + font-size: 0.9em; +} + +.status-badge { + display: inline-block; + padding: 0.25rem 0.5rem; + border-radius: 3px; + font-size: 0.85em; +} + +.status-pending { + background: #ffd700; + color: #000; +} + +.status-published { + background: #90ee90; + color: #000; +} + +.actions { + display: flex; + gap: 0.5rem; +} + +.btn { + padding: 0.35rem 0.75rem; + border-radius: 3px; + text-decoration: none; + font-size: 0.9em; + display: inline-block; +} + +.btn-view { + background: #4a90e2; + color: white; +} + +.btn-edit { + background: #f39c12; + color: white; +} + +.btn-publish { + background: #27ae60; + color: white; + border: none; + cursor: pointer; +} + +.btn-unpublish { + background: #95a5a6; + color: white; + border: none; + cursor: pointer; +} + +.publish-form { + display: inline; + margin: 0; +} + +.stats { + display: flex; + gap: 2rem; + margin-bottom: 2rem; + flex-wrap: wrap; +} + +.stat-card { + background: #f5f5f5; + padding: 1rem; + border-radius: 4px; + min-width: 150px; +} + +.stat-number { + font-size: 2em; + font-weight: bold; + color: #4a90e2; +} + +.stat-label { + color: #666; + font-size: 0.9em; +} + +.bulk-actions { + background: #f5f5f5; + padding: 1rem; + margin-bottom: 1rem; + border-radius: 4px; + display: flex; + gap: 1rem; + align-items: center; +} + +.bulk-actions-buttons { + display: flex; + gap: 0.5rem; +} + +.btn-bulk-publish { + background: #27ae60; + color: white; + border: none; + cursor: pointer; + padding: 0.5rem 1rem; + border-radius: 3px; +} + +.btn-bulk-unpublish { + background: #95a5a6; + color: white; + border: none; + cursor: pointer; + padding: 0.5rem 1rem; + border-radius: 3px; +} + +.select-checkbox { + cursor: pointer; +} + +.select-all-checkbox { + cursor: pointer; +} diff --git a/assets/common.css b/assets/common.css index 14f7f27..f22e3f1 100644 --- a/assets/common.css +++ b/assets/common.css @@ -1,103 +1,98 @@ - @font-face { - font-family: police1; - src: url("./Combinedd.otf"); - } +@font-face { + font-family: police1; + src: url("./fonts/Combinedd.otf"); +} /* Dark theme */ /* UTILE POUR FORCER UN MODE LIGHT */ -@media (prefers-color-scheme: dark) { - :root, - ::backdrop { - --bg: #fff; - --accent-bg: #f5f7ff; - --text: #212121; - --text-light: #585858; - --border: #898EA4; - --accent: #0d47a1; - --code: #d81b60; - --preformatted: #444; - --marked: #ffdd33; - --disabled: #efefef; +/* @media (prefers-color-scheme: dark) { */ +/* :root, */ +/* ::backdrop { */ +/* --bg: #fff; */ +/* --accent-bg: #f5f7ff; */ +/* --text: #212121; */ +/* --text-light: #585858; */ +/* --border: #898ea4; */ +/* --accent: #0d47a1; */ +/* --code: #d81b60; */ +/* --preformatted: #444; */ +/* --marked: #ffdd33; */ +/* --disabled: #efefef; */ +/* } */ +/* } */ + +body { + background-color: white; + margin: 0; +} + +/* FORMULAIRE */ +form label { + font-family: police1; + font-size: 1rem; +} +form input, +select, +textarea { + border-color: #c104fc; + overflow: visible; + outline: none; + background-color: white; +} + +form input:focus, +select:focus { + border: 3px solid rgba(77, 168, 112, 1); +} + +label { + margin-top: 2rem; +} + +input { + /* font-family: police1; */ + /* font-weight: bold; */ + background-color: none; + color: rgb(193, 4, 252); + border: 1px solid rgb(193, 4, 252); +} + +a { + color: rgb(193, 4, 252); +} + +a:hover { + text-decoration: none; +} +a, a:visited { + color: rgb(193, 4, 252); +} + +input:active { + border-color: rgba(77, 168, 112, 1); +} + +button, +[role="button"], +input[type="submit"], +input[type="reset"], +input[type="button"], +label[type="button"] { + background-color: white; + margin-top: 2rem; + font-size: 16px; + border-radius: 10px; + padding: 2ch; + margin: 1rem; + a { + color: black; + text-decoration: none; } } - body{ - background-color: white; - } +button {} - - /* ENTÊTE */ - header { - font-family: 'police1'; - background: linear-gradient(280deg, rgba(77, 168, 112, 1) 0%, rgba(193, 4, 252, 1) 85%); - text-decoration: none; - outline: none; - font-size: 2rem; - } - - body > header h1 { - color: white; - margin: 3rem auto auto auto; - } - - /* FORMULAIRE */ - - form label { - font-family: police1; - font-size: 1rem; - } - form input, - select, textarea { - - border-color: #c104fc; - overflow: visible; - outline: none; - background-color: white; - } - - form input:focus, - select:focus { - border: 3px solid rgba(77, 168, 112, 1); - } - - label{ - margin-top: 2rem; - } - -input { - /* font-family: police1; */ - /* font-weight: bold; */ - background-color: none; - color: rgb(193, 4, 252); - border: 1px solid rgb(193, 4, 252); - } - - a{ - color: rgb(193, 4, 252); - } - - a:hover { - text-decoration: none; - } - a, a:visited { - color: rgb(193, 4, 252); - } - - input:active { - border-color: rgba(77, 168, 112, 1); - - } - - button, [role="button"], input[type="submit"], input[type="reset"], input[type="button"], label[type="button"] { - background-color: rgb(193, 4, 252); - margin-top: 2rem; - } - - - - - - /* For Google Chrome, Safari, and newer versions of Opera */ +/* For Google Chrome, Safari, and newer versions of Opera */ ::placeholder { /* color: rgb(213, 73, 255); */ font-size: 0.8rem; @@ -106,4 +101,5 @@ input { /* For Mozilla Firefox */ ::-moz-placeholder { /* color: rgb(213, 73, 255); */ - font-size: 0.8rem;} + font-size: 0.8rem; +} diff --git a/assets/main.css b/assets/main.css index e69de29..3852ebe 100644 --- a/assets/main.css +++ b/assets/main.css @@ -0,0 +1,101 @@ +body { + margin: 0; + height: 100vh; + overflow: hidden; + display: flex; + flex-direction: column; +} + +header { + height: 20vh; +} +main { + height: 60vh; +} +footer { + height: 20vh; +} + +body { + display: flex; + flex-direction: column; +} + +header, main, footer { + padding: 1rem; + margin: 0; + border-radius: 40px; +} + +header { + font-family: "police1"; + background: #9557b5ff; + color: white; + font-size: 2rem; + display: flex; + gap: 6%; + padding: 1rem 4rem; + .title { + color: white; + } +} + +header section p:not(:first-child) { + font-size: 14px; +} + +header .title, header section, header nav { + text-decoration: none; + outline: none; + font-size: 18px; + text-decoration: none; + line-height: 2.5rem; +} +main { + height: 60vh; + + display: grid; + grid-template-rows: repeat(2, minmax(0, 1fr)); + grid-auto-flow: column; + + /* critical: force column width so new columns form */ + grid-auto-columns: 260px; + + gap: 1rem; + padding: 1rem; + box-sizing: border-box; + + overflow-x: auto; + overflow-y: hidden; +} +.card { + background: #eee; + border-radius: 10px; + padding: 1rem; +} + +main { + background: #3c856bff; +} + +footer { + background: #222222ff; +} + +/* .card { */ +/* width: 20%; */ +/* border: 1px solid white; */ +/* color: white; */ +/* margin: 1ch; */ +/* padding: 2ch; */ +/* } */ +main { + scroll-snap-type: x mandatory; +} + +.card { + scroll-snap-align: start; +} +.item { + width: 50%; +} diff --git a/database/DATABASE_SPECIFICATION.md b/database/docs/DATABASE_SPECIFICATION.md similarity index 100% rename from database/DATABASE_SPECIFICATION.md rename to database/docs/DATABASE_SPECIFICATION.md diff --git a/database/QUICK_SCHEMA_REFERENCE.md b/database/docs/QUICK_SCHEMA_REFERENCE.md similarity index 100% rename from database/QUICK_SCHEMA_REFERENCE.md rename to database/docs/QUICK_SCHEMA_REFERENCE.md diff --git a/database/SETUP.md b/database/docs/SETUP.md similarity index 100% rename from database/SETUP.md rename to database/docs/SETUP.md diff --git a/database/posterg_fiche-technique.md b/database/docs/posterg_fiche-technique.md similarity index 100% rename from database/posterg_fiche-technique.md rename to database/docs/posterg_fiche-technique.md diff --git a/inc/footer.php b/inc/footer.php index fb77120..5d7c8eb 100644 --- a/inc/footer.php +++ b/inc/footer.php @@ -1,9 +1,12 @@ -