diff --git a/PROJET.md b/PROJET.md new file mode 100644 index 0000000..4fea8e2 --- /dev/null +++ b/PROJET.md @@ -0,0 +1,69 @@ +# FOLIO + +Moteur de blog PHP — utilisé par plusieurs sites. + +## Dépôt + +`https://git.abonnel.fr/cedricAbonnel/folio` — branche `main` + +## Sites utilisant Folio + +| Site | Workspace local | Serveur | +|---|---|---| +| varlog.a5l.fr | `~/Projects/varlog/` | `ssh varlog` | +| www.abonnel.fr | `~/Projects/fr.abonnel.www/` | `ssh abonnel-wiki` | + +## Structure du moteur + +``` +folio/ +├── src/ Classes PHP (ArticleManager, PostManager, auth…) +├── public/ Point d'entrée web (index.php, route.php, assets/) +├── templates/ Vues PHP (layout, header, footer, post_*) +├── config/ Configuration (config.php) +├── database/ Schéma SQL + migrate.php +├── composer.json +└── CHANGELOG.md +``` + +## Workflow de modification du moteur + +### 1. Développement et test sur varlog.a5l.fr + +Modifier le code ici dans `~/Projects/folio/`, tester sur **varlog.a5l.fr** : + +```bash +# Déployer sur varlog pour test +~/Projects/varlog/scripts/sync.sh + +# Tester sur http://varlog.acegrp.lan (ou https://varlog.a5l.fr) +``` + +### 2. Validation + +Une fois validé sur varlog.a5l.fr : + +```bash +# Commiter sur le serveur varlog (git de déploiement) +~/Projects/varlog/scripts/commit.sh "description du changement" +``` + +### 3. Push vers le dépôt Folio + +Pousser le code validé vers le dépôt canonique Folio : + +```bash +cd ~/Projects/folio +./scripts/push.sh "description du changement" +``` + +### 4. Déployer sur les autres sites si nécessaire + +```bash +~/Projects/fr.abonnel.www/scripts/sync.sh +~/Projects/fr.abonnel.www/scripts/commit.sh "même message" +``` + +## Credentials locaux + +Aucun credential dans folio/ — les `.env` sont dans chaque workspace site. diff --git a/database/migration_000_initial_schema.sql b/database/migration_000_initial_schema.sql new file mode 100644 index 0000000..1ffaf23 --- /dev/null +++ b/database/migration_000_initial_schema.sql @@ -0,0 +1,49 @@ +-- Schéma initial : tables créées avant la mise en place du système de migrations. +-- Remplace tables_create.sql et interactions_create.sql. + +CREATE TABLE IF NOT EXISTS posts ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + content TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP, + is_published BOOLEAN DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS post_files ( + id SERIAL PRIMARY KEY, + post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE, + file_type TEXT, + file_path TEXT, + original_name TEXT, + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS article_reactions ( + id SERIAL PRIMARY KEY, + article_uuid TEXT NOT NULL, + reaction_type TEXT NOT NULL CHECK (reaction_type IN ('useful', 'important', 'interesting')), + visitor_hash TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + UNIQUE (article_uuid, reaction_type, visitor_hash) +); +CREATE INDEX IF NOT EXISTS article_reactions_article_uuid_idx ON article_reactions (article_uuid); + +CREATE TABLE IF NOT EXISTS comments ( + id SERIAL PRIMARY KEY, + article_uuid TEXT NOT NULL, + author_name TEXT NOT NULL, + author_email TEXT NOT NULL, + content TEXT NOT NULL CHECK (LENGTH(content) <= 2000), + verify_token TEXT, + verification_code TEXT, + verify_attempts INTEGER NOT NULL DEFAULT 0, + verified BOOLEAN NOT NULL DEFAULT FALSE, + published BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + ip_address TEXT, + user_agent TEXT +); +CREATE INDEX IF NOT EXISTS comments_article_uuid_idx ON comments (article_uuid, verified, published); +CREATE INDEX IF NOT EXISTS comments_verify_token_idx ON comments (verify_token) + WHERE verified = FALSE AND verify_token IS NOT NULL; diff --git a/database/migration_008_user_profiles.sql b/database/migration_008_user_profiles.sql new file mode 100644 index 0000000..7b77bd1 --- /dev/null +++ b/database/migration_008_user_profiles.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS user_profiles ( + email TEXT NOT NULL PRIMARY KEY, + display_name TEXT NOT NULL DEFAULT '', + updated_at TIMESTAMP DEFAULT now(), + profile_url TEXT NOT NULL DEFAULT '', + profile_slug TEXT NOT NULL DEFAULT '', + bio TEXT NOT NULL DEFAULT '' +); +CREATE UNIQUE INDEX IF NOT EXISTS user_profiles_profile_slug_idx + ON user_profiles (profile_slug) WHERE profile_slug <> ''; diff --git a/database/migration_009_journal_smtp.sql b/database/migration_009_journal_smtp.sql new file mode 100644 index 0000000..fa81841 --- /dev/null +++ b/database/migration_009_journal_smtp.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS journal_smtp ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + script_path VARCHAR(512), + to_email VARCHAR(255) NOT NULL, + subject VARCHAR(512), + content_html TEXT, + content_text TEXT, + status VARCHAR(20) NOT NULL DEFAULT 'queued', + ip VARCHAR(128), + user_agent VARCHAR(512), + error_message VARCHAR(1000), + sent_at TIMESTAMP WITH TIME ZONE +); +CREATE INDEX IF NOT EXISTS idx_journal_smtp_created_at ON journal_smtp (created_at DESC); +CREATE INDEX IF NOT EXISTS idx_journal_smtp_to_email ON journal_smtp (to_email); diff --git a/database/migration_010_role_capabilities.sql b/database/migration_010_role_capabilities.sql new file mode 100644 index 0000000..4678b8e --- /dev/null +++ b/database/migration_010_role_capabilities.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS role_capabilities ( + role_id INTEGER NOT NULL REFERENCES roles(id) ON DELETE CASCADE, + capability VARCHAR(50) NOT NULL, + PRIMARY KEY (role_id, capability) +); diff --git a/database/migration_011_user_capabilities.sql b/database/migration_011_user_capabilities.sql new file mode 100644 index 0000000..a1dc52b --- /dev/null +++ b/database/migration_011_user_capabilities.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS user_capabilities ( + user_email TEXT NOT NULL, + capability TEXT NOT NULL, + granted_by TEXT, + granted_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + PRIMARY KEY (user_email, capability) +); diff --git a/database/migration_012_users.sql b/database/migration_012_users.sql new file mode 100644 index 0000000..164997a --- /dev/null +++ b/database/migration_012_users.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + updated_at TIMESTAMP, + password_changed_at TIMESTAMP +); diff --git a/database/migration_013_profiles.sql b/database/migration_013_profiles.sql new file mode 100644 index 0000000..264ee59 --- /dev/null +++ b/database/migration_013_profiles.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS profiles ( + id SERIAL PRIMARY KEY, + slug TEXT NOT NULL UNIQUE, + label TEXT NOT NULL DEFAULT '', + description TEXT, + permissions JSONB NOT NULL DEFAULT '[]', + is_system BOOLEAN NOT NULL DEFAULT FALSE, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); diff --git a/database/migration_014_app_config.sql b/database/migration_014_app_config.sql new file mode 100644 index 0000000..138a2d6 --- /dev/null +++ b/database/migration_014_app_config.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS app_config ( + id INTEGER PRIMARY KEY DEFAULT 1, + allow_password BOOLEAN NOT NULL DEFAULT TRUE, + allow_oidc BOOLEAN NOT NULL DEFAULT FALSE, + registrations_open BOOLEAN NOT NULL DEFAULT TRUE, + oidc_issuer TEXT, + oidc_name TEXT, + oidc_client_id TEXT, + oidc_client_secret TEXT, + oidc_redirect_uri TEXT, + updated_at TIMESTAMP, + CONSTRAINT app_config_single_row CHECK (id = 1) +); diff --git a/database/migration_015_mail_queue.sql b/database/migration_015_mail_queue.sql new file mode 100644 index 0000000..8b0340c --- /dev/null +++ b/database/migration_015_mail_queue.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS mail_queue ( + id SERIAL PRIMARY KEY, + to_email TEXT NOT NULL, + subject TEXT NOT NULL, + body TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + attempts INTEGER NOT NULL DEFAULT 0, + available_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + locked_at TIMESTAMP WITH TIME ZONE, + last_error TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() +); +CREATE INDEX IF NOT EXISTS idx_mail_queue_pending + ON mail_queue (available_at ASC, id ASC) + WHERE status = 'pending'; diff --git a/database/migration_016_dictionary.sql b/database/migration_016_dictionary.sql new file mode 100644 index 0000000..b07812f --- /dev/null +++ b/database/migration_016_dictionary.sql @@ -0,0 +1,44 @@ +-- Tables du dictionnaire de données (formulaires dynamiques) + +CREATE TABLE IF NOT EXISTS dd_entities ( + id SERIAL PRIMARY KEY, + code TEXT NOT NULL UNIQUE, + label TEXT NOT NULL DEFAULT '', + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS dd_fields ( + id SERIAL PRIMARY KEY, + entity_id INTEGER NOT NULL REFERENCES dd_entities(id) ON DELETE CASCADE, + code TEXT NOT NULL, + label TEXT NOT NULL DEFAULT '', + field_type TEXT NOT NULL DEFAULT 'text', + ui_order INTEGER, + is_required BOOLEAN NOT NULL DEFAULT FALSE, + default_val TEXT, + UNIQUE (entity_id, code) +); + +CREATE TABLE IF NOT EXISTS dd_rules ( + id SERIAL PRIMARY KEY, + entity_id INTEGER NOT NULL REFERENCES dd_entities(id) ON DELETE CASCADE, + rule_type TEXT NOT NULL, + expression TEXT, + message TEXT, + active BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS dd_enums ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS dd_enum_values ( + id SERIAL PRIMARY KEY, + enum_id INTEGER NOT NULL REFERENCES dd_enums(id) ON DELETE CASCADE, + code TEXT NOT NULL, + label TEXT NOT NULL DEFAULT '', + active BOOLEAN NOT NULL DEFAULT TRUE, + sort_order INTEGER NOT NULL DEFAULT 0, + UNIQUE (enum_id, code) +); diff --git a/templates/404.php b/templates/404.php new file mode 100644 index 0000000..3abf21e --- /dev/null +++ b/templates/404.php @@ -0,0 +1,19 @@ + +
404
+
+ Cette adresse ne correspond à aucun contenu.
+ Vous avez peut-être suivi un ancien lien.
+