3a32e93ab1
Implements the JSON API for Le Donut Infolab per INSTRUCTIONS.md.
Schema (migrations/001-002):
- 8 tables: categories, members, projects, project_members, roadmap_steps,
discussions, resources, activity_log
- 6 categories seeded with colors / dimensions (social, ecologique, transversal)
- FK contraintes + soft delete sur projects, CHECK sur progress_percent
Bare PHP framework (src/):
- Front controller public/index.php + router :params + auth flag par route
- PSR-4 autoload sans Composer (bootstrap.php)
- Database PDO singleton + helpers fetchOne/All/insert/update/delete
- Validator fluent -> 422 {error: validation_failed, fields: {...}}
- Bearer token admin (hash_equals) sur POST/PATCH/DELETE
- Slugger avec unicité (suffixe -2, -3...) via intl Transliterator
- ActivityLogger best-effort sur activity_log
- Erreurs uniformes : 400/401/404/405/409/422/500 + CORS configurable
10 contrôleurs (Health, Categories, Members, Projects, ProjectMembers,
Roadmap, Discussions, Resources, Activity, Stats). Transitions de statut
loggées (created_project, completed_step, joined, etc.).
Cible prod :
- Dockerfile (php:8.3-apache + pdo_mysql + intl + opcache)
- docker-compose.yml sur réseau donut-net, bind 127.0.0.1:8003
- caddy.conf pour infolab.ledonut-marseille.org
- migrations/apply.sh idempotent (table schema_migrations)
- DEPLOY.md avec commandes SSH exactes
Tests :
- tests/smoke.sh exerce 10 endpoints clés, body envoyé via stdin
(--data-binary @-) pour gérer l UTF-8 sous Git Bash Windows.
Vérifié localement sous Laragon Apache + MySQL 8.4 : 10/10 OK + cas
négatifs (401, 404, 405, 409, 422) conformes au contrat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
154 lines
7.8 KiB
SQL
154 lines
7.8 KiB
SQL
SET NAMES utf8mb4;
|
|
SET FOREIGN_KEY_CHECKS = 0;
|
|
|
|
-- categories : 6 catégories thématiques de la maquette
|
|
DROP TABLE IF EXISTS categories;
|
|
CREATE TABLE categories (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
slug VARCHAR(64) NOT NULL,
|
|
name VARCHAR(128) NOT NULL,
|
|
description TEXT NULL,
|
|
color_hex CHAR(7) NOT NULL DEFAULT '#888888',
|
|
icon VARCHAR(32) NULL,
|
|
dimension ENUM('social', 'ecologique', 'transversal') NOT NULL DEFAULT 'transversal',
|
|
sort_order INT UNSIGNED NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_categories_slug (slug)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- members : annuaire des contributeurs (pas une table d'auth)
|
|
DROP TABLE IF EXISTS members;
|
|
CREATE TABLE members (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
slug VARCHAR(96) NOT NULL,
|
|
display_name VARCHAR(128) NOT NULL,
|
|
initials VARCHAR(8) NULL,
|
|
kind ENUM('person', 'collective', 'org') NOT NULL DEFAULT 'person',
|
|
bio TEXT NULL,
|
|
avatar_url VARCHAR(512) NULL,
|
|
website_url VARCHAR(512) NULL,
|
|
contact_email VARCHAR(255) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_members_slug (slug)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- projects : entité centrale
|
|
DROP TABLE IF EXISTS projects;
|
|
CREATE TABLE projects (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
slug VARCHAR(160) NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
category_id BIGINT UNSIGNED NOT NULL,
|
|
status ENUM('active', 'completed', 'archived') NOT NULL DEFAULT 'active',
|
|
territory VARCHAR(160) NOT NULL DEFAULT 'Marseille',
|
|
short_description TEXT NULL,
|
|
context_description LONGTEXT NULL,
|
|
objectives LONGTEXT NULL,
|
|
cover_image_url VARCHAR(512) NULL,
|
|
external_url VARCHAR(512) NULL,
|
|
progress_percent TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
|
started_at DATE NULL,
|
|
completed_at DATE NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
deleted_at DATETIME NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_projects_slug (slug),
|
|
KEY idx_projects_category (category_id),
|
|
KEY idx_projects_status (status),
|
|
KEY idx_projects_deleted (deleted_at),
|
|
CONSTRAINT fk_projects_category FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE RESTRICT,
|
|
CONSTRAINT chk_projects_progress CHECK (progress_percent BETWEEN 0 AND 100)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- project_members : pivot N..N projet/contributeur avec rôle
|
|
DROP TABLE IF EXISTS project_members;
|
|
CREATE TABLE project_members (
|
|
project_id BIGINT UNSIGNED NOT NULL,
|
|
member_id BIGINT UNSIGNED NOT NULL,
|
|
role ENUM('admin', 'contributor', 'observer') NOT NULL DEFAULT 'contributor',
|
|
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (project_id, member_id),
|
|
KEY idx_pm_member (member_id),
|
|
CONSTRAINT fk_pm_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_pm_member FOREIGN KEY (member_id) REFERENCES members(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- roadmap_steps : feuille de route
|
|
DROP TABLE IF EXISTS roadmap_steps;
|
|
CREATE TABLE roadmap_steps (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
project_id BIGINT UNSIGNED NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
description TEXT NULL,
|
|
step_date DATE NULL,
|
|
is_done BOOLEAN NOT NULL DEFAULT FALSE,
|
|
sort_order INT UNSIGNED NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_roadmap_project (project_id, sort_order),
|
|
CONSTRAINT fk_roadmap_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- discussions : onglet Discussions, arborescent (parent_id pour les réponses)
|
|
DROP TABLE IF EXISTS discussions;
|
|
CREATE TABLE discussions (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
project_id BIGINT UNSIGNED NOT NULL,
|
|
parent_id BIGINT UNSIGNED NULL,
|
|
author_name VARCHAR(128) NOT NULL,
|
|
author_member_id BIGINT UNSIGNED NULL,
|
|
body LONGTEXT NOT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_disc_project (project_id, created_at),
|
|
KEY idx_disc_parent (parent_id),
|
|
CONSTRAINT fk_disc_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_disc_parent FOREIGN KEY (parent_id) REFERENCES discussions(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_disc_member FOREIGN KEY (author_member_id) REFERENCES members(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- resources : onglet Ressources (liens, docs, datasets)
|
|
DROP TABLE IF EXISTS resources;
|
|
CREATE TABLE resources (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
project_id BIGINT UNSIGNED NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
description TEXT NULL,
|
|
url VARCHAR(1024) NOT NULL,
|
|
kind ENUM('link', 'document', 'dataset', 'video', 'other') NOT NULL DEFAULT 'link',
|
|
sort_order INT UNSIGNED NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_res_project (project_id, sort_order),
|
|
CONSTRAINT fk_res_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- activity_log : flux du dashboard ("Vous a rejoint le projet X")
|
|
DROP TABLE IF EXISTS activity_log;
|
|
CREATE TABLE activity_log (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
project_id BIGINT UNSIGNED NULL,
|
|
member_id BIGINT UNSIGNED NULL,
|
|
actor_label VARCHAR(128) NOT NULL DEFAULT 'Vous',
|
|
verb ENUM('joined', 'left', 'created_project', 'updated_project',
|
|
'completed_step', 'added_step', 'posted_discussion',
|
|
'added_resource', 'completed_project') NOT NULL,
|
|
summary VARCHAR(255) NOT NULL,
|
|
occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_act_project (project_id, occurred_at),
|
|
KEY idx_act_occurred (occurred_at),
|
|
CONSTRAINT fk_act_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_act_member FOREIGN KEY (member_id) REFERENCES members(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
SET FOREIGN_KEY_CHECKS = 1;
|