first commit
This commit is contained in:
19
database/migrations/001_create_users.php
Normal file
19
database/migrations/001_create_users.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 001 — Création de la table des utilisateurs.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'user',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
",
|
||||
|
||||
'down' => 'DROP TABLE IF EXISTS users',
|
||||
];
|
||||
23
database/migrations/002_create_categories.php
Normal file
23
database/migrations/002_create_categories.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 002 — Création de la table des catégories.
|
||||
*
|
||||
* category_id est une clé étrangère nullable vers category(id).
|
||||
* SET NULL garantit que les articles sont conservés si une catégorie est supprimée.
|
||||
*
|
||||
* SQLite ne supportant pas l'ajout de contrainte FK via ALTER TABLE,
|
||||
* la référence est déclarée inline dans le ADD COLUMN — SQLite l'enregistre
|
||||
* dans le schéma sans l'appliquer strictement à moins que PRAGMA foreign_keys = ON.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL
|
||||
);
|
||||
",
|
||||
|
||||
'down' => 'DROP TABLE IF EXISTS categories',
|
||||
];
|
||||
31
database/migrations/003_create_posts.php
Normal file
31
database/migrations/003_create_posts.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 003 — Création de la table des articles.
|
||||
*
|
||||
* author_id est une clé étrangère nullable vers users(id).
|
||||
* SET NULL garantit que les articles sont conservés si l'auteur est supprimé.
|
||||
*
|
||||
* Index explicite sur author_id : SQLite n'indexe pas automatiquement les FK.
|
||||
* Utilisé par findByUserId() (liste admin) et par les filtres de recherche FTS.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL DEFAULT '',
|
||||
author_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_author_id ON posts(author_id);
|
||||
",
|
||||
|
||||
'down' => "
|
||||
DROP INDEX IF EXISTS idx_posts_author_id;
|
||||
DROP TABLE IF EXISTS posts;
|
||||
",
|
||||
];
|
||||
29
database/migrations/004_create_media.php
Normal file
29
database/migrations/004_create_media.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 004 — Création de la table des médias uploadés.
|
||||
*
|
||||
* La colonne hash (SHA-256) est unique et permet la détection des doublons à l'upload.
|
||||
* user_id est nullable : SET NULL si le compte auteur est supprimé.
|
||||
*
|
||||
* Index explicite sur user_id : SQLite n'indexe pas automatiquement les FK.
|
||||
* Utilisé par findByUserId() dans la galerie média.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
filename TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
hash TEXT NOT NULL UNIQUE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_user_id ON media(user_id);
|
||||
",
|
||||
|
||||
'down' => "
|
||||
DROP INDEX IF EXISTS idx_media_user_id;
|
||||
DROP TABLE IF EXISTS media;
|
||||
",
|
||||
];
|
||||
26
database/migrations/005_create_password_resets.php
Normal file
26
database/migrations/005_create_password_resets.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 005 — Création de la table de réinitialisation de mot de passe.
|
||||
*
|
||||
* token_hash : hash SHA-256 du token envoyé par email — le token brut
|
||||
* n'est jamais stocké en base pour limiter l'impact d'une fuite.
|
||||
* expires_at : timestamp d'expiration (1 heure après création).
|
||||
* used_at : timestamp de consommation — le token est invalidé après usage
|
||||
* sans être supprimé immédiatement (traçabilité).
|
||||
* user_id : CASCADE — supprime les tokens si l'utilisateur est supprimé.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS password_resets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at DATETIME NOT NULL,
|
||||
used_at DATETIME DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
",
|
||||
|
||||
'down' => 'DROP TABLE IF EXISTS password_resets',
|
||||
];
|
||||
68
database/migrations/006_create_posts_fts.php
Normal file
68
database/migrations/006_create_posts_fts.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 006 — Création de l'index FTS5 pour la recherche plein texte.
|
||||
*
|
||||
* Crée une table virtuelle FTS5 `posts_fts` indexant le titre, le contenu
|
||||
* et le nom de l'auteur de chaque article. Le rowid de la table FTS correspond
|
||||
* à l'id de l'article dans `posts`.
|
||||
*
|
||||
* Trois triggers maintiennent la synchronisation automatique entre `posts`
|
||||
* et `posts_fts` à chaque INSERT, UPDATE et DELETE.
|
||||
*
|
||||
* La colonne `author_username` est résolue par sous-requête lors de l'écriture
|
||||
* dans le trigger, ce qui évite de stocker une valeur dénormalisée en dehors
|
||||
* de l'index FTS (dont le seul rôle est la recherche).
|
||||
*
|
||||
* Le contenu HTML est passé par `strip_tags()` (fonction PHP enregistrée sur la
|
||||
* connexion PDO via `sqliteCreateFunction`) avant indexation, afin d'éviter que
|
||||
* les balises et attributs HTML ne polluent l'index et ne génèrent du bruit.
|
||||
*
|
||||
* Tokenizer utilisé : unicode61 (défaut FTS5) — gère les diacritiques et les
|
||||
* caractères non-ASCII, insensible à la casse.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5(
|
||||
title,
|
||||
content,
|
||||
author_username,
|
||||
tokenize = 'unicode61 remove_diacritics 1'
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS posts_fts_insert
|
||||
AFTER INSERT ON posts BEGIN
|
||||
INSERT INTO posts_fts(rowid, title, content, author_username)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.title,
|
||||
COALESCE(strip_tags(NEW.content), ''),
|
||||
COALESCE((SELECT username FROM users WHERE id = NEW.author_id), '')
|
||||
);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS posts_fts_update
|
||||
AFTER UPDATE ON posts BEGIN
|
||||
DELETE FROM posts_fts WHERE rowid = OLD.id;
|
||||
INSERT INTO posts_fts(rowid, title, content, author_username)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.title,
|
||||
COALESCE(strip_tags(NEW.content), ''),
|
||||
COALESCE((SELECT username FROM users WHERE id = NEW.author_id), '')
|
||||
);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS posts_fts_delete
|
||||
AFTER DELETE ON posts BEGIN
|
||||
DELETE FROM posts_fts WHERE rowid = OLD.id;
|
||||
END;
|
||||
",
|
||||
|
||||
'down' => "
|
||||
DROP TRIGGER IF EXISTS posts_fts_delete;
|
||||
DROP TRIGGER IF EXISTS posts_fts_update;
|
||||
DROP TRIGGER IF EXISTS posts_fts_insert;
|
||||
DROP TABLE IF EXISTS posts_fts;
|
||||
",
|
||||
];
|
||||
27
database/migrations/007_create_login_attempts.php
Normal file
27
database/migrations/007_create_login_attempts.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Migration 007 — Table de protection contre le brute-force.
|
||||
*
|
||||
* Stocke les tentatives de connexion échouées par adresse IP.
|
||||
* Le champ `locked_until` est NULL tant que le seuil n'est pas atteint,
|
||||
* puis contient la date/heure ISO 8601 de fin de verrouillage.
|
||||
*
|
||||
* Le nettoyage des entrées expirées est effectué par LoginAttemptRepository
|
||||
* à chaque tentative pour éviter l'accumulation de lignes obsolètes,
|
||||
* sans nécessiter de tâche planifiée.
|
||||
*/
|
||||
return [
|
||||
'up' => "
|
||||
CREATE TABLE IF NOT EXISTS login_attempts (
|
||||
ip TEXT NOT NULL PRIMARY KEY,
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
locked_until TEXT DEFAULT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
",
|
||||
|
||||
'down' => "
|
||||
DROP TABLE IF EXISTS login_attempts;
|
||||
",
|
||||
];
|
||||
25
database/migrations/008_sync_posts_fts_when_users_change.php
Normal file
25
database/migrations/008_sync_posts_fts_when_users_change.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'up' => "
|
||||
DROP TRIGGER IF EXISTS posts_fts_users_delete;
|
||||
DROP TRIGGER IF EXISTS posts_fts_users_update;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS posts_fts_users_update
|
||||
AFTER UPDATE OF username ON users BEGIN
|
||||
DELETE FROM posts_fts
|
||||
WHERE rowid IN (SELECT id FROM posts WHERE author_id = NEW.id);
|
||||
|
||||
INSERT INTO posts_fts(rowid, title, content, author_username)
|
||||
SELECT p.id,
|
||||
p.title,
|
||||
COALESCE(strip_tags(p.content), ''),
|
||||
NEW.username
|
||||
FROM posts p
|
||||
WHERE p.author_id = NEW.id;
|
||||
END;
|
||||
",
|
||||
'down' => "
|
||||
DROP TRIGGER IF EXISTS posts_fts_users_update;
|
||||
",
|
||||
];
|
||||
Reference in New Issue
Block a user