diff --git a/README.md b/README.md
index d5acf3b..909e672 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,32 @@ docker compose up -d --build
# https://househub.nas.percolouco.com
```
-La base de données est **initialisée automatiquement** depuis `schema.sql` au premier démarrage.
+La base de données meta (`househub_meta`) et les permissions sont **initialisées automatiquement** au premier démarrage via `docker/init/00-setup.sql`.
-Compte par défaut : `admin` / `password` → **à changer immédiatement** dans la table `pf_users`.
+Aucun compte par défaut — le premier utilisateur s'inscrit via `/register.php` et choisit de créer un espace familial.
+
+## 👥 Gestion multi-tenant
+
+HouseHub supporte plusieurs familles indépendantes sur la même instance :
+
+| URL | Description |
+|-----|-------------|
+| `/register.php` | Créer un compte + nouvel espace, ou rejoindre un espace existant via code |
+| `/login.php` | Connexion |
+| `/settings.php` | Paramètres du compte : profil, mot de passe, code d'invitation, membres |
+| `/admin/` | Panneau d'administration (admin uniquement) |
+
+### Inviter quelqu'un dans son espace
+1. Aller sur `/settings.php` → copier le code d'invitation
+2. La personne va sur `/register.php` → onglet "Rejoindre" → coller le code
+3. Les deux comptes partagent le même environnement de données
+
+### Rôle admin
+Le premier utilisateur inscrit doit être promu admin manuellement :
+```sql
+UPDATE househub_meta.users SET is_admin = 1 WHERE username = 'ton_username';
+```
+L'admin peut ensuite promouvoir/désactiver d'autres utilisateurs depuis `/admin/`.
---
diff --git a/admin/index.php b/admin/index.php
new file mode 100644
index 0000000..2b641d5
--- /dev/null
+++ b/admin/index.php
@@ -0,0 +1,196 @@
+prepare("UPDATE users SET is_active = 1 - is_active WHERE id = ?")->execute([$id]);
+ } elseif ($action === 'toggle_family' && $id) {
+ $meta_pdo->prepare("UPDATE families SET is_active = 1 - is_active WHERE id = ?")->execute([$id]);
+ } elseif ($action === 'toggle_admin' && $id) {
+ // Ne peut pas se retirer ses propres droits admin
+ if ($id !== (int)$_SESSION['user']['id']) {
+ $meta_pdo->prepare("UPDATE users SET is_admin = 1 - is_admin WHERE id = ?")->execute([$id]);
+ }
+ } elseif ($action === 'delete_user' && $id) {
+ if ($id !== (int)$_SESSION['user']['id']) {
+ $meta_pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]);
+ }
+ } elseif ($action === 'regen_invite' && $id) {
+ $new_code = bin2hex(random_bytes(8));
+ $meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?")->execute([$new_code, $id]);
+ }
+ header('Location: /admin/');
+ exit;
+}
+
+// ─── Données ──────────────────────────────────────────────────────────────────
+$families = $meta_pdo->query("
+ SELECT f.*, COUNT(u.id) as member_count
+ FROM families f
+ LEFT JOIN users u ON u.family_id = f.id
+ GROUP BY f.id
+ ORDER BY f.id
+")->fetchAll();
+
+$users = $meta_pdo->query("
+ SELECT u.*, f.name as family_name
+ FROM users u
+ LEFT JOIN families f ON f.id = u.family_id
+ ORDER BY u.id
+")->fetchAll();
+
+$pageTitle = "Admin — HouseHub";
+$activePage = "admin";
+require __DIR__ . '/../header.php';
+?>
+
+
+
+
⚙️ Panneau d'administration
+
Gestion des espaces familiaux et des utilisateurs
+
+
+
+ 🏠 Espaces familiaux
+ = count($families) ?>
+
+
+
+
+
+
+ | # |
+ Nom |
+ DB |
+ Code invitation |
+ Membres |
+ Statut |
+ Actions |
+
+
+
+
+
+ | = $f['id'] ?> |
+ = htmlspecialchars($f['name']) ?> |
+ = $f['db_name'] ?> |
+
+ = $f['invite_code'] ?>
+ |
+ = $f['member_count'] ?> |
+
+
+ = $f['is_active'] ? 'Actif' : 'Inactif' ?>
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ 👤 Utilisateurs
+ = count($users) ?>
+
+
+
+
+
+
+ | # |
+ Utilisateur |
+ Espace famille |
+ Rôle |
+ Statut |
+ Créé le |
+ Actions |
+
+
+
+
+
+ | = $u['id'] ?> |
+
+ = htmlspecialchars($u['display_name']) ?>
+ @= htmlspecialchars($u['username']) ?>
+ (vous)
+ |
+ = $u['family_name'] ? htmlspecialchars($u['family_name']) : '—' ?> |
+
+
+ Admin
+
+ Membre
+
+ |
+
+
+ = $u['is_active'] ? 'Actif' : 'Inactif' ?>
+
+ |
+ = date('d/m/Y', strtotime($u['created_at'])) ?> |
+
+
+
+
+
+
+
+ —
+
+
+ |
+
+
+
+
+
+
+
+
+
diff --git a/docker/init/00-setup.sql b/docker/init/00-setup.sql
index 17f1853..ba43e80 100644
--- a/docker/init/00-setup.sql
+++ b/docker/init/00-setup.sql
@@ -1,7 +1,6 @@
-- Initialisation HouseHub — Meta DB + permissions
-- Exécuté automatiquement au premier démarrage MariaDB
--- Tables meta (dans la DB créée par MYSQL_DATABASE=househub_meta)
USE househub_meta;
CREATE TABLE IF NOT EXISTS families (
@@ -9,6 +8,7 @@ CREATE TABLE IF NOT EXISTS families (
name VARCHAR(100) NOT NULL,
db_name VARCHAR(64) NOT NULL UNIQUE,
invite_code VARCHAR(32) NOT NULL UNIQUE,
+ is_active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS users (
password_hash VARCHAR(255) NOT NULL,
display_name VARCHAR(100) NOT NULL,
family_id INT DEFAULT NULL,
+ is_admin TINYINT(1) DEFAULT 0,
+ is_active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (family_id) REFERENCES families(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/includes/admin_auth.php b/includes/admin_auth.php
new file mode 100644
index 0000000..719b1da
--- /dev/null
+++ b/includes/admin_auth.php
@@ -0,0 +1,8 @@
+403 — Accès réservé aux administrateursRetour');
+}
diff --git a/invite.php b/invite.php
deleted file mode 100644
index e520d8a..0000000
--- a/invite.php
+++ /dev/null
@@ -1,64 +0,0 @@
-prepare("SELECT * FROM families WHERE id = ?");
-$family->execute([$_SESSION['user']['family_id']]);
-$family = $family->fetch();
-
-$members = $meta_pdo->prepare("SELECT display_name, username, created_at FROM users WHERE family_id = ? ORDER BY id");
-$members->execute([$_SESSION['user']['family_id']]);
-$members = $members->fetchAll();
-
-$pageTitle = "Invitation — HouseHub";
-$activePage = "invite";
-require __DIR__ . '/header.php';
-?>
-
-
-
-
👨👩👧 Votre espace familial
-
Partagez le code ci-dessous pour inviter quelqu'un dans votre espace.
-
-
-
Famille : = htmlspecialchars($family['name']) ?>
-
Code d'invitation
-
- = htmlspecialchars($family['invite_code']) ?>
-
-
✓ Copié !
-
- Lien d'inscription :
- = $_SERVER['HTTP_HOST'] ?>/register.php
-
-
-
-
Membres de l'espace (= count($members) ?>)
-
-
-
-
- = htmlspecialchars($m['display_name']) ?>
- @= htmlspecialchars($m['username']) ?>
-
-
= date('d/m/Y', strtotime($m['created_at'])) ?>
-
-
-
-
-
-
-
-
-
diff --git a/login.php b/login.php
index 40d3258..523dd81 100644
--- a/login.php
+++ b/login.php
@@ -21,7 +21,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = tr('error_missing_fields');
} else {
$stmt = $meta_pdo->prepare("
- SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, f.db_name
+ SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, f.db_name, f.is_active as family_active
FROM users u
LEFT JOIN families f ON f.id = u.family_id
WHERE u.username = ?
@@ -30,16 +30,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password_hash'])) {
- $_SESSION['user'] = [
- 'id' => (int)$user['id'],
- 'username' => $user['username'],
- 'display_name' => $user['display_name'],
- 'family_id' => (int)$user['family_id'],
- ];
- $_SESSION['family_db'] = $user['db_name'];
- $redirectTo = $_GET['redirect'] ?? '/index.php';
- header('Location: ' . $redirectTo);
- exit;
+ if (!$user['is_active']) {
+ $error = "Compte désactivé. Contactez l'administrateur.";
+ } elseif ($user['family_id'] && !$user['family_active']) {
+ $error = "Espace familial désactivé. Contactez l'administrateur.";
+ } else {
+ $_SESSION['user'] = [
+ 'id' => (int)$user['id'],
+ 'username' => $user['username'],
+ 'display_name' => $user['display_name'],
+ 'family_id' => (int)$user['family_id'],
+ 'is_admin' => (bool)$user['is_admin'],
+ ];
+ $_SESSION['family_db'] = $user['db_name'];
+ $redirectTo = $_GET['redirect'] ?? '/index.php';
+ header('Location: ' . $redirectTo);
+ exit;
+ }
} else {
$error = tr('error_invalid_credentials');
}
diff --git a/settings.php b/settings.php
new file mode 100644
index 0000000..cff8f6e
--- /dev/null
+++ b/settings.php
@@ -0,0 +1,215 @@
+prepare("UPDATE users SET display_name = ? WHERE id = ?")
+ ->execute([$display_name, $user_id]);
+ $_SESSION['user']['display_name'] = $display_name;
+ $success = "Profil mis à jour.";
+ }
+ }
+
+ if ($action === 'change_password') {
+ $current = $_POST['current_password'] ?? '';
+ $new = $_POST['new_password'] ?? '';
+ $confirm = $_POST['confirm_password'] ?? '';
+
+ $row = $meta_pdo->prepare("SELECT password_hash FROM users WHERE id = ?");
+ $row->execute([$user_id]);
+ $row = $row->fetch();
+
+ if (!password_verify($current, $row['password_hash'])) {
+ $error = "Mot de passe actuel incorrect.";
+ } elseif (strlen($new) < 6) {
+ $error = "Le nouveau mot de passe doit faire au moins 6 caractères.";
+ } elseif ($new !== $confirm) {
+ $error = "Les mots de passe ne correspondent pas.";
+ } else {
+ $meta_pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
+ ->execute([password_hash($new, PASSWORD_BCRYPT), $user_id]);
+ $success = "Mot de passe modifié avec succès.";
+ }
+ }
+
+ if ($action === 'update_family_name' && $family_id) {
+ $name = trim($_POST['family_name'] ?? '');
+ if ($name) {
+ $meta_pdo->prepare("UPDATE families SET name = ? WHERE id = ?")
+ ->execute([$name, $family_id]);
+ $success = "Nom de l'espace mis à jour.";
+ }
+ }
+
+ if ($action === 'regen_invite' && $family_id) {
+ $new_code = bin2hex(random_bytes(8));
+ $meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?")
+ ->execute([$new_code, $family_id]);
+ $success = "Nouveau code d'invitation généré.";
+ }
+}
+
+// ─── Chargement données ───────────────────────────────────────────────────────
+$user = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?");
+$user->execute([$user_id]);
+$user = $user->fetch();
+
+$family = null;
+$members = [];
+if ($family_id) {
+ $fam = $meta_pdo->prepare("SELECT * FROM families WHERE id = ?");
+ $fam->execute([$family_id]);
+ $family = $fam->fetch();
+
+ $mem = $meta_pdo->prepare("SELECT display_name, username, created_at, is_admin FROM users WHERE family_id = ? ORDER BY id");
+ $mem->execute([$family_id]);
+ $members = $mem->fetchAll();
+}
+
+$pageTitle = "Paramètres — HouseHub";
+$activePage = "settings";
+require __DIR__ . '/header.php';
+?>
+
+
+
+
⚙️ Paramètres
+
+
+
✓ = htmlspecialchars($success) ?>
+
+
+
✗ = htmlspecialchars($error) ?>
+
+
+
+
+
+
+
+ 🔒 Changer le mot de passe
+
+
+
+
+
+
+ 🏠 Mon espace familial
+
+
+
+
+
+
Code d'invitation — partagez-le pour inviter quelqu'un
+
+
+ = htmlspecialchars($family['invite_code']) ?>
+
+ ✓ Copié !
+
+
+
+ Lien d'inscription : = $_SERVER['HTTP_HOST'] ?>/register.php
+
+
+
+
+ Membres (= count($members) ?>)
+
+
+
+
+ = htmlspecialchars($m['display_name']) ?>
+ @= htmlspecialchars($m['username']) ?>
+
+ Admin
+
+
+
= date('d/m/Y', strtotime($m['created_at'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+