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:
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);
|
||||
}
|
||||
Reference in New Issue
Block a user