feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API
- Mise à jour VERSION vers 3.3.4 - Optimisations et révisions architecture API (deploy-api.sh, scripts de migration) - Ajout documentation Stripe Tap to Pay complète - Migration vers polices Inter Variable pour Flutter - Optimisations build Android et nettoyage fichiers temporaires - Amélioration système de déploiement avec gestion backups - Ajout scripts CRON et migrations base de données 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
18
bao/.gitignore
vendored
Normal file
18
bao/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Configuration avec credentials
|
||||
config/.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Fichiers temporaires
|
||||
*.tmp
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
504
bao/README.md
Normal file
504
bao/README.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# BAO - Back-office Admin Operations
|
||||
|
||||
Toolkit d'administration pour consulter et gérer les données chiffrées de l'API Geosector.
|
||||
|
||||
## 🚀 Scripts disponibles
|
||||
|
||||
| Script | Description | Exemple |
|
||||
|--------|-------------|---------|
|
||||
| `./bin/bao` | Menu interactif principal | `./bin/bao` |
|
||||
| `./bin/search-user` | Rechercher un user par nom/prénom/username/secteur | `./bin/search-user rec dupont` |
|
||||
| `./bin/decrypt-user` | Afficher les infos complètes d'un user | `./bin/decrypt-user rec 123` |
|
||||
| `./bin/reset-password` | Générer et enregistrer un nouveau mot de passe | `./bin/reset-password rec 123` |
|
||||
| `./bin/search-email` | Rechercher par email | `./bin/search-email rec contact@example.com` |
|
||||
| `./bin/list-users` | Lister les utilisateurs | `./bin/list-users dev --entite=5` |
|
||||
| `./bin/search-entite` | Rechercher une entité (mode interactif) | `./bin/search-entite rec plumeliau` |
|
||||
| `./bin/decrypt-entite` | Afficher les infos complètes d'une entité | `./bin/decrypt-entite rec 10` |
|
||||
| `./bin/list-entites` | Lister les entités | `./bin/list-entites dev --stripe` |
|
||||
| `./bin/list-operations` | Lister les opérations | `./bin/list-operations dev --entite=5` |
|
||||
| `./bin/list-sectors` | Lister les secteurs d'une opération | `./bin/list-sectors dev --operation=123` |
|
||||
|
||||
## 📋 Vue d'ensemble
|
||||
|
||||
BAO est un ensemble de scripts **indépendants de l'API** permettant de :
|
||||
- Consulter les données **déchiffrées** des utilisateurs et entités
|
||||
- Rechercher dans les champs chiffrés (username, nom, email)
|
||||
- Réinitialiser les mots de passe utilisateurs
|
||||
- Se connecter aux **3 environnements** (DEV, REC, PROD) via tunnels SSH
|
||||
- Vérifier l'intégrité du chiffrement AES-256-CBC
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
PC Bureau (localhost)
|
||||
↓ SSH Tunnels
|
||||
├─→ IN3/dva-geo (DEV) → localhost:3306 → geo_app
|
||||
├─→ IN3/rca-geo (REC) → localhost:3306 → geo_app
|
||||
└─→ IN4/pra-geo (PROD) → localhost:3306 → pra_geo
|
||||
```
|
||||
|
||||
### Connexions SSH
|
||||
|
||||
Les tunnels SSH sont gérés automatiquement via `~/.ssh/config` :
|
||||
- **vpn-dva-geo** : Container DEV sur IN3 (via VPN)
|
||||
- **vpn-rca-geo** : Container REC sur IN3 (via VPN)
|
||||
- **pra-geo** : Container PROD sur IN4 (désactivé)
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### 1. Prérequis
|
||||
|
||||
```bash
|
||||
# PHP 8.3+ avec extensions
|
||||
php -v
|
||||
php -m | grep -E 'pdo|openssl|mbstring'
|
||||
|
||||
# SSH configuré dans ~/.ssh/config
|
||||
ssh vpn-dva-geo 'echo OK'
|
||||
ssh vpn-rca-geo 'echo OK'
|
||||
```
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
```bash
|
||||
cd /home/pierre/dev/geosector/bao
|
||||
|
||||
# Le fichier .env est déjà créé avec les bonnes valeurs
|
||||
# Vérifier la configuration
|
||||
cat config/.env
|
||||
```
|
||||
|
||||
## 🚀 Utilisation
|
||||
|
||||
### Menu interactif principal
|
||||
|
||||
```bash
|
||||
cd /home/pierre/dev/geosector/bao
|
||||
./bin/bao
|
||||
```
|
||||
|
||||
Menu avec :
|
||||
1. Sélection de l'environnement (DEV/REC/PROD)
|
||||
2. Actions disponibles (liste, recherche, décryptage)
|
||||
3. Gestion automatique des tunnels SSH
|
||||
|
||||
### Scripts individuels
|
||||
|
||||
#### Rechercher un utilisateur
|
||||
|
||||
```bash
|
||||
# Rechercher par nom, prénom, username ou secteur
|
||||
./bin/search-user dev dupont
|
||||
./bin/search-user rec pv_admin
|
||||
./bin/search-user dev jean
|
||||
```
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Déchiffre **tous** les utilisateurs pour chercher
|
||||
- Cherche dans : `encrypted_user_name`, `encrypted_name`, `first_name`, `sect_name`
|
||||
- Affiche un tableau avec les résultats
|
||||
- Propose d'afficher les détails complets si 1 seul résultat
|
||||
|
||||
#### Décrypter un utilisateur
|
||||
|
||||
```bash
|
||||
./bin/decrypt-user dev 123
|
||||
./bin/decrypt-user rec 456
|
||||
```
|
||||
|
||||
**Affiche :**
|
||||
- Username, email, nom, prénom (déchiffrés)
|
||||
- Téléphones (déchiffrés)
|
||||
- Rôle, entité, dates
|
||||
|
||||
#### Réinitialiser un mot de passe
|
||||
|
||||
```bash
|
||||
./bin/reset-password dev 123
|
||||
./bin/reset-password rec 456
|
||||
```
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Affiche les infos de l'utilisateur
|
||||
- Demande confirmation
|
||||
- Génère un mot de passe sécurisé (12-20 caractères, conforme aux règles API)
|
||||
- Hash avec bcrypt et met à jour la base
|
||||
- **Affiche le mot de passe en clair** (à sauvegarder)
|
||||
|
||||
#### Décrypter une entité (amicale)
|
||||
|
||||
```bash
|
||||
./bin/decrypt-entite dev 5
|
||||
./bin/decrypt-entite rec 10
|
||||
```
|
||||
|
||||
**Affiche :**
|
||||
- Nom, email, téléphones (déchiffrés)
|
||||
- Adresse complète
|
||||
- IBAN/BIC (déchiffrés)
|
||||
- Configuration (Stripe, mots de passe, etc.)
|
||||
- Statistiques (nb users, opérations)
|
||||
|
||||
#### Lister les utilisateurs
|
||||
|
||||
```bash
|
||||
# Tous les utilisateurs (limite 50)
|
||||
./bin/list-users dev
|
||||
|
||||
# Filtrer par entité
|
||||
./bin/list-users dev --entite=5
|
||||
|
||||
# Filtrer par rôle
|
||||
./bin/list-users rec --role=2
|
||||
|
||||
# Limite personnalisée
|
||||
./bin/list-users dev --limit=100
|
||||
|
||||
# Combinaison de filtres
|
||||
./bin/list-users dev --entite=5 --role=2 --limit=20
|
||||
```
|
||||
|
||||
**Affiche :** Tableau avec username, email, nom (déchiffrés)
|
||||
|
||||
#### Rechercher une entité
|
||||
|
||||
```bash
|
||||
# Rechercher par nom, adresse, ville ou email
|
||||
./bin/search-entite dev plumeliau
|
||||
./bin/search-entite rec amicale
|
||||
./bin/search-entite dev appli
|
||||
```
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Déchiffre **toutes** les entités pour chercher
|
||||
- Cherche dans : `encrypted_name`, `encrypted_email`, `adresse1`, `adresse2`, `ville`
|
||||
- Affiche un tableau numéroté avec les résultats
|
||||
- **Mode interactif** :
|
||||
1. Détail d'une entité → appelle `decrypt-entite`
|
||||
2. Opérations d'une entité → appelle `list-operations`
|
||||
3. Membres d'une entité → appelle `list-users`
|
||||
|
||||
#### Lister les entités
|
||||
|
||||
```bash
|
||||
# Toutes les entités
|
||||
./bin/list-entites dev
|
||||
|
||||
# Uniquement celles avec Stripe activé
|
||||
./bin/list-entites dev --stripe
|
||||
|
||||
# Limite personnalisée
|
||||
./bin/list-entites rec --limit=20
|
||||
```
|
||||
|
||||
**Affiche :** Tableau avec nom, email (déchiffrés), stats
|
||||
|
||||
#### Lister les opérations
|
||||
|
||||
```bash
|
||||
# Toutes les opérations
|
||||
./bin/list-operations dev
|
||||
|
||||
# Opérations d'une entité spécifique
|
||||
./bin/list-operations dev --entite=5
|
||||
|
||||
# Limite personnalisée
|
||||
./bin/list-operations rec --entite=10 --limit=20
|
||||
```
|
||||
|
||||
**Affiche :** Tableau avec entité, libellé, dates, nb passages, nb users, nb secteurs
|
||||
|
||||
#### Lister les secteurs d'une opération
|
||||
|
||||
```bash
|
||||
# Secteurs d'une opération spécifique
|
||||
./bin/list-sectors dev --operation=123
|
||||
./bin/list-sectors rec --operation=456
|
||||
```
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Affiche le libellé de l'opération
|
||||
- Liste tous les secteurs (actifs et inactifs)
|
||||
- Compte le nombre d'utilisateurs par secteur (depuis `ope_users_sectors`)
|
||||
- Compte le nombre de passages par secteur
|
||||
- Affiche des statistiques globales (total users, passages, secteurs actifs)
|
||||
|
||||
**Affiche :** Tableau avec ID, libellé, couleur, nb users, nb passages, actif, date création
|
||||
|
||||
#### Rechercher par email
|
||||
|
||||
```bash
|
||||
./bin/search-email dev contact@example.com
|
||||
./bin/search-email rec admin@amicale.fr
|
||||
```
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Déchiffre **tous** les emails pour chercher
|
||||
- Affiche **tous** les comptes avec cet email (système autorise les doublons)
|
||||
- Propose d'afficher les détails complets si 1 seul résultat
|
||||
|
||||
### Gestion des tunnels SSH
|
||||
|
||||
```bash
|
||||
# Afficher l'état des tunnels
|
||||
./bin/_ssh-tunnel.sh status
|
||||
|
||||
# Ouvrir un tunnel manuellement
|
||||
./bin/_ssh-tunnel.sh open dev
|
||||
./bin/_ssh-tunnel.sh open rec
|
||||
|
||||
# Fermer un tunnel
|
||||
./bin/_ssh-tunnel.sh close dev
|
||||
|
||||
# Fermer tous les tunnels
|
||||
./bin/_ssh-tunnel.sh close-all
|
||||
```
|
||||
|
||||
## 📁 Structure du projet
|
||||
|
||||
```
|
||||
bao/
|
||||
├── README.md # Cette documentation
|
||||
├── .gitignore # Exclut config/.env
|
||||
│
|
||||
├── config/
|
||||
│ ├── .env # Configuration (gitignored)
|
||||
│ ├── .env.example # Template
|
||||
│ └── database.php # Classe DatabaseConfig
|
||||
│
|
||||
├── lib/
|
||||
│ ├── CryptoService.php # Chiffrement/déchiffrement AES-256-CBC
|
||||
│ ├── DatabaseConnection.php # Connexion PDO aux bases
|
||||
│ └── helpers.php # Fonctions utilitaires (affichage, couleurs)
|
||||
│
|
||||
└── bin/
|
||||
├── bao # Script principal (menu interactif)
|
||||
├── search-user # Rechercher un utilisateur
|
||||
├── decrypt-user # Décrypter un utilisateur
|
||||
├── reset-password # Réinitialiser un mot de passe
|
||||
├── search-email # Rechercher par email
|
||||
├── list-users # Lister les utilisateurs
|
||||
├── search-entite # Rechercher une entité (interactif)
|
||||
├── decrypt-entite # Décrypter une entité
|
||||
├── list-entites # Lister les entités
|
||||
├── list-operations # Lister les opérations
|
||||
├── list-sectors # Lister les secteurs d'une opération
|
||||
└── _ssh-tunnel.sh # Helper gestion tunnels SSH
|
||||
```
|
||||
|
||||
## 🔐 Sécurité
|
||||
|
||||
### Chiffrement
|
||||
|
||||
L'API utilise **deux méthodes de chiffrement AES-256-CBC** différentes :
|
||||
|
||||
#### 1. Données "Searchable" (recherche)
|
||||
- **Champs** : `encrypted_user_name`, `encrypted_email`
|
||||
- **IV** : Fixe (16 bytes de `\0`)
|
||||
- **Format** : `base64(encrypted_data)`
|
||||
- **Usage** : Permet la recherche par égalité dans la base
|
||||
|
||||
#### 2. Données avec IV aléatoire
|
||||
- **Champs** : `encrypted_name`, `encrypted_phone`, `encrypted_mobile`, `encrypted_iban`, `encrypted_bic`
|
||||
- **IV** : Aléatoire (16 bytes)
|
||||
- **Format** : `base64(IV + encrypted_data)`
|
||||
- **Usage** : Sécurité maximale, pas de recherche directe
|
||||
|
||||
**Clé de chiffrement** : Base64 encodée (32 bytes) partagée avec l'API
|
||||
|
||||
### Données chiffrées
|
||||
|
||||
**Utilisateurs (`users`) :**
|
||||
- `encrypted_user_name` → Username (searchable)
|
||||
- `encrypted_email` → Email (searchable)
|
||||
- `encrypted_name` → Nom complet (IV aléatoire)
|
||||
- `encrypted_phone` → Téléphone fixe (IV aléatoire)
|
||||
- `encrypted_mobile` → Téléphone mobile (IV aléatoire)
|
||||
|
||||
**Entités (`entites`) :**
|
||||
- `encrypted_name` → Nom de l'amicale (IV aléatoire)
|
||||
- `encrypted_email` → Email (searchable)
|
||||
- `encrypted_phone` → Téléphone (IV aléatoire)
|
||||
- `encrypted_mobile` → Mobile (IV aléatoire)
|
||||
- `encrypted_iban` → Coordonnées bancaires (IV aléatoire)
|
||||
- `encrypted_bic` → Code BIC (IV aléatoire)
|
||||
|
||||
### Bonnes pratiques
|
||||
|
||||
✅ **Faire :**
|
||||
- Garder `.env` hors du versioning (déjà dans `.gitignore`)
|
||||
- Fermer les tunnels après usage (`Ctrl+C` ou `close-all`)
|
||||
- Utiliser le menu interactif pour les tâches courantes
|
||||
|
||||
❌ **Ne pas faire :**
|
||||
- Partager la clé de chiffrement (`ENCRYPTION_KEY`)
|
||||
- Laisser les tunnels ouverts en permanence
|
||||
- Exporter les données déchiffrées sans précaution
|
||||
|
||||
## 🛠️ Dépannage
|
||||
|
||||
### Erreur : "Impossible d'ouvrir le tunnel SSH"
|
||||
|
||||
```bash
|
||||
# Vérifier la connexion SSH
|
||||
ssh dva-geo 'echo OK'
|
||||
|
||||
# Vérifier qu'aucun processus n'utilise le port
|
||||
lsof -i :3307
|
||||
lsof -i :3308
|
||||
lsof -i :3309
|
||||
|
||||
# Fermer les tunnels zombies
|
||||
./bin/_ssh-tunnel.sh close-all
|
||||
```
|
||||
|
||||
### Erreur : "Impossible de se connecter à la base"
|
||||
|
||||
```bash
|
||||
# Vérifier que le tunnel est actif
|
||||
./bin/_ssh-tunnel.sh status
|
||||
|
||||
# Tester manuellement
|
||||
ssh dva-geo 'mysql -u geo_app_user_dev -p geo_app -e "SELECT 1"'
|
||||
```
|
||||
|
||||
### Erreur : "Échec du déchiffrement"
|
||||
|
||||
```bash
|
||||
# Vérifier la clé de chiffrement
|
||||
grep ENCRYPTION_KEY config/.env
|
||||
|
||||
# Comparer avec l'API
|
||||
grep encryption_key ../api/src/Config/AppConfig.php
|
||||
```
|
||||
|
||||
### Permissions refusées sur les scripts
|
||||
|
||||
```bash
|
||||
# Rendre tous les scripts exécutables
|
||||
chmod +x bin/*
|
||||
```
|
||||
|
||||
## 📊 Exemples d'utilisation
|
||||
|
||||
### Cas d'usage 1 : Rechercher un utilisateur par nom
|
||||
|
||||
```bash
|
||||
./bin/search-user rec dupont
|
||||
```
|
||||
|
||||
Résultat : Tableau des utilisateurs contenant "dupont" (nom, prénom, username ou secteur)
|
||||
|
||||
### Cas d'usage 2 : Réinitialiser le mot de passe d'un utilisateur
|
||||
|
||||
```bash
|
||||
./bin/search-user rec martin # Trouver l'ID
|
||||
./bin/reset-password rec 56930 # Réinitialiser avec l'ID
|
||||
```
|
||||
|
||||
Résultat : Nouveau mot de passe généré et affiché en clair
|
||||
|
||||
### Cas d'usage 3 : Trouver tous les comptes d'un email
|
||||
|
||||
```bash
|
||||
./bin/search-email dev contact@amicale.fr
|
||||
```
|
||||
|
||||
Résultat : Liste tous les utilisateurs avec cet email (peut être multiple)
|
||||
|
||||
### Cas d'usage 4 : Lister les admins d'une amicale
|
||||
|
||||
```bash
|
||||
./bin/list-users dev --entite=5 --role=2
|
||||
```
|
||||
|
||||
Résultat : Tableau des administrateurs (rôle 2) de l'entité 5
|
||||
|
||||
### Cas d'usage 5 : Explorer une entité (mode interactif)
|
||||
|
||||
```bash
|
||||
./bin/search-entite rec plumeliau # Rechercher l'entité
|
||||
# Sélectionner n° de ligne: 1
|
||||
# Choisir action: 2 (Opérations)
|
||||
```
|
||||
|
||||
Résultat : Workflow complet pour explorer une entité et ses données liées
|
||||
|
||||
### Cas d'usage 6 : Vérifier les entités avec Stripe
|
||||
|
||||
```bash
|
||||
./bin/list-entites dev --stripe
|
||||
```
|
||||
|
||||
Résultat : Toutes les amicales ayant activé Stripe Connect
|
||||
|
||||
### Cas d'usage 7 : Lister les opérations d'une entité
|
||||
|
||||
```bash
|
||||
./bin/list-operations rec --entite=662
|
||||
```
|
||||
|
||||
Résultat : Tableau des opérations avec stats (passages, users, secteurs)
|
||||
|
||||
### Cas d'usage 8 : Explorer les secteurs d'une opération
|
||||
|
||||
```bash
|
||||
./bin/list-sectors dev --operation=123
|
||||
```
|
||||
|
||||
Résultat : Tableau des secteurs avec nb users/passages + stats globales
|
||||
|
||||
### Cas d'usage 9 : Audit complet d'un utilisateur
|
||||
|
||||
```bash
|
||||
./bin/decrypt-user dev 123
|
||||
```
|
||||
|
||||
Résultat : Toutes les informations déchiffrées + métadonnées
|
||||
|
||||
## 🔄 Évolutions futures
|
||||
|
||||
### À court terme
|
||||
- [ ] Export CSV avec données déchiffrées
|
||||
- [ ] Statistiques globales par environnement
|
||||
- [x] ~~Recherche par nom (déchiffré)~~ ✓ Implémenté (`search-user`)
|
||||
- [x] ~~Réinitialisation de mots de passe~~ ✓ Implémenté (`reset-password`)
|
||||
|
||||
### À moyen terme
|
||||
- [ ] Vérification HIBP pour `reset-password`
|
||||
- [ ] Version Go pour performances accrues
|
||||
- [ ] Cache local des données déchiffrées
|
||||
- [ ] Interface TUI avec bibliotèque comme `gum`
|
||||
|
||||
### À long terme
|
||||
- [ ] Modification de données complètes (avec validation)
|
||||
- [ ] Audit trail des accès
|
||||
- [ ] Synchronisation inter-environnements
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
### Environnement PROD
|
||||
|
||||
⚠️ **Attention :** L'environnement PROD n'est **pas encore créé** (base de données inexistante).
|
||||
|
||||
Pour l'activer :
|
||||
1. Créer la base `pra_geo` sur IN4/maria4
|
||||
2. Migrer les données depuis REC
|
||||
3. Changer `PROD_ENABLED=true` dans `config/.env`
|
||||
|
||||
### Comptes multiples par email
|
||||
|
||||
Le système Geosector **autorise plusieurs comptes avec le même email** (choix client).
|
||||
|
||||
Le script `search-email` gère cette particularité en affichant **tous** les comptes trouvés.
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
Pour toute question ou amélioration :
|
||||
1. Consulter le TECHBOOK de l'API : `../api/docs/TECHBOOK.md`
|
||||
2. Vérifier les logs SSH : `~/.ssh/`
|
||||
3. Tester la connexion directe aux containers
|
||||
|
||||
## 📜 Licence
|
||||
|
||||
Usage interne - Projet Geosector © 2025
|
||||
278
bao/bin/_ssh-tunnel.sh
Executable file
278
bao/bin/_ssh-tunnel.sh
Executable file
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =============================================================================
|
||||
# SSH Tunnel Management Helper
|
||||
# Gère l'ouverture/fermeture des tunnels SSH vers les containers Incus
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Répertoire du script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BAO_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
ENV_FILE="$BAO_ROOT/config/.env"
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Fonctions d'affichage
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $*"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✓${NC} $*"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}✗${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $*"
|
||||
}
|
||||
|
||||
# Charge une variable depuis .env
|
||||
get_env_var() {
|
||||
local key="$1"
|
||||
grep "^${key}=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- | tr -d ' "'
|
||||
}
|
||||
|
||||
# Vérifie si un tunnel SSH est actif
|
||||
is_tunnel_active() {
|
||||
local port="$1"
|
||||
|
||||
# Vérifier si le port est en écoute
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -tuln | grep -q ":${port} " && return 0
|
||||
elif command -v netstat >/dev/null 2>&1; then
|
||||
netstat -tuln | grep -q ":${port} " && return 0
|
||||
elif command -v lsof >/dev/null 2>&1; then
|
||||
lsof -i ":${port}" -sTCP:LISTEN >/dev/null 2>&1 && return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Trouve le PID du processus SSH pour un tunnel
|
||||
get_tunnel_pid() {
|
||||
local port="$1"
|
||||
|
||||
# Chercher le processus SSH qui utilise ce port local
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
lsof -ti ":${port}" -sTCP:LISTEN 2>/dev/null | head -n1
|
||||
else
|
||||
# Fallback avec ps et grep
|
||||
ps aux | grep "ssh.*:${port}:localhost:3306" | grep -v grep | awk '{print $2}' | head -n1
|
||||
fi
|
||||
}
|
||||
|
||||
# Ouvre un tunnel SSH
|
||||
open_tunnel() {
|
||||
local env="${1^^}" # Convertir en majuscules
|
||||
|
||||
local ssh_host=$(get_env_var "${env}_SSH_HOST")
|
||||
local local_port=$(get_env_var "${env}_SSH_PORT_LOCAL")
|
||||
local enabled=$(get_env_var "${env}_ENABLED")
|
||||
|
||||
if [[ "$enabled" != "true" ]]; then
|
||||
log_error "Environnement ${env} désactivé dans .env"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$ssh_host" || -z "$local_port" ]]; then
|
||||
log_error "Configuration ${env} incomplète dans .env"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Vérifier si le tunnel est déjà actif
|
||||
if is_tunnel_active "$local_port"; then
|
||||
log_warning "Tunnel ${env} déjà actif sur le port ${local_port}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Ouverture du tunnel SSH vers ${ssh_host} (port local ${local_port})..."
|
||||
|
||||
# Créer le tunnel en arrière-plan
|
||||
# -N : Ne pas exécuter de commande distante
|
||||
# -f : Passer en arrière-plan
|
||||
# -o ExitOnForwardFailure=yes : Quitter si le tunnel échoue
|
||||
# -o ServerAliveInterval=60 : Garder la connexion active
|
||||
ssh -N -f \
|
||||
-L "${local_port}:localhost:3306" \
|
||||
-o ExitOnForwardFailure=yes \
|
||||
-o ServerAliveInterval=60 \
|
||||
-o ServerAliveCountMax=3 \
|
||||
"$ssh_host" 2>/dev/null
|
||||
|
||||
# Attendre que le tunnel soit actif
|
||||
local max_attempts=10
|
||||
local attempt=0
|
||||
|
||||
while [[ $attempt -lt $max_attempts ]]; do
|
||||
if is_tunnel_active "$local_port"; then
|
||||
log_success "Tunnel ${env} actif sur le port ${local_port}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 0.5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "Impossible d'ouvrir le tunnel ${env}"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Ferme un tunnel SSH
|
||||
close_tunnel() {
|
||||
local env="${1^^}"
|
||||
|
||||
local local_port=$(get_env_var "${env}_SSH_PORT_LOCAL")
|
||||
|
||||
if [[ -z "$local_port" ]]; then
|
||||
log_error "Port local pour ${env} introuvable dans .env"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! is_tunnel_active "$local_port"; then
|
||||
log_warning "Tunnel ${env} non actif sur le port ${local_port}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local pid=$(get_tunnel_pid "$local_port")
|
||||
|
||||
if [[ -z "$pid" ]]; then
|
||||
log_error "Impossible de trouver le PID du tunnel ${env}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Fermeture du tunnel ${env} (PID ${pid})..."
|
||||
|
||||
kill "$pid" 2>/dev/null
|
||||
|
||||
sleep 0.5
|
||||
|
||||
if ! is_tunnel_active "$local_port"; then
|
||||
log_success "Tunnel ${env} fermé"
|
||||
return 0
|
||||
else
|
||||
log_error "Impossible de fermer le tunnel ${env}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Affiche le statut des tunnels
|
||||
status_tunnels() {
|
||||
echo -e "\n${CYAN}╔════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ État des tunnels SSH ║${NC}"
|
||||
echo -e "${CYAN}╚════════════════════════════════════════╝${NC}\n"
|
||||
|
||||
for env in DEV REC PROD; do
|
||||
local enabled=$(get_env_var "${env}_ENABLED")
|
||||
local local_port=$(get_env_var "${env}_SSH_PORT_LOCAL")
|
||||
local ssh_host=$(get_env_var "${env}_SSH_HOST")
|
||||
|
||||
if [[ "$enabled" != "true" ]]; then
|
||||
echo -e " ${env}: ${YELLOW}Désactivé${NC}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if is_tunnel_active "$local_port"; then
|
||||
local pid=$(get_tunnel_pid "$local_port")
|
||||
echo -e " ${env}: ${GREEN}✓ Actif${NC} (port ${local_port}, PID ${pid})"
|
||||
else
|
||||
echo -e " ${env}: ${RED}✗ Inactif${NC} (port ${local_port})"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Ferme tous les tunnels
|
||||
close_all_tunnels() {
|
||||
log_info "Fermeture de tous les tunnels..."
|
||||
|
||||
for env in DEV REC PROD; do
|
||||
local enabled=$(get_env_var "${env}_ENABLED")
|
||||
|
||||
if [[ "$enabled" == "true" ]]; then
|
||||
close_tunnel "$env" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Tous les tunnels ont été fermés"
|
||||
}
|
||||
|
||||
# Usage
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") <command> [environment]
|
||||
|
||||
Commandes:
|
||||
open <env> Ouvre un tunnel SSH vers l'environnement
|
||||
close <env> Ferme le tunnel SSH
|
||||
status Affiche l'état de tous les tunnels
|
||||
close-all Ferme tous les tunnels actifs
|
||||
|
||||
Environnements: DEV, REC, PROD
|
||||
|
||||
Exemples:
|
||||
$(basename "$0") open dev
|
||||
$(basename "$0") close rec
|
||||
$(basename "$0") status
|
||||
$(basename "$0") close-all
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
log_error "Fichier .env introuvable: $ENV_FILE"
|
||||
log_info "Copiez config/.env.example vers config/.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local command="$1"
|
||||
|
||||
case "$command" in
|
||||
open)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
log_error "Environnement manquant"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
open_tunnel "$2"
|
||||
;;
|
||||
close)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
log_error "Environnement manquant"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
close_tunnel "$2"
|
||||
;;
|
||||
status)
|
||||
status_tunnels
|
||||
;;
|
||||
close-all)
|
||||
close_all_tunnels
|
||||
;;
|
||||
*)
|
||||
log_error "Commande invalide: $command"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
272
bao/bin/bao
Executable file
272
bao/bin/bao
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =============================================================================
|
||||
# BAO - Back-office Admin Operations
|
||||
# Menu principal interactif pour gérer les données Geosector
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Répertoire du script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BAO_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
ENV_FILE="$BAO_ROOT/config/.env"
|
||||
|
||||
# Couleurs
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
MAGENTA='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Fonctions d'affichage
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $*"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✓${NC} $*"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}✗${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $*"
|
||||
}
|
||||
|
||||
# Affiche le titre principal
|
||||
show_header() {
|
||||
clear
|
||||
echo -e "${CYAN}"
|
||||
cat << "EOF"
|
||||
╔════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ██████╗ █████╗ ██████╗ ║
|
||||
║ ██╔══██╗██╔══██╗██╔═══██╗ ║
|
||||
║ ██████╔╝███████║██║ ██║ ║
|
||||
║ ██╔══██╗██╔══██║██║ ██║ ║
|
||||
║ ██████╔╝██║ ██║╚██████╔╝ ║
|
||||
║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ║
|
||||
║ ║
|
||||
║ Back-office Admin Operations - Geosector ║
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════════════════════╝
|
||||
EOF
|
||||
echo -e "${NC}\n"
|
||||
}
|
||||
|
||||
# Charge une variable depuis .env
|
||||
get_env_var() {
|
||||
local key="$1"
|
||||
grep "^${key}=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- | tr -d ' "'
|
||||
}
|
||||
|
||||
# Sélectionne l'environnement
|
||||
select_environment() {
|
||||
echo -e "${BOLD}Sélectionnez un environnement :${NC}\n"
|
||||
|
||||
local envs=()
|
||||
local counter=1
|
||||
|
||||
for env in DEV REC PROD; do
|
||||
local enabled=$(get_env_var "${env}_ENABLED")
|
||||
if [[ "$enabled" == "true" ]]; then
|
||||
local ssh_host=$(get_env_var "${env}_SSH_HOST")
|
||||
echo -e " ${GREEN}${counter})${NC} ${BOLD}${env}${NC} (${ssh_host})"
|
||||
envs+=("$env")
|
||||
((counter++))
|
||||
else
|
||||
echo -e " ${YELLOW}-${NC} ${env} ${YELLOW}(désactivé)${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " ${RED}q)${NC} Quitter\n"
|
||||
|
||||
while true; do
|
||||
read -p "Votre choix: " choice
|
||||
|
||||
if [[ "$choice" == "q" || "$choice" == "Q" ]]; then
|
||||
echo ""
|
||||
log_info "Au revoir !"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [[ $choice -ge 1 ]] && [[ $choice -le ${#envs[@]} ]]; then
|
||||
SELECTED_ENV="${envs[$((choice-1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Choix invalide"
|
||||
done
|
||||
}
|
||||
|
||||
# Affiche le menu des actions
|
||||
show_action_menu() {
|
||||
echo -e "\n${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BOLD}Environnement: ${GREEN}${SELECTED_ENV}${NC}"
|
||||
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}\n"
|
||||
|
||||
echo -e "${BOLD}Actions disponibles :${NC}\n"
|
||||
|
||||
echo -e " ${CYAN}1)${NC} Lister les utilisateurs"
|
||||
echo -e " ${CYAN}2)${NC} Décrypter un utilisateur spécifique"
|
||||
echo -e " ${CYAN}3)${NC} Rechercher par email"
|
||||
echo -e " ${CYAN}4)${NC} Lister les entités (amicales)"
|
||||
echo -e " ${CYAN}5)${NC} Décrypter une entité spécifique"
|
||||
echo -e " ${CYAN}6)${NC} État des tunnels SSH"
|
||||
echo -e " ${CYAN}7)${NC} Fermer tous les tunnels"
|
||||
echo -e " ${YELLOW}8)${NC} Changer d'environnement"
|
||||
echo -e " ${RED}q)${NC} Quitter\n"
|
||||
}
|
||||
|
||||
# Execute une action
|
||||
execute_action() {
|
||||
local action="$1"
|
||||
|
||||
case "$action" in
|
||||
1)
|
||||
echo ""
|
||||
read -p "Filtrer par entité ? (ID ou vide): " entite_filter
|
||||
read -p "Filtrer par rôle ? (ID ou vide): " role_filter
|
||||
read -p "Limite de résultats ? (défaut: 50): " limit_input
|
||||
|
||||
local cmd="$SCRIPT_DIR/list-users $SELECTED_ENV"
|
||||
[[ -n "$entite_filter" ]] && cmd="$cmd --entite=$entite_filter"
|
||||
[[ -n "$role_filter" ]] && cmd="$cmd --role=$role_filter"
|
||||
[[ -n "$limit_input" ]] && cmd="$cmd --limit=$limit_input"
|
||||
|
||||
echo ""
|
||||
$cmd
|
||||
;;
|
||||
|
||||
2)
|
||||
echo ""
|
||||
read -p "ID de l'utilisateur: " user_id
|
||||
|
||||
if [[ -z "$user_id" || ! "$user_id" =~ ^[0-9]+$ ]]; then
|
||||
log_error "ID invalide"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
"$SCRIPT_DIR/decrypt-user" "$SELECTED_ENV" "$user_id"
|
||||
;;
|
||||
|
||||
3)
|
||||
echo ""
|
||||
read -p "Email à rechercher: " email
|
||||
|
||||
if [[ -z "$email" ]]; then
|
||||
log_error "Email vide"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
"$SCRIPT_DIR/search-email" "$SELECTED_ENV" "$email"
|
||||
;;
|
||||
|
||||
4)
|
||||
echo ""
|
||||
read -p "Uniquement les entités avec Stripe ? (o/N): " stripe_filter
|
||||
read -p "Limite de résultats ? (défaut: 50): " limit_input
|
||||
|
||||
local cmd="$SCRIPT_DIR/list-entites $SELECTED_ENV"
|
||||
[[ "$stripe_filter" == "o" || "$stripe_filter" == "O" ]] && cmd="$cmd --stripe"
|
||||
[[ -n "$limit_input" ]] && cmd="$cmd --limit=$limit_input"
|
||||
|
||||
echo ""
|
||||
$cmd
|
||||
;;
|
||||
|
||||
5)
|
||||
echo ""
|
||||
read -p "ID de l'entité: " entite_id
|
||||
|
||||
if [[ -z "$entite_id" || ! "$entite_id" =~ ^[0-9]+$ ]]; then
|
||||
log_error "ID invalide"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
"$SCRIPT_DIR/decrypt-entite" "$SELECTED_ENV" "$entite_id"
|
||||
;;
|
||||
|
||||
6)
|
||||
echo ""
|
||||
"$SCRIPT_DIR/_ssh-tunnel.sh" status
|
||||
;;
|
||||
|
||||
7)
|
||||
echo ""
|
||||
"$SCRIPT_DIR/_ssh-tunnel.sh" close-all
|
||||
;;
|
||||
|
||||
8)
|
||||
return 2 # Signal pour changer d'environnement
|
||||
;;
|
||||
|
||||
q|Q)
|
||||
echo ""
|
||||
log_info "Fermeture des tunnels..."
|
||||
"$SCRIPT_DIR/_ssh-tunnel.sh" close-all
|
||||
echo ""
|
||||
log_success "Au revoir !"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
log_error "Action invalide"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Boucle principale
|
||||
main() {
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
log_error "Fichier .env introuvable: $ENV_FILE"
|
||||
log_info "Copiez config/.env.example vers config/.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérifier que PHP est installé
|
||||
if ! command -v php >/dev/null 2>&1; then
|
||||
log_error "PHP n'est pas installé"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
show_header
|
||||
select_environment
|
||||
|
||||
while true; do
|
||||
show_header
|
||||
show_action_menu
|
||||
|
||||
read -p "Votre choix: " action
|
||||
|
||||
execute_action "$action"
|
||||
local exit_code=$?
|
||||
|
||||
if [[ $exit_code -eq 2 ]]; then
|
||||
# Changer d'environnement
|
||||
break
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
# Gestion de Ctrl+C
|
||||
trap 'echo ""; log_info "Fermeture des tunnels..."; $SCRIPT_DIR/_ssh-tunnel.sh close-all 2>/dev/null; echo ""; exit 0' INT
|
||||
|
||||
main "$@"
|
||||
114
bao/bin/decrypt-entite
Executable file
114
bao/bin/decrypt-entite
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de décryptage d'une entité (amicale) spécifique
|
||||
* Usage: ./decrypt-entite <environment> <entite_id>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <entite_id>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev 5");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$entiteId = (int)$argv[2];
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche de l'entité #$entiteId...\n");
|
||||
|
||||
// Requête pour récupérer l'entité
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
e.*,
|
||||
COUNT(DISTINCT u.id) as nb_users,
|
||||
COUNT(DISTINCT o.id) as nb_operations
|
||||
FROM entites e
|
||||
LEFT JOIN users u ON u.fk_entite = e.id
|
||||
LEFT JOIN operations o ON o.fk_entite = e.id
|
||||
WHERE e.id = :entite_id
|
||||
GROUP BY e.id
|
||||
");
|
||||
|
||||
$stmt->execute(['entite_id' => $entiteId]);
|
||||
$entite = $stmt->fetch();
|
||||
|
||||
if (!$entite) {
|
||||
error("Entité #$entiteId introuvable");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Déchiffrer les données
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$entite['name'] = $crypto->decryptWithIV($entite['encrypted_name']);
|
||||
$entite['email'] = $crypto->decryptSearchable($entite['encrypted_email']);
|
||||
$entite['phone'] = $crypto->decryptWithIV($entite['encrypted_phone']);
|
||||
$entite['mobile'] = $crypto->decryptWithIV($entite['encrypted_mobile']);
|
||||
$entite['iban'] = $crypto->decryptWithIV($entite['encrypted_iban']);
|
||||
$entite['bic'] = $crypto->decryptWithIV($entite['encrypted_bic']);
|
||||
|
||||
// Affichage
|
||||
title("ENTITÉ (AMICALE) #" . $entite['id']);
|
||||
|
||||
echo color("Identité\n", 'bold');
|
||||
display("Nom", $entite['name']);
|
||||
display("Email", $entite['email']);
|
||||
display("Téléphone", $entite['phone']);
|
||||
display("Mobile", $entite['mobile']);
|
||||
|
||||
echo "\n" . color("Adresse\n", 'bold');
|
||||
display("Adresse 1", $entite['adresse1'] ?: '-');
|
||||
display("Adresse 2", $entite['adresse2'] ?: '-');
|
||||
display("Code postal", $entite['code_postal'] ?: '-');
|
||||
display("Ville", $entite['ville'] ?: '-');
|
||||
|
||||
echo "\n" . color("Coordonnées bancaires\n", 'bold');
|
||||
display("IBAN", $entite['iban'] ?: '-');
|
||||
display("BIC", $entite['bic'] ?: '-');
|
||||
|
||||
echo "\n" . color("Configuration\n", 'bold');
|
||||
display("Stripe activé", $entite['chk_stripe'] ? 'Oui' : 'Non');
|
||||
display("Gestion MDP manuelle", $entite['chk_mdp_manuel'] ? 'Oui' : 'Non');
|
||||
display("Gestion username manuelle", $entite['chk_username_manuel'] ? 'Oui' : 'Non');
|
||||
display("Copie mail reçu", $entite['chk_copie_mail_recu'] ? 'Oui' : 'Non');
|
||||
display("Accepte SMS", $entite['chk_accept_sms'] ? 'Oui' : 'Non');
|
||||
|
||||
echo "\n" . color("Statistiques\n", 'bold');
|
||||
display("Nombre d'utilisateurs", (string)$entite['nb_users']);
|
||||
display("Nombre d'opérations", (string)$entite['nb_operations']);
|
||||
|
||||
echo "\n" . color("Dates\n", 'bold');
|
||||
display("Date création", formatDate($entite['created_at']));
|
||||
display("Dernière modif", formatDate($entite['updated_at']));
|
||||
|
||||
echo "\n";
|
||||
success("Entité déchiffrée avec succès");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
119
bao/bin/decrypt-user
Executable file
119
bao/bin/decrypt-user
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de décryptage d'un utilisateur spécifique
|
||||
* Usage: ./decrypt-user <environment> <user_id>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <user_id>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev 123");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$userId = (int)$argv[2];
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche de l'utilisateur #$userId...\n");
|
||||
|
||||
// Requête pour récupérer l'utilisateur
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name,
|
||||
u.encrypted_phone,
|
||||
u.encrypted_mobile,
|
||||
u.sect_name,
|
||||
u.fk_role,
|
||||
u.fk_entite,
|
||||
u.fk_titre,
|
||||
u.date_naissance,
|
||||
u.date_embauche,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
r.libelle as role_name,
|
||||
e.encrypted_name as entite_encrypted_name
|
||||
FROM users u
|
||||
LEFT JOIN x_users_roles r ON u.fk_role = r.id
|
||||
LEFT JOIN entites e ON u.fk_entite = e.id
|
||||
WHERE u.id = :user_id
|
||||
");
|
||||
|
||||
$stmt->execute(['user_id' => $userId]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
error("Utilisateur #$userId introuvable");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Déchiffrer les données
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$user['user_name'] = $crypto->decryptSearchable($user['encrypted_user_name']);
|
||||
$user['email'] = $crypto->decryptSearchable($user['encrypted_email']);
|
||||
$user['name'] = $crypto->decryptWithIV($user['encrypted_name']);
|
||||
$user['phone'] = $crypto->decryptWithIV($user['encrypted_phone']);
|
||||
$user['mobile'] = $crypto->decryptWithIV($user['encrypted_mobile']);
|
||||
$user['entite_name'] = $crypto->decryptWithIV($user['entite_encrypted_name']);
|
||||
|
||||
// Affichage
|
||||
title("UTILISATEUR #" . $user['id']);
|
||||
|
||||
echo color("Identité\n", 'bold');
|
||||
display("Username", $user['user_name']);
|
||||
display("Prénom", $user['first_name']);
|
||||
display("Nom", $user['name']);
|
||||
display("Email", $user['email']);
|
||||
display("Téléphone", $user['phone']);
|
||||
display("Mobile", $user['mobile']);
|
||||
|
||||
echo "\n" . color("Fonction\n", 'bold');
|
||||
display("Rôle", $user['role_name'] . " (ID: " . $user['fk_role'] . ")");
|
||||
display("Secteur", $user['sect_name'] ?: '-');
|
||||
display("Titre", $user['fk_titre'] ? "#" . $user['fk_titre'] : '-');
|
||||
|
||||
echo "\n" . color("Amicale\n", 'bold');
|
||||
display("ID Entité", (string)$user['fk_entite']);
|
||||
display("Nom Entité", $user['entite_name']);
|
||||
|
||||
echo "\n" . color("Dates\n", 'bold');
|
||||
display("Date naissance", formatDate($user['date_naissance']));
|
||||
display("Date embauche", formatDate($user['date_embauche']));
|
||||
display("Date création", formatDate($user['created_at']));
|
||||
display("Dernière modif", formatDate($user['updated_at']));
|
||||
|
||||
echo "\n";
|
||||
success("Utilisateur déchiffré avec succès");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
132
bao/bin/list-entites
Executable file
132
bao/bin/list-entites
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de listing des entités (amicales) avec données déchiffrées
|
||||
* Usage: ./list-entites <environment> [--stripe] [--limit=N]
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 2) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> [--stripe] [--limit=N]");
|
||||
error("Exemple: " . basename($argv[0]) . " dev --stripe");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$stripeOnly = false;
|
||||
$limit = 50;
|
||||
|
||||
// Parser les options
|
||||
for ($i = 2; $i < $argc; $i++) {
|
||||
if ($argv[$i] === '--stripe') {
|
||||
$stripeOnly = true;
|
||||
} elseif (preg_match('/--limit=(\d+)/', $argv[$i], $matches)) {
|
||||
$limit = (int)$matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
if ($stripeOnly) {
|
||||
info("Filtre: Stripe activé uniquement");
|
||||
}
|
||||
info("Limite: $limit\n");
|
||||
|
||||
// Construction de la requête
|
||||
$sql = "
|
||||
SELECT
|
||||
e.id,
|
||||
e.encrypted_name,
|
||||
e.encrypted_email,
|
||||
e.encrypted_phone,
|
||||
e.ville,
|
||||
e.chk_stripe,
|
||||
e.chk_mdp_manuel,
|
||||
e.chk_username_manuel,
|
||||
COUNT(DISTINCT u.id) as nb_users,
|
||||
COUNT(DISTINCT o.id) as nb_operations
|
||||
FROM entites e
|
||||
LEFT JOIN users u ON u.fk_entite = e.id
|
||||
LEFT JOIN operations o ON o.fk_entite = e.id
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
if ($stripeOnly) {
|
||||
$sql .= " AND e.chk_stripe = 1";
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY e.id ORDER BY e.id DESC LIMIT :limit";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$entites = $stmt->fetchAll();
|
||||
|
||||
if (empty($entites)) {
|
||||
warning("Aucune entité trouvée");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Déchiffrer les données
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$decryptedEntites = [];
|
||||
|
||||
foreach ($entites as $entite) {
|
||||
$decryptedEntites[] = [
|
||||
'id' => $entite['id'],
|
||||
'name' => truncate($crypto->decryptWithIV($entite['encrypted_name']) ?? '-', 30),
|
||||
'ville' => truncate($entite['ville'] ?? '-', 20),
|
||||
'email' => truncate($crypto->decryptSearchable($entite['encrypted_email']) ?? '-', 30),
|
||||
'phone' => $crypto->decryptWithIV($entite['encrypted_phone']) ?? '-',
|
||||
'users' => $entite['nb_users'],
|
||||
'ops' => $entite['nb_operations'],
|
||||
'stripe' => $entite['chk_stripe'] ? '✓' : '✗',
|
||||
];
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("LISTE DES ENTITÉS (AMICALES) - " . count($decryptedEntites) . " résultat(s)");
|
||||
|
||||
table(
|
||||
[
|
||||
'id' => 'ID',
|
||||
'name' => 'Nom',
|
||||
'ville' => 'Ville',
|
||||
'email' => 'Email',
|
||||
'phone' => 'Téléphone',
|
||||
'users' => 'Users',
|
||||
'ops' => 'Ops',
|
||||
'stripe' => 'Stripe',
|
||||
],
|
||||
$decryptedEntites,
|
||||
true
|
||||
);
|
||||
|
||||
success("Affichage terminé");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
156
bao/bin/list-operations
Executable file
156
bao/bin/list-operations
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de listage des opérations
|
||||
* Usage: ./list-operations <environment> [--entite=<id>] [--limit=<n>]
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 2) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> [--entite=<id>] [--limit=<n>]");
|
||||
error("Exemple: " . basename($argv[0]) . " dev");
|
||||
error(" " . basename($argv[0]) . " rec --entite=5");
|
||||
error(" " . basename($argv[0]) . " dev --entite=10 --limit=20");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
|
||||
// Parser les options
|
||||
$filters = [];
|
||||
$limit = 50;
|
||||
|
||||
for ($i = 2; $i < $argc; $i++) {
|
||||
if (preg_match('/^--entite=(\d+)$/', $argv[$i], $matches)) {
|
||||
$filters['entite'] = (int)$matches[1];
|
||||
} elseif (preg_match('/^--limit=(\d+)$/', $argv[$i], $matches)) {
|
||||
$limit = (int)$matches[1];
|
||||
} else {
|
||||
error("Option invalide: " . $argv[$i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
if (!empty($filters)) {
|
||||
info("Filtres: " . json_encode($filters));
|
||||
}
|
||||
info("Limite: $limit\n");
|
||||
|
||||
// Construction de la requête
|
||||
$sql = "
|
||||
SELECT
|
||||
o.id,
|
||||
o.fk_entite,
|
||||
o.libelle,
|
||||
o.date_deb,
|
||||
o.date_fin,
|
||||
o.chk_distinct_sectors,
|
||||
o.chk_active,
|
||||
o.created_at,
|
||||
o.updated_at,
|
||||
e.encrypted_name as entite_encrypted_name,
|
||||
COUNT(DISTINCT p.id) as nb_passages,
|
||||
COUNT(DISTINCT u.id) as nb_users,
|
||||
COUNT(DISTINCT s.id) as nb_sectors
|
||||
FROM operations o
|
||||
LEFT JOIN entites e ON e.id = o.fk_entite
|
||||
LEFT JOIN ope_pass p ON p.fk_operation = o.id
|
||||
LEFT JOIN ope_users u ON u.fk_operation = o.id
|
||||
LEFT JOIN ope_sectors s ON s.fk_operation = o.id
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
$params = [];
|
||||
|
||||
if (isset($filters['entite'])) {
|
||||
$sql .= " AND o.fk_entite = :entite";
|
||||
$params['entite'] = $filters['entite'];
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY o.id";
|
||||
$sql .= " ORDER BY o.date_deb DESC";
|
||||
$sql .= " LIMIT :limit";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// Bind des paramètres
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue(':' . $key, $value, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
|
||||
$stmt->execute();
|
||||
$operations = $stmt->fetchAll();
|
||||
|
||||
if (empty($operations)) {
|
||||
warning("\nAucune opération trouvée");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Déchiffrer les noms d'entités
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$operationsData = [];
|
||||
|
||||
foreach ($operations as $op) {
|
||||
$operationsData[] = [
|
||||
'id' => $op['id'],
|
||||
'entite' => truncate($crypto->decryptWithIV($op['entite_encrypted_name']) ?? '-', 25),
|
||||
'libelle' => truncate($op['libelle'], 35),
|
||||
'date_deb' => date('d/m/Y', strtotime($op['date_deb'])),
|
||||
'date_fin' => date('d/m/Y', strtotime($op['date_fin'])),
|
||||
'passages' => $op['nb_passages'],
|
||||
'users' => $op['nb_users'],
|
||||
'sectors' => $op['nb_sectors'],
|
||||
'actif' => $op['chk_active'] ? '✓' : '✗',
|
||||
];
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("LISTE DES OPÉRATIONS - " . count($operationsData) . " résultat(s)");
|
||||
|
||||
table(
|
||||
[
|
||||
'id' => 'ID',
|
||||
'entite' => 'Entité',
|
||||
'libelle' => 'Libellé',
|
||||
'date_deb' => 'Début',
|
||||
'date_fin' => 'Fin',
|
||||
'passages' => 'Passages',
|
||||
'users' => 'Users',
|
||||
'sectors' => 'Secteurs',
|
||||
'actif' => 'Actif',
|
||||
],
|
||||
$operationsData,
|
||||
true
|
||||
);
|
||||
|
||||
success("Opérations listées avec succès");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
145
bao/bin/list-sectors
Executable file
145
bao/bin/list-sectors
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de listage des secteurs d'une opération
|
||||
* Usage: ./list-sectors <environment> --operation=<id>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> --operation=<id>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev --operation=123");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
|
||||
// Parser les options
|
||||
$operationId = null;
|
||||
|
||||
for ($i = 2; $i < $argc; $i++) {
|
||||
if (preg_match('/^--operation=(\d+)$/', $argv[$i], $matches)) {
|
||||
$operationId = (int)$matches[1];
|
||||
} else {
|
||||
error("Option invalide: " . $argv[$i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($operationId === null) {
|
||||
error("Le paramètre --operation=<id> est obligatoire");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Opération: #$operationId\n");
|
||||
|
||||
// Vérifier que l'opération existe
|
||||
$stmtOp = $pdo->prepare("SELECT libelle FROM operations WHERE id = :operation_id");
|
||||
$stmtOp->execute(['operation_id' => $operationId]);
|
||||
$operation = $stmtOp->fetch();
|
||||
|
||||
if (!$operation) {
|
||||
error("Opération #$operationId introuvable");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
info("Libellé opération: " . $operation['libelle'] . "\n");
|
||||
|
||||
// Récupérer les secteurs avec le nombre d'utilisateurs et de passages
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
s.id,
|
||||
s.libelle,
|
||||
s.color,
|
||||
s.chk_active,
|
||||
s.created_at,
|
||||
COUNT(DISTINCT us.fk_user) as nb_users,
|
||||
COUNT(DISTINCT p.id) as nb_passages
|
||||
FROM ope_sectors s
|
||||
LEFT JOIN ope_users_sectors us ON us.fk_sector = s.id AND us.chk_active = 1
|
||||
LEFT JOIN ope_pass p ON p.fk_sector = s.id AND p.chk_active = 1
|
||||
WHERE s.fk_operation = :operation_id
|
||||
GROUP BY s.id
|
||||
ORDER BY s.id
|
||||
");
|
||||
|
||||
$stmt->execute(['operation_id' => $operationId]);
|
||||
$sectors = $stmt->fetchAll();
|
||||
|
||||
if (empty($sectors)) {
|
||||
warning("\nAucun secteur trouvé pour cette opération");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Préparer les données pour le tableau
|
||||
$sectorsData = [];
|
||||
|
||||
foreach ($sectors as $sector) {
|
||||
$sectorsData[] = [
|
||||
'id' => $sector['id'],
|
||||
'libelle' => truncate($sector['libelle'], 40),
|
||||
'color' => $sector['color'],
|
||||
'users' => $sector['nb_users'],
|
||||
'passages' => $sector['nb_passages'],
|
||||
'actif' => $sector['chk_active'] ? '✓' : '✗',
|
||||
'created' => date('d/m/Y', strtotime($sector['created_at'])),
|
||||
];
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("SECTEURS - Opération #$operationId - " . count($sectorsData) . " secteur(s)");
|
||||
|
||||
table(
|
||||
[
|
||||
'id' => 'ID',
|
||||
'libelle' => 'Libellé',
|
||||
'color' => 'Couleur',
|
||||
'users' => 'Users',
|
||||
'passages' => 'Passages',
|
||||
'actif' => 'Actif',
|
||||
'created' => 'Créé le',
|
||||
],
|
||||
$sectorsData,
|
||||
true
|
||||
);
|
||||
|
||||
success("Secteurs listés avec succès");
|
||||
|
||||
// Afficher les statistiques globales
|
||||
$totalUsers = array_sum(array_column($sectorsData, 'users'));
|
||||
$totalPassages = array_sum(array_column($sectorsData, 'passages'));
|
||||
$activeSectors = count(array_filter($sectorsData, fn($s) => $s['actif'] === '✓'));
|
||||
|
||||
echo "\n";
|
||||
echo color("Statistiques:\n", 'bold');
|
||||
display("Secteurs actifs", "$activeSectors / " . count($sectorsData));
|
||||
display("Total utilisateurs", (string)$totalUsers);
|
||||
display("Total passages", (string)$totalPassages);
|
||||
echo "\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
149
bao/bin/list-users
Executable file
149
bao/bin/list-users
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de listing des utilisateurs avec données déchiffrées
|
||||
* Usage: ./list-users <environment> [--entite=X] [--role=Y] [--limit=N]
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 2) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> [--entite=X] [--role=Y] [--limit=N]");
|
||||
error("Exemple: " . basename($argv[0]) . " dev --entite=5 --limit=20");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$filters = [];
|
||||
$limit = 50;
|
||||
|
||||
// Parser les options
|
||||
for ($i = 2; $i < $argc; $i++) {
|
||||
if (preg_match('/--entite=(\d+)/', $argv[$i], $matches)) {
|
||||
$filters['entite'] = (int)$matches[1];
|
||||
} elseif (preg_match('/--role=(\d+)/', $argv[$i], $matches)) {
|
||||
$filters['role'] = (int)$matches[1];
|
||||
} elseif (preg_match('/--limit=(\d+)/', $argv[$i], $matches)) {
|
||||
$limit = (int)$matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
if (!empty($filters)) {
|
||||
info("Filtres: " . json_encode($filters));
|
||||
}
|
||||
info("Limite: $limit\n");
|
||||
|
||||
// Construction de la requête
|
||||
$sql = "
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name,
|
||||
u.fk_role,
|
||||
u.fk_entite,
|
||||
r.libelle as role_name,
|
||||
e.encrypted_name as entite_encrypted_name
|
||||
FROM users u
|
||||
LEFT JOIN x_users_roles r ON u.fk_role = r.id
|
||||
LEFT JOIN entites e ON u.fk_entite = e.id
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
$params = [];
|
||||
|
||||
if (isset($filters['entite'])) {
|
||||
$sql .= " AND u.fk_entite = :entite";
|
||||
$params['entite'] = $filters['entite'];
|
||||
}
|
||||
|
||||
if (isset($filters['role'])) {
|
||||
$sql .= " AND u.fk_role = :role";
|
||||
$params['role'] = $filters['role'];
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY u.id DESC LIMIT :limit";
|
||||
$params['limit'] = $limit;
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// Bind des paramètres
|
||||
foreach ($params as $key => $value) {
|
||||
if ($key === 'limit') {
|
||||
$stmt->bindValue(':' . $key, $value, PDO::PARAM_INT);
|
||||
} else {
|
||||
$stmt->bindValue(':' . $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
if (empty($users)) {
|
||||
warning("Aucun utilisateur trouvé");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Déchiffrer les données
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$decryptedUsers = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$decryptedUsers[] = [
|
||||
'id' => $user['id'],
|
||||
'username' => truncate($crypto->decryptSearchable($user['encrypted_user_name']) ?? '-', 20),
|
||||
'email' => truncate($crypto->decryptSearchable($user['encrypted_email']) ?? '-', 30),
|
||||
'prenom' => truncate($user['first_name'] ?? '-', 15),
|
||||
'nom' => truncate($crypto->decryptWithIV($user['encrypted_name']) ?? '-', 20),
|
||||
'role' => $user['role_name'] ?? '-',
|
||||
'entite' => truncate($crypto->decryptWithIV($user['entite_encrypted_name']) ?? '-', 25),
|
||||
];
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("LISTE DES UTILISATEURS - " . count($decryptedUsers) . " résultat(s)");
|
||||
|
||||
table(
|
||||
[
|
||||
'id' => 'ID',
|
||||
'username' => 'Username',
|
||||
'prenom' => 'Prénom',
|
||||
'nom' => 'Nom',
|
||||
'email' => 'Email',
|
||||
'role' => 'Rôle',
|
||||
'entite' => 'Entité',
|
||||
],
|
||||
$decryptedUsers,
|
||||
true
|
||||
);
|
||||
|
||||
success("Affichage terminé");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
174
bao/bin/reset-password
Executable file
174
bao/bin/reset-password
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de réinitialisation du mot de passe d'un utilisateur
|
||||
* Génère un nouveau mot de passe sécurisé et l'enregistre
|
||||
* Usage: ./reset-password <environment> <user_id>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <user_id>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev 123");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$userId = (int)$argv[2];
|
||||
|
||||
/**
|
||||
* Génère un mot de passe sécurisé selon les règles de l'API
|
||||
* Règles conformes à PasswordSecurityService de l'API
|
||||
*
|
||||
* @param int $length Longueur du mot de passe (12-20)
|
||||
* @return string Mot de passe généré
|
||||
*/
|
||||
function generateSecurePassword(int $length = 14): string {
|
||||
// Limiter la longueur entre 12 et 20 (règles API)
|
||||
$length = max(12, min(20, $length));
|
||||
|
||||
// Caractères autorisés (sans ambiguïté visuelle - règles API)
|
||||
$lowercase = 'abcdefghijkmnopqrstuvwxyz'; // sans l
|
||||
$uppercase = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // sans I, O
|
||||
$numbers = '23456789'; // sans 0, 1
|
||||
$special = '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
||||
|
||||
$password = '';
|
||||
|
||||
// Garantir au moins un caractère de chaque type (règle API)
|
||||
$password .= $lowercase[random_int(0, strlen($lowercase) - 1)];
|
||||
$password .= $uppercase[random_int(0, strlen($uppercase) - 1)];
|
||||
$password .= $numbers[random_int(0, strlen($numbers) - 1)];
|
||||
$password .= $special[random_int(0, strlen($special) - 1)];
|
||||
|
||||
// Compléter avec des caractères aléatoires
|
||||
$allChars = $lowercase . $uppercase . $numbers . $special;
|
||||
for ($i = strlen($password); $i < $length; $i++) {
|
||||
$password .= $allChars[random_int(0, strlen($allChars) - 1)];
|
||||
}
|
||||
|
||||
// Mélanger les caractères (règle API)
|
||||
$passwordArray = str_split($password);
|
||||
shuffle($passwordArray);
|
||||
|
||||
return implode('', $passwordArray);
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche de l'utilisateur #$userId...\n");
|
||||
|
||||
// Vérifier que l'utilisateur existe
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name
|
||||
FROM users u
|
||||
WHERE u.id = :user_id
|
||||
");
|
||||
|
||||
$stmt->execute(['user_id' => $userId]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
error("Utilisateur #$userId introuvable");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Déchiffrer les données pour affichage
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$userName = $crypto->decryptSearchable($user['encrypted_user_name']);
|
||||
$email = $crypto->decryptSearchable($user['encrypted_email']);
|
||||
$name = $crypto->decryptWithIV($user['encrypted_name']);
|
||||
$firstName = $user['first_name'];
|
||||
|
||||
// Afficher les infos utilisateur
|
||||
title("RÉINITIALISATION DU MOT DE PASSE");
|
||||
|
||||
echo color("Utilisateur\n", 'bold');
|
||||
display("ID", (string)$user['id']);
|
||||
display("Username", $userName);
|
||||
display("Prénom", $firstName);
|
||||
display("Nom", $name);
|
||||
display("Email", $email);
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Demander confirmation
|
||||
if (!confirm("Confirmer la réinitialisation du mot de passe ?")) {
|
||||
warning("Opération annulée");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Générer un nouveau mot de passe
|
||||
$newPassword = generateSecurePassword(14);
|
||||
$passwordHash = password_hash($newPassword, PASSWORD_BCRYPT);
|
||||
|
||||
info("\nGénération du nouveau mot de passe...");
|
||||
|
||||
// Mettre à jour la base de données
|
||||
$updateStmt = $pdo->prepare("
|
||||
UPDATE users
|
||||
SET user_pass_hash = :password_hash,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = :user_id
|
||||
");
|
||||
|
||||
$updateStmt->execute([
|
||||
'password_hash' => $passwordHash,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
|
||||
if ($updateStmt->rowCount() === 0) {
|
||||
error("Erreur lors de la mise à jour du mot de passe");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Afficher le résultat
|
||||
echo "\n";
|
||||
success("Mot de passe réinitialisé avec succès !");
|
||||
|
||||
echo "\n";
|
||||
echo color("═══════════════════════════════════════════\n", 'cyan');
|
||||
echo color(" NOUVEAU MOT DE PASSE\n", 'bold');
|
||||
echo color("═══════════════════════════════════════════\n", 'cyan');
|
||||
echo "\n";
|
||||
echo color(" ", 'green') . color($newPassword, 'yellow') . "\n";
|
||||
echo "\n";
|
||||
echo color("═══════════════════════════════════════════\n", 'cyan');
|
||||
echo "\n";
|
||||
|
||||
warning("⚠ Conservez ce mot de passe en lieu sûr !");
|
||||
warning(" Il ne sera pas possible de le récupérer.");
|
||||
|
||||
echo "\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
131
bao/bin/search-email
Executable file
131
bao/bin/search-email
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de recherche par email (déchiffré)
|
||||
* Usage: ./search-email <environment> <email>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <email>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev contact@example.com");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$searchEmail = strtolower(trim($argv[2]));
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche de l'email: $searchEmail\n");
|
||||
|
||||
// Récupérer TOUS les utilisateurs (on doit déchiffrer pour chercher)
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name,
|
||||
u.fk_role,
|
||||
u.fk_entite,
|
||||
r.libelle as role_name,
|
||||
e.encrypted_name as entite_encrypted_name
|
||||
FROM users u
|
||||
LEFT JOIN x_users_roles r ON u.fk_role = r.id
|
||||
LEFT JOIN entites e ON u.fk_entite = e.id
|
||||
ORDER BY u.id
|
||||
");
|
||||
|
||||
$allUsers = $stmt->fetchAll();
|
||||
|
||||
// Déchiffrer et filtrer
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$matchedUsers = [];
|
||||
|
||||
info("Analyse de " . count($allUsers) . " utilisateurs...");
|
||||
|
||||
foreach ($allUsers as $user) {
|
||||
$email = $crypto->decryptSearchable($user['encrypted_email']);
|
||||
|
||||
if ($email && strtolower($email) === $searchEmail) {
|
||||
$matchedUsers[] = [
|
||||
'id' => $user['id'],
|
||||
'username' => $crypto->decryptSearchable($user['encrypted_user_name']),
|
||||
'email' => $email,
|
||||
'prenom' => $user['first_name'],
|
||||
'nom' => $crypto->decryptWithIV($user['encrypted_name']),
|
||||
'role' => $user['role_name'],
|
||||
'entite_id' => $user['fk_entite'],
|
||||
'entite' => $crypto->decryptWithIV($user['entite_encrypted_name']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($matchedUsers)) {
|
||||
warning("\nAucun utilisateur trouvé avec l'email: $searchEmail");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Affichage
|
||||
echo "\n";
|
||||
title("RÉSULTATS DE LA RECHERCHE - " . count($matchedUsers) . " utilisateur(s) trouvé(s)");
|
||||
|
||||
if (count($matchedUsers) > 1) {
|
||||
warning("Attention: Plusieurs comptes utilisent le même email (autorisé par le système)");
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
foreach ($matchedUsers as $index => $user) {
|
||||
echo color("═══ Utilisateur #" . $user['id'] . " ═══", 'cyan') . "\n";
|
||||
|
||||
display("Username", $user['username']);
|
||||
display("Prénom", $user['prenom']);
|
||||
display("Nom", $user['nom']);
|
||||
display("Email", $user['email']);
|
||||
display("Rôle", $user['role']);
|
||||
display("Entité ID", (string)$user['entite_id']);
|
||||
display("Entité Nom", $user['entite']);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
success("Recherche terminée");
|
||||
|
||||
// Proposer d'afficher les détails complets
|
||||
if (count($matchedUsers) === 1) {
|
||||
echo "\n";
|
||||
if (confirm("Afficher les détails complets de cet utilisateur ?")) {
|
||||
echo "\n";
|
||||
$userId = $matchedUsers[0]['id'];
|
||||
$decryptUserScript = __DIR__ . '/decrypt-user';
|
||||
passthru("$decryptUserScript $environment $userId");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
316
bao/bin/search-entite
Executable file
316
bao/bin/search-entite
Executable file
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de recherche d'entité par nom/adresse/ville/email
|
||||
* Usage: ./search-entite <environment> <search_term>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <search_term>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev plumeliau");
|
||||
error("Exemple: " . basename($argv[0]) . " rec amicale");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$searchTerm = strtolower(trim($argv[2]));
|
||||
|
||||
if (strlen($searchTerm) < 2) {
|
||||
error("La recherche doit contenir au moins 2 caractères");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche: \"$searchTerm\"");
|
||||
info("Champs: nom, email, adresse1, adresse2, ville\n");
|
||||
|
||||
// Pré-filtrage sur les champs en clair (adresse1, adresse2, ville)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
e.id,
|
||||
e.encrypted_name,
|
||||
e.encrypted_email,
|
||||
e.adresse1,
|
||||
e.adresse2,
|
||||
e.code_postal,
|
||||
e.ville,
|
||||
e.chk_stripe,
|
||||
COUNT(DISTINCT u.id) as nb_users,
|
||||
COUNT(DISTINCT o.id) as nb_operations
|
||||
FROM entites e
|
||||
LEFT JOIN users u ON u.fk_entite = e.id
|
||||
LEFT JOIN operations o ON o.fk_entite = e.id
|
||||
WHERE e.chk_active = 1
|
||||
AND (LOWER(e.adresse1) LIKE :search1
|
||||
OR LOWER(e.adresse2) LIKE :search2
|
||||
OR LOWER(e.ville) LIKE :search3)
|
||||
GROUP BY e.id
|
||||
ORDER BY e.id
|
||||
");
|
||||
|
||||
$searchPattern = '%' . $searchTerm . '%';
|
||||
$stmt->execute([
|
||||
'search1' => $searchPattern,
|
||||
'search2' => $searchPattern,
|
||||
'search3' => $searchPattern
|
||||
]);
|
||||
|
||||
$preFilteredEntites = $stmt->fetchAll();
|
||||
|
||||
// Récupérer TOUTES les entités pour chercher dans les champs chiffrés
|
||||
$stmtAll = $pdo->query("
|
||||
SELECT
|
||||
e.id,
|
||||
e.encrypted_name,
|
||||
e.encrypted_email,
|
||||
e.adresse1,
|
||||
e.adresse2,
|
||||
e.code_postal,
|
||||
e.ville,
|
||||
e.chk_stripe,
|
||||
COUNT(DISTINCT u.id) as nb_users,
|
||||
COUNT(DISTINCT o.id) as nb_operations
|
||||
FROM entites e
|
||||
LEFT JOIN users u ON u.fk_entite = e.id
|
||||
LEFT JOIN operations o ON o.fk_entite = e.id
|
||||
WHERE e.chk_active = 1
|
||||
GROUP BY e.id
|
||||
ORDER BY e.id
|
||||
");
|
||||
|
||||
$allEntites = $stmtAll->fetchAll();
|
||||
|
||||
info("Analyse de " . count($allEntites) . " entités actives...\n");
|
||||
|
||||
// Déchiffrer et filtrer
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$matchedEntites = [];
|
||||
$seenIds = [];
|
||||
|
||||
// Ajouter d'abord les résultats pré-filtrés (adresse1, adresse2, ville)
|
||||
foreach ($preFilteredEntites as $entite) {
|
||||
$name = $crypto->decryptWithIV($entite['encrypted_name']);
|
||||
$email = $crypto->decryptSearchable($entite['encrypted_email']);
|
||||
|
||||
// Vérifier aussi dans les champs chiffrés
|
||||
$matches = false;
|
||||
$matchedFields = [];
|
||||
|
||||
if (stripos($entite['adresse1'], $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'adresse1';
|
||||
}
|
||||
if (stripos($entite['adresse2'], $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'adresse2';
|
||||
}
|
||||
if (stripos($entite['ville'], $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'ville';
|
||||
}
|
||||
if ($name && stripos($name, $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'nom';
|
||||
}
|
||||
if ($email && stripos($email, $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'email';
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$matchedEntites[] = [
|
||||
'id' => $entite['id'],
|
||||
'name' => $name ?? '-',
|
||||
'email' => $email ?? '-',
|
||||
'adresse1' => $entite['adresse1'] ?? '-',
|
||||
'code_postal' => $entite['code_postal'] ?? '-',
|
||||
'ville' => $entite['ville'] ?? '-',
|
||||
'stripe' => $entite['chk_stripe'] ? 'Oui' : 'Non',
|
||||
'nb_users' => $entite['nb_users'],
|
||||
'nb_operations' => $entite['nb_operations'],
|
||||
'matched_in' => implode(', ', $matchedFields),
|
||||
];
|
||||
$seenIds[$entite['id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Chercher dans les entités restantes (pour nom et email chiffrés)
|
||||
foreach ($allEntites as $entite) {
|
||||
if (isset($seenIds[$entite['id']])) {
|
||||
continue; // Déjà trouvé
|
||||
}
|
||||
|
||||
$name = $crypto->decryptWithIV($entite['encrypted_name']);
|
||||
$email = $crypto->decryptSearchable($entite['encrypted_email']);
|
||||
|
||||
$matches = false;
|
||||
$matchedFields = [];
|
||||
|
||||
if ($name && stripos($name, $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'nom';
|
||||
}
|
||||
if ($email && stripos($email, $searchTerm) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'email';
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$matchedEntites[] = [
|
||||
'id' => $entite['id'],
|
||||
'name' => $name ?? '-',
|
||||
'email' => $email ?? '-',
|
||||
'adresse1' => $entite['adresse1'] ?? '-',
|
||||
'code_postal' => $entite['code_postal'] ?? '-',
|
||||
'ville' => $entite['ville'] ?? '-',
|
||||
'stripe' => $entite['chk_stripe'] ? 'Oui' : 'Non',
|
||||
'nb_users' => $entite['nb_users'],
|
||||
'nb_operations' => $entite['nb_operations'],
|
||||
'matched_in' => implode(', ', $matchedFields),
|
||||
];
|
||||
$seenIds[$entite['id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($matchedEntites)) {
|
||||
warning("\nAucune entité trouvée avec: \"$searchTerm\"");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("RÉSULTATS - " . count($matchedEntites) . " entité(s) trouvée(s)");
|
||||
|
||||
// Préparer les données pour le tableau
|
||||
$tableData = [];
|
||||
$lineNumber = 1;
|
||||
foreach ($matchedEntites as $entite) {
|
||||
$tableData[] = [
|
||||
'line' => $lineNumber++,
|
||||
'id' => $entite['id'],
|
||||
'name' => truncate($entite['name'], 30),
|
||||
'ville' => truncate($entite['ville'], 20),
|
||||
'cp' => $entite['code_postal'],
|
||||
'users' => $entite['nb_users'],
|
||||
'ops' => $entite['nb_operations'],
|
||||
'stripe' => $entite['stripe'],
|
||||
'match' => $entite['matched_in'],
|
||||
];
|
||||
}
|
||||
|
||||
table(
|
||||
[
|
||||
'line' => '#',
|
||||
'id' => 'ID',
|
||||
'name' => 'Nom',
|
||||
'ville' => 'Ville',
|
||||
'cp' => 'CP',
|
||||
'users' => 'Users',
|
||||
'ops' => 'Ops',
|
||||
'stripe' => 'Stripe',
|
||||
'match' => 'Trouvé dans',
|
||||
],
|
||||
$tableData,
|
||||
true
|
||||
);
|
||||
|
||||
success("Recherche terminée");
|
||||
|
||||
// Menu interactif
|
||||
if (count($matchedEntites) > 0) {
|
||||
echo "\n";
|
||||
echo color("═══════════════════════════════════════\n", 'cyan');
|
||||
echo color(" Actions disponibles\n", 'bold');
|
||||
echo color("═══════════════════════════════════════\n", 'cyan');
|
||||
echo " 1. Détail d'une entité\n";
|
||||
echo " 2. Opérations d'une entité\n";
|
||||
echo " 3. Membres d'une entité\n";
|
||||
echo " 0. Quitter\n";
|
||||
echo color("═══════════════════════════════════════\n", 'cyan');
|
||||
|
||||
echo color("\nVotre choix: ", 'yellow');
|
||||
$handle = fopen('php://stdin', 'r');
|
||||
$choice = trim(fgets($handle));
|
||||
|
||||
if ($choice === '0' || $choice === '') {
|
||||
echo "\n";
|
||||
info("Au revoir !");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Demander le numéro de ligne (sauf si une seule trouvée)
|
||||
$entiteId = null;
|
||||
if (count($matchedEntites) === 1) {
|
||||
$entiteId = $matchedEntites[0]['id'];
|
||||
info("\nEntité sélectionnée: #$entiteId - " . $matchedEntites[0]['name']);
|
||||
} else {
|
||||
echo color("\nEntrez le n° de ligne (1-" . count($matchedEntites) . "): ", 'yellow');
|
||||
$lineInput = trim(fgets($handle));
|
||||
|
||||
if (!is_numeric($lineInput) || (int)$lineInput < 1 || (int)$lineInput > count($matchedEntites)) {
|
||||
fclose($handle);
|
||||
error("Numéro de ligne invalide (doit être entre 1 et " . count($matchedEntites) . ")");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$lineNumber = (int)$lineInput;
|
||||
$entiteId = $matchedEntites[$lineNumber - 1]['id'];
|
||||
info("\nEntité sélectionnée: #$entiteId - " . $matchedEntites[$lineNumber - 1]['name']);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
echo "\n";
|
||||
|
||||
// Exécuter l'action choisie
|
||||
switch ($choice) {
|
||||
case '1':
|
||||
// Détail de l'entité
|
||||
$decryptEntiteScript = __DIR__ . '/decrypt-entite';
|
||||
passthru("$decryptEntiteScript $environment $entiteId");
|
||||
break;
|
||||
|
||||
case '2':
|
||||
// Opérations de l'entité
|
||||
$listOperationsScript = __DIR__ . '/list-operations';
|
||||
passthru("$listOperationsScript $environment --entite=$entiteId");
|
||||
break;
|
||||
|
||||
case '3':
|
||||
// Membres de l'entité
|
||||
$listUsersScript = __DIR__ . '/list-users';
|
||||
passthru("$listUsersScript $environment --entite=$entiteId");
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Choix invalide: $choice");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
254
bao/bin/search-user
Executable file
254
bao/bin/search-user
Executable file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Script de recherche d'utilisateurs par chaîne
|
||||
* Recherche dans : username, nom, prénom, secteur
|
||||
* Usage: ./search-user <environment> <search_string>
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/CryptoService.php';
|
||||
require_once __DIR__ . '/../lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/../lib/helpers.php';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc < 3) {
|
||||
error("Usage: " . basename($argv[0]) . " <environment> <search_string>");
|
||||
error("Exemple: " . basename($argv[0]) . " dev dupont");
|
||||
error(" " . basename($argv[0]) . " dev secteur_a");
|
||||
error(" " . basename($argv[0]) . " dev j.dupont");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$environment = strtoupper($argv[1]);
|
||||
$searchString = strtolower(trim($argv[2]));
|
||||
|
||||
if (strlen($searchString) < 2) {
|
||||
error("La recherche doit contenir au moins 2 caractères");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Ouvrir le tunnel SSH si nécessaire
|
||||
$tunnelScript = __DIR__ . '/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connexion à la base de données
|
||||
$db = new DatabaseConnection($environment);
|
||||
$pdo = $db->connect();
|
||||
|
||||
info("Environnement: $environment");
|
||||
info("Recherche de: '$searchString'");
|
||||
info("Champs: username, nom, prénom, secteur\n");
|
||||
|
||||
// Récupérer les utilisateurs avec sect_name (en clair) et first_name (en clair)
|
||||
// On peut filtrer directement sur ces deux champs
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name,
|
||||
u.sect_name,
|
||||
u.fk_role,
|
||||
u.fk_entite,
|
||||
r.libelle as role_name,
|
||||
e.encrypted_name as entite_encrypted_name
|
||||
FROM users u
|
||||
LEFT JOIN x_users_roles r ON u.fk_role = r.id
|
||||
LEFT JOIN entites e ON u.fk_entite = e.id
|
||||
WHERE LOWER(u.first_name) LIKE :search1
|
||||
OR LOWER(u.sect_name) LIKE :search2
|
||||
ORDER BY u.id
|
||||
");
|
||||
|
||||
$searchPattern = '%' . $searchString . '%';
|
||||
$stmt->execute([
|
||||
'search1' => $searchPattern,
|
||||
'search2' => $searchPattern
|
||||
]);
|
||||
|
||||
$preFilteredUsers = $stmt->fetchAll();
|
||||
|
||||
// Récupérer TOUS les utilisateurs pour chercher dans les champs chiffrés
|
||||
$stmtAll = $pdo->query("
|
||||
SELECT
|
||||
u.id,
|
||||
u.encrypted_user_name,
|
||||
u.encrypted_email,
|
||||
u.encrypted_name,
|
||||
u.first_name,
|
||||
u.sect_name,
|
||||
u.fk_role,
|
||||
u.fk_entite,
|
||||
r.libelle as role_name,
|
||||
e.encrypted_name as entite_encrypted_name
|
||||
FROM users u
|
||||
LEFT JOIN x_users_roles r ON u.fk_role = r.id
|
||||
LEFT JOIN entites e ON u.fk_entite = e.id
|
||||
ORDER BY u.id
|
||||
");
|
||||
|
||||
$allUsers = $stmtAll->fetchAll();
|
||||
|
||||
info("Analyse de " . count($allUsers) . " utilisateurs...\n");
|
||||
|
||||
// Déchiffrer et filtrer
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$crypto = new CryptoService($config->getEncryptionKey());
|
||||
|
||||
$matchedUsers = [];
|
||||
$seenIds = [];
|
||||
|
||||
// Ajouter d'abord les résultats pré-filtrés (sect_name, first_name)
|
||||
foreach ($preFilteredUsers as $user) {
|
||||
$username = $crypto->decryptSearchable($user['encrypted_user_name']);
|
||||
$name = $crypto->decryptWithIV($user['encrypted_name']);
|
||||
|
||||
// Vérifier aussi dans les champs chiffrés
|
||||
$matches = false;
|
||||
$matchedFields = [];
|
||||
|
||||
if (stripos($user['first_name'], $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'prénom';
|
||||
}
|
||||
if (stripos($user['sect_name'], $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'secteur';
|
||||
}
|
||||
if ($username && stripos($username, $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'username';
|
||||
}
|
||||
if ($name && stripos($name, $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'nom';
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$matchedUsers[] = [
|
||||
'id' => $user['id'],
|
||||
'username' => $username ?? '-',
|
||||
'prenom' => $user['first_name'] ?? '-',
|
||||
'nom' => $name ?? '-',
|
||||
'secteur' => $user['sect_name'] ?? '-',
|
||||
'role' => $user['role_name'] ?? '-',
|
||||
'entite' => $crypto->decryptWithIV($user['entite_encrypted_name']) ?? '-',
|
||||
'matched_in' => implode(', ', $matchedFields),
|
||||
];
|
||||
$seenIds[$user['id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Chercher dans les utilisateurs restants (pour username et nom chiffrés)
|
||||
foreach ($allUsers as $user) {
|
||||
if (isset($seenIds[$user['id']])) {
|
||||
continue; // Déjà trouvé
|
||||
}
|
||||
|
||||
$username = $crypto->decryptSearchable($user['encrypted_user_name']);
|
||||
$name = $crypto->decryptWithIV($user['encrypted_name']);
|
||||
|
||||
$matches = false;
|
||||
$matchedFields = [];
|
||||
|
||||
if ($username && stripos($username, $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'username';
|
||||
}
|
||||
if ($name && stripos($name, $searchString) !== false) {
|
||||
$matches = true;
|
||||
$matchedFields[] = 'nom';
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$matchedUsers[] = [
|
||||
'id' => $user['id'],
|
||||
'username' => $username ?? '-',
|
||||
'prenom' => $user['first_name'] ?? '-',
|
||||
'nom' => $name ?? '-',
|
||||
'secteur' => $user['sect_name'] ?? '-',
|
||||
'role' => $user['role_name'] ?? '-',
|
||||
'entite' => $crypto->decryptWithIV($user['entite_encrypted_name']) ?? '-',
|
||||
'matched_in' => implode(', ', $matchedFields),
|
||||
];
|
||||
$seenIds[$user['id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($matchedUsers)) {
|
||||
warning("\nAucun utilisateur trouvé avec: '$searchString'");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Affichage
|
||||
title("RÉSULTATS DE LA RECHERCHE - " . count($matchedUsers) . " utilisateur(s) trouvé(s)");
|
||||
|
||||
// Préparer les données pour le tableau
|
||||
$tableData = [];
|
||||
foreach ($matchedUsers as $user) {
|
||||
$tableData[] = [
|
||||
'id' => $user['id'],
|
||||
'username' => truncate($user['username'], 20),
|
||||
'prenom' => truncate($user['prenom'], 15),
|
||||
'nom' => truncate($user['nom'], 20),
|
||||
'secteur' => truncate($user['secteur'], 15),
|
||||
'role' => truncate($user['role'], 12),
|
||||
'match' => $user['matched_in'],
|
||||
];
|
||||
}
|
||||
|
||||
table(
|
||||
[
|
||||
'id' => 'ID',
|
||||
'username' => 'Username',
|
||||
'prenom' => 'Prénom',
|
||||
'nom' => 'Nom',
|
||||
'secteur' => 'Secteur',
|
||||
'role' => 'Rôle',
|
||||
'match' => 'Trouvé dans',
|
||||
],
|
||||
$tableData,
|
||||
true
|
||||
);
|
||||
|
||||
success("Recherche terminée");
|
||||
|
||||
// Proposer d'afficher les détails complets
|
||||
if (count($matchedUsers) === 1) {
|
||||
echo "\n";
|
||||
if (confirm("Afficher les détails complets de cet utilisateur ?")) {
|
||||
echo "\n";
|
||||
$userId = $matchedUsers[0]['id'];
|
||||
$decryptUserScript = __DIR__ . '/decrypt-user';
|
||||
passthru("$decryptUserScript $environment $userId");
|
||||
}
|
||||
} elseif (count($matchedUsers) > 1 && count($matchedUsers) <= 10) {
|
||||
echo "\n";
|
||||
if (confirm("Afficher les détails d'un utilisateur spécifique ?")) {
|
||||
echo color("\nEntrez l'ID de l'utilisateur: ", 'yellow');
|
||||
$handle = fopen('php://stdin', 'r');
|
||||
$userId = trim(fgets($handle));
|
||||
fclose($handle);
|
||||
|
||||
if (is_numeric($userId) && (int)$userId > 0) {
|
||||
echo "\n";
|
||||
$decryptUserScript = __DIR__ . '/decrypt-user';
|
||||
passthru("$decryptUserScript $environment $userId");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error("Erreur: " . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
89
bao/config/database.php
Normal file
89
bao/config/database.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Configuration de la base de données pour BAO
|
||||
* Charge les paramètres depuis le fichier .env
|
||||
*/
|
||||
class DatabaseConfig {
|
||||
private static ?self $instance = null;
|
||||
private array $config;
|
||||
|
||||
private function __construct() {
|
||||
$this->loadEnv();
|
||||
}
|
||||
|
||||
public static function getInstance(): self {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function loadEnv(): void {
|
||||
$envPath = __DIR__ . '/.env';
|
||||
|
||||
if (!file_exists($envPath)) {
|
||||
throw new RuntimeException("Fichier .env introuvable. Copiez .env.example vers .env et configurez-le.");
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
// Ignorer les commentaires
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parser les variables
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
$this->config[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string $key, $default = null) {
|
||||
return $this->config[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function getEncryptionKey(): string {
|
||||
$key = $this->get('ENCRYPTION_KEY');
|
||||
if (empty($key)) {
|
||||
throw new RuntimeException("ENCRYPTION_KEY manquante dans .env");
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function getEnvironmentConfig(string $env): array {
|
||||
$env = strtoupper($env);
|
||||
|
||||
$enabled = $this->get("{$env}_ENABLED", 'false');
|
||||
if ($enabled !== 'true') {
|
||||
throw new RuntimeException("Environnement {$env} désactivé dans .env");
|
||||
}
|
||||
|
||||
return [
|
||||
'use_vpn' => $this->get("{$env}_USE_VPN", 'false') === 'true',
|
||||
'ssh_host' => $this->get("{$env}_SSH_HOST"),
|
||||
'ssh_port_local' => (int)$this->get("{$env}_SSH_PORT_LOCAL"),
|
||||
'host' => $this->get("{$env}_DB_HOST"),
|
||||
'port' => (int)$this->get("{$env}_DB_PORT"),
|
||||
'name' => $this->get("{$env}_DB_NAME"),
|
||||
'user' => $this->get("{$env}_DB_USER"),
|
||||
'pass' => $this->get("{$env}_DB_PASS"),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAvailableEnvironments(): array {
|
||||
$envs = [];
|
||||
foreach (['DEV', 'REC', 'PROD'] as $env) {
|
||||
if ($this->get("{$env}_ENABLED") === 'true') {
|
||||
$envs[] = $env;
|
||||
}
|
||||
}
|
||||
return $envs;
|
||||
}
|
||||
}
|
||||
0
bao/dump-geo_app-202510011905.sql
Normal file
0
bao/dump-geo_app-202510011905.sql
Normal file
0
bao/dump-geo_app-202510011916.sql
Normal file
0
bao/dump-geo_app-202510011916.sql
Normal file
0
bao/dump-geosector-202510021710.sql
Normal file
0
bao/dump-geosector-202510021710.sql
Normal file
0
bao/dump-geosector-202510021837.sql
Normal file
0
bao/dump-geosector-202510021837.sql
Normal file
0
bao/dump-geosector-202510051844.sql
Normal file
0
bao/dump-geosector-202510051844.sql
Normal file
474
bao/geo_app.sql
Executable file
474
bao/geo_app.sql
Executable file
@@ -0,0 +1,474 @@
|
||||
-- -------------------------------------------------------------
|
||||
-- TablePlus 6.4.8(608)
|
||||
--
|
||||
-- https://tableplus.com/
|
||||
--
|
||||
-- Database: geo_app
|
||||
-- Generation Time: 2025-06-09 18:03:43.5140
|
||||
-- -------------------------------------------------------------
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
|
||||
-- Tables préfixées "chat_"
|
||||
CREATE TABLE chat_rooms (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
type ENUM('private', 'group', 'broadcast'),
|
||||
created_at TIMESTAMP,
|
||||
created_by INT
|
||||
);
|
||||
|
||||
CREATE TABLE chat_messages (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
room_id VARCHAR(36),
|
||||
content TEXT,
|
||||
sender_id INT,
|
||||
sent_at TIMESTAMP,
|
||||
FOREIGN KEY (room_id) REFERENCES chat_rooms(id)
|
||||
);
|
||||
|
||||
CREATE TABLE chat_participants (
|
||||
room_id VARCHAR(36),
|
||||
user_id INT,
|
||||
role INT,
|
||||
entite_id INT,
|
||||
joined_at TIMESTAMP,
|
||||
PRIMARY KEY (room_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE chat_read_receipts (
|
||||
message_id VARCHAR(36),
|
||||
user_id INT,
|
||||
read_at TIMESTAMP,
|
||||
PRIMARY KEY (message_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE `email_counter` (
|
||||
`id` int(10) unsigned NOT NULL DEFAULT 1,
|
||||
`hour_start` timestamp NULL DEFAULT NULL,
|
||||
`count` int(10) unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `email_queue` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`to_email` varchar(255) DEFAULT NULL,
|
||||
`subject` varchar(255) DEFAULT NULL,
|
||||
`body` text DEFAULT NULL,
|
||||
`headers` text DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||
`status` enum('pending','sent','failed') DEFAULT 'pending',
|
||||
`attempts` int(10) unsigned DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `entites` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`encrypted_name` varchar(255) DEFAULT NULL,
|
||||
`adresse1` varchar(45) DEFAULT '',
|
||||
`adresse2` varchar(45) DEFAULT '',
|
||||
`code_postal` varchar(5) DEFAULT '',
|
||||
`ville` varchar(45) DEFAULT '',
|
||||
`fk_region` int(10) unsigned DEFAULT NULL,
|
||||
`fk_type` int(10) unsigned DEFAULT 1,
|
||||
`encrypted_phone` varchar(128) DEFAULT '',
|
||||
`encrypted_mobile` varchar(128) DEFAULT '',
|
||||
`encrypted_email` varchar(255) DEFAULT '',
|
||||
`gps_lat` varchar(20) NOT NULL DEFAULT '',
|
||||
`gps_lng` varchar(20) NOT NULL DEFAULT '',
|
||||
`chk_stripe` tinyint(1) unsigned DEFAULT 0,
|
||||
`encrypted_stripe_id` varchar(255) DEFAULT '',
|
||||
`encrypted_iban` varchar(255) DEFAULT '',
|
||||
`encrypted_bic` varchar(128) DEFAULT '',
|
||||
`chk_demo` tinyint(1) unsigned DEFAULT 1,
|
||||
`chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Gestion des mots de passe manuelle (1) ou automatique (0)',
|
||||
`chk_username_manuel` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Gestion des usernames manuelle (1) ou automatique (0)',
|
||||
`chk_copie_mail_recu` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`chk_accept_sms` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `entites_ibfk_1` (`fk_region`),
|
||||
KEY `entites_ibfk_2` (`fk_type`),
|
||||
CONSTRAINT `entites_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `entites_ibfk_2` FOREIGN KEY (`fk_type`) REFERENCES `x_entites_types` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1230 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `medias` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`support` varchar(45) NOT NULL DEFAULT '' COMMENT 'Type de support (entite, user, operation, passage)',
|
||||
`support_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'ID de élément associé',
|
||||
`fichier` varchar(250) NOT NULL DEFAULT '' COMMENT 'Nom du fichier stocké',
|
||||
`file_type` varchar(50) DEFAULT NULL COMMENT 'Extension du fichier (pdf, jpg, xlsx, etc.)',
|
||||
`file_category` varchar(50) DEFAULT NULL COMMENT 'export, logo, carte, etc.',
|
||||
`file_size` int(10) unsigned DEFAULT NULL COMMENT 'Taille du fichier en octets',
|
||||
`mime_type` varchar(100) DEFAULT NULL COMMENT 'Type MIME du fichier',
|
||||
`original_name` varchar(255) DEFAULT NULL COMMENT 'Nom original du fichier uploadé',
|
||||
`fk_entite` int(10) unsigned DEFAULT NULL COMMENT 'ID de entité propriétaire',
|
||||
`fk_operation` int(10) unsigned DEFAULT NULL COMMENT 'ID de opération (pour passages)',
|
||||
`file_path` varchar(500) DEFAULT NULL COMMENT 'Chemin complet du fichier',
|
||||
`original_width` int(10) unsigned DEFAULT NULL COMMENT 'Largeur originale de image',
|
||||
`original_height` int(10) unsigned DEFAULT NULL COMMENT 'Hauteur originale de image',
|
||||
`processed_width` int(10) unsigned DEFAULT NULL COMMENT 'Largeur après traitement',
|
||||
`processed_height` int(10) unsigned DEFAULT NULL COMMENT 'Hauteur après traitement',
|
||||
`is_processed` tinyint(1) unsigned DEFAULT 0 COMMENT 'Image redimensionnée (1) ou originale (0)',
|
||||
`description` varchar(100) NOT NULL DEFAULT '' COMMENT 'Description du fichier',
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
|
||||
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `idx_entite` (`fk_entite`),
|
||||
KEY `idx_operation` (`fk_operation`),
|
||||
KEY `idx_support_type` (`support`, `support_id`),
|
||||
KEY `idx_file_type` (`file_type`),
|
||||
KEY `idx_file_category` (`file_category`),
|
||||
CONSTRAINT `fk_medias_entite` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_medias_operation` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `ope_pass` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_sector` int(10) unsigned DEFAULT 0,
|
||||
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_adresse` varchar(25) DEFAULT '' COMMENT 'adresses.cp??.id',
|
||||
`passed_at` timestamp NULL DEFAULT NULL COMMENT 'Date du passage',
|
||||
`fk_type` int(10) unsigned DEFAULT 0,
|
||||
`numero` varchar(10) NOT NULL DEFAULT '',
|
||||
`rue` varchar(75) NOT NULL DEFAULT '',
|
||||
`rue_bis` varchar(1) NOT NULL DEFAULT '',
|
||||
`ville` varchar(75) NOT NULL DEFAULT '',
|
||||
`fk_habitat` int(10) unsigned DEFAULT 1,
|
||||
`appt` varchar(5) DEFAULT '',
|
||||
`niveau` varchar(5) DEFAULT '',
|
||||
`residence` varchar(75) DEFAULT '',
|
||||
`gps_lat` varchar(20) NOT NULL DEFAULT '',
|
||||
`gps_lng` varchar(20) NOT NULL DEFAULT '',
|
||||
`encrypted_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`montant` decimal(7,2) NOT NULL DEFAULT 0.00,
|
||||
`fk_type_reglement` int(10) unsigned DEFAULT 1,
|
||||
`remarque` text DEFAULT '',
|
||||
`encrypted_email` varchar(255) DEFAULT '',
|
||||
`nom_recu` varchar(50) DEFAULT NULL,
|
||||
`date_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de réception',
|
||||
`date_creat_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de création du reçu',
|
||||
`date_sent_recu` timestamp NULL DEFAULT NULL COMMENT 'Date envoi du reçu',
|
||||
`email_erreur` varchar(30) DEFAULT '',
|
||||
`chk_email_sent` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`encrypted_phone` varchar(128) NOT NULL DEFAULT '',
|
||||
`is_striped` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`docremis` tinyint(1) unsigned DEFAULT 0,
|
||||
`date_repasser` timestamp NULL DEFAULT NULL COMMENT 'Date prévue pour repasser',
|
||||
`nb_passages` int(11) DEFAULT 1 COMMENT 'Nb passages pour les a repasser',
|
||||
`chk_gps_maj` tinyint(1) unsigned DEFAULT 0,
|
||||
`chk_map_create` tinyint(1) unsigned DEFAULT 0,
|
||||
`chk_mobile` tinyint(1) unsigned DEFAULT 0,
|
||||
`chk_synchro` tinyint(1) unsigned DEFAULT 1 COMMENT 'chk synchro entre web et appli',
|
||||
`chk_api_adresse` tinyint(1) unsigned DEFAULT 0,
|
||||
`chk_maj_adresse` tinyint(1) unsigned DEFAULT 0,
|
||||
`anomalie` tinyint(1) unsigned DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_operation` (`fk_operation`),
|
||||
KEY `fk_sector` (`fk_sector`),
|
||||
KEY `fk_user` (`fk_user`),
|
||||
KEY `fk_type` (`fk_type`),
|
||||
KEY `fk_type_reglement` (`fk_type_reglement`),
|
||||
KEY `email` (`encrypted_email`),
|
||||
CONSTRAINT `ope_pass_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_pass_ibfk_2` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_pass_ibfk_3` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_pass_ibfk_4` FOREIGN KEY (`fk_type_reglement`) REFERENCES `x_types_reglements` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=19499566 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `ope_pass_histo` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`date_histo` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date historique',
|
||||
`sujet` varchar(50) DEFAULT NULL,
|
||||
`remarque` varchar(250) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ope_pass_histo_fk_pass_IDX` (`fk_pass`) USING BTREE,
|
||||
KEY `ope_pass_histo_date_histo_IDX` (`date_histo`) USING BTREE,
|
||||
CONSTRAINT `ope_pass_histo_ibfk_1` FOREIGN KEY (`fk_pass`) REFERENCES `ope_pass` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6752 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `ope_sectors` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_old_sector` int(10) unsigned DEFAULT NULL,
|
||||
`libelle` varchar(75) NOT NULL DEFAULT '',
|
||||
`sector` text NOT NULL DEFAULT '',
|
||||
`color` varchar(7) NOT NULL DEFAULT '#4B77BE',
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id` (`id`),
|
||||
KEY `fk_operation` (`fk_operation`),
|
||||
CONSTRAINT `ope_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=27675 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `ope_users` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `ope_users_ibfk_1` (`fk_operation`),
|
||||
KEY `ope_users_ibfk_2` (`fk_user`),
|
||||
CONSTRAINT `ope_users_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_users_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=199006 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `ope_users_sectors` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id` (`id`),
|
||||
KEY `fk_operation` (`fk_operation`),
|
||||
KEY `fk_user` (`fk_user`),
|
||||
KEY `fk_sector` (`fk_sector`),
|
||||
CONSTRAINT `ope_users_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_users_sectors_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `ope_users_sectors_ibfk_3` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=48082 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `operations` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_entite` int(10) unsigned NOT NULL DEFAULT 1,
|
||||
`libelle` varchar(75) NOT NULL DEFAULT '',
|
||||
`date_deb` date NOT NULL DEFAULT '0000-00-00',
|
||||
`date_fin` date NOT NULL DEFAULT '0000-00-00',
|
||||
`chk_distinct_sectors` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_entite` (`fk_entite`),
|
||||
KEY `date_deb` (`date_deb`),
|
||||
CONSTRAINT `operations_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3121 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `params` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(35) NOT NULL DEFAULT '',
|
||||
`valeur` varchar(255) NOT NULL DEFAULT '',
|
||||
`aide` varchar(150) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `sectors_adresses` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_adresse` varchar(25) DEFAULT NULL COMMENT 'adresses.cp??.id',
|
||||
`osm_id` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`osm_name` varchar(50) NOT NULL DEFAULT '',
|
||||
`numero` varchar(5) NOT NULL DEFAULT '',
|
||||
`rue_bis` varchar(5) NOT NULL DEFAULT '',
|
||||
`rue` varchar(60) NOT NULL DEFAULT '',
|
||||
`cp` varchar(5) NOT NULL DEFAULT '',
|
||||
`ville` varchar(60) NOT NULL DEFAULT '',
|
||||
`gps_lat` varchar(20) NOT NULL DEFAULT '',
|
||||
`gps_lng` varchar(20) NOT NULL DEFAULT '',
|
||||
`osm_date_creat` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `sectors_adresses_fk_sector_index` (`fk_sector`),
|
||||
KEY `sectors_adresses_numero_index` (`numero`),
|
||||
KEY `sectors_adresses_rue_index` (`rue`),
|
||||
KEY `sectors_adresses_ville_index` (`ville`),
|
||||
CONSTRAINT `sectors_adresses_ibfk_1` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1562946 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_entite` int(10) unsigned DEFAULT 1,
|
||||
`fk_role` int(10) unsigned DEFAULT 1,
|
||||
`fk_titre` int(10) unsigned DEFAULT 1,
|
||||
`encrypted_name` varchar(255) DEFAULT NULL,
|
||||
`first_name` varchar(45) DEFAULT NULL,
|
||||
`sect_name` varchar(60) DEFAULT '',
|
||||
`encrypted_user_name` varchar(128) DEFAULT '',
|
||||
`user_pass_hash` varchar(60) DEFAULT NULL,
|
||||
`encrypted_phone` varchar(128) DEFAULT NULL,
|
||||
`encrypted_mobile` varchar(128) DEFAULT NULL,
|
||||
`encrypted_email` varchar(255) DEFAULT '',
|
||||
`chk_alert_email` tinyint(1) unsigned DEFAULT 1,
|
||||
`chk_suivi` tinyint(1) unsigned DEFAULT 0,
|
||||
`date_naissance` date DEFAULT NULL,
|
||||
`date_embauche` date DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
|
||||
`fk_user_creat` int(10) unsigned DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
|
||||
`fk_user_modif` int(10) unsigned DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_entite` (`fk_entite`),
|
||||
KEY `username` (`encrypted_user_name`),
|
||||
KEY `users_ibfk_2` (`fk_role`),
|
||||
KEY `users_ibfk_3` (`fk_titre`),
|
||||
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `users_ibfk_2` FOREIGN KEY (`fk_role`) REFERENCES `x_users_roles` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `users_ibfk_3` FOREIGN KEY (`fk_titre`) REFERENCES `x_users_titres` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10027748 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_departements` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(3) DEFAULT NULL,
|
||||
`fk_region` int(10) unsigned DEFAULT 1,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `x_departements_ibfk_1` (`fk_region`),
|
||||
CONSTRAINT `x_departements_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_devises` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(3) DEFAULT NULL,
|
||||
`symbole` varchar(6) DEFAULT NULL,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_entites_types` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_pays` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(3) DEFAULT NULL,
|
||||
`fk_continent` int(10) unsigned DEFAULT NULL,
|
||||
`fk_devise` int(10) unsigned DEFAULT 1,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `x_pays_ibfk_1` (`fk_devise`),
|
||||
CONSTRAINT `x_pays_ibfk_1` FOREIGN KEY (`fk_devise`) REFERENCES `x_devises` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table des pays avec leurs codes' `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_regions` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_pays` int(10) unsigned DEFAULT 1,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`libelle_long` varchar(45) DEFAULT NULL,
|
||||
`table_osm` varchar(45) DEFAULT NULL,
|
||||
`departements` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `x_regions_ibfk_1` (`fk_pays`),
|
||||
CONSTRAINT `x_regions_ibfk_1` FOREIGN KEY (`fk_pays`) REFERENCES `x_pays` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_types_passages` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(10) DEFAULT NULL,
|
||||
`color_button` varchar(15) DEFAULT NULL,
|
||||
`color_mark` varchar(15) DEFAULT NULL,
|
||||
`color_table` varchar(15) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_types_reglements` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_users_roles` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents rôles des utilisateurs' `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_users_titres` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`libelle` varchar(45) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents titres des utilisateurs' `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `x_villes` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`fk_departement` int(10) unsigned DEFAULT 1,
|
||||
`libelle` varchar(65) DEFAULT NULL,
|
||||
`code_postal` varchar(5) DEFAULT NULL,
|
||||
`code_insee` varchar(5) DEFAULT NULL,
|
||||
`chk_active` tinyint(1) unsigned DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `x_villes_ibfk_1` (`fk_departement`),
|
||||
CONSTRAINT `x_villes_ibfk_1` FOREIGN KEY (`fk_departement`) REFERENCES `x_departements` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=38950 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE TABLE `z_sessions` (
|
||||
`sid` text NOT NULL,
|
||||
`fk_user` int(11) NOT NULL,
|
||||
`role` varchar(10) DEFAULT NULL,
|
||||
`date_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
`ip` varchar(50) NOT NULL,
|
||||
`browser` varchar(150) NOT NULL,
|
||||
`data` mediumtext DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
|
||||
|
||||
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `chat_conversations_unread` AS select `r`.`id` AS `id`,`r`.`type` AS `type`,`r`.`title` AS `title`,`r`.`date_creation` AS `date_creation`,`r`.`fk_user` AS `fk_user`,`r`.`fk_entite` AS `fk_entite`,`r`.`statut` AS `statut`,`r`.`description` AS `description`,`r`.`reply_permission` AS `reply_permission`,`r`.`is_pinned` AS `is_pinned`,`r`.`expiry_date` AS `expiry_date`,`r`.`updated_at` AS `updated_at`,count(distinct `m`.`id`) AS `total_messages`,count(distinct `rm`.`id`) AS `read_messages`,count(distinct `m`.`id`) - count(distinct `rm`.`id`) AS `unread_messages`,(select `geo_app`.`chat_messages`.`date_sent` from `chat_messages` where `geo_app`.`chat_messages`.`fk_room` = `r`.`id` order by `geo_app`.`chat_messages`.`date_sent` desc limit 1) AS `last_message_date` from ((`chat_rooms` `r` left join `chat_messages` `m` on(`r`.`id` = `m`.`fk_room`)) left join `chat_read_messages` `rm` on(`m`.`id` = `rm`.`fk_message`)) group by `r`.`id`;
|
||||
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
188
bao/lib/CryptoService.php
Normal file
188
bao/lib/CryptoService.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Service de chiffrement/déchiffrement AES-256-CBC
|
||||
* Compatible avec le système de chiffrement de l'API Geosector
|
||||
*/
|
||||
class CryptoService {
|
||||
private string $encryptionKey;
|
||||
private string $cipher = 'AES-256-CBC';
|
||||
|
||||
public function __construct(string $encryptionKey) {
|
||||
// Décoder la clé base64
|
||||
$this->encryptionKey = base64_decode($encryptionKey);
|
||||
|
||||
if (strlen($this->encryptionKey) !== 32) {
|
||||
throw new RuntimeException("La clé de chiffrement doit faire 32 bytes (256 bits)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre les données "searchable" (encrypted_user_name, encrypted_email)
|
||||
* Format: base64 simple avec IV fixe
|
||||
*/
|
||||
public function decryptSearchable(?string $encryptedData): ?string {
|
||||
if (empty($encryptedData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$encrypted = base64_decode($encryptedData);
|
||||
if ($encrypted === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$iv = str_repeat("\0", 16); // IV fixe
|
||||
|
||||
$decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);
|
||||
|
||||
if ($decrypted === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Supprimer le caractère de contrôle ajouté
|
||||
if (substr($decrypted, -1) === "\x01") {
|
||||
return substr($decrypted, 0, -1);
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre les données avec IV aléatoire (encrypted_name, encrypted_phone, etc.)
|
||||
* Format: base64(IV + encrypted)
|
||||
*/
|
||||
public function decryptWithIV(?string $encryptedData): ?string {
|
||||
if (empty($encryptedData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = base64_decode($encryptedData);
|
||||
if ($data === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ivLength = openssl_cipher_iv_length($this->cipher);
|
||||
|
||||
if (strlen($data) <= $ivLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$iv = substr($data, 0, $ivLength);
|
||||
$encrypted = substr($data, $ivLength);
|
||||
|
||||
$decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);
|
||||
|
||||
return $decrypted !== false ? $decrypted : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre une valeur chiffrée
|
||||
*
|
||||
* @param string|null $encryptedValue Valeur chiffrée (format base64:iv:data)
|
||||
* @return string|null Valeur déchiffrée ou null
|
||||
*/
|
||||
public function decrypt(?string $encryptedValue): ?string {
|
||||
if (empty($encryptedValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Le format de l'API est : base64:iv:encrypted_data
|
||||
$parts = explode(':', $encryptedValue);
|
||||
|
||||
if (count($parts) !== 3 || $parts[0] !== 'base64') {
|
||||
// Format invalide, peut-être déjà en clair
|
||||
return $encryptedValue;
|
||||
}
|
||||
|
||||
$iv = base64_decode($parts[1]);
|
||||
$encrypted = base64_decode($parts[2]);
|
||||
|
||||
if ($iv === false || $encrypted === false) {
|
||||
throw new RuntimeException("Impossible de décoder les données chiffrées");
|
||||
}
|
||||
|
||||
$decrypted = openssl_decrypt(
|
||||
$encrypted,
|
||||
$this->cipher,
|
||||
$this->encryptionKey,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($decrypted === false) {
|
||||
throw new RuntimeException("Échec du déchiffrement : " . openssl_error_string());
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chiffre une valeur
|
||||
*
|
||||
* @param string $value Valeur à chiffrer
|
||||
* @return string Valeur chiffrée (format base64:iv:data)
|
||||
*/
|
||||
public function encrypt(string $value): string {
|
||||
$ivLength = openssl_cipher_iv_length($this->cipher);
|
||||
$iv = openssl_random_pseudo_bytes($ivLength);
|
||||
|
||||
$encrypted = openssl_encrypt(
|
||||
$value,
|
||||
$this->cipher,
|
||||
$this->encryptionKey,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($encrypted === false) {
|
||||
throw new RuntimeException("Échec du chiffrement : " . openssl_error_string());
|
||||
}
|
||||
|
||||
return 'base64:' . base64_encode($iv) . ':' . base64_encode($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre plusieurs colonnes d'un tableau
|
||||
*
|
||||
* @param array $row Ligne de base de données
|
||||
* @param array $encryptedColumns Liste des colonnes à déchiffrer (sans le préfixe encrypted_)
|
||||
* @return array Tableau avec colonnes déchiffrées
|
||||
*/
|
||||
public function decryptRow(array $row, array $encryptedColumns): array {
|
||||
$decrypted = $row;
|
||||
|
||||
foreach ($encryptedColumns as $column) {
|
||||
$encryptedColumn = 'encrypted_' . $column;
|
||||
|
||||
if (isset($row[$encryptedColumn])) {
|
||||
$decrypted[$column] = $this->decrypt($row[$encryptedColumn]);
|
||||
}
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre les colonnes encrypted_* d'un utilisateur
|
||||
*
|
||||
* @param array $user Données utilisateur
|
||||
* @return array Utilisateur avec données déchiffrées
|
||||
*/
|
||||
public function decryptUser(array $user): array {
|
||||
$columns = ['user_name', 'email', 'name', 'phone', 'mobile'];
|
||||
return $this->decryptRow($user, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déchiffre les colonnes encrypted_* d'une entité
|
||||
*
|
||||
* @param array $entite Données entité
|
||||
* @return array Entité avec données déchiffrées
|
||||
*/
|
||||
public function decryptEntite(array $entite): array {
|
||||
$columns = ['name', 'email', 'phone', 'mobile', 'iban', 'bic'];
|
||||
return $this->decryptRow($entite, $columns);
|
||||
}
|
||||
}
|
||||
114
bao/lib/DatabaseConnection.php
Normal file
114
bao/lib/DatabaseConnection.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
/**
|
||||
* Gestion de la connexion à la base de données
|
||||
*/
|
||||
class DatabaseConnection {
|
||||
private ?PDO $pdo = null;
|
||||
private array $config;
|
||||
private string $environment;
|
||||
|
||||
public function __construct(string $environment) {
|
||||
$this->environment = strtoupper($environment);
|
||||
$dbConfig = DatabaseConfig::getInstance();
|
||||
$this->config = $dbConfig->getEnvironmentConfig($this->environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Établit la connexion PDO
|
||||
*/
|
||||
public function connect(): PDO {
|
||||
if ($this->pdo !== null) {
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4',
|
||||
$this->config['host'],
|
||||
$this->config['port'],
|
||||
$this->config['name']
|
||||
);
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
|
||||
];
|
||||
|
||||
$this->pdo = new PDO(
|
||||
$dsn,
|
||||
$this->config['user'],
|
||||
$this->config['pass'],
|
||||
$options
|
||||
);
|
||||
|
||||
return $this->pdo;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException(
|
||||
"Impossible de se connecter à la base {$this->environment}: " . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la connexion PDO active
|
||||
*/
|
||||
public function getPdo(): PDO {
|
||||
if ($this->pdo === null) {
|
||||
$this->connect();
|
||||
}
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferme la connexion
|
||||
*/
|
||||
public function close(): void {
|
||||
$this->pdo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nom de l'environnement
|
||||
*/
|
||||
public function getEnvironment(): string {
|
||||
return $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la configuration SSH pour le tunnel
|
||||
*/
|
||||
public function getSshConfig(): array {
|
||||
return [
|
||||
'host' => $this->config['ssh_host'],
|
||||
'port_local' => $this->config['ssh_port_local'],
|
||||
'port_remote' => 3306,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'environnement utilise le VPN (pas besoin de tunnel SSH)
|
||||
*/
|
||||
public function usesVpn(): bool {
|
||||
return $this->config['use_vpn'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Teste la connexion
|
||||
*/
|
||||
public function testConnection(): bool {
|
||||
try {
|
||||
$pdo = $this->connect();
|
||||
$stmt = $pdo->query('SELECT 1');
|
||||
return $stmt !== false;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
226
bao/lib/helpers.php
Normal file
226
bao/lib/helpers.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fonctions utilitaires pour BAO
|
||||
*/
|
||||
|
||||
/**
|
||||
* Affiche un message coloré dans le terminal
|
||||
*/
|
||||
function color(string $text, string $color = 'default'): string {
|
||||
static $colors = [
|
||||
'default' => "\033[0m",
|
||||
'black' => "\033[0;30m",
|
||||
'red' => "\033[0;31m",
|
||||
'green' => "\033[0;32m",
|
||||
'yellow' => "\033[0;33m",
|
||||
'blue' => "\033[0;34m",
|
||||
'magenta' => "\033[0;35m",
|
||||
'cyan' => "\033[0;36m",
|
||||
'white' => "\033[0;37m",
|
||||
'bold' => "\033[1m",
|
||||
'underline' => "\033[4m",
|
||||
];
|
||||
|
||||
$config = DatabaseConfig::getInstance();
|
||||
$colorsEnabled = $config->get('COLORS_ENABLED', 'true') === 'true';
|
||||
|
||||
if (!$colorsEnabled || !isset($colors[$color])) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return $colors[$color] . $text . $colors['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un titre encadré
|
||||
*/
|
||||
function title(string $text): void {
|
||||
$length = strlen($text);
|
||||
$border = str_repeat('═', $length + 4);
|
||||
|
||||
echo color("\n╔{$border}╗\n", 'cyan');
|
||||
echo color("║ {$text} ║\n", 'cyan');
|
||||
echo color("╚{$border}╝\n\n", 'cyan');
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message de succès
|
||||
*/
|
||||
function success(string $message): void {
|
||||
echo color("✓ ", 'green') . color($message, 'white') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'erreur
|
||||
*/
|
||||
function error(string $message): void {
|
||||
echo color("✗ ", 'red') . color($message, 'white') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'avertissement
|
||||
*/
|
||||
function warning(string $message): void {
|
||||
echo color("⚠ ", 'yellow') . color($message, 'white') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'information
|
||||
*/
|
||||
function info(string $message): void {
|
||||
echo color("ℹ ", 'blue') . color($message, 'white') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un label et sa valeur
|
||||
*/
|
||||
function display(string $label, ?string $value, bool $encrypted = false): void {
|
||||
$labelColored = color($label . ':', 'yellow');
|
||||
|
||||
if ($value === null) {
|
||||
$valueColored = color('(null)', 'magenta');
|
||||
} elseif ($encrypted && strpos($value, 'base64:') === 0) {
|
||||
$valueColored = color('[ENCRYPTED]', 'red');
|
||||
} else {
|
||||
$valueColored = color($value, 'white');
|
||||
}
|
||||
|
||||
echo " {$labelColored} {$valueColored}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche une ligne de séparation
|
||||
*/
|
||||
function separator(int $length = 80): void {
|
||||
echo color(str_repeat('─', $length), 'cyan') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Demande une confirmation à l'utilisateur
|
||||
*/
|
||||
function confirm(string $question, bool $default = false): bool {
|
||||
$suffix = $default ? '[O/n]' : '[o/N]';
|
||||
echo color("{$question} {$suffix}: ", 'yellow');
|
||||
|
||||
$handle = fopen('php://stdin', 'r');
|
||||
$line = trim(fgets($handle));
|
||||
fclose($handle);
|
||||
|
||||
if (empty($line)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return in_array(strtolower($line), ['o', 'oui', 'y', 'yes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demande un choix parmi plusieurs options
|
||||
*/
|
||||
function choice(string $question, array $options, $default = null): string {
|
||||
echo color("\n{$question}\n", 'yellow');
|
||||
|
||||
foreach ($options as $key => $label) {
|
||||
$prefix = ($key === $default) ? color('*', 'green') : ' ';
|
||||
echo " {$prefix} " . color((string)$key, 'cyan') . ") {$label}\n";
|
||||
}
|
||||
|
||||
echo color("\nVotre choix: ", 'yellow');
|
||||
|
||||
$handle = fopen('php://stdin', 'r');
|
||||
$line = trim(fgets($handle));
|
||||
fclose($handle);
|
||||
|
||||
if (empty($line) && $default !== null) {
|
||||
return (string)$default;
|
||||
}
|
||||
|
||||
if (!isset($options[$line])) {
|
||||
error("Choix invalide");
|
||||
return choice($question, $options, $default);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un tableau formaté
|
||||
*/
|
||||
function table(array $headers, array $rows, bool $showIndex = true): void {
|
||||
if (empty($rows)) {
|
||||
warning("Aucune donnée à afficher");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer les largeurs de colonnes
|
||||
$widths = [];
|
||||
|
||||
if ($showIndex) {
|
||||
$widths['#'] = max(strlen((string)count($rows)), 1) + 1;
|
||||
}
|
||||
|
||||
foreach ($headers as $key => $label) {
|
||||
$widths[$key] = max(
|
||||
strlen($label),
|
||||
max(array_map(fn($row) => strlen((string)($row[$key] ?? '')), $rows))
|
||||
) + 2;
|
||||
}
|
||||
|
||||
// En-tête
|
||||
separator();
|
||||
|
||||
if ($showIndex) {
|
||||
echo color(str_pad('#', $widths['#']), 'bold');
|
||||
}
|
||||
|
||||
foreach ($headers as $key => $label) {
|
||||
echo color(str_pad($label, $widths[$key]), 'bold');
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
separator();
|
||||
|
||||
// Lignes
|
||||
foreach ($rows as $index => $row) {
|
||||
if ($showIndex) {
|
||||
echo color(str_pad((string)($index + 1), $widths['#']), 'cyan');
|
||||
}
|
||||
|
||||
foreach ($headers as $key => $label) {
|
||||
$value = $row[$key] ?? '';
|
||||
echo str_pad((string)$value, $widths[$key]);
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
separator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate une date MySQL en français
|
||||
*/
|
||||
function formatDate(?string $date): string {
|
||||
if (empty($date) || $date === '0000-00-00' || $date === '0000-00-00 00:00:00') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
try {
|
||||
$dt = new DateTime($date);
|
||||
return $dt->format('d/m/Y H:i');
|
||||
} catch (Exception $e) {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tronque une chaîne si elle est trop longue
|
||||
*/
|
||||
function truncate(string $text, int $length = 50): string {
|
||||
if (strlen($text) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return substr($text, 0, $length - 3) . '...';
|
||||
}
|
||||
48
bao/lib/init-connection.php
Normal file
48
bao/lib/init-connection.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Initialisation de la connexion base de données
|
||||
* Gère automatiquement les tunnels SSH ou connexion VPN directe
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
|
||||
/**
|
||||
* Initialise la connexion pour un environnement
|
||||
* Ouvre le tunnel SSH si nécessaire (mode non-VPN)
|
||||
*
|
||||
* @param string $environment Environnement (DEV, REC, PROD)
|
||||
* @return DatabaseConnection Connexion initialisée
|
||||
* @throws RuntimeException Si la connexion échoue
|
||||
*/
|
||||
function initConnection(string $environment): DatabaseConnection {
|
||||
$db = new DatabaseConnection($environment);
|
||||
|
||||
// Si on utilise le VPN, pas besoin de tunnel SSH
|
||||
if ($db->usesVpn()) {
|
||||
info("Mode VPN détecté - connexion directe à la base");
|
||||
return $db;
|
||||
}
|
||||
|
||||
// Mode tunnel SSH
|
||||
info("Mode tunnel SSH - ouverture du tunnel...");
|
||||
|
||||
$tunnelScript = __DIR__ . '/../bin/_ssh-tunnel.sh';
|
||||
exec("$tunnelScript open $environment 2>&1", $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
error("Impossible d'ouvrir le tunnel SSH");
|
||||
if (!empty($output)) {
|
||||
foreach ($output as $line) {
|
||||
error(" " . $line);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Échec de l'ouverture du tunnel SSH");
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
Reference in New Issue
Block a user