chore : ajouter fichiers non versionnés (migrations SQL, 404, PROJET.md)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||||
@@ -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;
|
||||||
@@ -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 <> '';
|
||||||
@@ -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);
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
@@ -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';
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
// Page d'erreur 404 — à inclure après http_response_code(404).
|
||||||
|
// Aucune variable externe requise.
|
||||||
|
$title = '404 — ' . siteTitle();
|
||||||
|
$metaRobots = 'noindex, nofollow';
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<div class="container py-5 text-center">
|
||||||
|
<p class="display-1 fw-bold text-muted mb-0">404</p>
|
||||||
|
<h1 class="h3 mb-3">Page introuvable</h1>
|
||||||
|
<p class="text-muted mb-4">
|
||||||
|
Cette adresse ne correspond à aucun contenu.<br>
|
||||||
|
Vous avez peut-être suivi un ancien lien.
|
||||||
|
</p>
|
||||||
|
<a href="/" class="btn btn-primary">← Retour à l'accueil</a>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
$content = ob_get_clean();
|
||||||
|
include __DIR__ . '/layout.php';
|
||||||
Reference in New Issue
Block a user