Release v3.1.4 - Mode terrain et génération PDF #10
135
.cline
135
.cline
@@ -1,135 +0,0 @@
|
|||||||
{
|
|
||||||
"memoryBank": {
|
|
||||||
"enabled": true,
|
|
||||||
"path": "./docs",
|
|
||||||
"maxContextSize": 100000
|
|
||||||
},
|
|
||||||
"contextSettings": {
|
|
||||||
"maxTokens": 100000,
|
|
||||||
"cline.maxAutoApprovedRequests": 100,
|
|
||||||
"cline.enableMemoryBank": true,
|
|
||||||
"cline.includeSnippetsFromMemory": true,
|
|
||||||
"cline.contextLength": 10000,
|
|
||||||
"cline.autoFormat": true
|
|
||||||
},
|
|
||||||
"mcpServers": {
|
|
||||||
"github.com/modelcontextprotocol/servers/tree/main/src/git": {
|
|
||||||
"command": "python",
|
|
||||||
"args": [
|
|
||||||
"-m",
|
|
||||||
"mcp_server_git"
|
|
||||||
],
|
|
||||||
"disabled": false,
|
|
||||||
"autoApprove": []
|
|
||||||
},
|
|
||||||
"github.com/GLips/Figma-Context-MCP": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": [
|
|
||||||
"-y",
|
|
||||||
"figma-developer-mcp",
|
|
||||||
"--figma-api-key=figd_2SyOIL_LeFVIIpUuRFT6F2tNWPQl89lBmUWAnOsy",
|
|
||||||
"--stdio"
|
|
||||||
],
|
|
||||||
"disabled": false,
|
|
||||||
"autoApprove": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"projectStructure": {
|
|
||||||
"api": {
|
|
||||||
"type": "php",
|
|
||||||
"version": "8.3",
|
|
||||||
"structure": [
|
|
||||||
"Controllers",
|
|
||||||
"Core",
|
|
||||||
"Services",
|
|
||||||
"Config",
|
|
||||||
"Utils"
|
|
||||||
],
|
|
||||||
"conventions": {
|
|
||||||
"classes": "PascalCase",
|
|
||||||
"methods": "camelCase",
|
|
||||||
"namespaces": "App\\*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"type": "flutter",
|
|
||||||
"version": "3.19",
|
|
||||||
"structure": [
|
|
||||||
"core",
|
|
||||||
"presentation",
|
|
||||||
"shared",
|
|
||||||
"chat"
|
|
||||||
],
|
|
||||||
"conventions": {
|
|
||||||
"classes": "PascalCase",
|
|
||||||
"methods": "camelCase",
|
|
||||||
"variables": "camelCase"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"type": "svelte",
|
|
||||||
"version": "5",
|
|
||||||
"structure": [
|
|
||||||
"src/components",
|
|
||||||
"src/lib",
|
|
||||||
"src/pages"
|
|
||||||
],
|
|
||||||
"conventions": {
|
|
||||||
"components": "PascalCase.svelte",
|
|
||||||
"functions": "camelCase",
|
|
||||||
"stores": "camelCase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitWorkflow": {
|
|
||||||
"mainBranch": "main",
|
|
||||||
"developBranch": "develop",
|
|
||||||
"featureBranchPrefix": "feature/",
|
|
||||||
"bugfixBranchPrefix": "bugfix/",
|
|
||||||
"releaseBranchPrefix": "release/",
|
|
||||||
"commitMessageFormat": "type(scope): message",
|
|
||||||
"commitTypes": [
|
|
||||||
"feat",
|
|
||||||
"fix",
|
|
||||||
"docs",
|
|
||||||
"style",
|
|
||||||
"refactor",
|
|
||||||
"test",
|
|
||||||
"chore"
|
|
||||||
],
|
|
||||||
"mergeStrategy": "fast-forward",
|
|
||||||
"requireCodeReview": true
|
|
||||||
},
|
|
||||||
"security": {
|
|
||||||
"sensitiveDataPatterns": [
|
|
||||||
"encrypted_email",
|
|
||||||
"encrypted_name",
|
|
||||||
"encrypted_phone",
|
|
||||||
"encrypted_mobile",
|
|
||||||
"encrypted_stripe_id",
|
|
||||||
"user_pass_hash"
|
|
||||||
],
|
|
||||||
"requireEncryption": true,
|
|
||||||
"avoidHardcodedCredentials": true,
|
|
||||||
"inputValidation": "required"
|
|
||||||
},
|
|
||||||
"documentation": {
|
|
||||||
"requireForPublicAPI": true,
|
|
||||||
"includeExamples": true,
|
|
||||||
"updateChangelogOnRelease": true,
|
|
||||||
"primaryDocumentationFile": "CONTEXT-AI.md",
|
|
||||||
"referenceFiles": {
|
|
||||||
"database": "docs/geo_app.dump",
|
|
||||||
"apiEndpoints": "docs/api_endpoints.md",
|
|
||||||
"architecture": "docs/architecture.md"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"codeQuality": {
|
|
||||||
"phpStandard": "PSR-12",
|
|
||||||
"dartAnalyzer": "strong-mode",
|
|
||||||
"eslintConfig": "recommended",
|
|
||||||
"maximumMethodLength": 50,
|
|
||||||
"maximumFileLength": 500,
|
|
||||||
"preferImmutability": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
CONTEXT-AI.md
Normal file → Executable file
0
CONTEXT-AI.md
Normal file → Executable file
75
api/CLAUDE.md
Executable file
75
api/CLAUDE.md
Executable file
@@ -0,0 +1,75 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Directives importantes
|
||||||
|
- **Langue** : Toujours répondre en français
|
||||||
|
- **Approche de travail** :
|
||||||
|
- Travailler par étapes claires et structurées
|
||||||
|
- TOUJOURS présenter et proposer les modifications avant de les implémenter
|
||||||
|
- Attendre la validation de l'utilisateur avant de modifier le code
|
||||||
|
- Expliquer le problème identifié et la solution proposée
|
||||||
|
- **Vérification du schéma** : TOUJOURS vérifier `docs/geo_app.sql` avant de faire des modifications sur les tables de la base de données
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
- Install dependencies: `composer install` - install PHP dependencies
|
||||||
|
- Update dependencies: `composer update` - update PHP dependencies to latest versions
|
||||||
|
- Deploy to REC: `./livre-api.sh rec` - deploy from DVA to RECETTE environment
|
||||||
|
- Deploy to PROD: `./livre-api.sh prod` - deploy from RECETTE to PRODUCTION environment
|
||||||
|
- Export operations: `php export_operation.php` - export operations data
|
||||||
|
|
||||||
|
## Code Architecture
|
||||||
|
This is a PHP 8.3 API without framework, using a custom MVC-like architecture:
|
||||||
|
|
||||||
|
- **Entry point**: `index.php` handles all requests through custom Router
|
||||||
|
- **Core components**:
|
||||||
|
- `Router`: Maps URLs to controller methods, handles HTTP methods
|
||||||
|
- `Database`: PDO wrapper for MariaDB connections
|
||||||
|
- `Session`: Secure session management
|
||||||
|
- `Request/Response`: HTTP request/response handling
|
||||||
|
- **Controllers**: Located in `src/Controllers/`, handle business logic
|
||||||
|
- **Services**: Located in `src/Services/`, provide reusable functionality (logging, email, exports)
|
||||||
|
- **Configuration**: `src/Config/AppConfig.php` - singleton configuration management
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
- No framework dependency - pure PHP 8.3 with composer autoloading
|
||||||
|
- PDO for database access with prepared statements
|
||||||
|
- RESTful API design with JSON responses
|
||||||
|
- CORS handling for cross-origin requests
|
||||||
|
- Session-based authentication
|
||||||
|
- File uploads handled in `uploads/` directory
|
||||||
|
- Logs stored in `logs/` directory
|
||||||
|
|
||||||
|
## Database
|
||||||
|
- MariaDB 10.11 with InnoDB tables
|
||||||
|
- Migration scripts in `scripts/php/migrate_*.php`
|
||||||
|
- Schema comparison tool: `scripts/python/compare_schemas.py`
|
||||||
|
- Database sync: `scripts/cron/sync_databases.php`
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
- Session cookies with httponly, secure flags
|
||||||
|
- CORS configured for specific origins
|
||||||
|
- XSS, clickjacking protection headers
|
||||||
|
- PDO prepared statements for SQL injection prevention
|
||||||
|
- File upload validation in FileService
|
||||||
|
|
||||||
|
## Bonnes pratiques spécifiques
|
||||||
|
|
||||||
|
### Gestion des transactions PDO
|
||||||
|
- Toujours vérifier `$db->inTransaction()` avant d'appeler `rollBack()`
|
||||||
|
- Encadrer les opérations critiques dans des try/catch avec transaction
|
||||||
|
|
||||||
|
### Paramètres SQL
|
||||||
|
- Utiliser des noms de paramètres uniques dans les requêtes SQL
|
||||||
|
- Ne jamais réutiliser le même nom de paramètre plusieurs fois dans une requête
|
||||||
|
- Exemple : `:sector_polygon1`, `:sector_polygon2` au lieu de `:sector_polygon` répété
|
||||||
|
|
||||||
|
### Format des réponses API
|
||||||
|
- Les données doivent être placées à la racine de la réponse JSON, pas dans un groupe "data"
|
||||||
|
- Suivre le modèle de `LoginController` pour la structure des réponses
|
||||||
|
- Retourner des objets complets, pas seulement des IDs (ex: sector complet, pas sector_id)
|
||||||
|
|
||||||
|
### Gestion des sessions
|
||||||
|
- La session stocke `entity_id` depuis `fk_entite` lors du login
|
||||||
|
- Utiliser `Session::getEntityId()` pour récupérer l'ID de l'entité
|
||||||
|
- L'authentification utilise des Bearer tokens contenant le session_id
|
||||||
13
api/alter_table_geometry.sql
Normal file
13
api/alter_table_geometry.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- Modifier la table pour accepter tous les types de géométries (POLYGON et MULTIPOLYGON)
|
||||||
|
|
||||||
|
-- Option 1 : Modifier la colonne existante (recommandé)
|
||||||
|
ALTER TABLE x_departements_contours
|
||||||
|
MODIFY COLUMN contour GEOMETRY NOT NULL COMMENT 'Géométrie du contour du département (Polygon ou MultiPolygon)';
|
||||||
|
|
||||||
|
-- Vérifier la modification
|
||||||
|
DESCRIBE x_departements_contours;
|
||||||
|
|
||||||
|
-- Option 2 : Si l'option 1 ne fonctionne pas, recréer la colonne
|
||||||
|
-- ALTER TABLE x_departements_contours DROP COLUMN contour;
|
||||||
|
-- ALTER TABLE x_departements_contours ADD COLUMN contour GEOMETRY NOT NULL AFTER nom_dept;
|
||||||
|
-- ALTER TABLE x_departements_contours ADD SPATIAL INDEX idx_contour (contour);
|
||||||
0
api/bootstrap.php
Normal file → Executable file
0
api/bootstrap.php
Normal file → Executable file
0
api/composer.json
Normal file → Executable file
0
api/composer.json
Normal file → Executable file
0
api/composer.lock
generated
Normal file → Executable file
0
api/composer.lock
generated
Normal file → Executable file
27
api/create_table_x_departements_contours.sql
Normal file
27
api/create_table_x_departements_contours.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- Script de création de la table x_departements_contours
|
||||||
|
-- À exécuter manuellement en tant qu'administrateur de la base de données
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `x_departements_contours` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`code_dept` varchar(3) NOT NULL COMMENT 'Code département (22, 2A, 971...)',
|
||||||
|
`nom_dept` varchar(100) NOT NULL,
|
||||||
|
`contour` POLYGON NOT NULL COMMENT 'Polygone du contour du département',
|
||||||
|
`bbox_min_lat` decimal(10,8) DEFAULT NULL COMMENT 'Latitude min de la bounding box',
|
||||||
|
`bbox_max_lat` decimal(10,8) DEFAULT NULL COMMENT 'Latitude max de la bounding box',
|
||||||
|
`bbox_min_lng` decimal(11,8) DEFAULT NULL COMMENT 'Longitude min de la bounding box',
|
||||||
|
`bbox_max_lng` decimal(11,8) DEFAULT NULL COMMENT 'Longitude max de la bounding box',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_code_dept` (`code_dept`),
|
||||||
|
SPATIAL KEY `idx_contour` (`contour`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Contours géographiques des départements français';
|
||||||
|
|
||||||
|
-- Index pour améliorer les performances des requêtes par bounding box
|
||||||
|
CREATE INDEX idx_dept_bbox ON x_departements_contours (bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng);
|
||||||
|
|
||||||
|
-- Vérifier que la table a été créée
|
||||||
|
SHOW CREATE TABLE x_departements_contours\G
|
||||||
|
|
||||||
|
-- Vérifier les index
|
||||||
|
SHOW INDEX FROM x_departements_contours;
|
||||||
@@ -10,7 +10,7 @@ set -euo pipefail
|
|||||||
JUMP_USER="root"
|
JUMP_USER="root"
|
||||||
JUMP_HOST="195.154.80.116"
|
JUMP_HOST="195.154.80.116"
|
||||||
JUMP_PORT="22"
|
JUMP_PORT="22"
|
||||||
JUMP_KEY="/Users/pierre/.ssh/id_rsa_mbpi"
|
JUMP_KEY="/home/pierre/.ssh/id_rsa_mbpi"
|
||||||
|
|
||||||
# Paramètres du container Incus
|
# Paramètres du container Incus
|
||||||
INCUS_PROJECT=default
|
INCUS_PROJECT=default
|
||||||
@@ -73,6 +73,7 @@ fi
|
|||||||
|
|
||||||
# Étape 0: Définir le nom de l'archive
|
# Étape 0: Définir le nom de l'archive
|
||||||
ARCHIVE_NAME="api-deploy-$(date +%s).tar.gz"
|
ARCHIVE_NAME="api-deploy-$(date +%s).tar.gz"
|
||||||
|
TEMP_ARCHIVE="/tmp/${ARCHIVE_NAME}"
|
||||||
echo_info "Archive name will be: $ARCHIVE_NAME"
|
echo_info "Archive name will be: $ARCHIVE_NAME"
|
||||||
|
|
||||||
# Étape 1: Créer une archive du projet
|
# Étape 1: Créer une archive du projet
|
||||||
@@ -88,18 +89,24 @@ tar --exclude='.git' \
|
|||||||
--exclude='.DS_Store' \
|
--exclude='.DS_Store' \
|
||||||
--exclude='README.md' \
|
--exclude='README.md' \
|
||||||
--exclude="*.tar.gz" \
|
--exclude="*.tar.gz" \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='vendor' \
|
||||||
|
--exclude='*.swp' \
|
||||||
|
--exclude='*.swo' \
|
||||||
|
--exclude='*~' \
|
||||||
|
--warning=no-file-changed \
|
||||||
--no-xattrs \
|
--no-xattrs \
|
||||||
-czf "${ARCHIVE_NAME}" . || echo_error "Failed to create archive"
|
-czf "${TEMP_ARCHIVE}" . || echo_error "Failed to create archive"
|
||||||
|
|
||||||
# Vérifier la taille de l'archive
|
# Vérifier la taille de l'archive
|
||||||
ARCHIVE_SIZE=$(du -h "${ARCHIVE_NAME}" | cut -f1)
|
ARCHIVE_SIZE=$(du -h "${TEMP_ARCHIVE}" | cut -f1)
|
||||||
|
|
||||||
SSH_JUMP_CMD="ssh -i ${JUMP_KEY} -p ${JUMP_PORT} ${JUMP_USER}@${JUMP_HOST}"
|
SSH_JUMP_CMD="ssh -i ${JUMP_KEY} -p ${JUMP_PORT} ${JUMP_USER}@${JUMP_HOST}"
|
||||||
|
|
||||||
# Étape 2: Copier l'archive vers le serveur de saut
|
# Étape 2: Copier l'archive vers le serveur de saut
|
||||||
echo_step "Copying archive to jump server..."
|
echo_step "Copying archive to jump server..."
|
||||||
echo_info "Archive size: $ARCHIVE_SIZE"
|
echo_info "Archive size: $ARCHIVE_SIZE"
|
||||||
scp -i "${JUMP_KEY}" -P "${JUMP_PORT}" "${ARCHIVE_NAME}" "${JUMP_USER}@${JUMP_HOST}:/tmp/${ARCHIVE_NAME}" || echo_error "Failed to copy archive to jump server"
|
scp -i "${JUMP_KEY}" -P "${JUMP_PORT}" "${TEMP_ARCHIVE}" "${JUMP_USER}@${JUMP_HOST}:/tmp/${ARCHIVE_NAME}" || echo_error "Failed to copy archive to jump server"
|
||||||
|
|
||||||
# Étape 3: Exécuter les commandes sur le serveur de saut pour déployer dans le container Incus
|
# Étape 3: Exécuter les commandes sur le serveur de saut pour déployer dans le container Incus
|
||||||
echo_step "Deploying to Incus container..."
|
echo_step "Deploying to Incus container..."
|
||||||
@@ -140,7 +147,7 @@ $SSH_JUMP_CMD "
|
|||||||
"
|
"
|
||||||
|
|
||||||
# Nettoyage local
|
# Nettoyage local
|
||||||
rm -f "${ARCHIVE_NAME}"
|
rm -f "${TEMP_ARCHIVE}"
|
||||||
|
|
||||||
# Résumé final
|
# Résumé final
|
||||||
echo_step "Deployment completed successfully."
|
echo_step "Deployment completed successfully."
|
||||||
|
|||||||
0
api/docs/CDC.md
Normal file → Executable file
0
api/docs/CDC.md
Normal file → Executable file
0
api/docs/EXPORT-SYSTEM.md
Normal file → Executable file
0
api/docs/EXPORT-SYSTEM.md
Normal file → Executable file
0
api/docs/FILE-SYSTEM-API.md
Normal file → Executable file
0
api/docs/FILE-SYSTEM-API.md
Normal file → Executable file
430
api/docs/GESTION-SECTORS.md
Normal file
430
api/docs/GESTION-SECTORS.md
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
# GESTION-SECTORS.md
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Ce document décrit le système de gestion des secteurs dans l'API Geosector, incluant la connexion aux bases de données d'adresses externes, la validation des limites départementales, et le processus complet de création de secteurs avec génération automatique des passages.
|
||||||
|
|
||||||
|
## Évolutions récentes
|
||||||
|
|
||||||
|
### Gestion des sessions
|
||||||
|
- La session stocke maintenant `entity_id` depuis `fk_entite` lors du login
|
||||||
|
- Méthode `Session::getEntityId()` disponible pour récupérer l'ID de l'entité
|
||||||
|
- Utilisation cohérente de l'entity_id dans toutes les opérations
|
||||||
|
|
||||||
|
### Gestion des passages orphelins
|
||||||
|
- Les passages avec `fk_sector = 0` sont automatiquement intégrés au nouveau secteur
|
||||||
|
- Évite les doublons pour les passages ayant déjà une `fk_adresse`
|
||||||
|
- Mise à jour atomique dans la transaction de création du secteur
|
||||||
|
|
||||||
|
## Architecture multi-bases
|
||||||
|
|
||||||
|
### Bases de données principales
|
||||||
|
|
||||||
|
1. **Base principale** (`geosector_app`)
|
||||||
|
- Contient toutes les tables de l'application
|
||||||
|
- Tables concernées : `ope_sectors`, `sectors_adresses`, `ope_pass`, `ope_users_sectors`, `x_departements_contours`
|
||||||
|
|
||||||
|
2. **Base adresses** (dans conteneurs Incus séparés)
|
||||||
|
- DVA : `dva-maria` (13.23.33.46) - base `adresses`
|
||||||
|
- RCA : `rca-maria` (13.23.33.36) - base `adresses`
|
||||||
|
- PRA : `pra-maria` (13.23.33.26) - base `adresses`
|
||||||
|
- Credentials : `adr_geo_user` / `d66,AdrGeoDev.User`
|
||||||
|
- Tables par département : `cp22`, `cp23`, etc.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Dans `src/Config/AppConfig.php` :
|
||||||
|
|
||||||
|
```php
|
||||||
|
'addresses_database' => [
|
||||||
|
'host' => '13.23.33.46', // Varie selon l'environnement
|
||||||
|
'name' => 'adresses',
|
||||||
|
'username' => 'adr_geo_user',
|
||||||
|
'password' => 'd66,AdrGeoDev.User',
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gestion des contours départementaux
|
||||||
|
|
||||||
|
### Table x_departements_contours
|
||||||
|
|
||||||
|
Création manuelle de la table (sans DROP permissions) :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS `x_departements_contours` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`code_dept` varchar(3) NOT NULL,
|
||||||
|
`nom_dept` varchar(100) NOT NULL,
|
||||||
|
`contour` GEOMETRY NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_code_dept` (`code_dept`),
|
||||||
|
SPATIAL KEY `idx_contour` (`contour`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||||
|
COMMENT='Contours géographiques des départements français';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import des contours
|
||||||
|
|
||||||
|
1. **Fichier source** : `docs/contour-des-departements.geojson` (depuis data.gouv.fr)
|
||||||
|
2. **Import automatique** : Uniquement lors de la connexion de l'admin `d6soft`
|
||||||
|
3. **Script** : `scripts/init_departements_contours.php`
|
||||||
|
4. **Résultat** : 96 départements importés avec support Polygon et MultiPolygon
|
||||||
|
|
||||||
|
## Services principaux
|
||||||
|
|
||||||
|
### AddressService
|
||||||
|
|
||||||
|
Gère la récupération des adresses depuis la base externe :
|
||||||
|
|
||||||
|
```php
|
||||||
|
class AddressService {
|
||||||
|
// Récupère toutes les adresses dans un polygone
|
||||||
|
public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array
|
||||||
|
|
||||||
|
// Compte les adresses dans un polygone
|
||||||
|
public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caractéristiques** :
|
||||||
|
- Détection automatique des départements touchés par le secteur
|
||||||
|
- Interrogation de toutes les tables cp{dept} concernées
|
||||||
|
- Gestion des secteurs multi-départements
|
||||||
|
|
||||||
|
### DepartmentBoundaryService
|
||||||
|
|
||||||
|
Vérifie les limites départementales des secteurs :
|
||||||
|
|
||||||
|
```php
|
||||||
|
class DepartmentBoundaryService {
|
||||||
|
// Vérifie si un secteur est contenu dans un département
|
||||||
|
public function checkSectorInDepartment(array $sectorCoordinates, string $departmentCode): array
|
||||||
|
|
||||||
|
// Liste tous les départements touchés par un secteur
|
||||||
|
public function getDepartmentsForSector(array $sectorCoordinates): array
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Retour type** :
|
||||||
|
```php
|
||||||
|
[
|
||||||
|
'is_contained' => bool,
|
||||||
|
'message' => string,
|
||||||
|
'intersecting_departments' => [
|
||||||
|
['code_dept' => '22', 'nom_dept' => 'Côtes-d\'Armor', 'percentage_overlap' => 75.5],
|
||||||
|
['code_dept' => '29', 'nom_dept' => 'Finistère', 'percentage_overlap' => 24.5]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Processus de création de secteur
|
||||||
|
|
||||||
|
### 1. Structure du payload
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id": 123,
|
||||||
|
"fk_entite": 45,
|
||||||
|
"operation_id": 789,
|
||||||
|
"sector": {
|
||||||
|
"id": 0,
|
||||||
|
"libelle": "Secteur Centre-Ville",
|
||||||
|
"color": "#FF5733",
|
||||||
|
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
|
||||||
|
},
|
||||||
|
"users": [12, 34, 56, 78]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Étapes de création
|
||||||
|
|
||||||
|
1. **Validation** des données et de l'opération
|
||||||
|
2. **Vérification** des limites départementales (warning si débordement)
|
||||||
|
3. **Début de transaction** pour garantir la cohérence des données
|
||||||
|
4. **Insertion** du secteur dans `ope_sectors`
|
||||||
|
5. **Affectation** des utilisateurs dans `ope_users_sectors` avec :
|
||||||
|
- `fk_operation`, `fk_user`, `fk_sector`
|
||||||
|
- `created_at`, `fk_user_creat`, `chk_active = 1`
|
||||||
|
6. **Intégration des passages orphelins** :
|
||||||
|
- Recherche des passages avec `fk_sector = 0` dans le polygone
|
||||||
|
- Mise à jour de leur `fk_sector` vers le nouveau secteur
|
||||||
|
- Exclusion des passages ayant déjà une `fk_adresse`
|
||||||
|
7. **Récupération** des adresses via `AddressService`
|
||||||
|
8. **Stockage** des adresses dans `sectors_adresses`
|
||||||
|
9. **Création** des passages dans `ope_pass` pour chaque adresse :
|
||||||
|
- Affectés au premier utilisateur de la liste
|
||||||
|
- Avec toutes les FK nécessaires (entité, opération, secteur, user)
|
||||||
|
- Données d'adresse complètes
|
||||||
|
10. **Commit** de la transaction ou **rollback** en cas d'erreur
|
||||||
|
|
||||||
|
### 3. Réponse API pour CREATE
|
||||||
|
|
||||||
|
**Format standardisé** : Les données sont placées à la racine, sans groupe "data" intermédiaire.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Secteur créé avec succès",
|
||||||
|
"sector": {
|
||||||
|
"id": 123,
|
||||||
|
"libelle": "Secteur Centre-Ville",
|
||||||
|
"color": "#FF5733",
|
||||||
|
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
|
||||||
|
},
|
||||||
|
"passages_sector": [
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"fk_operation": 789,
|
||||||
|
"fk_sector": 123,
|
||||||
|
"fk_user": 12,
|
||||||
|
"fk_type": 2,
|
||||||
|
"fk_adresse": "cp22.12345",
|
||||||
|
"passed_at": null,
|
||||||
|
"numero": "10",
|
||||||
|
"rue": "Rue de la Paix",
|
||||||
|
"rue_bis": "",
|
||||||
|
"ville": "Saint-Brieuc",
|
||||||
|
"residence": null,
|
||||||
|
"fk_habitat": null,
|
||||||
|
"appt": null,
|
||||||
|
"niveau": null,
|
||||||
|
"gps_lat": "48.117266",
|
||||||
|
"gps_lng": "-1.6777926",
|
||||||
|
"nom_recu": null,
|
||||||
|
"name": "", // Décrypté depuis encrypted_name
|
||||||
|
"remarque": null,
|
||||||
|
"email": "", // Décrypté depuis encrypted_email
|
||||||
|
"phone": "", // Décrypté depuis encrypted_phone
|
||||||
|
"montant": null,
|
||||||
|
"fk_type_reglement": null,
|
||||||
|
"email_erreur": null,
|
||||||
|
"nb_passages": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"passages_integrated": 5, // Passages orphelins intégrés
|
||||||
|
"passages_created": 10, // Nouveaux passages créés
|
||||||
|
"users_sectors": [
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"first_name": "Jean",
|
||||||
|
"sect_name": "JDU",
|
||||||
|
"fk_sector": 123,
|
||||||
|
"name": "Dupont" // Décrypté depuis encrypted_name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Réponse API pour UPDATE
|
||||||
|
|
||||||
|
La réponse est identique à CREATE avec des compteurs supplémentaires :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Secteur modifié avec succès",
|
||||||
|
"sector": {
|
||||||
|
"id": 123,
|
||||||
|
"libelle": "Secteur Centre-Ville Modifié",
|
||||||
|
"color": "#FF5733",
|
||||||
|
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
|
||||||
|
},
|
||||||
|
"passages_sector": [
|
||||||
|
// Liste complète de TOUS les passages actuels du secteur
|
||||||
|
],
|
||||||
|
"passages_orphaned": 3, // Passages mis en orphelin (hors polygone)
|
||||||
|
"passages_updated": 5, // Passages mis à jour avec fk_adresse
|
||||||
|
"passages_created": 10, // Nouveaux passages créés
|
||||||
|
"passages_total": 25, // Nombre total de passages dans le secteur
|
||||||
|
"users_sectors": [
|
||||||
|
// Liste des utilisateurs affectés
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes importantes** :
|
||||||
|
- Les champs sensibles (name, email, phone) sont stockés cryptés et décryptés à la volée
|
||||||
|
- La structure est identique entre CREATE et UPDATE pour faciliter l'intégration
|
||||||
|
- Tous les champs sont retournés, même s'ils sont null
|
||||||
|
- Code HTTP : 201 pour CREATE, 200 pour UPDATE
|
||||||
|
|
||||||
|
## Gestion des secteurs multi-départements
|
||||||
|
|
||||||
|
### Détection automatique
|
||||||
|
|
||||||
|
Le système détecte automatiquement quand un secteur touche plusieurs départements :
|
||||||
|
|
||||||
|
1. **Analyse spatiale** : Utilisation de `ST_Intersects` pour identifier tous les départements touchés
|
||||||
|
2. **Calcul de pourcentage** : `ST_Area(ST_Intersection)` pour calculer le % de recouvrement
|
||||||
|
3. **Interrogation multi-tables** : Requête sur toutes les tables cp{dept} concernées
|
||||||
|
|
||||||
|
### Exemple de secteur multi-départements
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Secteur à cheval sur 22 (Côtes-d'Armor) et 29 (Finistère)
|
||||||
|
$coordinates = [
|
||||||
|
[48.5778, -3.8280], // Morlaix (29)
|
||||||
|
[48.5778, -3.7280], // Vers l'est (22)
|
||||||
|
[48.4778, -3.7280],
|
||||||
|
[48.4778, -3.8280]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Le système va automatiquement :
|
||||||
|
// 1. Détecter que le secteur touche 22 et 29
|
||||||
|
// 2. Interroger cp22 et cp29 pour les adresses
|
||||||
|
// 3. Créer les passages pour toutes les adresses trouvées
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tables de données
|
||||||
|
|
||||||
|
### ope_sectors
|
||||||
|
- `id` : Identifiant unique
|
||||||
|
- `libelle` : Nom du secteur
|
||||||
|
- `color` : Couleur d'affichage
|
||||||
|
- `sector` : Coordonnées (format lat/lng#lat/lng#...)
|
||||||
|
- `fk_entite` : Lien vers l'entité
|
||||||
|
|
||||||
|
### sectors_adresses
|
||||||
|
- `fk_sector` : Lien vers le secteur
|
||||||
|
- `fk_address` : ID de l'adresse dans la base externe
|
||||||
|
- `numero`, `voie`, `code_postal`, `commune`
|
||||||
|
- `latitude`, `longitude`
|
||||||
|
|
||||||
|
### ope_pass (passages)
|
||||||
|
- `fk_entite`, `fk_operation`, `fk_sector`, `fk_user`
|
||||||
|
- `numero`, `voie`, `code_postal`, `commune`
|
||||||
|
- `latitude`, `longitude`
|
||||||
|
- `created_at`, `fk_user_creat`, `chk_active`
|
||||||
|
|
||||||
|
### ope_users_sectors
|
||||||
|
- `fk_operation` : Lien vers l'opération
|
||||||
|
- `fk_user` : Lien vers l'utilisateur (ope_users)
|
||||||
|
- `fk_sector` : Lien vers le secteur
|
||||||
|
- `created_at`, `fk_user_creat`, `chk_active`
|
||||||
|
|
||||||
|
## Logs et monitoring
|
||||||
|
|
||||||
|
Le système génère des logs détaillés pour :
|
||||||
|
- Nombre d'adresses trouvées par département
|
||||||
|
- Secteurs hors limites départementales
|
||||||
|
- Passages créés avec succès
|
||||||
|
- Erreurs de connexion aux bases d'adresses
|
||||||
|
- Performance des requêtes spatiales
|
||||||
|
|
||||||
|
## Scripts de test
|
||||||
|
|
||||||
|
- `test_sector_departments.php` : Test des limites départementales
|
||||||
|
- `test_addresses_connection.php` : Test de connexion à la base d'adresses
|
||||||
|
|
||||||
|
## Notes importantes
|
||||||
|
|
||||||
|
1. **Fail-safe** : La création de secteur continue même si la base d'adresses est inaccessible
|
||||||
|
2. **Transactions** :
|
||||||
|
- Toute la création est dans une transaction pour garantir la cohérence
|
||||||
|
- Toujours vérifier `inTransaction()` avant d'appeler `rollBack()`
|
||||||
|
- Gestion correcte des erreurs PDO avec try/catch
|
||||||
|
3. **Performance** : Les requêtes spatiales utilisent des index spatiaux pour optimiser les performances
|
||||||
|
4. **Modification de secteur** : Plus complexe car nécessite de gérer les passages existants (non implémenté)
|
||||||
|
5. **Paramètres SQL** : Utiliser des noms uniques pour éviter l'erreur "Invalid parameter number"
|
||||||
|
6. **Jointures** : Les données utilisateur viennent de la table `users`, pas `ope_users` (qui n'a pas nom/prenom)
|
||||||
|
|
||||||
|
## Bilan de la gestion des adresses et passages
|
||||||
|
|
||||||
|
### Vue d'ensemble du cycle de vie
|
||||||
|
|
||||||
|
```
|
||||||
|
Base Adresses (cp22, cp23...) → sectors_adresses → ope_pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. CRÉATION D'UN SECTEUR
|
||||||
|
|
||||||
|
#### Flux des données :
|
||||||
|
1. **Récupération des adresses** depuis la base externe (`AddressService`)
|
||||||
|
2. **Intégration des passages orphelins** (`fk_sector = NULL`) situés dans le polygone
|
||||||
|
3. **Stockage dans `sectors_adresses`** de toutes les adresses du polygone
|
||||||
|
4. **Création automatique de passages** (`ope_pass`) pour chaque adresse SAUF celles déjà utilisées par les passages orphelins
|
||||||
|
|
||||||
|
#### Détails :
|
||||||
|
- **Passages créés** : `fk_type = 2`, `encrypted_name = ''` (vide), affectés au premier utilisateur
|
||||||
|
- **Passages orphelins** : mis à jour avec le nouveau `fk_sector`
|
||||||
|
- **Évite les doublons** : les adresses déjà utilisées par des passages orphelins ne génèrent pas de nouveau passage
|
||||||
|
|
||||||
|
### 2. MISE À JOUR D'UN SECTEUR
|
||||||
|
|
||||||
|
#### Processus de mise à jour :
|
||||||
|
1. **Mise à jour des attributs** (libelle, color, sector)
|
||||||
|
2. **Mise à jour des membres affectés**
|
||||||
|
3. **Suppression/recréation des adresses** dans `sectors_adresses`
|
||||||
|
4. **Gestion intelligente des passages** via `updatePassagesForSector` :
|
||||||
|
|
||||||
|
#### Gestion des passages lors de l'UPDATE :
|
||||||
|
|
||||||
|
##### a) Vérification géographique des passages existants
|
||||||
|
- Pour chaque passage du secteur, vérification si ses coordonnées GPS sont dans le nouveau polygone
|
||||||
|
- **Si DANS le polygone** : Conservation du passage
|
||||||
|
- **Si HORS du polygone** : Mise en orphelin (`fk_sector = NULL`)
|
||||||
|
|
||||||
|
##### b) Traitement des nouvelles adresses
|
||||||
|
Pour chaque adresse dans `sectors_adresses` :
|
||||||
|
1. **Vérification primaire** : Recherche par `fk_adresse`
|
||||||
|
2. **Vérification secondaire** : Si pas trouvé, recherche par `numero`, `rue_bis`, `rue`, `ville`
|
||||||
|
- Si trouvé → Mise à jour du `fk_adresse` dans le(s) passage(s)
|
||||||
|
3. **Création** : Si aucun passage existant, création avec :
|
||||||
|
- `fk_type = 2`, `encrypted_name = ''`
|
||||||
|
- Affecté au premier utilisateur du secteur
|
||||||
|
- Toutes les données de l'adresse
|
||||||
|
|
||||||
|
### 3. SUPPRESSION D'UN SECTEUR
|
||||||
|
|
||||||
|
#### Traitement différencié des passages :
|
||||||
|
1. **Passages "non visités"** (`fk_type = 2` ET `encrypted_name` vide) :
|
||||||
|
- Suppression définitive de la base
|
||||||
|
- Ces passages correspondent aux adresses non visitées
|
||||||
|
|
||||||
|
2. **Passages "visités"** (tous les autres) :
|
||||||
|
- Mise à jour : `fk_sector = NULL`
|
||||||
|
- Deviennent des passages orphelins
|
||||||
|
- Conservent toutes leurs données (contact, montant, etc.)
|
||||||
|
|
||||||
|
#### Autres suppressions :
|
||||||
|
- Suppression des affectations membres (`ope_users_sectors`)
|
||||||
|
- Suppression des adresses (`sectors_adresses`)
|
||||||
|
- Suppression du secteur lui-même
|
||||||
|
|
||||||
|
### Tableau récapitulatif
|
||||||
|
|
||||||
|
| Action | sectors_adresses | ope_pass dans polygone | ope_pass hors polygone | Nouvelles adresses |
|
||||||
|
|--------|------------------|------------------------|------------------------|-------------------|
|
||||||
|
| CREATE | Insertion depuis base externe | - | Intégration si orphelins | Création automatique de passages |
|
||||||
|
| UPDATE | Suppression/recréation | Conservation | Mise en orphelin | Création si pas de passage existant |
|
||||||
|
| DELETE | Suppression totale | Suppression si non visités / Orphelin si visités | - | - |
|
||||||
|
|
||||||
|
### Points d'attention
|
||||||
|
|
||||||
|
1. **Cohérence géographique** : Lors d'un UPDATE, le système vérifie automatiquement et met en orphelin les passages hors du nouveau périmètre
|
||||||
|
2. **Passages orphelins** : Peuvent être réintégrés lors de la création d'un nouveau secteur englobant
|
||||||
|
3. **Mise à jour du fk_adresse** : Lors d'un UPDATE, les passages existants peuvent recevoir leur `fk_adresse` s'ils correspondent à une adresse
|
||||||
|
4. **Performance** : La création/mise à jour génère potentiellement des milliers de passages selon la densité d'adresses
|
||||||
|
|
||||||
|
## Erreurs communes et solutions
|
||||||
|
|
||||||
|
### "There is no active transaction"
|
||||||
|
- **Cause** : Appel à `rollBack()` sans transaction active
|
||||||
|
- **Solution** : Vérifier `$db->inTransaction()` avant rollback
|
||||||
|
|
||||||
|
### "Column not found: fk_address"
|
||||||
|
- **Cause** : La colonne s'appelle `fk_adresse` (avec 'e')
|
||||||
|
- **Solution** : Corriger les noms de colonnes dans les requêtes
|
||||||
|
|
||||||
|
### "Invalid parameter number"
|
||||||
|
- **Cause** : Réutilisation du même nom de paramètre dans une requête
|
||||||
|
- **Solution** : Utiliser des noms uniques (`:param1`, `:param2`, etc.)
|
||||||
|
|
||||||
|
### "Unknown column 'ou.nom'"
|
||||||
|
- **Cause** : La table `ope_users` n'a pas de colonnes nom/prenom
|
||||||
|
- **Solution** : Joindre avec la table `users` qui contient `encrypted_name` et `first_name`
|
||||||
|
|
||||||
|
### "Class 'ApiService' not found"
|
||||||
|
- **Cause** : Import manquant dans le controller
|
||||||
|
- **Solution** : Ajouter `use App\Services\ApiService;` et `require_once`
|
||||||
0
api/docs/README-UPLOAD.md
Normal file → Executable file
0
api/docs/README-UPLOAD.md
Normal file → Executable file
0
api/docs/TECHBOOK.md
Normal file → Executable file
0
api/docs/TECHBOOK.md
Normal file → Executable file
0
api/docs/api-analysis.md
Normal file → Executable file
0
api/docs/api-analysis.md
Normal file → Executable file
1
api/docs/contour-des-departements.geojson
Normal file
1
api/docs/contour-des-departements.geojson
Normal file
File diff suppressed because one or more lines are too long
53
api/docs/departements_limitrophes.md
Normal file
53
api/docs/departements_limitrophes.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Départements limitrophes
|
||||||
|
|
||||||
|
Ce document liste les départements limitrophes pour chaque département français.
|
||||||
|
À utiliser pour remplir le champ `dept_limitrophes` dans la table `x_departements`.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
Le champ `dept_limitrophes` contient une liste de codes départements séparés par des virgules.
|
||||||
|
Exemple : "22,35,56" pour un département limitrophe avec les Côtes-d'Armor (22), l'Ille-et-Vilaine (35) et le Morbihan (56).
|
||||||
|
|
||||||
|
## Liste par département
|
||||||
|
|
||||||
|
### Bretagne
|
||||||
|
- **22 - Côtes-d'Armor** : 29,35,56
|
||||||
|
- **29 - Finistère** : 22,56
|
||||||
|
- **35 - Ille-et-Vilaine** : 22,44,49,50,53,56
|
||||||
|
- **56 - Morbihan** : 22,29,35,44
|
||||||
|
|
||||||
|
### Pays de la Loire
|
||||||
|
- **44 - Loire-Atlantique** : 35,49,56,85
|
||||||
|
- **49 - Maine-et-Loire** : 35,37,44,53,72,79,85,86
|
||||||
|
- **53 - Mayenne** : 14,35,49,50,61,72
|
||||||
|
- **72 - Sarthe** : 14,27,28,37,41,49,53,61
|
||||||
|
- **85 - Vendée** : 17,44,49,79
|
||||||
|
|
||||||
|
### Normandie
|
||||||
|
- **14 - Calvados** : 27,50,53,61,72
|
||||||
|
- **27 - Eure** : 14,28,60,61,72,76,78,95
|
||||||
|
- **50 - Manche** : 14,35,53,61
|
||||||
|
- **61 - Orne** : 14,27,28,35,41,50,53,72
|
||||||
|
- **76 - Seine-Maritime** : 27,60,80
|
||||||
|
|
||||||
|
### Île-de-France
|
||||||
|
- **75 - Paris** : 92,93,94
|
||||||
|
- **77 - Seine-et-Marne** : 02,10,45,51,60,89,91,93,94,95
|
||||||
|
- **78 - Yvelines** : 27,28,91,92,95
|
||||||
|
- **91 - Essonne** : 28,45,77,78,92,94
|
||||||
|
- **92 - Hauts-de-Seine** : 75,78,91,93,94,95
|
||||||
|
- **93 - Seine-Saint-Denis** : 75,77,92,94,95
|
||||||
|
- **94 - Val-de-Marne** : 75,77,91,92,93
|
||||||
|
- **95 - Val-d'Oise** : 27,60,77,78,92,93
|
||||||
|
|
||||||
|
### Hauts-de-France
|
||||||
|
- **02 - Aisne** : 08,51,59,60,77,80
|
||||||
|
- **59 - Nord** : 02,62,80 (+ frontière Belgique)
|
||||||
|
- **60 - Oise** : 02,27,76,77,80,95
|
||||||
|
- **62 - Pas-de-Calais** : 59,80 (+ frontière Belgique et côte Manche)
|
||||||
|
- **80 - Somme** : 02,27,59,60,62,76
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Cette liste est à compléter pour tous les départements français
|
||||||
|
- Les départements d'outre-mer n'ont généralement pas de départements limitrophes terrestres
|
||||||
|
- Certains départements peuvent avoir des limites maritimes non représentées ici
|
||||||
|
- Source recommandée : données INSEE ou IGN pour une liste complète et exacte
|
||||||
0
api/docs/flowIncus.md
Normal file → Executable file
0
api/docs/flowIncus.md
Normal file → Executable file
0
api/docs/geo_app.sql
Normal file → Executable file
0
api/docs/geo_app.sql
Normal file → Executable file
0
api/docs/geosector-db-diagram.md
Normal file → Executable file
0
api/docs/geosector-db-diagram.md
Normal file → Executable file
0
api/docs/geosector_app.sql
Normal file → Executable file
0
api/docs/geosector_app.sql
Normal file → Executable file
128
api/docs/x_departements_contours.sql
Normal file
128
api/docs/x_departements_contours.sql
Normal file
File diff suppressed because one or more lines are too long
128
api/docs/x_departements_contours_corrected.sql
Normal file
128
api/docs/x_departements_contours_corrected.sql
Normal file
File diff suppressed because one or more lines are too long
128
api/docs/x_departements_contours_fixed.sql
Normal file
128
api/docs/x_departements_contours_fixed.sql
Normal file
File diff suppressed because one or more lines are too long
0
api/export_operation.php
Normal file → Executable file
0
api/export_operation.php
Normal file → Executable file
10
api/index.php
Normal file → Executable file
10
api/index.php
Normal file → Executable file
@@ -7,6 +7,7 @@ require_once __DIR__ . '/bootstrap.php';
|
|||||||
// Chargement des fichiers principaux
|
// Chargement des fichiers principaux
|
||||||
require_once __DIR__ . '/src/Config/AppConfig.php';
|
require_once __DIR__ . '/src/Config/AppConfig.php';
|
||||||
require_once __DIR__ . '/src/Core/Database.php';
|
require_once __DIR__ . '/src/Core/Database.php';
|
||||||
|
require_once __DIR__ . '/src/Core/AddressesDatabase.php';
|
||||||
require_once __DIR__ . '/src/Core/Router.php';
|
require_once __DIR__ . '/src/Core/Router.php';
|
||||||
require_once __DIR__ . '/src/Core/Session.php';
|
require_once __DIR__ . '/src/Core/Session.php';
|
||||||
require_once __DIR__ . '/src/Core/Request.php';
|
require_once __DIR__ . '/src/Core/Request.php';
|
||||||
@@ -20,14 +21,21 @@ require_once __DIR__ . '/src/Controllers/LoginController.php';
|
|||||||
require_once __DIR__ . '/src/Controllers/EntiteController.php';
|
require_once __DIR__ . '/src/Controllers/EntiteController.php';
|
||||||
require_once __DIR__ . '/src/Controllers/UserController.php';
|
require_once __DIR__ . '/src/Controllers/UserController.php';
|
||||||
require_once __DIR__ . '/src/Controllers/OperationController.php';
|
require_once __DIR__ . '/src/Controllers/OperationController.php';
|
||||||
|
require_once __DIR__ . '/src/Controllers/PassageController.php';
|
||||||
|
require_once __DIR__ . '/src/Controllers/VilleController.php';
|
||||||
|
require_once __DIR__ . '/src/Controllers/FileController.php';
|
||||||
|
require_once __DIR__ . '/src/Controllers/SectorController.php';
|
||||||
|
|
||||||
// Initialiser la configuration
|
// Initialiser la configuration
|
||||||
$appConfig = AppConfig::getInstance();
|
$appConfig = AppConfig::getInstance();
|
||||||
$config = $appConfig->getFullConfig();
|
$config = $appConfig->getFullConfig();
|
||||||
|
|
||||||
// Initialiser la base de données
|
// Initialiser la base de données principale
|
||||||
Database::init($config['database']);
|
Database::init($config['database']);
|
||||||
|
|
||||||
|
// Initialiser la base de données des adresses
|
||||||
|
AddressesDatabase::init($appConfig->getAddressesDatabaseConfig());
|
||||||
|
|
||||||
// Configuration CORS
|
// Configuration CORS
|
||||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||||
$allowedOrigins = $config['api']['allowed_origins'];
|
$allowedOrigins = $config['api']['allowed_origins'];
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ fi
|
|||||||
|
|
||||||
HOST_IP="195.154.80.116"
|
HOST_IP="195.154.80.116"
|
||||||
HOST_USER=root
|
HOST_USER=root
|
||||||
HOST_KEY=/Users/pierre/.ssh/id_rsa_mbpi
|
HOST_KEY=/home/pierre/.ssh/id_rsa_mbpi
|
||||||
HOST_PORT=22
|
HOST_PORT=22
|
||||||
|
|
||||||
# Mapping des environnements
|
# Mapping des environnements
|
||||||
ENVIRONMENT=$1
|
ENVIRONMENT=$1
|
||||||
case $ENVIRONMENT in
|
case $ENVIRONMENT in
|
||||||
"rec")
|
"rca")
|
||||||
SOURCE_CONTAINER="dva-geo"
|
SOURCE_CONTAINER="dva-geo"
|
||||||
DEST_CONTAINER="rca-geo"
|
DEST_CONTAINER="rca-geo"
|
||||||
ENV_NAME="RECETTE"
|
ENV_NAME="RECETTE"
|
||||||
;;
|
;;
|
||||||
"prod")
|
"pra")
|
||||||
SOURCE_CONTAINER="rca-geo"
|
SOURCE_CONTAINER="rca-geo"
|
||||||
DEST_CONTAINER="pra-geo"
|
DEST_CONTAINER="pra-geo"
|
||||||
ENV_NAME="PRODUCTION"
|
ENV_NAME="PRODUCTION"
|
||||||
|
|||||||
28
api/migration_add_departements_contours.sql
Normal file
28
api/migration_add_departements_contours.sql
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-- Table pour stocker les contours (polygones) des départements
|
||||||
|
CREATE TABLE IF NOT EXISTS `x_departements_contours` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`code_dept` varchar(3) NOT NULL COMMENT 'Code département (22, 2A, 971...)',
|
||||||
|
`nom_dept` varchar(100) NOT NULL,
|
||||||
|
`contour` POLYGON NOT NULL COMMENT 'Polygone du contour du département',
|
||||||
|
`bbox_min_lat` decimal(10,8) DEFAULT NULL COMMENT 'Latitude min de la bounding box',
|
||||||
|
`bbox_max_lat` decimal(10,8) DEFAULT NULL COMMENT 'Latitude max de la bounding box',
|
||||||
|
`bbox_min_lng` decimal(11,8) DEFAULT NULL COMMENT 'Longitude min de la bounding box',
|
||||||
|
`bbox_max_lng` decimal(11,8) DEFAULT NULL COMMENT 'Longitude max de la bounding box',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_code_dept` (`code_dept`),
|
||||||
|
SPATIAL KEY `idx_contour` (`contour`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Index pour améliorer les performances
|
||||||
|
CREATE INDEX idx_dept_bbox ON x_departements_contours (bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng);
|
||||||
|
|
||||||
|
-- Exemples d'insertion (à remplacer par les vraies données)
|
||||||
|
-- Les coordonnées doivent être dans l'ordre : longitude latitude
|
||||||
|
-- Le polygone doit être fermé (premier point = dernier point)
|
||||||
|
/*
|
||||||
|
INSERT INTO x_departements_contours (code_dept, nom_dept, contour, bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng) VALUES
|
||||||
|
('22', 'Côtes-d\'Armor', ST_GeomFromText('POLYGON((-3.65 48.90, -2.00 48.90, -2.00 48.05, -3.65 48.05, -3.65 48.90))'), 48.05, 48.90, -3.65, -2.00),
|
||||||
|
('29', 'Finistère', ST_GeomFromText('POLYGON((-5.14 48.75, -3.38 48.75, -3.38 47.64, -5.14 47.64, -5.14 48.75))'), 47.64, 48.75, -5.14, -3.38);
|
||||||
|
*/
|
||||||
0
api/migration_add_file_category.sql
Normal file → Executable file
0
api/migration_add_file_category.sql
Normal file → Executable file
0
api/migration_add_ope_users_fields.sql
Normal file → Executable file
0
api/migration_add_ope_users_fields.sql
Normal file → Executable file
29
api/migration_add_sectors_adresses.sql
Normal file
29
api/migration_add_sectors_adresses.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- Migration pour ajouter la table sectors_adresses
|
||||||
|
-- Cette table stocke les adresses associées à chaque secteur
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `sectors_adresses` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`fk_sector` int(11) NOT NULL,
|
||||||
|
`fk_address` bigint(20) NOT NULL COMMENT 'ID de l''adresse dans la base adresses',
|
||||||
|
`numero` varchar(10) DEFAULT NULL,
|
||||||
|
`voie` varchar(255) DEFAULT NULL,
|
||||||
|
`code_postal` varchar(5) DEFAULT NULL,
|
||||||
|
`commune` varchar(100) DEFAULT NULL,
|
||||||
|
`latitude` decimal(10,8) NOT NULL,
|
||||||
|
`longitude` decimal(11,8) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_sector` (`fk_sector`),
|
||||||
|
KEY `idx_address` (`fk_address`),
|
||||||
|
KEY `idx_code_postal` (`code_postal`),
|
||||||
|
KEY `idx_commune` (`commune`),
|
||||||
|
KEY `idx_coords` (`latitude`, `longitude`),
|
||||||
|
CONSTRAINT `fk_sectors_adresses_sector` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Index pour améliorer les performances des requêtes géographiques
|
||||||
|
CREATE INDEX idx_sectors_adresses_geo ON sectors_adresses (fk_sector, latitude, longitude);
|
||||||
|
|
||||||
|
-- Commentaire sur la table
|
||||||
|
ALTER TABLE `sectors_adresses`
|
||||||
|
COMMENT = 'Table de liaison entre les secteurs et les adresses géographiques contenues dans leurs périmètres';
|
||||||
45
api/migrations/add_dept_limitrophes.sql
Normal file
45
api/migrations/add_dept_limitrophes.sql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
-- Ajout du champ dept_limitrophes dans la table x_departements
|
||||||
|
-- Ce champ contiendra la liste des codes départements limitrophes séparés par des virgules
|
||||||
|
-- Exemple : "22,35,44,56" pour le Morbihan (56)
|
||||||
|
|
||||||
|
ALTER TABLE x_departements
|
||||||
|
ADD COLUMN dept_limitrophes VARCHAR(100) DEFAULT NULL
|
||||||
|
COMMENT 'Liste des codes départements limitrophes séparés par des virgules'
|
||||||
|
AFTER libelle;
|
||||||
|
|
||||||
|
-- Exemples de mise à jour pour quelques départements bretons
|
||||||
|
-- À compléter avec tous les départements
|
||||||
|
|
||||||
|
-- Côtes-d'Armor (22) : limitrophe avec 29, 35, 56
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '29,35,56' WHERE code = '22';
|
||||||
|
|
||||||
|
-- Finistère (29) : limitrophe avec 22, 56
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,56' WHERE code = '29';
|
||||||
|
|
||||||
|
-- Ille-et-Vilaine (35) : limitrophe avec 22, 44, 49, 50, 53, 56
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,44,49,50,53,56' WHERE code = '35';
|
||||||
|
|
||||||
|
-- Morbihan (56) : limitrophe avec 22, 29, 35, 44
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,29,35,44' WHERE code = '56';
|
||||||
|
|
||||||
|
-- Loire-Atlantique (44) : limitrophe avec 35, 49, 56, 85
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '35,49,56,85' WHERE code = '44';
|
||||||
|
|
||||||
|
-- Hauts-de-France
|
||||||
|
-- Aisne (02) : limitrophe avec 08, 51, 59, 60, 77, 80
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '08,51,59,60,77,80' WHERE code = '02';
|
||||||
|
|
||||||
|
-- Nord (59) : limitrophe avec 02, 62, 80 (+ frontière Belgique)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,62,80' WHERE code = '59';
|
||||||
|
|
||||||
|
-- Oise (60) : limitrophe avec 02, 27, 76, 77, 80, 95
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,27,76,77,80,95' WHERE code = '60';
|
||||||
|
|
||||||
|
-- Pas-de-Calais (62) : limitrophe avec 59, 80
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '59,80' WHERE code = '62';
|
||||||
|
|
||||||
|
-- Somme (80) : limitrophe avec 02, 27, 59, 60, 62, 76
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,27,59,60,62,76' WHERE code = '80';
|
||||||
|
|
||||||
|
-- Note : Ces données sont à compléter pour tous les départements français
|
||||||
|
-- Source recommandée : données INSEE ou IGN pour la liste complète et exacte
|
||||||
55
api/migrations/integrate_contours_to_departements.sql
Normal file
55
api/migrations/integrate_contours_to_departements.sql
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
-- Script pour intégrer les données de x_departements_contours dans x_departements
|
||||||
|
-- Ce script ajoute une colonne contour à x_departements et y copie les données
|
||||||
|
-- Note : Les colonnes bbox_* ne sont pas migrées car elles ne sont pas utilisées dans l'API
|
||||||
|
|
||||||
|
-- 1. Ajouter la colonne contour à x_departements si elle n'existe pas déjà
|
||||||
|
ALTER TABLE x_departements
|
||||||
|
ADD COLUMN IF NOT EXISTS contour GEOMETRY DEFAULT NULL
|
||||||
|
COMMENT 'Contour géographique du département'
|
||||||
|
AFTER dept_limitrophes;
|
||||||
|
|
||||||
|
-- 2. Mettre à jour x_departements avec les contours depuis x_departements_contours
|
||||||
|
-- On copie directement les contours non NULL
|
||||||
|
UPDATE x_departements d
|
||||||
|
INNER JOIN x_departements_contours dc ON d.code = dc.code_dept
|
||||||
|
SET d.contour = dc.contour
|
||||||
|
WHERE dc.contour IS NOT NULL;
|
||||||
|
|
||||||
|
-- 3. Vérifier les départements sans contour (hors DOM-TOM et Corse historique)
|
||||||
|
SELECT
|
||||||
|
d.code,
|
||||||
|
d.libelle,
|
||||||
|
CASE
|
||||||
|
WHEN d.code IN ('20', '971', '972', '973', '974', '975', '976') THEN 'DOM-TOM ou Corse historique - Normal'
|
||||||
|
WHEN d.contour IS NULL THEN 'Pas de contour'
|
||||||
|
ELSE 'OK'
|
||||||
|
END as statut
|
||||||
|
FROM x_departements d
|
||||||
|
WHERE d.contour IS NULL
|
||||||
|
AND d.code NOT IN ('20', '971', '972', '973', '974', '975', '976')
|
||||||
|
ORDER BY d.code;
|
||||||
|
|
||||||
|
-- 4. Créer l'index spatial
|
||||||
|
-- Les valeurs NULL sont autorisées dans un index spatial MySQL
|
||||||
|
ALTER TABLE x_departements
|
||||||
|
ADD SPATIAL INDEX idx_contour (contour);
|
||||||
|
|
||||||
|
-- 5. Vérifier le nombre de départements mis à jour
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_departements,
|
||||||
|
SUM(CASE WHEN contour IS NOT NULL THEN 1 ELSE 0 END) as departements_avec_contour,
|
||||||
|
SUM(CASE WHEN contour IS NULL THEN 1 ELSE 0 END) as departements_sans_contour
|
||||||
|
FROM x_departements;
|
||||||
|
|
||||||
|
-- 6. Lister les départements qui n'ont pas de contour (s'il y en a)
|
||||||
|
SELECT code, libelle
|
||||||
|
FROM x_departements
|
||||||
|
WHERE contour IS NULL
|
||||||
|
ORDER BY code;
|
||||||
|
|
||||||
|
-- 7. Optionnel : Après vérification, vous pouvez supprimer la table x_departements_contours
|
||||||
|
-- ATTENTION : Ne décommentez cette ligne qu'après avoir vérifié que toutes les données sont bien migrées
|
||||||
|
-- DROP TABLE IF EXISTS x_departements_contours;
|
||||||
|
|
||||||
|
-- 8. Mettre à jour les statistiques de la table pour optimiser les requêtes spatiales
|
||||||
|
ANALYZE TABLE x_departements;
|
||||||
131
api/migrations/update_all_dept_limitrophes.sql
Normal file
131
api/migrations/update_all_dept_limitrophes.sql
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
-- Mise à jour complète des départements limitrophes pour tous les départements français
|
||||||
|
-- Format : liste des codes départements séparés par des virgules
|
||||||
|
|
||||||
|
-- Auvergne-Rhône-Alpes
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '07,15,43,48' WHERE code = '01'; -- Ain : Ardèche, Cantal, Haute-Loire, Lozère
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,05,26,84' WHERE code = '03'; -- Allier : Alpes-de-Haute-Provence, Hautes-Alpes, Drôme, Vaucluse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,26,30,43,48,84' WHERE code = '07'; -- Ardèche : Ain, Drôme, Gard, Haute-Loire, Lozère, Vaucluse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '12,19,43,46,48' WHERE code = '15'; -- Cantal : Aveyron, Corrèze, Haute-Loire, Lot, Lozère
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,05,73,84' WHERE code = '26'; -- Drôme : Alpes-de-Haute-Provence, Hautes-Alpes, Savoie, Vaucluse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '05,06,13,73' WHERE code = '38'; -- Isère : Hautes-Alpes, Alpes-Maritimes, Bouches-du-Rhône, Savoie
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '07,15,19,43,48,63' WHERE code = '42'; -- Loire : Ardèche, Cantal, Corrèze, Haute-Loire, Lozère, Puy-de-Dôme
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,07,15,42,48,63' WHERE code = '43'; -- Haute-Loire : Ain, Ardèche, Cantal, Loire, Lozère, Puy-de-Dôme
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '03,15,23,42,43' WHERE code = '63'; -- Puy-de-Dôme : Allier, Cantal, Creuse, Loire, Haute-Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,38,39,71' WHERE code = '69'; -- Rhône : Ain, Isère, Jura, Saône-et-Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,25,38,39,74' WHERE code = '73'; -- Savoie : Ain, Doubs, Isère, Jura, Haute-Savoie
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,73' WHERE code = '74'; -- Haute-Savoie : Ain, Savoie (+ frontières Suisse et Italie)
|
||||||
|
|
||||||
|
-- Bourgogne-Franche-Comté
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '10,45,52,58,77,89' WHERE code = '21'; -- Côte-d'Or : Aube, Loiret, Haute-Marne, Nièvre, Seine-et-Marne, Yonne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '39,68,70,73,90' WHERE code = '25'; -- Doubs : Jura, Haut-Rhin, Haute-Saône, Savoie, Territoire de Belfort (+ frontière Suisse)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,25,69,70,71,73' WHERE code = '39'; -- Jura : Ain, Doubs, Rhône, Haute-Saône, Saône-et-Loire, Savoie
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '03,18,21,45,71,89' WHERE code = '58'; -- Nièvre : Allier, Cher, Côte-d'Or, Loiret, Saône-et-Loire, Yonne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '21,25,39,52,88' WHERE code = '70'; -- Haute-Saône : Côte-d'Or, Doubs, Jura, Haute-Marne, Vosges
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '01,03,21,39,58,69' WHERE code = '71'; -- Saône-et-Loire : Ain, Allier, Côte-d'Or, Jura, Nièvre, Rhône
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '10,21,45,58,77' WHERE code = '89'; -- Yonne : Aube, Côte-d'Or, Loiret, Nièvre, Seine-et-Marne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '25,68,70' WHERE code = '90'; -- Territoire de Belfort : Doubs, Haut-Rhin, Haute-Saône
|
||||||
|
|
||||||
|
-- Bretagne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '29,35,56' WHERE code = '22'; -- Côtes-d'Armor
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,56' WHERE code = '29'; -- Finistère
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,44,49,50,53,56' WHERE code = '35'; -- Ille-et-Vilaine
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '22,29,35,44' WHERE code = '56'; -- Morbihan
|
||||||
|
|
||||||
|
-- Centre-Val de Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '03,23,36,41,58' WHERE code = '18'; -- Cher
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '27,37,41,45,61,72,78,91' WHERE code = '28'; -- Eure-et-Loir
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '18,23,37,41,86,87' WHERE code = '36'; -- Indre
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '36,41,49,72,86' WHERE code = '37'; -- Indre-et-Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '18,28,36,37,45,72' WHERE code = '41'; -- Loir-et-Cher
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '18,21,28,41,58,77,89,91' WHERE code = '45'; -- Loiret
|
||||||
|
|
||||||
|
-- Corse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '2B' WHERE code = '2A'; -- Corse-du-Sud : Haute-Corse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '2A' WHERE code = '2B'; -- Haute-Corse : Corse-du-Sud
|
||||||
|
|
||||||
|
-- Grand Est
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,51,55' WHERE code = '08'; -- Ardennes (+ frontière Belgique)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '21,51,52,77,89' WHERE code = '10'; -- Aube
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,08,10,52,77' WHERE code = '51'; -- Marne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '10,21,51,55,70,88' WHERE code = '52'; -- Haute-Marne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '55,57,88' WHERE code = '54'; -- Meurthe-et-Moselle
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '08,52,54,57' WHERE code = '55'; -- Meuse (+ frontière Belgique)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '54,55,67' WHERE code = '57'; -- Moselle (+ frontières Luxembourg et Allemagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '25,57,68,88,90' WHERE code = '67'; -- Bas-Rhin (+ frontière Allemagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '67,70,88,90' WHERE code = '68'; -- Haut-Rhin (+ frontières Allemagne et Suisse)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '52,54,67,68,70' WHERE code = '88'; -- Vosges
|
||||||
|
|
||||||
|
-- Hauts-de-France
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '08,51,59,60,77,80' WHERE code = '02'; -- Aisne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,62,80' WHERE code = '59'; -- Nord (+ frontière Belgique)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,27,76,77,80,95' WHERE code = '60'; -- Oise
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '59,80' WHERE code = '62'; -- Pas-de-Calais (+ frontière Belgique)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,27,59,60,62,76' WHERE code = '80'; -- Somme
|
||||||
|
|
||||||
|
-- Île-de-France
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '92,93,94' WHERE code = '75'; -- Paris
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '02,10,45,51,60,89,91,93,94,95' WHERE code = '77'; -- Seine-et-Marne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '27,28,91,92,95' WHERE code = '78'; -- Yvelines
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '28,45,77,78,92,94' WHERE code = '91'; -- Essonne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '75,78,91,93,94,95' WHERE code = '92'; -- Hauts-de-Seine
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '75,77,92,94,95' WHERE code = '93'; -- Seine-Saint-Denis
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '75,77,91,92,93' WHERE code = '94'; -- Val-de-Marne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '27,60,77,78,92,93' WHERE code = '95'; -- Val-d'Oise
|
||||||
|
|
||||||
|
-- Normandie
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '27,50,53,61,72' WHERE code = '14'; -- Calvados
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '14,28,60,61,72,76,78,95' WHERE code = '27'; -- Eure
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '14,35,53,61' WHERE code = '50'; -- Manche
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '14,27,28,35,41,50,53,72' WHERE code = '61'; -- Orne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '27,60,80' WHERE code = '76'; -- Seine-Maritime
|
||||||
|
|
||||||
|
-- Nouvelle-Aquitaine
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '17,24,33,87' WHERE code = '16'; -- Charente
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '16,33,79,85' WHERE code = '17'; -- Charente-Maritime
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '15,23,24,46,87' WHERE code = '19'; -- Corrèze
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '18,19,36,86,87' WHERE code = '23'; -- Creuse
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '16,19,33,46,47,87' WHERE code = '24'; -- Dordogne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '16,17,24,40,47' WHERE code = '33'; -- Gironde
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '32,33,47,64,65' WHERE code = '40'; -- Landes
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '24,32,40,46,82' WHERE code = '47'; -- Lot-et-Garonne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '40,65' WHERE code = '64'; -- Pyrénées-Atlantiques (+ frontière Espagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '17,49,85,86' WHERE code = '79'; -- Deux-Sèvres
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '16,23,36,37,79' WHERE code = '86'; -- Vienne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '16,19,23,24,36,86' WHERE code = '87'; -- Haute-Vienne
|
||||||
|
|
||||||
|
-- Occitanie
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '11,31,66' WHERE code = '09'; -- Ariège : Aude, Haute-Garonne, Pyrénées-Orientales (+ frontières Espagne et Andorre)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '09,31,66' WHERE code = '11'; -- Aude
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '15,30,34,46,48,81,82' WHERE code = '12'; -- Aveyron
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '07,12,34,48,84' WHERE code = '30'; -- Gard
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '09,32,65,82' WHERE code = '31'; -- Haute-Garonne : Ariège, Gers, Hautes-Pyrénées, Tarn-et-Garonne (+ frontière Espagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '31,40,47,65,82' WHERE code = '32'; -- Gers
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '11,12,30' WHERE code = '34'; -- Hérault
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '12,15,19,24,47,81,82' WHERE code = '46'; -- Lot
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '07,12,15,30,43' WHERE code = '48'; -- Lozère
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '31,32,40,64' WHERE code = '65'; -- Hautes-Pyrénées (+ frontière Espagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '09,11' WHERE code = '66'; -- Pyrénées-Orientales (+ frontière Espagne)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '12,34,46,82' WHERE code = '81'; -- Tarn
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '12,31,32,46,47,81' WHERE code = '82'; -- Tarn-et-Garonne
|
||||||
|
|
||||||
|
-- Pays de la Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '35,49,56,85' WHERE code = '44'; -- Loire-Atlantique
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '35,37,44,53,72,79,85,86' WHERE code = '49'; -- Maine-et-Loire
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '14,35,49,50,61,72' WHERE code = '53'; -- Mayenne
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '14,27,28,37,41,49,53,61' WHERE code = '72'; -- Sarthe
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '17,44,49,79' WHERE code = '85'; -- Vendée
|
||||||
|
|
||||||
|
-- Provence-Alpes-Côte d'Azur
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '05,06,26,83,84' WHERE code = '04'; -- Alpes-de-Haute-Provence
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,26,38,73' WHERE code = '05'; -- Hautes-Alpes (+ frontière Italie)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,83' WHERE code = '06'; -- Alpes-Maritimes (+ frontières Italie et Monaco)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '30,83,84' WHERE code = '13'; -- Bouches-du-Rhône
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,06,13,84' WHERE code = '83'; -- Var
|
||||||
|
UPDATE x_departements SET dept_limitrophes = '04,07,13,26,30,83' WHERE code = '84'; -- Vaucluse
|
||||||
|
|
||||||
|
-- Départements et régions d'outre-mer (pas de limitrophes terrestres)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = NULL WHERE code = '971'; -- Guadeloupe
|
||||||
|
UPDATE x_departements SET dept_limitrophes = NULL WHERE code = '972'; -- Martinique
|
||||||
|
UPDATE x_departements SET dept_limitrophes = NULL WHERE code = '973'; -- Guyane (frontières Brésil et Suriname)
|
||||||
|
UPDATE x_departements SET dept_limitrophes = NULL WHERE code = '974'; -- La Réunion
|
||||||
|
UPDATE x_departements SET dept_limitrophes = NULL WHERE code = '976'; -- Mayotte
|
||||||
0
api/scripts/README.md
Normal file → Executable file
0
api/scripts/README.md
Normal file → Executable file
33
api/scripts/check_geometry_validity.sql
Normal file
33
api/scripts/check_geometry_validity.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
-- Script de diagnostic pour vérifier les problèmes de géométrie dans x_departements_contours
|
||||||
|
|
||||||
|
-- 1. Vérifier les contours NULL
|
||||||
|
SELECT 'Contours NULL:' as diagnostic;
|
||||||
|
SELECT code_dept, nom_dept
|
||||||
|
FROM x_departements_contours
|
||||||
|
WHERE contour IS NULL;
|
||||||
|
|
||||||
|
-- 2. Vérifier les types de géométrie et si elles sont vides
|
||||||
|
SELECT 'Types de géométrie:' as diagnostic;
|
||||||
|
SELECT
|
||||||
|
code_dept,
|
||||||
|
nom_dept,
|
||||||
|
ST_GeometryType(contour) as geometry_type,
|
||||||
|
ST_IsEmpty(contour) as is_empty
|
||||||
|
FROM x_departements_contours
|
||||||
|
WHERE contour IS NOT NULL;
|
||||||
|
|
||||||
|
-- 3. Statistiques générales
|
||||||
|
SELECT 'Statistiques:' as diagnostic;
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total,
|
||||||
|
SUM(CASE WHEN contour IS NULL THEN 1 ELSE 0 END) as contours_null,
|
||||||
|
SUM(CASE WHEN contour IS NOT NULL THEN 1 ELSE 0 END) as contours_non_null
|
||||||
|
FROM x_departements_contours;
|
||||||
|
|
||||||
|
-- 4. Lister spécifiquement les DOM-TOM et Corse
|
||||||
|
SELECT 'DOM-TOM et Corse:' as diagnostic;
|
||||||
|
SELECT code_dept, nom_dept,
|
||||||
|
CASE WHEN contour IS NULL THEN 'NULL' ELSE 'OK' END as contour_status
|
||||||
|
FROM x_departements_contours
|
||||||
|
WHERE code_dept IN ('20', '2A', '2B', '971', '972', '973', '974', '975', '976')
|
||||||
|
ORDER BY code_dept;
|
||||||
0
api/scripts/config.php
Normal file → Executable file
0
api/scripts/config.php
Normal file → Executable file
32
api/scripts/create_addresses_users.sql
Normal file
32
api/scripts/create_addresses_users.sql
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
-- Script de création des utilisateurs pour la base de données des adresses
|
||||||
|
-- À exécuter sur chaque serveur MariaDB (dva-maria, rca-maria, pra-maria)
|
||||||
|
|
||||||
|
-- Créer l'utilisateur avec accès depuis l'IP du container API correspondant
|
||||||
|
-- IMPORTANT: Remplacer 'API_CONTAINER_IP' par l'IP réelle du container API
|
||||||
|
|
||||||
|
-- Pour l'environnement DEV (dva-maria)
|
||||||
|
-- Si l'API est dans le container dva-api avec l'IP 13.23.33.45 par exemple :
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.45' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.45';
|
||||||
|
|
||||||
|
-- Pour l'environnement RECETTE (rca-maria)
|
||||||
|
-- Si l'API est dans le container rca-api avec l'IP 13.23.33.35 par exemple :
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.35' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.35';
|
||||||
|
|
||||||
|
-- Pour l'environnement PROD (pra-maria)
|
||||||
|
-- Si l'API est dans le container pra-api avec l'IP 13.23.33.25 par exemple :
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.25' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.25';
|
||||||
|
|
||||||
|
-- Alternative : Créer un utilisateur accessible depuis tout le sous-réseau
|
||||||
|
-- ATTENTION : Moins sécurisé, à utiliser uniquement si les containers sont dans un réseau privé isolé
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.%' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.%';
|
||||||
|
|
||||||
|
-- Appliquer les privilèges
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
|
-- Vérifier la création
|
||||||
|
SELECT user, host FROM mysql.user WHERE user = 'adresses_user';
|
||||||
|
SHOW GRANTS FOR 'adresses_user'@'13.23.33.%';
|
||||||
41
api/scripts/create_addresses_users_by_env.sql
Normal file
41
api/scripts/create_addresses_users_by_env.sql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
-- Script de création des utilisateurs pour la base de données des adresses
|
||||||
|
-- Avec segmentation par environnement basée sur les plages d'IPs
|
||||||
|
|
||||||
|
-- ===================================
|
||||||
|
-- DÉVELOPPEMENT (dva-maria)
|
||||||
|
-- IPs autorisées : 13.23.33.40-49
|
||||||
|
-- ===================================
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.4%' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.4%';
|
||||||
|
|
||||||
|
-- Aussi créer un accès localhost pour les tests directs
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'localhost' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'localhost';
|
||||||
|
|
||||||
|
-- ===================================
|
||||||
|
-- RECETTE (rca-maria)
|
||||||
|
-- IPs autorisées : 13.23.33.30-39
|
||||||
|
-- ===================================
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.3%' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.3%';
|
||||||
|
|
||||||
|
-- Aussi créer un accès localhost pour les tests directs
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'localhost' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'localhost';
|
||||||
|
|
||||||
|
-- ===================================
|
||||||
|
-- PRODUCTION (pra-maria)
|
||||||
|
-- IPs autorisées : 13.23.33.20-29
|
||||||
|
-- ===================================
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.2%' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.2%';
|
||||||
|
|
||||||
|
-- Aussi créer un accès localhost pour les tests directs
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'localhost' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'localhost';
|
||||||
|
|
||||||
|
-- Appliquer les privilèges
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
|
-- Vérifier la création
|
||||||
|
SELECT user, host FROM mysql.user WHERE user = 'adresses_user' ORDER BY host;
|
||||||
0
api/scripts/cron/sync_databases.php
Normal file → Executable file
0
api/scripts/cron/sync_databases.php
Normal file → Executable file
37
api/scripts/fix_geometry_for_spatial_index.sql
Normal file
37
api/scripts/fix_geometry_for_spatial_index.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
-- Script pour diagnostiquer et corriger les problèmes d'index spatial
|
||||||
|
|
||||||
|
-- 1. Vérifier s'il y a des géométries vides
|
||||||
|
SELECT 'Géométries vides:' as diagnostic;
|
||||||
|
SELECT code, libelle
|
||||||
|
FROM x_departements
|
||||||
|
WHERE contour IS NOT NULL AND ST_IsEmpty(contour) = 1;
|
||||||
|
|
||||||
|
-- 2. Essayer de créer un index spatial sur une copie de la table pour tester
|
||||||
|
CREATE TABLE x_departements_test LIKE x_departements;
|
||||||
|
|
||||||
|
-- 3. Copier uniquement les départements métropolitains avec contours
|
||||||
|
INSERT INTO x_departements_test
|
||||||
|
SELECT * FROM x_departements
|
||||||
|
WHERE contour IS NOT NULL
|
||||||
|
AND code NOT IN ('20', '971', '972', '973', '974', '975', '976');
|
||||||
|
|
||||||
|
-- 4. Tenter de créer l'index spatial sur la table de test
|
||||||
|
ALTER TABLE x_departements_test ADD SPATIAL INDEX idx_contour_test (contour);
|
||||||
|
|
||||||
|
-- Si ça fonctionne, le problème vient des départements spécifiques
|
||||||
|
-- Si ça ne fonctionne pas, il y a un problème avec les données géométriques
|
||||||
|
|
||||||
|
-- 5. Alternative : recréer les géométries à partir du texte WKT
|
||||||
|
-- Cela peut corriger certains problèmes de format
|
||||||
|
UPDATE x_departements d
|
||||||
|
INNER JOIN x_departements_contours dc ON d.code = dc.code_dept
|
||||||
|
SET d.contour = ST_GeomFromText(ST_AsText(dc.contour))
|
||||||
|
WHERE dc.contour IS NOT NULL
|
||||||
|
AND d.code IN (SELECT code FROM x_departements WHERE contour IS NOT NULL LIMIT 1);
|
||||||
|
|
||||||
|
-- 6. Nettoyer
|
||||||
|
DROP TABLE IF EXISTS x_departements_test;
|
||||||
|
|
||||||
|
-- Note : Pour l'instant, l'index normal créé avec contour(32) permettra
|
||||||
|
-- le fonctionnement de l'API, même si les performances seront moindres
|
||||||
|
-- qu'avec un vrai index spatial.
|
||||||
0
api/scripts/geosector.sql
Normal file → Executable file
0
api/scripts/geosector.sql
Normal file → Executable file
0
api/scripts/geosector_app.sql
Normal file → Executable file
0
api/scripts/geosector_app.sql
Normal file → Executable file
263
api/scripts/import_departements_from_file.php
Normal file
263
api/scripts/import_departements_from_file.php
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script d'import des contours départementaux depuis un fichier GeoJSON local
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DepartementContoursFileImporter {
|
||||||
|
private PDO $db;
|
||||||
|
private array $log = [];
|
||||||
|
|
||||||
|
public function __construct(PDO $db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la table existe
|
||||||
|
*/
|
||||||
|
public function tableExists(): bool {
|
||||||
|
try {
|
||||||
|
$sql = "SHOW TABLES LIKE 'x_departements_contours'";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
return $stmt->rowCount() > 0;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la table est vide
|
||||||
|
*/
|
||||||
|
private function isTableEmpty(): bool {
|
||||||
|
try {
|
||||||
|
$sql = "SELECT COUNT(*) as count FROM x_departements_contours";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result['count'] == 0;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importe les départements depuis un fichier GeoJSON
|
||||||
|
*/
|
||||||
|
public function importFromFile(string $filePath): array {
|
||||||
|
$this->log[] = "Début de l'import depuis le fichier : $filePath";
|
||||||
|
$this->log[] = "";
|
||||||
|
|
||||||
|
// Vérifier que le fichier existe
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
$this->log[] = "✗ Fichier non trouvé : $filePath";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que la table existe
|
||||||
|
if (!$this->tableExists()) {
|
||||||
|
$this->log[] = "✗ La table x_departements_contours n'existe pas";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que la table est vide
|
||||||
|
if (!$this->isTableEmpty()) {
|
||||||
|
$this->log[] = "✗ La table x_departements_contours contient déjà des données";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire le fichier GeoJSON
|
||||||
|
$this->log[] = "Lecture du fichier GeoJSON...";
|
||||||
|
$jsonContent = file_get_contents($filePath);
|
||||||
|
|
||||||
|
if ($jsonContent === false) {
|
||||||
|
$this->log[] = "✗ Impossible de lire le fichier";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser le JSON
|
||||||
|
$geojson = json_decode($jsonContent, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$this->log[] = "✗ Erreur JSON : " . json_last_error_msg();
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($geojson['features']) || !is_array($geojson['features'])) {
|
||||||
|
$this->log[] = "✗ Format GeoJSON invalide : pas de features";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log[] = "✓ Fichier chargé : " . count($geojson['features']) . " départements trouvés";
|
||||||
|
$this->log[] = "";
|
||||||
|
|
||||||
|
// Préparer la requête d'insertion
|
||||||
|
$sql = "INSERT INTO x_departements_contours
|
||||||
|
(code_dept, nom_dept, contour, bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng)
|
||||||
|
VALUES
|
||||||
|
(:code, :nom, ST_GeomFromText(:polygon, 4326), :min_lat, :max_lat, :min_lng, :max_lng)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$success = 0;
|
||||||
|
$errors = 0;
|
||||||
|
|
||||||
|
// Démarrer une transaction
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($geojson['features'] as $feature) {
|
||||||
|
// Extraire les informations
|
||||||
|
$code = $feature['properties']['code'] ?? null;
|
||||||
|
$nom = $feature['properties']['nom'] ?? null;
|
||||||
|
$geometry = $feature['geometry'] ?? null;
|
||||||
|
|
||||||
|
if (!$code || !$nom || !$geometry) {
|
||||||
|
$this->log[] = "✗ Données manquantes pour un département";
|
||||||
|
$errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir la géométrie en WKT
|
||||||
|
$wktData = $this->geometryToWkt($geometry);
|
||||||
|
|
||||||
|
if (!$wktData) {
|
||||||
|
$this->log[] = "✗ Conversion échouée pour $code ($nom)";
|
||||||
|
$errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt->execute([
|
||||||
|
'code' => $code,
|
||||||
|
'nom' => $nom,
|
||||||
|
'polygon' => $wktData['wkt'],
|
||||||
|
'min_lat' => $wktData['bbox']['min_lat'],
|
||||||
|
'max_lat' => $wktData['bbox']['max_lat'],
|
||||||
|
'min_lng' => $wktData['bbox']['min_lng'],
|
||||||
|
'max_lng' => $wktData['bbox']['max_lng']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log[] = "✓ $code - $nom importé";
|
||||||
|
$success++;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log[] = "✗ Erreur SQL pour $code ($nom) : " . $e->getMessage();
|
||||||
|
$errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider ou annuler la transaction
|
||||||
|
if ($success > 0) {
|
||||||
|
$this->db->commit();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✓ Transaction validée";
|
||||||
|
} else {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✗ Transaction annulée (aucun import réussi)";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✗ Erreur fatale : " . $e->getMessage();
|
||||||
|
$this->log[] = "✗ Transaction annulée";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "Import terminé : $success réussis, $errors erreurs";
|
||||||
|
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit une géométrie GeoJSON en WKT
|
||||||
|
*/
|
||||||
|
private function geometryToWkt(array $geometry): ?array {
|
||||||
|
$type = $geometry['type'] ?? null;
|
||||||
|
$coordinates = $geometry['coordinates'] ?? null;
|
||||||
|
|
||||||
|
if (!$type || !$coordinates) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wkt = null;
|
||||||
|
$allPoints = [];
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'Polygon':
|
||||||
|
// Un seul polygone
|
||||||
|
$ring = $coordinates[0]; // Anneau extérieur
|
||||||
|
$points = [];
|
||||||
|
foreach ($ring as $point) {
|
||||||
|
$points[] = $point[0] . ' ' . $point[1];
|
||||||
|
$allPoints[] = $point;
|
||||||
|
}
|
||||||
|
$wkt = 'POLYGON((' . implode(',', $points) . '))';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MultiPolygon':
|
||||||
|
// Plusieurs polygones
|
||||||
|
$polygons = [];
|
||||||
|
foreach ($coordinates as $polygon) {
|
||||||
|
$ring = $polygon[0]; // Anneau extérieur du polygone
|
||||||
|
$points = [];
|
||||||
|
foreach ($ring as $point) {
|
||||||
|
$points[] = $point[0] . ' ' . $point[1];
|
||||||
|
$allPoints[] = $point;
|
||||||
|
}
|
||||||
|
$polygons[] = '((' . implode(',', $points) . '))';
|
||||||
|
}
|
||||||
|
$wkt = 'MULTIPOLYGON(' . implode(',', $polygons) . ')';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$wkt || empty($allPoints)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculer la bounding box
|
||||||
|
$lats = array_map(function($p) { return $p[1]; }, $allPoints);
|
||||||
|
$lngs = array_map(function($p) { return $p[0]; }, $allPoints);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'wkt' => $wkt,
|
||||||
|
'bbox' => [
|
||||||
|
'min_lat' => min($lats),
|
||||||
|
'max_lat' => max($lats),
|
||||||
|
'min_lng' => min($lngs),
|
||||||
|
'max_lng' => max($lngs)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si le script est exécuté directement
|
||||||
|
if (php_sapi_name() === 'cli' && basename(__FILE__) === basename($_SERVER['PHP_SELF'] ?? __FILE__)) {
|
||||||
|
require_once __DIR__ . '/../src/Config/AppConfig.php';
|
||||||
|
require_once __DIR__ . '/../src/Core/Database.php';
|
||||||
|
|
||||||
|
// Chemin vers le fichier GeoJSON
|
||||||
|
$filePath = __DIR__ . '/../docs/contour-des-departements.geojson';
|
||||||
|
|
||||||
|
// Vérifier les arguments
|
||||||
|
if ($argc > 1) {
|
||||||
|
$filePath = $argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Import des contours départementaux depuis un fichier\n";
|
||||||
|
echo "==================================================\n\n";
|
||||||
|
echo "Fichier : $filePath\n\n";
|
||||||
|
|
||||||
|
$appConfig = AppConfig::getInstance();
|
||||||
|
Database::init($appConfig->getDatabaseConfig());
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$importer = new DepartementContoursFileImporter($db);
|
||||||
|
$log = $importer->importFromFile($filePath);
|
||||||
|
|
||||||
|
foreach ($log as $line) {
|
||||||
|
echo $line . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
175
api/scripts/import_department_boundaries.php
Normal file
175
api/scripts/import_department_boundaries.php
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script d'import des contours des départements français
|
||||||
|
*
|
||||||
|
* Les données peuvent provenir de :
|
||||||
|
* - IGN Admin Express : https://geoservices.ign.fr/adminexpress
|
||||||
|
* - data.gouv.fr : https://www.data.gouv.fr/fr/datasets/contours-des-departements-francais-issus-d-openstreetmap/
|
||||||
|
* - OpenStreetMap via Overpass API
|
||||||
|
*
|
||||||
|
* Format attendu : GeoJSON ou Shapefile converti en SQL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/Config/AppConfig.php';
|
||||||
|
require_once __DIR__ . '/../src/Core/Database.php';
|
||||||
|
|
||||||
|
echo "Import des contours des départements\n";
|
||||||
|
echo "===================================\n\n";
|
||||||
|
|
||||||
|
// Initialiser la base de données
|
||||||
|
$appConfig = AppConfig::getInstance();
|
||||||
|
Database::init($appConfig->getDatabaseConfig());
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Exemple de données pour quelques départements bretons
|
||||||
|
// En production, ces données viendraient d'un fichier GeoJSON ou d'une API
|
||||||
|
$departements = [
|
||||||
|
[
|
||||||
|
'code' => '22',
|
||||||
|
'nom' => 'Côtes-d\'Armor',
|
||||||
|
// Contour simplifié - en réalité il faudrait des centaines de points
|
||||||
|
'points' => [
|
||||||
|
[-3.6546, 48.9012], [-3.3856, 48.8756], [-3.1234, 48.8234],
|
||||||
|
[-2.7856, 48.7845], [-2.4567, 48.7234], [-2.1234, 48.6456],
|
||||||
|
[-2.0123, 48.5234], [-2.0456, 48.3456], [-2.1567, 48.1234],
|
||||||
|
[-2.3456, 48.0567], [-2.6789, 48.0789], [-3.0123, 48.1234],
|
||||||
|
[-3.3456, 48.2345], [-3.5678, 48.4567], [-3.6234, 48.6789],
|
||||||
|
[-3.6546, 48.9012] // Fermer le polygone
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '29',
|
||||||
|
'nom' => 'Finistère',
|
||||||
|
'points' => [
|
||||||
|
[-5.1423, 48.7523], [-4.8234, 48.6845], [-4.5123, 48.6234],
|
||||||
|
[-4.2345, 48.5678], [-3.9876, 48.4567], [-3.7234, 48.3456],
|
||||||
|
[-3.4567, 48.2345], [-3.3876, 48.0123], [-3.4234, 47.8234],
|
||||||
|
[-3.5678, 47.6456], [-3.8765, 47.6789], [-4.2345, 47.7234],
|
||||||
|
[-4.5678, 47.8234], [-4.8765, 47.9345], [-5.0876, 48.1234],
|
||||||
|
[-5.1234, 48.3456], [-5.1345, 48.5678], [-5.1423, 48.7523]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '35',
|
||||||
|
'nom' => 'Ille-et-Vilaine',
|
||||||
|
'points' => [
|
||||||
|
[-2.0123, 48.6456], [-1.7234, 48.5678], [-1.4567, 48.4567],
|
||||||
|
[-1.2345, 48.3456], [-1.0234, 48.2345], [-1.0567, 48.0123],
|
||||||
|
[-1.1234, 47.8234], [-1.2567, 47.6456], [-1.4678, 47.6789],
|
||||||
|
[-1.7234, 47.7234], [-1.9876, 47.8234], [-2.1234, 47.9345],
|
||||||
|
[-2.2345, 48.1234], [-2.1567, 48.3456], [-2.0678, 48.5234],
|
||||||
|
[-2.0123, 48.6456]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '56',
|
||||||
|
'nom' => 'Morbihan',
|
||||||
|
'points' => [
|
||||||
|
[-3.4567, 48.2345], [-3.2345, 48.1234], [-2.9876, 48.0123],
|
||||||
|
[-2.7234, 47.9234], [-2.4567, 47.8345], [-2.2345, 47.7456],
|
||||||
|
[-2.1234, 47.6234], [-2.2567, 47.4567], [-2.4678, 47.3456],
|
||||||
|
[-2.7234, 47.3789], [-3.0123, 47.4234], [-3.2876, 47.5234],
|
||||||
|
[-3.5234, 47.6345], [-3.6789, 47.7456], [-3.7234, 47.9234],
|
||||||
|
[-3.6567, 48.0789], [-3.4567, 48.2345]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
// Préparer la requête d'insertion
|
||||||
|
$sql = "INSERT INTO departements_contours
|
||||||
|
(code_dept, nom_dept, contour, bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng)
|
||||||
|
VALUES
|
||||||
|
(:code, :nom, ST_GeomFromText(:polygon, 4326), :min_lat, :max_lat, :min_lng, :max_lng)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
nom_dept = VALUES(nom_dept),
|
||||||
|
contour = VALUES(contour),
|
||||||
|
bbox_min_lat = VALUES(bbox_min_lat),
|
||||||
|
bbox_max_lat = VALUES(bbox_max_lat),
|
||||||
|
bbox_min_lng = VALUES(bbox_min_lng),
|
||||||
|
bbox_max_lng = VALUES(bbox_max_lng),
|
||||||
|
updated_at = CURRENT_TIMESTAMP";
|
||||||
|
|
||||||
|
$stmt = $db->prepare($sql);
|
||||||
|
|
||||||
|
foreach ($departements as $dept) {
|
||||||
|
echo "Import du département {$dept['code']} - {$dept['nom']}...\n";
|
||||||
|
|
||||||
|
// Créer le polygone WKT
|
||||||
|
$polygonPoints = [];
|
||||||
|
$lats = [];
|
||||||
|
$lngs = [];
|
||||||
|
|
||||||
|
foreach ($dept['points'] as $point) {
|
||||||
|
$lng = $point[0];
|
||||||
|
$lat = $point[1];
|
||||||
|
$polygonPoints[] = "$lng $lat";
|
||||||
|
$lats[] = $lat;
|
||||||
|
$lngs[] = $lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
$polygon = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||||
|
|
||||||
|
// Calculer la bounding box
|
||||||
|
$minLat = min($lats);
|
||||||
|
$maxLat = max($lats);
|
||||||
|
$minLng = min($lngs);
|
||||||
|
$maxLng = max($lngs);
|
||||||
|
|
||||||
|
// Exécuter l'insertion
|
||||||
|
$stmt->execute([
|
||||||
|
'code' => $dept['code'],
|
||||||
|
'nom' => $dept['nom'],
|
||||||
|
'polygon' => $polygon,
|
||||||
|
'min_lat' => $minLat,
|
||||||
|
'max_lat' => $maxLat,
|
||||||
|
'min_lng' => $minLng,
|
||||||
|
'max_lng' => $maxLng
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "✓ Département {$dept['code']} importé\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
echo "\n✓ Import terminé avec succès!\n\n";
|
||||||
|
|
||||||
|
// Vérifier les données importées
|
||||||
|
$checkSql = "SELECT code_dept, nom_dept,
|
||||||
|
ST_Area(contour) as area,
|
||||||
|
ST_NumPoints(ST_ExteriorRing(contour)) as num_points
|
||||||
|
FROM x_departements_contours
|
||||||
|
ORDER BY code_dept";
|
||||||
|
|
||||||
|
$result = $db->query($checkSql);
|
||||||
|
echo "Départements importés:\n";
|
||||||
|
echo "---------------------\n";
|
||||||
|
|
||||||
|
foreach ($result as $row) {
|
||||||
|
echo sprintf("- %s (%s) : %d points, aire: %.4f\n",
|
||||||
|
$row['nom_dept'],
|
||||||
|
$row['code_dept'],
|
||||||
|
$row['num_points'],
|
||||||
|
$row['area']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
echo "✗ Erreur lors de l'import : " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
|
echo "Note importante:\n";
|
||||||
|
echo "---------------\n";
|
||||||
|
echo "Ce script utilise des données simplifiées pour l'exemple.\n";
|
||||||
|
echo "Pour un usage en production, vous devez :\n";
|
||||||
|
echo "1. Télécharger les vrais contours depuis l'IGN ou data.gouv.fr\n";
|
||||||
|
echo "2. Les convertir en format GeoJSON ou SQL\n";
|
||||||
|
echo "3. Adapter ce script pour lire ces fichiers\n";
|
||||||
|
echo "\n";
|
||||||
|
echo "Sources recommandées:\n";
|
||||||
|
echo "- IGN Admin Express: https://geoservices.ign.fr/adminexpress\n";
|
||||||
|
echo "- data.gouv.fr: https://www.data.gouv.fr/fr/datasets/contours-des-departements-francais-issus-d-openstreetmap/\n";
|
||||||
318
api/scripts/init_departements_contours.php
Normal file
318
api/scripts/init_departements_contours.php
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script d'initialisation des contours des départements français
|
||||||
|
* À exécuter une seule fois lors de la connexion de l'admin d6soft
|
||||||
|
*
|
||||||
|
* Utilise l'API geo.api.gouv.fr pour récupérer les contours GeoJSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DepartementContoursInitializer {
|
||||||
|
private PDO $db;
|
||||||
|
private array $log = [];
|
||||||
|
|
||||||
|
// Liste complète des départements français (métropole + DOM-TOM)
|
||||||
|
private array $departements = [
|
||||||
|
// Métropole
|
||||||
|
'01' => 'Ain', '02' => 'Aisne', '03' => 'Allier', '04' => 'Alpes-de-Haute-Provence',
|
||||||
|
'05' => 'Hautes-Alpes', '06' => 'Alpes-Maritimes', '07' => 'Ardèche', '08' => 'Ardennes',
|
||||||
|
'09' => 'Ariège', '10' => 'Aube', '11' => 'Aude', '12' => 'Aveyron',
|
||||||
|
'13' => 'Bouches-du-Rhône', '14' => 'Calvados', '15' => 'Cantal', '16' => 'Charente',
|
||||||
|
'17' => 'Charente-Maritime', '18' => 'Cher', '19' => 'Corrèze', '2A' => 'Corse-du-Sud',
|
||||||
|
'2B' => 'Haute-Corse', '21' => 'Côte-d\'Or', '22' => 'Côtes-d\'Armor', '23' => 'Creuse',
|
||||||
|
'24' => 'Dordogne', '25' => 'Doubs', '26' => 'Drôme', '27' => 'Eure',
|
||||||
|
'28' => 'Eure-et-Loir', '29' => 'Finistère', '30' => 'Gard', '31' => 'Haute-Garonne',
|
||||||
|
'32' => 'Gers', '33' => 'Gironde', '34' => 'Hérault', '35' => 'Ille-et-Vilaine',
|
||||||
|
'36' => 'Indre', '37' => 'Indre-et-Loire', '38' => 'Isère', '39' => 'Jura',
|
||||||
|
'40' => 'Landes', '41' => 'Loir-et-Cher', '42' => 'Loire', '43' => 'Haute-Loire',
|
||||||
|
'44' => 'Loire-Atlantique', '45' => 'Loiret', '46' => 'Lot', '47' => 'Lot-et-Garonne',
|
||||||
|
'48' => 'Lozère', '49' => 'Maine-et-Loire', '50' => 'Manche', '51' => 'Marne',
|
||||||
|
'52' => 'Haute-Marne', '53' => 'Mayenne', '54' => 'Meurthe-et-Moselle', '55' => 'Meuse',
|
||||||
|
'56' => 'Morbihan', '57' => 'Moselle', '58' => 'Nièvre', '59' => 'Nord',
|
||||||
|
'60' => 'Oise', '61' => 'Orne', '62' => 'Pas-de-Calais', '63' => 'Puy-de-Dôme',
|
||||||
|
'64' => 'Pyrénées-Atlantiques', '65' => 'Hautes-Pyrénées', '66' => 'Pyrénées-Orientales', '67' => 'Bas-Rhin',
|
||||||
|
'68' => 'Haut-Rhin', '69' => 'Rhône', '70' => 'Haute-Saône', '71' => 'Saône-et-Loire',
|
||||||
|
'72' => 'Sarthe', '73' => 'Savoie', '74' => 'Haute-Savoie', '75' => 'Paris',
|
||||||
|
'76' => 'Seine-Maritime', '77' => 'Seine-et-Marne', '78' => 'Yvelines', '79' => 'Deux-Sèvres',
|
||||||
|
'80' => 'Somme', '81' => 'Tarn', '82' => 'Tarn-et-Garonne', '83' => 'Var',
|
||||||
|
'84' => 'Vaucluse', '85' => 'Vendée', '86' => 'Vienne', '87' => 'Haute-Vienne',
|
||||||
|
'88' => 'Vosges', '89' => 'Yonne', '90' => 'Territoire de Belfort', '91' => 'Essonne',
|
||||||
|
'92' => 'Hauts-de-Seine', '93' => 'Seine-Saint-Denis', '94' => 'Val-de-Marne', '95' => 'Val-d\'Oise',
|
||||||
|
// DOM-TOM
|
||||||
|
'971' => 'Guadeloupe', '972' => 'Martinique', '973' => 'Guyane', '974' => 'La Réunion',
|
||||||
|
'975' => 'Saint-Pierre-et-Miquelon', '976' => 'Mayotte', '977' => 'Saint-Barthélemy',
|
||||||
|
'978' => 'Saint-Martin', '984' => 'Terres australes et antarctiques françaises',
|
||||||
|
'986' => 'Wallis-et-Futuna', '987' => 'Polynésie française', '988' => 'Nouvelle-Calédonie'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(PDO $db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la table existe
|
||||||
|
*/
|
||||||
|
public function tableExists(): bool {
|
||||||
|
try {
|
||||||
|
$sql = "SHOW TABLES LIKE 'x_departements_contours'";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
return $stmt->rowCount() > 0;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la table est vide
|
||||||
|
*/
|
||||||
|
private function isTableEmpty(): bool {
|
||||||
|
try {
|
||||||
|
$sql = "SELECT COUNT(*) as count FROM x_departements_contours";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
return $result['count'] == 0;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère le contour d'un département depuis l'API geo.api.gouv.fr
|
||||||
|
*/
|
||||||
|
private function fetchDepartementContour(string $code, string $nom): ?array {
|
||||||
|
// URL de l'API pour récupérer le contour du département en GeoJSON
|
||||||
|
$url = "https://geo.api.gouv.fr/departements/{$code}?geometry=contour";
|
||||||
|
|
||||||
|
$context = stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'timeout' => 30,
|
||||||
|
'header' => "User-Agent: Geosector/1.0\r\n"
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = @file_get_contents($url, false, $context);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$this->log[] = "✗ Erreur API pour département $code ($nom)";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
|
||||||
|
// L'API peut retourner le contour dans 'contour' ou 'geometry'
|
||||||
|
if (isset($data['contour']) && isset($data['contour']['coordinates'])) {
|
||||||
|
return $data['contour'];
|
||||||
|
} elseif (isset($data['geometry']) && isset($data['geometry']['coordinates'])) {
|
||||||
|
return $data['geometry'];
|
||||||
|
} else {
|
||||||
|
$this->log[] = "✗ Pas de contour pour département $code ($nom)";
|
||||||
|
// Debug : afficher les clés disponibles
|
||||||
|
if (is_array($data)) {
|
||||||
|
$this->log[] = " Clés disponibles : " . implode(', ', array_keys($data));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit les coordonnées GeoJSON en WKT Polygon pour MySQL
|
||||||
|
*/
|
||||||
|
private function geoJsonToWkt(array $coordinates): ?array {
|
||||||
|
if (empty($coordinates) || !is_array($coordinates[0])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeoJSON peut avoir plusieurs niveaux d'imbrication selon le type
|
||||||
|
// Pour un Polygon simple
|
||||||
|
if (isset($coordinates[0][0]) && is_numeric($coordinates[0][0])) {
|
||||||
|
$ring = $coordinates;
|
||||||
|
}
|
||||||
|
// Pour un MultiPolygon, prendre le premier polygone
|
||||||
|
elseif (isset($coordinates[0][0][0])) {
|
||||||
|
$ring = $coordinates[0][0];
|
||||||
|
}
|
||||||
|
// Pour un Polygon standard
|
||||||
|
else {
|
||||||
|
$ring = $coordinates[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$points = [];
|
||||||
|
$lats = [];
|
||||||
|
$lngs = [];
|
||||||
|
|
||||||
|
foreach ($ring as $point) {
|
||||||
|
if (count($point) >= 2) {
|
||||||
|
$lng = $point[0];
|
||||||
|
$lat = $point[1];
|
||||||
|
$points[] = "$lng $lat";
|
||||||
|
$lats[] = $lat;
|
||||||
|
$lngs[] = $lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($points) < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le polygone si nécessaire
|
||||||
|
if ($points[0] !== $points[count($points) - 1]) {
|
||||||
|
$points[] = $points[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'wkt' => 'POLYGON((' . implode(',', $points) . '))',
|
||||||
|
'bbox' => [
|
||||||
|
'min_lat' => min($lats),
|
||||||
|
'max_lat' => max($lats),
|
||||||
|
'min_lng' => min($lngs),
|
||||||
|
'max_lng' => max($lngs)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importe tous les départements
|
||||||
|
*/
|
||||||
|
public function importAll(): array {
|
||||||
|
$this->log[] = "Début de l'import des contours départementaux";
|
||||||
|
$this->log[] = "Source : API geo.api.gouv.fr";
|
||||||
|
$this->log[] = "";
|
||||||
|
|
||||||
|
// Vérifier que la table est vide avant d'importer
|
||||||
|
if (!$this->isTableEmpty()) {
|
||||||
|
$this->log[] = "✗ La table x_departements_contours contient déjà des données";
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Préparer la requête d'insertion
|
||||||
|
$sql = "INSERT INTO x_departements_contours
|
||||||
|
(code_dept, nom_dept, contour, bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng)
|
||||||
|
VALUES
|
||||||
|
(:code, :nom, ST_GeomFromText(:polygon, 4326), :min_lat, :max_lat, :min_lng, :max_lng)";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
|
||||||
|
$success = 0;
|
||||||
|
$errors = 0;
|
||||||
|
|
||||||
|
// Démarrer une transaction
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($this->departements as $code => $nom) {
|
||||||
|
// Petite pause pour ne pas surcharger l'API
|
||||||
|
usleep(100000); // 100ms
|
||||||
|
|
||||||
|
$contour = $this->fetchDepartementContour($code, $nom);
|
||||||
|
|
||||||
|
if (!$contour) {
|
||||||
|
$errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wktData = $this->geoJsonToWkt($contour['coordinates']);
|
||||||
|
|
||||||
|
if (!$wktData) {
|
||||||
|
$this->log[] = "✗ Conversion échouée pour $code ($nom)";
|
||||||
|
$errors++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt->execute([
|
||||||
|
'code' => $code,
|
||||||
|
'nom' => $nom,
|
||||||
|
'polygon' => $wktData['wkt'],
|
||||||
|
'min_lat' => $wktData['bbox']['min_lat'],
|
||||||
|
'max_lat' => $wktData['bbox']['max_lat'],
|
||||||
|
'min_lng' => $wktData['bbox']['min_lng'],
|
||||||
|
'max_lng' => $wktData['bbox']['max_lng']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->log[] = "✓ $code - $nom importé";
|
||||||
|
$success++;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log[] = "✗ Erreur SQL pour $code ($nom) : " . $e->getMessage();
|
||||||
|
$errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si tout s'est bien passé, valider la transaction
|
||||||
|
if ($success > 0) {
|
||||||
|
$this->db->commit();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✓ Transaction validée";
|
||||||
|
} else {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✗ Transaction annulée (aucun import réussi)";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "✗ Erreur fatale : " . $e->getMessage();
|
||||||
|
$this->log[] = "✗ Transaction annulée";
|
||||||
|
$errors = count($this->departements);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log[] = "";
|
||||||
|
$this->log[] = "Import terminé : $success réussis, $errors erreurs";
|
||||||
|
|
||||||
|
return $this->log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exécute l'initialisation si nécessaire
|
||||||
|
*/
|
||||||
|
public static function runIfNeeded(PDO $db, string $username): ?array {
|
||||||
|
// Vérifier que c'est bien l'admin d6soft
|
||||||
|
if ($username !== 'd6soft') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$initializer = new self($db);
|
||||||
|
|
||||||
|
// Vérifier si la table existe
|
||||||
|
if (!$initializer->tableExists()) {
|
||||||
|
return ["✗ La table x_departements_contours n'existe pas. Veuillez la créer avec le script SQL fourni."];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si elle est vide
|
||||||
|
if (!$initializer->isTableEmpty()) {
|
||||||
|
return null; // Table déjà remplie, rien à faire
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si le fichier local existe
|
||||||
|
$localFile = __DIR__ . '/../docs/contour-des-departements.geojson';
|
||||||
|
if (file_exists($localFile)) {
|
||||||
|
// Utiliser le fichier local
|
||||||
|
require_once __DIR__ . '/import_departements_from_file.php';
|
||||||
|
$fileImporter = new \DepartementContoursFileImporter($db);
|
||||||
|
return $fileImporter->importFromFile($localFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, utiliser l'API (qui ne fonctionne pas bien actuellement)
|
||||||
|
return $initializer->importAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si le script est exécuté directement (pour tests)
|
||||||
|
if (php_sapi_name() === 'cli' && basename(__FILE__) === basename($_SERVER['PHP_SELF'] ?? __FILE__)) {
|
||||||
|
require_once __DIR__ . '/../src/Config/AppConfig.php';
|
||||||
|
require_once __DIR__ . '/../src/Core/Database.php';
|
||||||
|
|
||||||
|
$appConfig = AppConfig::getInstance();
|
||||||
|
Database::init($appConfig->getDatabaseConfig());
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
echo "Test d'import des contours départementaux\n";
|
||||||
|
echo "========================================\n\n";
|
||||||
|
|
||||||
|
$initializer = new DepartementContoursInitializer($db);
|
||||||
|
$log = $initializer->importAll();
|
||||||
|
|
||||||
|
foreach ($log as $line) {
|
||||||
|
echo $line . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
0
api/scripts/php/MigrationConfig.php
Normal file → Executable file
0
api/scripts/php/MigrationConfig.php
Normal file → Executable file
0
api/scripts/php/migrate.php
Normal file → Executable file
0
api/scripts/php/migrate.php
Normal file → Executable file
0
api/scripts/php/migrate_entites.php
Normal file → Executable file
0
api/scripts/php/migrate_entites.php
Normal file → Executable file
0
api/scripts/php/migrate_medias.php
Normal file → Executable file
0
api/scripts/php/migrate_medias.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_pass.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_pass.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_pass_histo.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_pass_histo.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_sectors.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_sectors.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_users.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_users.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_users_sectors.php
Normal file → Executable file
0
api/scripts/php/migrate_ope_users_sectors.php
Normal file → Executable file
0
api/scripts/php/migrate_operations.php
Normal file → Executable file
0
api/scripts/php/migrate_operations.php
Normal file → Executable file
0
api/scripts/php/migrate_sectors_adresses.php
Normal file → Executable file
0
api/scripts/php/migrate_sectors_adresses.php
Normal file → Executable file
0
api/scripts/php/migrate_users.php
Normal file → Executable file
0
api/scripts/php/migrate_users.php
Normal file → Executable file
0
api/scripts/php/migrate_x_departements.php
Normal file → Executable file
0
api/scripts/php/migrate_x_departements.php
Normal file → Executable file
0
api/scripts/php/migrate_x_devises.php
Normal file → Executable file
0
api/scripts/php/migrate_x_devises.php
Normal file → Executable file
0
api/scripts/php/migrate_x_entites_types.php
Normal file → Executable file
0
api/scripts/php/migrate_x_entites_types.php
Normal file → Executable file
0
api/scripts/php/migrate_x_pays.php
Normal file → Executable file
0
api/scripts/php/migrate_x_pays.php
Normal file → Executable file
0
api/scripts/php/migrate_x_regions.php
Normal file → Executable file
0
api/scripts/php/migrate_x_regions.php
Normal file → Executable file
0
api/scripts/php/migrate_x_types_passages.php
Normal file → Executable file
0
api/scripts/php/migrate_x_types_passages.php
Normal file → Executable file
0
api/scripts/php/migrate_x_types_reglements.php
Normal file → Executable file
0
api/scripts/php/migrate_x_types_reglements.php
Normal file → Executable file
0
api/scripts/php/migrate_x_users_roles.php
Normal file → Executable file
0
api/scripts/php/migrate_x_users_roles.php
Normal file → Executable file
0
api/scripts/php/migrate_x_villes.php
Normal file → Executable file
0
api/scripts/php/migrate_x_villes.php
Normal file → Executable file
106
api/scripts/setup_addresses_access.sh
Normal file
106
api/scripts/setup_addresses_access.sh
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script pour configurer l'accès à la base de données des adresses
|
||||||
|
# sur chaque environnement Incus
|
||||||
|
|
||||||
|
echo "Configuration de l'accès à la base de données des adresses"
|
||||||
|
echo "=========================================================="
|
||||||
|
|
||||||
|
# Fonction pour créer l'utilisateur sur un container
|
||||||
|
create_user_on_container() {
|
||||||
|
local container=$1
|
||||||
|
local api_ip=$2
|
||||||
|
local maria_ip=$3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Configuration pour $container..."
|
||||||
|
echo "Container MariaDB IP: $maria_ip"
|
||||||
|
echo "Container API IP: $api_ip"
|
||||||
|
|
||||||
|
# Se connecter au container MariaDB et créer l'utilisateur
|
||||||
|
incus exec $container -- mysql -u root -p -e "
|
||||||
|
-- Créer l'utilisateur pour l'accès depuis l'API
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'$api_ip' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'$api_ip';
|
||||||
|
|
||||||
|
-- Créer aussi un utilisateur localhost pour les tests directs
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'localhost' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'localhost';
|
||||||
|
|
||||||
|
-- Optionnel : créer un utilisateur pour tout le sous-réseau
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'13.23.33.%' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'13.23.33.%';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
|
-- Vérifier
|
||||||
|
SELECT user, host FROM mysql.user WHERE user = 'adresses_user';
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "✓ Utilisateur créé sur $container"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fonction pour tester la connexion
|
||||||
|
test_connection() {
|
||||||
|
local api_container=$1
|
||||||
|
local maria_ip=$2
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test de connexion depuis $api_container vers $maria_ip..."
|
||||||
|
|
||||||
|
incus exec $api_container -- mysql -h $maria_ip -u adresses_user -p'd66,AdrGeo.User' -e "
|
||||||
|
SELECT DATABASE();
|
||||||
|
SHOW TABLES FROM adresses LIMIT 5;
|
||||||
|
"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ Connexion réussie!"
|
||||||
|
else
|
||||||
|
echo "✗ Échec de la connexion"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration pour chaque environnement
|
||||||
|
echo ""
|
||||||
|
echo "1. DÉVELOPPEMENT (DVA)"
|
||||||
|
read -p "IP du container dva-api [par défaut: 13.23.33.45]: " DVA_API_IP
|
||||||
|
DVA_API_IP=${DVA_API_IP:-13.23.33.45}
|
||||||
|
create_user_on_container "dva-maria" "$DVA_API_IP" "13.23.33.46"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "2. RECETTE (RCA)"
|
||||||
|
read -p "IP du container rca-api [par défaut: 13.23.33.35]: " RCA_API_IP
|
||||||
|
RCA_API_IP=${RCA_API_IP:-13.23.33.35}
|
||||||
|
create_user_on_container "rca-maria" "$RCA_API_IP" "13.23.33.36"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "3. PRODUCTION (PRA)"
|
||||||
|
read -p "IP du container pra-api [par défaut: 13.23.33.25]: " PRA_API_IP
|
||||||
|
PRA_API_IP=${PRA_API_IP:-13.23.33.25}
|
||||||
|
create_user_on_container "pra-maria" "$PRA_API_IP" "13.23.33.26"
|
||||||
|
|
||||||
|
# Tests de connexion
|
||||||
|
echo ""
|
||||||
|
echo "=========================================================="
|
||||||
|
echo "Tests de connexion"
|
||||||
|
echo "=========================================================="
|
||||||
|
|
||||||
|
read -p "Voulez-vous tester les connexions? (o/n): " test_choice
|
||||||
|
if [ "$test_choice" = "o" ]; then
|
||||||
|
test_connection "dva-api" "13.23.33.46"
|
||||||
|
test_connection "rca-api" "13.23.33.36"
|
||||||
|
test_connection "pra-api" "13.23.33.26"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Configuration terminée!"
|
||||||
|
echo ""
|
||||||
|
echo "Notes importantes:"
|
||||||
|
echo "- Les utilisateurs ont été créés avec accès SELECT uniquement sur la base 'adresses'"
|
||||||
|
echo "- Trois types d'accès ont été configurés:"
|
||||||
|
echo " 1. Depuis l'IP spécifique de chaque container API"
|
||||||
|
echo " 2. Depuis localhost (pour les tests directs)"
|
||||||
|
echo " 3. Depuis tout le sous-réseau 13.23.33.% (optionnel, moins sécurisé)"
|
||||||
|
echo ""
|
||||||
|
echo "Pour tester manuellement depuis un container API:"
|
||||||
|
echo "incus exec [container-api] -- mysql -h [ip-maria] -u adresses_user -p'd66,AdrGeo.User' adresses"
|
||||||
136
api/scripts/setup_addresses_access_by_env.sh
Normal file
136
api/scripts/setup_addresses_access_by_env.sh
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script pour configurer l'accès à la base de données des adresses
|
||||||
|
# avec segmentation par environnement basée sur les plages d'IPs
|
||||||
|
|
||||||
|
echo "Configuration de l'accès à la base de données des adresses"
|
||||||
|
echo "=========================================================="
|
||||||
|
echo ""
|
||||||
|
echo "Architecture des IPs par environnement :"
|
||||||
|
echo "- DÉVELOPPEMENT : 13.23.33.40-49 (13.23.33.4%)"
|
||||||
|
echo "- RECETTE : 13.23.33.30-39 (13.23.33.3%)"
|
||||||
|
echo "- PRODUCTION : 13.23.33.20-29 (13.23.33.2%)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Fonction pour créer l'utilisateur sur un container
|
||||||
|
create_user_on_container() {
|
||||||
|
local container=$1
|
||||||
|
local ip_pattern=$2
|
||||||
|
local env_name=$3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Configuration pour $env_name ($container)..."
|
||||||
|
echo "Pattern IP autorisé : $ip_pattern"
|
||||||
|
|
||||||
|
# Se connecter au container MariaDB et créer l'utilisateur
|
||||||
|
incus exec $container -- mysql -u root -p -e "
|
||||||
|
-- Créer l'utilisateur pour l'accès depuis la plage IP de l'environnement
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'$ip_pattern' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'$ip_pattern';
|
||||||
|
|
||||||
|
-- Créer aussi un utilisateur localhost pour les tests directs
|
||||||
|
CREATE USER IF NOT EXISTS 'adresses_user'@'localhost' IDENTIFIED BY 'd66,AdrGeo.User';
|
||||||
|
GRANT SELECT ON adresses.* TO 'adresses_user'@'localhost';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
|
-- Vérifier
|
||||||
|
SELECT user, host FROM mysql.user WHERE user = 'adresses_user' ORDER BY host;
|
||||||
|
"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ Utilisateur créé sur $container"
|
||||||
|
else
|
||||||
|
echo "✗ Erreur lors de la création sur $container"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fonction pour tester la connexion
|
||||||
|
test_connection() {
|
||||||
|
local api_container=$1
|
||||||
|
local maria_ip=$2
|
||||||
|
local env_name=$3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test de connexion $env_name : $api_container -> $maria_ip..."
|
||||||
|
|
||||||
|
incus exec $api_container -- mysql -h $maria_ip -u adresses_user -p'd66,AdrGeo.User' -e "
|
||||||
|
SELECT CONCAT('Connexion réussie depuis ', @@hostname, ' vers ', '$maria_ip') as Status;
|
||||||
|
SELECT DATABASE();
|
||||||
|
SHOW TABLES FROM adresses LIMIT 3;
|
||||||
|
" 2>/dev/null
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ Connexion réussie!"
|
||||||
|
else
|
||||||
|
echo "✗ Échec de la connexion"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Menu de sélection
|
||||||
|
echo ""
|
||||||
|
echo "Que voulez-vous configurer ?"
|
||||||
|
echo "1. Environnement DÉVELOPPEMENT uniquement (dva-maria)"
|
||||||
|
echo "2. Environnement RECETTE uniquement (rca-maria)"
|
||||||
|
echo "3. Environnement PRODUCTION uniquement (pra-maria)"
|
||||||
|
echo "4. Tous les environnements"
|
||||||
|
echo ""
|
||||||
|
read -p "Votre choix (1-4): " choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
create_user_on_container "dva-maria" "13.23.33.4%" "DÉVELOPPEMENT"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
create_user_on_container "rca-maria" "13.23.33.3%" "RECETTE"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
create_user_on_container "pra-maria" "13.23.33.2%" "PRODUCTION"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
create_user_on_container "dva-maria" "13.23.33.4%" "DÉVELOPPEMENT"
|
||||||
|
create_user_on_container "rca-maria" "13.23.33.3%" "RECETTE"
|
||||||
|
create_user_on_container "pra-maria" "13.23.33.2%" "PRODUCTION"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Choix invalide"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Tests de connexion
|
||||||
|
echo ""
|
||||||
|
echo "=========================================================="
|
||||||
|
echo "Tests de connexion"
|
||||||
|
echo "=========================================================="
|
||||||
|
|
||||||
|
read -p "Voulez-vous tester les connexions? (o/n): " test_choice
|
||||||
|
if [ "$test_choice" = "o" ]; then
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
test_connection "dva-api" "13.23.33.46" "DÉVELOPPEMENT"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
test_connection "rca-api" "13.23.33.36" "RECETTE"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
test_connection "pra-api" "13.23.33.26" "PRODUCTION"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
test_connection "dva-api" "13.23.33.46" "DÉVELOPPEMENT"
|
||||||
|
test_connection "rca-api" "13.23.33.36" "RECETTE"
|
||||||
|
test_connection "pra-api" "13.23.33.26" "PRODUCTION"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Configuration terminée!"
|
||||||
|
echo ""
|
||||||
|
echo "Récapitulatif de la sécurité mise en place :"
|
||||||
|
echo "- Chaque environnement a sa propre plage d'IPs autorisée"
|
||||||
|
echo "- DÉVELOPPEMENT : seuls les containers en 13.23.33.4x peuvent accéder à dva-maria"
|
||||||
|
echo "- RECETTE : seuls les containers en 13.23.33.3x peuvent accéder à rca-maria"
|
||||||
|
echo "- PRODUCTION : seuls les containers en 13.23.33.2x peuvent accéder à pra-maria"
|
||||||
|
echo ""
|
||||||
|
echo "Cela empêche un container de DEV d'accéder aux données de PROD par exemple."
|
||||||
27
api/src/Config/AppConfig.php
Normal file → Executable file
27
api/src/Config/AppConfig.php
Normal file → Executable file
@@ -88,6 +88,12 @@ class AppConfig {
|
|||||||
'username' => 'geo_app_user_prod',
|
'username' => 'geo_app_user_prod',
|
||||||
'password' => 'QO:96-SrHJ6k7-df*?k{4W6m',
|
'password' => 'QO:96-SrHJ6k7-df*?k{4W6m',
|
||||||
],
|
],
|
||||||
|
'addresses_database' => [
|
||||||
|
'host' => '13.23.33.26',
|
||||||
|
'name' => 'adresses',
|
||||||
|
'username' => 'adr_geo_user',
|
||||||
|
'password' => 'd66,AdrGeo.User',
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Configuration RECETTE
|
// Configuration RECETTE
|
||||||
@@ -99,6 +105,12 @@ class AppConfig {
|
|||||||
'username' => 'geo_app_user_rec',
|
'username' => 'geo_app_user_rec',
|
||||||
'password' => 'QO:96df*?k-dS3KiO-{4W6m',
|
'password' => 'QO:96df*?k-dS3KiO-{4W6m',
|
||||||
],
|
],
|
||||||
|
'addresses_database' => [
|
||||||
|
'host' => '13.23.33.36',
|
||||||
|
'name' => 'adresses',
|
||||||
|
'username' => 'adr_geo_user',
|
||||||
|
'password' => 'd66,AdrGeoRec.User',
|
||||||
|
],
|
||||||
// Vous pouvez remplacer d'autres paramètres spécifiques à l'environnement de recette ici
|
// Vous pouvez remplacer d'autres paramètres spécifiques à l'environnement de recette ici
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -111,6 +123,12 @@ class AppConfig {
|
|||||||
'username' => 'geo_app_user_dev',
|
'username' => 'geo_app_user_dev',
|
||||||
'password' => '34GOz-X5gJu-oH@Fa3$#Z',
|
'password' => '34GOz-X5gJu-oH@Fa3$#Z',
|
||||||
],
|
],
|
||||||
|
'addresses_database' => [
|
||||||
|
'host' => '13.23.33.46',
|
||||||
|
'name' => 'adresses',
|
||||||
|
'username' => 'adr_geo_user',
|
||||||
|
'password' => 'd66,AdrGeoDev.User',
|
||||||
|
],
|
||||||
// Vous pouvez activer des fonctionnalités de débogage en développement
|
// Vous pouvez activer des fonctionnalités de débogage en développement
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
// Configurez des endpoints de test pour Stripe, etc.
|
// Configurez des endpoints de test pour Stripe, etc.
|
||||||
@@ -234,6 +252,15 @@ class AppConfig {
|
|||||||
return $this->getCurrentConfig()['database'];
|
return $this->getCurrentConfig()['database'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la configuration de la base de données des adresses
|
||||||
|
*
|
||||||
|
* @return array Configuration de la base de données des adresses
|
||||||
|
*/
|
||||||
|
public function getAddressesDatabaseConfig(): array {
|
||||||
|
return $this->getCurrentConfig()['addresses_database'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne la clé de chiffrement
|
* Retourne la clé de chiffrement
|
||||||
*
|
*
|
||||||
|
|||||||
0
api/src/Controllers/EntiteController.php
Normal file → Executable file
0
api/src/Controllers/EntiteController.php
Normal file → Executable file
0
api/src/Controllers/FileController.php
Normal file → Executable file
0
api/src/Controllers/FileController.php
Normal file → Executable file
0
api/src/Controllers/LogController.php
Normal file → Executable file
0
api/src/Controllers/LogController.php
Normal file → Executable file
30
api/src/Controllers/LoginController.php
Normal file → Executable file
30
api/src/Controllers/LoginController.php
Normal file → Executable file
@@ -49,8 +49,8 @@ class LoginController {
|
|||||||
|
|
||||||
// Récupérer le type d'utilisateur
|
// Récupérer le type d'utilisateur
|
||||||
// admin accessible uniquement aux fk_role>1
|
// admin accessible uniquement aux fk_role>1
|
||||||
// sinon tout user peut se connecter à l'interface utilisateur
|
// user accessible uniquement aux fk_role=1
|
||||||
$roleCondition = ($interface === 'user') ? '' : 'AND fk_role>1';
|
$roleCondition = ($interface === 'user') ? 'AND fk_role=1' : 'AND fk_role>1';
|
||||||
|
|
||||||
// Log pour le debug
|
// Log pour le debug
|
||||||
LogService::log('Tentative de connexion GeoSector', [
|
LogService::log('Tentative de connexion GeoSector', [
|
||||||
@@ -159,6 +159,32 @@ class LoginController {
|
|||||||
'fk_entite' => $user['fk_entite'] ?? '0',
|
'fk_entite' => $user['fk_entite'] ?? '0',
|
||||||
];
|
];
|
||||||
Session::login($sessionData);
|
Session::login($sessionData);
|
||||||
|
|
||||||
|
// Vérifier et exécuter l'initialisation des contours départementaux pour d6soft
|
||||||
|
if ($username === 'd6soft') {
|
||||||
|
require_once __DIR__ . '/../../scripts/init_departements_contours.php';
|
||||||
|
$initLog = \DepartementContoursInitializer::runIfNeeded($this->db, $username);
|
||||||
|
|
||||||
|
if ($initLog !== null) {
|
||||||
|
// Logger l'initialisation
|
||||||
|
LogService::log('Initialisation des contours départementaux', [
|
||||||
|
'level' => 'info',
|
||||||
|
'username' => $username,
|
||||||
|
'log_count' => count($initLog)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Logger aussi les dernières lignes du log pour diagnostic
|
||||||
|
$lastLines = array_slice($initLog, -5);
|
||||||
|
foreach ($lastLines as $line) {
|
||||||
|
if (strpos($line, '✗') !== false || strpos($line, 'terminé') !== false) {
|
||||||
|
LogService::log('Import contours: ' . $line, [
|
||||||
|
'level' => 'info',
|
||||||
|
'username' => $username
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Préparation des données utilisateur pour la réponse (uniquement les champs du user)
|
// Préparation des données utilisateur pour la réponse (uniquement les champs du user)
|
||||||
$userData = [
|
$userData = [
|
||||||
|
|||||||
0
api/src/Controllers/OperationController.php
Normal file → Executable file
0
api/src/Controllers/OperationController.php
Normal file → Executable file
0
api/src/Controllers/PassageController.php
Normal file → Executable file
0
api/src/Controllers/PassageController.php
Normal file → Executable file
1307
api/src/Controllers/SectorController.php
Normal file
1307
api/src/Controllers/SectorController.php
Normal file
File diff suppressed because it is too large
Load Diff
162
api/src/Controllers/UserController.php
Normal file → Executable file
162
api/src/Controllers/UserController.php
Normal file → Executable file
@@ -550,28 +550,27 @@ class UserController {
|
|||||||
|
|
||||||
// ——— Gestion du transfert éventuel ———
|
// ——— Gestion du transfert éventuel ———
|
||||||
$transferTo = isset($_GET['transfer_to']) ? trim($_GET['transfer_to']) : null;
|
$transferTo = isset($_GET['transfer_to']) ? trim($_GET['transfer_to']) : null;
|
||||||
$operationId = isset($_GET['operation_id']) ? trim($_GET['operation_id']) : null;
|
|
||||||
|
|
||||||
if (($transferTo && !$operationId) || (!$transferTo && $operationId)) {
|
if ($transferTo) {
|
||||||
Response::json([
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => "Il faut fournir transfer_to ET operation_id ou aucun des deux"
|
|
||||||
], 400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($transferTo && $operationId) {
|
|
||||||
try {
|
try {
|
||||||
|
// Transférer TOUS les passages de l'utilisateur vers l'utilisateur désigné
|
||||||
$stmt3 = $this->db->prepare('
|
$stmt3 = $this->db->prepare('
|
||||||
UPDATE passages
|
UPDATE ope_pass
|
||||||
SET fk_user = :new_user_id
|
SET fk_user = :new_user_id
|
||||||
WHERE fk_user = :delete_user_id
|
WHERE fk_user = :delete_user_id
|
||||||
AND fk_operation = :operation_id
|
|
||||||
');
|
');
|
||||||
$stmt3->execute([
|
$stmt3->execute([
|
||||||
'new_user_id' => $transferTo,
|
'new_user_id' => $transferTo,
|
||||||
'delete_user_id' => $id,
|
'delete_user_id' => $id
|
||||||
'operation_id' => $operationId
|
]);
|
||||||
|
|
||||||
|
$transferredCount = $stmt3->rowCount();
|
||||||
|
|
||||||
|
LogService::log('Passages transférés avant suppression utilisateur', [
|
||||||
|
'level' => 'info',
|
||||||
|
'from_user' => $id,
|
||||||
|
'to_user' => $transferTo,
|
||||||
|
'passages_transferred' => $transferredCount
|
||||||
]);
|
]);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
Response::json([
|
Response::json([
|
||||||
@@ -589,7 +588,9 @@ class UserController {
|
|||||||
$stmtOpeUsers = $this->db->prepare('DELETE FROM ope_users WHERE fk_user = ?');
|
$stmtOpeUsers = $this->db->prepare('DELETE FROM ope_users WHERE fk_user = ?');
|
||||||
$stmtOpeUsers->execute([$id]);
|
$stmtOpeUsers->execute([$id]);
|
||||||
|
|
||||||
// Ici éventuellement : d'autres suppressions en cascade si besoin
|
// Supprimer les enregistrements dépendants dans ope_users_sectors
|
||||||
|
$stmtOpeUsersSectors = $this->db->prepare('DELETE FROM ope_users_sectors WHERE fk_user = ?');
|
||||||
|
$stmtOpeUsersSectors->execute([$id]);
|
||||||
|
|
||||||
$stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
|
$stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
|
||||||
$stmt->execute([$id]);
|
$stmt->execute([$id]);
|
||||||
@@ -606,7 +607,7 @@ class UserController {
|
|||||||
'level' => 'info',
|
'level' => 'info',
|
||||||
'deletedBy' => $currentUserId,
|
'deletedBy' => $currentUserId,
|
||||||
'userId' => $id,
|
'userId' => $id,
|
||||||
'passage_transfer' => $transferTo && $operationId ? "Vers utilisateur $transferTo pour operation $operationId" : 'Aucun'
|
'passage_transfer' => $transferTo ? "Tous les passages transférés vers utilisateur $transferTo" : 'Aucun transfert'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Response::json([
|
Response::json([
|
||||||
@@ -626,6 +627,135 @@ class UserController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resetPassword(string $id): void {
|
||||||
|
Session::requireAuth();
|
||||||
|
|
||||||
|
$currentUserId = Session::getUserId();
|
||||||
|
|
||||||
|
// Récupérer les infos de l'utilisateur courant
|
||||||
|
$stmt = $this->db->prepare('SELECT fk_role, fk_entite, chk_active FROM users WHERE id = ?');
|
||||||
|
$stmt->execute([$currentUserId]);
|
||||||
|
$currentUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$currentUser) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Utilisateur courant non trouvé'
|
||||||
|
], 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que l'utilisateur courant est actif
|
||||||
|
if ($currentUser['chk_active'] != 1) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Votre compte n\'est pas actif'
|
||||||
|
], 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userRole = (int)$currentUser['fk_role'];
|
||||||
|
$userEntite = $currentUser['fk_entite'];
|
||||||
|
|
||||||
|
// Récupérer l'utilisateur cible
|
||||||
|
$stmt = $this->db->prepare('
|
||||||
|
SELECT id, encrypted_email, encrypted_name, fk_entite, chk_active
|
||||||
|
FROM users
|
||||||
|
WHERE id = ?
|
||||||
|
');
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$targetUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$targetUser) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Utilisateur non trouvé'
|
||||||
|
], 404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que l'utilisateur est actif
|
||||||
|
if ($targetUser['chk_active'] != 1) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'L\'utilisateur n\'est pas actif'
|
||||||
|
], 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contrôle des droits selon le rôle
|
||||||
|
if ($userRole === 1) {
|
||||||
|
// Role 1 : peut uniquement réinitialiser son propre mot de passe
|
||||||
|
if ($currentUserId != $id) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Vous ne pouvez réinitialiser que votre propre mot de passe'
|
||||||
|
], 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} elseif ($userRole === 2) {
|
||||||
|
// Role 2 : peut réinitialiser les mots de passe de sa propre entité
|
||||||
|
if ($userEntite != $targetUser['fk_entite']) {
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Vous ne pouvez réinitialiser que les mots de passe des utilisateurs de votre entité'
|
||||||
|
], 403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Role > 2 : peut tout faire
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Déchiffrement des données
|
||||||
|
$email = ApiService::decryptSearchableData($targetUser['encrypted_email']);
|
||||||
|
$name = ApiService::decryptData($targetUser['encrypted_name']);
|
||||||
|
|
||||||
|
// Génération d'un nouveau mot de passe sécurisé
|
||||||
|
$newPassword = ApiService::generateSecurePassword();
|
||||||
|
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Mise à jour du mot de passe en base de données
|
||||||
|
$updateStmt = $this->db->prepare('
|
||||||
|
UPDATE users
|
||||||
|
SET user_pass_hash = :password,
|
||||||
|
updated_at = NOW(),
|
||||||
|
fk_user_modif = :modifier_id
|
||||||
|
WHERE id = :id
|
||||||
|
');
|
||||||
|
$updateStmt->execute([
|
||||||
|
'password' => $passwordHash,
|
||||||
|
'modifier_id' => $currentUserId,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Envoi de l'email avec le nouveau mot de passe
|
||||||
|
ApiService::sendEmail($email, $name, 'password_reset', ['password' => $newPassword]);
|
||||||
|
|
||||||
|
LogService::log('Mot de passe réinitialisé', [
|
||||||
|
'level' => 'info',
|
||||||
|
'resetBy' => $currentUserId,
|
||||||
|
'userId' => $id,
|
||||||
|
'email' => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
Response::json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Mot de passe réinitialisé avec succès. Un email a été envoyé à l\'utilisateur.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
LogService::log('Erreur lors de la réinitialisation du mot de passe', [
|
||||||
|
'level' => 'error',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'userId' => $id
|
||||||
|
]);
|
||||||
|
Response::json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Erreur serveur'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Méthodes auxiliaires
|
// Méthodes auxiliaires
|
||||||
private function validateUpdateData(array $data): ?string {
|
private function validateUpdateData(array $data): ?string {
|
||||||
// Validation de l'email
|
// Validation de l'email
|
||||||
|
|||||||
0
api/src/Controllers/VilleController.php
Normal file → Executable file
0
api/src/Controllers/VilleController.php
Normal file → Executable file
46
api/src/Core/AddressesDatabase.php
Normal file
46
api/src/Core/AddressesDatabase.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class AddressesDatabase {
|
||||||
|
private static ?PDO $instance = null;
|
||||||
|
private static array $config;
|
||||||
|
|
||||||
|
public static function init(array $config): void {
|
||||||
|
self::$config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance(): PDO {
|
||||||
|
if (self::$instance === null) {
|
||||||
|
try {
|
||||||
|
$dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",
|
||||||
|
self::$config['host'],
|
||||||
|
self::$config['name']
|
||||||
|
);
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
self::$instance = new PDO(
|
||||||
|
$dsn,
|
||||||
|
self::$config['username'],
|
||||||
|
self::$config['password'],
|
||||||
|
$options
|
||||||
|
);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("Addresses database connection failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ferme la connexion à la base de données des adresses
|
||||||
|
*/
|
||||||
|
public static function close(): void {
|
||||||
|
self::$instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
api/src/Core/Database.php
Normal file → Executable file
0
api/src/Core/Database.php
Normal file → Executable file
0
api/src/Core/Request.php
Normal file → Executable file
0
api/src/Core/Request.php
Normal file → Executable file
0
api/src/Core/Response.php
Normal file → Executable file
0
api/src/Core/Response.php
Normal file → Executable file
7
api/src/Core/Router.php
Normal file → Executable file
7
api/src/Core/Router.php
Normal file → Executable file
@@ -37,6 +37,7 @@ class Router {
|
|||||||
$this->post('users', ['UserController', 'createUser']);
|
$this->post('users', ['UserController', 'createUser']);
|
||||||
$this->put('users/:id', ['UserController', 'updateUser']);
|
$this->put('users/:id', ['UserController', 'updateUser']);
|
||||||
$this->delete('users/:id', ['UserController', 'deleteUser']);
|
$this->delete('users/:id', ['UserController', 'deleteUser']);
|
||||||
|
$this->post('users/:id/reset-password', ['UserController', 'resetPassword']);
|
||||||
$this->post('logout', ['LoginController', 'logout']);
|
$this->post('logout', ['LoginController', 'logout']);
|
||||||
|
|
||||||
// Routes entités
|
// Routes entités
|
||||||
@@ -80,6 +81,12 @@ class Router {
|
|||||||
$this->get('files/info/:id', ['FileController', 'getFileInfo']);
|
$this->get('files/info/:id', ['FileController', 'getFileInfo']);
|
||||||
$this->get('files/download/:id', ['FileController', 'download']);
|
$this->get('files/download/:id', ['FileController', 'download']);
|
||||||
$this->delete('files/:id', ['FileController', 'deleteFile']);
|
$this->delete('files/:id', ['FileController', 'deleteFile']);
|
||||||
|
|
||||||
|
// Routes secteurs
|
||||||
|
$this->get('sectors', ['SectorController', 'index']);
|
||||||
|
$this->post('sectors', ['SectorController', 'create']);
|
||||||
|
$this->put('sectors/:id', ['SectorController', 'update']);
|
||||||
|
$this->delete('sectors/:id', ['SectorController', 'delete']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void {
|
public function handle(): void {
|
||||||
|
|||||||
5
api/src/Core/Session.php
Normal file → Executable file
5
api/src/Core/Session.php
Normal file → Executable file
@@ -27,6 +27,7 @@ class Session {
|
|||||||
public static function login(array $userData): void {
|
public static function login(array $userData): void {
|
||||||
$_SESSION['user_id'] = $userData['id'];
|
$_SESSION['user_id'] = $userData['id'];
|
||||||
$_SESSION['user_email'] = $userData['email'] ?? '';
|
$_SESSION['user_email'] = $userData['email'] ?? '';
|
||||||
|
$_SESSION['entity_id'] = $userData['fk_entite'] ?? null;
|
||||||
$_SESSION['authenticated'] = true;
|
$_SESSION['authenticated'] = true;
|
||||||
$_SESSION['last_activity'] = time();
|
$_SESSION['last_activity'] = time();
|
||||||
|
|
||||||
@@ -51,6 +52,10 @@ class Session {
|
|||||||
return $_SESSION['user_email'] ?? null;
|
return $_SESSION['user_email'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getEntityId(): ?int {
|
||||||
|
return $_SESSION['entity_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function requireAuth(): void {
|
public static function requireAuth(): void {
|
||||||
if (!self::isAuthenticated()) {
|
if (!self::isAuthenticated()) {
|
||||||
// Log détaillé pour le debug
|
// Log détaillé pour le debug
|
||||||
|
|||||||
345
api/src/Services/AddressService.php
Normal file
345
api/src/Services/AddressService.php
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/LogService.php';
|
||||||
|
|
||||||
|
class AddressService {
|
||||||
|
private ?PDO $addressesDb = null;
|
||||||
|
private PDO $mainDb;
|
||||||
|
private LogService $logService;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->logService = new LogService();
|
||||||
|
try {
|
||||||
|
$this->addressesDb = AddressesDatabase::getInstance();
|
||||||
|
$this->logService->info('[AddressService] Connexion à la base d\'adresses réussie');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Si la connexion échoue, on continue sans la base d'adresses
|
||||||
|
$this->logService->error('[AddressService] Connexion à la base d\'adresses impossible', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
$this->addressesDb = null;
|
||||||
|
}
|
||||||
|
$this->mainDb = Database::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la connexion à la base d'adresses est active
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool {
|
||||||
|
return $this->addressesDb !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine le département de l'entité courante
|
||||||
|
*
|
||||||
|
* @param int|null $entityId ID de l'entité
|
||||||
|
* @return string|null Code département (ex: "22", "23")
|
||||||
|
*/
|
||||||
|
private function getDepartmentForEntity(?int $entityId = null): ?string {
|
||||||
|
if (!$entityId) {
|
||||||
|
$entityId = $_SESSION['entity_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$entityId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = "SELECT departement FROM entites WHERE id = :entity_id";
|
||||||
|
$stmt = $this->mainDb->prepare($query);
|
||||||
|
$stmt->execute(['entity_id' => $entityId]);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
|
||||||
|
return $result ? $result['departement'] : null;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les adresses contenues dans un polygone défini par des coordonnées
|
||||||
|
* Gère automatiquement les secteurs multi-départements
|
||||||
|
*
|
||||||
|
* @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
|
||||||
|
* @param int|null $entityId ID de l'entité (pour déterminer le département principal)
|
||||||
|
* @return array Array des adresses trouvées
|
||||||
|
*/
|
||||||
|
public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array {
|
||||||
|
// Si pas de connexion à la base d'adresses, retourner un tableau vide
|
||||||
|
if (!$this->addressesDb) {
|
||||||
|
$this->logService->error('[AddressService] Pas de connexion à la base d\'adresses externe', [
|
||||||
|
'entity_id' => $entityId
|
||||||
|
]);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logService->info('[AddressService] Début recherche adresses', [
|
||||||
|
'entity_id' => $entityId,
|
||||||
|
'nb_coordinates' => count($coordinates)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (count($coordinates) < 3) {
|
||||||
|
throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
|
||||||
|
}
|
||||||
|
|
||||||
|
// D'abord, déterminer tous les départements touchés par ce secteur
|
||||||
|
require_once __DIR__ . '/DepartmentBoundaryService.php';
|
||||||
|
$boundaryService = new \DepartmentBoundaryService();
|
||||||
|
$departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);
|
||||||
|
|
||||||
|
if (empty($departmentsTouched)) {
|
||||||
|
// Si aucun département n'est trouvé par analyse spatiale,
|
||||||
|
// chercher d'abord dans le département de l'entité et ses limitrophes
|
||||||
|
$entityDept = $this->getDepartmentForEntity($entityId);
|
||||||
|
$this->logService->info('[AddressService] Département de l\'entité', [
|
||||||
|
'departement' => $entityDept
|
||||||
|
]);
|
||||||
|
if (!$entityDept) {
|
||||||
|
$this->logService->error('[AddressService] Impossible de déterminer le département de l\'entité', [
|
||||||
|
'entity_id' => $entityId
|
||||||
|
]);
|
||||||
|
throw new RuntimeException("Impossible de déterminer le département");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtenir les départements prioritaires (entité + limitrophes)
|
||||||
|
$priorityDepts = $boundaryService->getPriorityDepartments($entityDept);
|
||||||
|
|
||||||
|
// Log pour debug
|
||||||
|
$this->logService->warning('[AddressService] Aucun département trouvé par analyse spatiale', [
|
||||||
|
'departements_prioritaires' => implode(', ', $priorityDepts)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Utiliser les départements prioritaires pour la recherche
|
||||||
|
$departmentsTouched = [];
|
||||||
|
foreach ($priorityDepts as $deptCode) {
|
||||||
|
$departmentsTouched[] = ['code_dept' => $deptCode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le polygone SQL à partir des coordonnées
|
||||||
|
$polygonPoints = [];
|
||||||
|
foreach ($coordinates as $coord) {
|
||||||
|
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||||
|
throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
|
||||||
|
}
|
||||||
|
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le polygone
|
||||||
|
$polygonPoints[] = $polygonPoints[0];
|
||||||
|
|
||||||
|
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||||
|
|
||||||
|
// Collecter les adresses de tous les départements touchés
|
||||||
|
$allAddresses = [];
|
||||||
|
|
||||||
|
foreach ($departmentsTouched as $dept) {
|
||||||
|
$deptCode = $dept['code_dept'];
|
||||||
|
$tableName = "cp" . $deptCode;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Requête pour récupérer les adresses dans le polygone pour ce département
|
||||||
|
$sql = "SELECT
|
||||||
|
id,
|
||||||
|
numero,
|
||||||
|
rue as voie,
|
||||||
|
cp as code_postal,
|
||||||
|
ville as commune,
|
||||||
|
gps_lat as latitude,
|
||||||
|
gps_lng as longitude,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
code_insee,
|
||||||
|
nom_ld,
|
||||||
|
ville_acheminement,
|
||||||
|
rue_afnor,
|
||||||
|
source,
|
||||||
|
certification,
|
||||||
|
:dept_code as departement
|
||||||
|
FROM `$tableName`
|
||||||
|
WHERE ST_Contains(
|
||||||
|
ST_GeomFromText(:polygon, 4326),
|
||||||
|
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
|
||||||
|
)
|
||||||
|
AND gps_lat != ''
|
||||||
|
AND gps_lng != ''";
|
||||||
|
|
||||||
|
$stmt = $this->addressesDb->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'polygon' => $polygonString,
|
||||||
|
'dept_code' => $deptCode
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addresses = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Ajouter les adresses à la collection globale
|
||||||
|
foreach ($addresses as $address) {
|
||||||
|
$allAddresses[] = $address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log pour debug
|
||||||
|
$this->logService->info('[AddressService] Recherche dans table', [
|
||||||
|
'table' => $tableName,
|
||||||
|
'departement' => $deptCode,
|
||||||
|
'nb_adresses' => count($addresses)
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Log l'erreur mais continue avec les autres départements
|
||||||
|
$this->logService->error('[AddressService] Erreur SQL', [
|
||||||
|
'table' => $tableName,
|
||||||
|
'departement' => $deptCode,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'code' => $e->getCode()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logService->info('[AddressService] Fin recherche adresses', [
|
||||||
|
'total_adresses' => count($allAddresses)
|
||||||
|
]);
|
||||||
|
return $allAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les adresses dans un rayon autour d'un point
|
||||||
|
*
|
||||||
|
* @param float $latitude Latitude du centre
|
||||||
|
* @param float $longitude Longitude du centre
|
||||||
|
* @param float $radiusMeters Rayon en mètres
|
||||||
|
* @param int|null $entityId ID de l'entité (pour déterminer le département)
|
||||||
|
* @return array Array des adresses trouvées
|
||||||
|
*/
|
||||||
|
public function getAddressesInRadius(float $latitude, float $longitude, float $radiusMeters, ?int $entityId = null): array {
|
||||||
|
// Déterminer le département
|
||||||
|
$dept = $this->getDepartmentForEntity($entityId);
|
||||||
|
if (!$dept) {
|
||||||
|
throw new RuntimeException("Impossible de déterminer le département de l'entité");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nom de la table selon le département
|
||||||
|
$tableName = "cp" . $dept;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Utiliser ST_Distance_Sphere pour calculer la distance en mètres
|
||||||
|
$sql = "SELECT
|
||||||
|
id,
|
||||||
|
numero,
|
||||||
|
rue as voie,
|
||||||
|
cp as code_postal,
|
||||||
|
ville as commune,
|
||||||
|
gps_lat as latitude,
|
||||||
|
gps_lng as longitude,
|
||||||
|
ST_Distance_Sphere(
|
||||||
|
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
|
||||||
|
ST_GeomFromText(:point, 4326)
|
||||||
|
) as distance
|
||||||
|
FROM `$tableName`
|
||||||
|
WHERE ST_Distance_Sphere(
|
||||||
|
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
|
||||||
|
ST_GeomFromText(:point, 4326)
|
||||||
|
) <= :radius
|
||||||
|
AND gps_lat != ''
|
||||||
|
AND gps_lng != ''
|
||||||
|
ORDER BY distance";
|
||||||
|
|
||||||
|
$point = "POINT($longitude $latitude)";
|
||||||
|
|
||||||
|
$stmt = $this->addressesDb->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'point' => $point,
|
||||||
|
'radius' => $radiusMeters
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new RuntimeException("Erreur lors de la récupération des adresses dans la table $tableName : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compte le nombre d'adresses dans un polygone
|
||||||
|
* Gère automatiquement les secteurs multi-départements
|
||||||
|
*
|
||||||
|
* @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
|
||||||
|
* @param int|null $entityId ID de l'entité (pour déterminer le département principal)
|
||||||
|
* @return int Nombre d'adresses
|
||||||
|
*/
|
||||||
|
public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int {
|
||||||
|
// Si pas de connexion à la base d'adresses, retourner 0
|
||||||
|
if (!$this->addressesDb) {
|
||||||
|
error_log("AddressService: Pas de connexion à la base d'adresses, retour de 0 adresses");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($coordinates) < 3) {
|
||||||
|
throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
|
||||||
|
}
|
||||||
|
|
||||||
|
// D'abord, déterminer tous les départements touchés par ce secteur
|
||||||
|
require_once __DIR__ . '/DepartmentBoundaryService.php';
|
||||||
|
$boundaryService = new \DepartmentBoundaryService();
|
||||||
|
$departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);
|
||||||
|
|
||||||
|
if (empty($departmentsTouched)) {
|
||||||
|
// Si aucun département n'est trouvé, utiliser le département de l'entité
|
||||||
|
$dept = $this->getDepartmentForEntity($entityId);
|
||||||
|
if (!$dept) {
|
||||||
|
throw new RuntimeException("Impossible de déterminer le département");
|
||||||
|
}
|
||||||
|
$departmentsTouched = [['code_dept' => $dept]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le polygone SQL à partir des coordonnées
|
||||||
|
$polygonPoints = [];
|
||||||
|
foreach ($coordinates as $coord) {
|
||||||
|
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||||
|
throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
|
||||||
|
}
|
||||||
|
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le polygone
|
||||||
|
$polygonPoints[] = $polygonPoints[0];
|
||||||
|
|
||||||
|
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||||
|
|
||||||
|
// Compter les adresses dans tous les départements touchés
|
||||||
|
$totalCount = 0;
|
||||||
|
|
||||||
|
foreach ($departmentsTouched as $dept) {
|
||||||
|
$deptCode = $dept['code_dept'];
|
||||||
|
$tableName = "cp" . $deptCode;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT COUNT(*) as count
|
||||||
|
FROM `$tableName`
|
||||||
|
WHERE ST_Contains(
|
||||||
|
ST_GeomFromText(:polygon, 4326),
|
||||||
|
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
|
||||||
|
)
|
||||||
|
AND gps_lat != ''
|
||||||
|
AND gps_lng != ''";
|
||||||
|
|
||||||
|
$stmt = $this->addressesDb->prepare($sql);
|
||||||
|
$stmt->execute(['polygon' => $polygonString]);
|
||||||
|
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
$deptCount = (int)$result['count'];
|
||||||
|
$totalCount += $deptCount;
|
||||||
|
|
||||||
|
// Log pour debug
|
||||||
|
error_log("Département $deptCode : $deptCount adresses comptées");
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Log l'erreur mais continue avec les autres départements
|
||||||
|
error_log("Erreur de comptage pour le département $deptCode : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $totalCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
api/src/Services/ApiService.php
Normal file → Executable file
0
api/src/Services/ApiService.php
Normal file → Executable file
0
api/src/Services/BackupEncryptionService.php
Normal file → Executable file
0
api/src/Services/BackupEncryptionService.php
Normal file → Executable file
249
api/src/Services/DepartmentBoundaryService.php
Normal file
249
api/src/Services/DepartmentBoundaryService.php
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class DepartmentBoundaryService {
|
||||||
|
private PDO $db;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = Database::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si un polygone (secteur) est entièrement contenu dans un département
|
||||||
|
*
|
||||||
|
* @param array $sectorCoordinates Coordonnées du secteur [[lat, lng], ...]
|
||||||
|
* @param string $departmentCode Code du département (22, 29, etc.)
|
||||||
|
* @return array ['is_contained' => bool, 'message' => string, 'intersecting_departments' => array]
|
||||||
|
*/
|
||||||
|
public function checkSectorInDepartment(array $sectorCoordinates, string $departmentCode): array {
|
||||||
|
if (count($sectorCoordinates) < 3) {
|
||||||
|
return [
|
||||||
|
'is_contained' => false,
|
||||||
|
'message' => 'Un secteur doit avoir au moins 3 points',
|
||||||
|
'intersecting_departments' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le polygone du secteur
|
||||||
|
$polygonPoints = [];
|
||||||
|
foreach ($sectorCoordinates as $coord) {
|
||||||
|
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||||
|
return [
|
||||||
|
'is_contained' => false,
|
||||||
|
'message' => 'Coordonnées invalides',
|
||||||
|
'intersecting_departments' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // longitude latitude
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fermer le polygone
|
||||||
|
$polygonPoints[] = $polygonPoints[0];
|
||||||
|
$sectorPolygon = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Vérifier si le secteur est entièrement dans le département cible
|
||||||
|
$sql = "SELECT
|
||||||
|
code_dept,
|
||||||
|
nom_dept,
|
||||||
|
ST_Contains(contour, ST_GeomFromText(:sector_polygon, 4326)) as is_contained,
|
||||||
|
ST_Intersects(contour, ST_GeomFromText(:sector_polygon, 4326)) as intersects
|
||||||
|
FROM x_departements
|
||||||
|
WHERE code = :dept_code
|
||||||
|
AND contour IS NOT NULL";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'sector_polygon' => $sectorPolygon,
|
||||||
|
'dept_code' => $departmentCode
|
||||||
|
]);
|
||||||
|
|
||||||
|
$targetDept = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$targetDept) {
|
||||||
|
return [
|
||||||
|
'is_contained' => false,
|
||||||
|
'message' => "Le département $departmentCode n'a pas de contour défini",
|
||||||
|
'intersecting_departments' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Si le secteur n'est pas entièrement contenu, trouver tous les départements qu'il touche
|
||||||
|
if (!$targetDept['is_contained']) {
|
||||||
|
$sql = "SELECT
|
||||||
|
code_dept,
|
||||||
|
nom_dept,
|
||||||
|
ST_Area(ST_Intersection(contour, ST_GeomFromText(:sector_polygon1, 4326))) /
|
||||||
|
ST_Area(ST_GeomFromText(:sector_polygon2, 4326)) * 100 as percentage_overlap
|
||||||
|
FROM x_departements_contours
|
||||||
|
WHERE ST_Intersects(contour, ST_GeomFromText(:sector_polygon3, 4326))
|
||||||
|
ORDER BY percentage_overlap DESC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'sector_polygon1' => $sectorPolygon,
|
||||||
|
'sector_polygon2' => $sectorPolygon,
|
||||||
|
'sector_polygon3' => $sectorPolygon
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intersectingDepts = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Formater le message
|
||||||
|
$deptsList = array_map(function($d) {
|
||||||
|
return sprintf("%s (%s) : %.1f%%",
|
||||||
|
$d['nom_dept'],
|
||||||
|
$d['code_dept'],
|
||||||
|
$d['percentage_overlap']
|
||||||
|
);
|
||||||
|
}, $intersectingDepts);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'is_contained' => false,
|
||||||
|
'message' => "Le secteur déborde du département {$targetDept['nom_dept']}. Il est à cheval sur : " . implode(', ', $deptsList),
|
||||||
|
'intersecting_departments' => $intersectingDepts
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'is_contained' => true,
|
||||||
|
'message' => "Le secteur est entièrement contenu dans le département {$targetDept['nom_dept']}",
|
||||||
|
'intersecting_departments' => [$targetDept]
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
throw new RuntimeException("Erreur lors de la vérification des limites départementales : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les départements qui intersectent avec un secteur
|
||||||
|
*
|
||||||
|
* @param array $sectorCoordinates Coordonnées du secteur [[lat, lng], ...]
|
||||||
|
* @return array Liste des départements avec leur pourcentage de recouvrement
|
||||||
|
*/
|
||||||
|
public function getDepartmentsForSector(array $sectorCoordinates): array {
|
||||||
|
if (count($sectorCoordinates) < 3) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le polygone du secteur
|
||||||
|
$polygonPoints = [];
|
||||||
|
foreach ($sectorCoordinates as $coord) {
|
||||||
|
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // longitude latitude
|
||||||
|
}
|
||||||
|
$polygonPoints[] = $polygonPoints[0];
|
||||||
|
$sectorPolygon = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT
|
||||||
|
code_dept,
|
||||||
|
nom_dept,
|
||||||
|
ST_Area(ST_Intersection(contour, ST_GeomFromText(:sector_polygon1, 4326))) /
|
||||||
|
ST_Area(ST_GeomFromText(:sector_polygon2, 4326)) * 100 as percentage_overlap
|
||||||
|
FROM x_departements_contours
|
||||||
|
WHERE ST_Intersects(contour, ST_GeomFromText(:sector_polygon3, 4326))
|
||||||
|
ORDER BY percentage_overlap DESC";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'sector_polygon1' => $sectorPolygon,
|
||||||
|
'sector_polygon2' => $sectorPolygon,
|
||||||
|
'sector_polygon3' => $sectorPolygon
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
throw new RuntimeException("Erreur lors de la recherche des départements : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si les contours des départements sont chargés
|
||||||
|
*
|
||||||
|
* @return array ['loaded' => bool, 'count' => int, 'missing' => array]
|
||||||
|
*/
|
||||||
|
public function checkDepartmentContoursStatus(): array {
|
||||||
|
try {
|
||||||
|
// Compter les départements avec contours
|
||||||
|
$sql = "SELECT COUNT(*) as count FROM x_departements_contours";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
$count = $stmt->fetch()['count'];
|
||||||
|
|
||||||
|
// Récupérer la liste des départements utilisés dans les entités
|
||||||
|
$sql = "SELECT DISTINCT departement
|
||||||
|
FROM entites
|
||||||
|
WHERE departement IS NOT NULL
|
||||||
|
ORDER BY departement";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
$usedDepts = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Vérifier lesquels ont des contours
|
||||||
|
$sql = "SELECT code_dept FROM x_departements_contours WHERE code_dept IN ('" . implode("','", $usedDepts) . "')";
|
||||||
|
$stmt = $this->db->query($sql);
|
||||||
|
$loadedDepts = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$missingDepts = array_diff($usedDepts, $loadedDepts);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'loaded' => count($missingDepts) === 0,
|
||||||
|
'count' => $count,
|
||||||
|
'total_used' => count($usedDepts),
|
||||||
|
'missing' => array_values($missingDepts)
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
return [
|
||||||
|
'loaded' => false,
|
||||||
|
'count' => 0,
|
||||||
|
'total_used' => 0,
|
||||||
|
'missing' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les départements limitrophes d'un département donné
|
||||||
|
*
|
||||||
|
* @param string $departmentCode Code du département
|
||||||
|
* @return array Array des codes départements limitrophes
|
||||||
|
*/
|
||||||
|
public function getAdjacentDepartments(string $departmentCode): array {
|
||||||
|
try {
|
||||||
|
$sql = "SELECT dept_limitrophes FROM x_departements WHERE code = :dept_code AND chk_active = 1";
|
||||||
|
$stmt = $this->db->prepare($sql);
|
||||||
|
$stmt->execute(['dept_code' => $departmentCode]);
|
||||||
|
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$result || empty($result['dept_limitrophes'])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir la chaîne CSV en array
|
||||||
|
return array_map('trim', explode(',', $result['dept_limitrophes']));
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Erreur lors de la récupération des départements limitrophes : " . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les départements à prioriser pour la recherche
|
||||||
|
* (département de l'entité + ses limitrophes)
|
||||||
|
*
|
||||||
|
* @param string $entityDepartment Code du département de l'entité
|
||||||
|
* @return array Array des codes départements à prioriser
|
||||||
|
*/
|
||||||
|
public function getPriorityDepartments(string $entityDepartment): array {
|
||||||
|
$priorityDepts = [$entityDepartment]; // Commencer par le département de l'entité
|
||||||
|
|
||||||
|
// Ajouter les départements limitrophes
|
||||||
|
$adjacentDepts = $this->getAdjacentDepartments($entityDepartment);
|
||||||
|
$priorityDepts = array_merge($priorityDepts, $adjacentDepts);
|
||||||
|
|
||||||
|
// Retourner sans doublons
|
||||||
|
return array_unique($priorityDepts);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
api/src/Services/EmailTemplates.php
Normal file → Executable file
0
api/src/Services/EmailTemplates.php
Normal file → Executable file
0
api/src/Services/ExportService.php
Normal file → Executable file
0
api/src/Services/ExportService.php
Normal file → Executable file
0
api/src/Services/FileService.php
Normal file → Executable file
0
api/src/Services/FileService.php
Normal file → Executable file
48
api/src/Services/LogService.php
Normal file → Executable file
48
api/src/Services/LogService.php
Normal file → Executable file
@@ -25,6 +25,19 @@ class LogService {
|
|||||||
if ($clientType === 'mobile' && isset($clientInfo['appIdentifier'])) {
|
if ($clientType === 'mobile' && isset($clientInfo['appIdentifier'])) {
|
||||||
$defaultMetadata['app_identifier'] = $clientInfo['appIdentifier'];
|
$defaultMetadata['app_identifier'] = $clientInfo['appIdentifier'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ajouter les informations de session si disponibles
|
||||||
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
$defaultMetadata['user_id'] = $_SESSION['user_id'];
|
||||||
|
}
|
||||||
|
if (isset($_SESSION['entity_id'])) {
|
||||||
|
$defaultMetadata['entity_id'] = $_SESSION['entity_id'];
|
||||||
|
}
|
||||||
|
if (isset($_SESSION['operation_id'])) {
|
||||||
|
$defaultMetadata['operation_id'] = $_SESSION['operation_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$metadata = array_merge_recursive($defaultMetadata, $metadata);
|
$metadata = array_merge_recursive($defaultMetadata, $metadata);
|
||||||
|
|
||||||
@@ -73,15 +86,31 @@ class LogService {
|
|||||||
// timestamp;browser.name@browser.version;os.name@os.version;client_type;$metadata;$message
|
// timestamp;browser.name@browser.version;os.name@os.version;client_type;$metadata;$message
|
||||||
$timestamp = date('Y-m-d\TH:i:s');
|
$timestamp = date('Y-m-d\TH:i:s');
|
||||||
$browserInfo = $clientInfo['browser']['name'] . '@' . $clientInfo['browser']['version'];
|
$browserInfo = $clientInfo['browser']['name'] . '@' . $clientInfo['browser']['version'];
|
||||||
$osInfo = $clientInfo['os']['name'] . '@' . $clientInfo['os']['version'];
|
|
||||||
|
// Ne pas afficher l'OS s'il est unknown
|
||||||
|
$osInfo = '';
|
||||||
|
if ($clientInfo['os']['name'] !== 'unknown' && $clientInfo['os']['version'] !== 'unknown') {
|
||||||
|
$osInfo = $clientInfo['os']['name'] . '@' . $clientInfo['os']['version'];
|
||||||
|
}
|
||||||
|
|
||||||
// Extraire le niveau de log
|
// Extraire le niveau de log
|
||||||
$level = isset($metadata['level']) ? (is_array($metadata['level']) ? 'info' : $metadata['level']) : 'info';
|
$level = isset($metadata['level']) ? (is_array($metadata['level']) ? 'info' : $metadata['level']) : 'info';
|
||||||
|
|
||||||
// Préparer les métadonnées supplémentaires (exclure celles déjà incluses dans le format)
|
// Préparer les métadonnées supplémentaires (exclure celles déjà incluses dans le format)
|
||||||
$additionalMetadata = [];
|
$additionalMetadata = [];
|
||||||
|
|
||||||
|
// Ajouter user_id, entity_id et operation_id en premier s'ils existent
|
||||||
|
$priorityKeys = ['user_id', 'entity_id', 'operation_id'];
|
||||||
|
foreach ($priorityKeys as $key) {
|
||||||
|
if (isset($metadata[$key]) && !is_array($metadata[$key])) {
|
||||||
|
$additionalMetadata[$key] = $metadata[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter les autres métadonnées
|
||||||
foreach ($metadata as $key => $value) {
|
foreach ($metadata as $key => $value) {
|
||||||
if (!in_array($key, ['browser', 'os', 'client_type', 'side', 'version', 'level', 'environment', 'client'])) {
|
if (!in_array($key, ['browser', 'os', 'client_type', 'side', 'version', 'level', 'environment', 'client'])
|
||||||
|
&& !in_array($key, $priorityKeys)) {
|
||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
$additionalMetadata[$key] = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
$additionalMetadata[$key] = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
} else {
|
} else {
|
||||||
@@ -114,4 +143,19 @@ class LogService {
|
|||||||
error_log("Erreur lors de l'écriture des logs: " . $e->getMessage());
|
error_log("Erreur lors de l'écriture des logs: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function info(string $message, array $metadata = []): void {
|
||||||
|
$metadata['level'] = 'info';
|
||||||
|
self::log($message, $metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function warning(string $message, array $metadata = []): void {
|
||||||
|
$metadata['level'] = 'warning';
|
||||||
|
self::log($message, $metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function error(string $message, array $metadata = []): void {
|
||||||
|
$metadata['level'] = 'error';
|
||||||
|
self::log($message, $metadata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
api/src/Services/OperationDataService.php
Normal file → Executable file
0
api/src/Services/OperationDataService.php
Normal file → Executable file
0
api/src/Utils/ClientDetector.php
Normal file → Executable file
0
api/src/Utils/ClientDetector.php
Normal file → Executable file
54
api/test_addresses_connection.php
Normal file
54
api/test_addresses_connection.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script de test de la connexion à la base de données des adresses
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/src/Config/AppConfig.php';
|
||||||
|
require_once __DIR__ . '/src/Core/AddressesDatabase.php';
|
||||||
|
|
||||||
|
echo "Test de connexion à la base de données des adresses\n";
|
||||||
|
echo "==================================================\n\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialiser la configuration
|
||||||
|
$appConfig = AppConfig::getInstance();
|
||||||
|
$addressesConfig = $appConfig->getAddressesDatabaseConfig();
|
||||||
|
|
||||||
|
echo "Configuration:\n";
|
||||||
|
echo "- Environnement: " . $appConfig->getEnvironment() . "\n";
|
||||||
|
echo "- Host: " . $addressesConfig['host'] . "\n";
|
||||||
|
echo "- Database: " . $addressesConfig['name'] . "\n";
|
||||||
|
echo "- Username: " . $addressesConfig['username'] . "\n\n";
|
||||||
|
|
||||||
|
// Initialiser la connexion
|
||||||
|
AddressesDatabase::init($addressesConfig);
|
||||||
|
$db = AddressesDatabase::getInstance();
|
||||||
|
|
||||||
|
echo "✓ Connexion réussie!\n\n";
|
||||||
|
|
||||||
|
// Tester une requête simple
|
||||||
|
echo "Test de requête...\n";
|
||||||
|
$stmt = $db->query("SELECT COUNT(*) as total FROM adresses LIMIT 1");
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
echo "✓ Nombre total d'adresses: " . number_format($result['total']) . "\n\n";
|
||||||
|
|
||||||
|
// Tester les fonctions géospatiales
|
||||||
|
echo "Test des fonctions géospatiales...\n";
|
||||||
|
$stmt = $db->query("SELECT ST_AsText(ST_GeomFromText('POINT(2.3522 48.8566)', 4326)) as point");
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
echo "✓ Fonctions géospatiales disponibles: " . $result['point'] . "\n\n";
|
||||||
|
|
||||||
|
// Afficher les colonnes de la table adresses
|
||||||
|
echo "Structure de la table adresses:\n";
|
||||||
|
$stmt = $db->query("DESCRIBE adresses");
|
||||||
|
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
echo "- " . $column['Field'] . " (" . $column['Type'] . ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "✗ Erreur: " . $e->getMessage() . "\n";
|
||||||
|
echo "Trace:\n" . $e->getTraceAsString() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
110
api/test_addresses_dept.php
Normal file
110
api/test_addresses_dept.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script de test de la connexion à la base de données des adresses
|
||||||
|
* Adapté pour la structure par département
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/src/Config/AppConfig.php';
|
||||||
|
require_once __DIR__ . '/src/Core/Database.php';
|
||||||
|
require_once __DIR__ . '/src/Core/AddressesDatabase.php';
|
||||||
|
|
||||||
|
echo "Test de connexion à la base de données des adresses par département\n";
|
||||||
|
echo "==================================================================\n\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialiser la configuration
|
||||||
|
$appConfig = AppConfig::getInstance();
|
||||||
|
$addressesConfig = $appConfig->getAddressesDatabaseConfig();
|
||||||
|
|
||||||
|
echo "Configuration:\n";
|
||||||
|
echo "- Environnement: " . $appConfig->getEnvironment() . "\n";
|
||||||
|
echo "- Host: " . $addressesConfig['host'] . "\n";
|
||||||
|
echo "- Database: " . $addressesConfig['name'] . "\n";
|
||||||
|
echo "- Username: " . $addressesConfig['username'] . "\n\n";
|
||||||
|
|
||||||
|
// Initialiser les connexions
|
||||||
|
Database::init($appConfig->getDatabaseConfig());
|
||||||
|
AddressesDatabase::init($addressesConfig);
|
||||||
|
|
||||||
|
$mainDb = Database::getInstance();
|
||||||
|
$addressesDb = AddressesDatabase::getInstance();
|
||||||
|
|
||||||
|
echo "✓ Connexion réussie aux deux bases!\n\n";
|
||||||
|
|
||||||
|
// Lister les tables cp* disponibles
|
||||||
|
echo "Tables d'adresses disponibles:\n";
|
||||||
|
$stmt = $addressesDb->query("SHOW TABLES LIKE 'cp%'");
|
||||||
|
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
// Extraire le numéro de département
|
||||||
|
$dept = str_replace('cp', '', $table);
|
||||||
|
|
||||||
|
// Compter les lignes
|
||||||
|
$countStmt = $addressesDb->query("SELECT COUNT(*) as total FROM `$table`");
|
||||||
|
$count = $countStmt->fetch()['total'];
|
||||||
|
|
||||||
|
echo "- $table (département $dept): " . number_format($count) . " adresses\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
|
|
||||||
|
// Tester avec une entité
|
||||||
|
echo "Test de récupération du département d'une entité:\n";
|
||||||
|
$testEntityId = 1; // Changez selon votre base
|
||||||
|
|
||||||
|
$stmt = $mainDb->prepare("SELECT id, nom, departement FROM entites WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $testEntityId]);
|
||||||
|
$entity = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($entity) {
|
||||||
|
echo "- Entité #{$entity['id']}: {$entity['nom']} (département {$entity['departement']})\n";
|
||||||
|
$tableName = "cp" . $entity['departement'];
|
||||||
|
|
||||||
|
// Vérifier si la table existe
|
||||||
|
if (in_array($tableName, $tables)) {
|
||||||
|
echo "✓ Table $tableName existe\n";
|
||||||
|
|
||||||
|
// Tester une requête sur cette table
|
||||||
|
$stmt = $addressesDb->query("SELECT numero, rue, cp, ville, gps_lat, gps_lng
|
||||||
|
FROM `$tableName`
|
||||||
|
WHERE gps_lat != '' AND gps_lng != ''
|
||||||
|
LIMIT 3");
|
||||||
|
$addresses = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo "\nExemples d'adresses dans le département {$entity['departement']}:\n";
|
||||||
|
foreach ($addresses as $addr) {
|
||||||
|
echo "- {$addr['numero']} {$addr['rue']}, {$addr['cp']} {$addr['ville']} ";
|
||||||
|
echo "(lat: {$addr['gps_lat']}, lng: {$addr['gps_lng']})\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tester une requête géospatiale
|
||||||
|
echo "\nTest de requête géospatiale:\n";
|
||||||
|
$testLat = $addresses[0]['gps_lat'] ?? 48.8566;
|
||||||
|
$testLng = $addresses[0]['gps_lng'] ?? 2.3522;
|
||||||
|
|
||||||
|
$sql = "SELECT COUNT(*) as total
|
||||||
|
FROM `$tableName`
|
||||||
|
WHERE ST_Distance_Sphere(
|
||||||
|
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
|
||||||
|
POINT(:lng, :lat)
|
||||||
|
) <= 1000
|
||||||
|
AND gps_lat != '' AND gps_lng != ''";
|
||||||
|
|
||||||
|
$stmt = $addressesDb->prepare($sql);
|
||||||
|
$stmt->execute(['lat' => $testLat, 'lng' => $testLng]);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
|
||||||
|
echo "✓ Adresses dans un rayon de 1km autour de ($testLat, $testLng): " . $result['total'] . "\n";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo "✗ Table $tableName n'existe pas dans la base adresses\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "✗ Erreur: " . $e->getMessage() . "\n";
|
||||||
|
echo "Trace:\n" . $e->getTraceAsString() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
59
api/test_api_geo.php
Normal file
59
api/test_api_geo.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Test rapide de l'API geo.api.gouv.fr
|
||||||
|
*/
|
||||||
|
|
||||||
|
echo "Test des différentes URLs de l'API geo.api.gouv.fr\n";
|
||||||
|
echo "=================================================\n\n";
|
||||||
|
|
||||||
|
$urls = [
|
||||||
|
"fields=contour" => "https://geo.api.gouv.fr/departements/22?fields=contour",
|
||||||
|
"geometry=contour" => "https://geo.api.gouv.fr/departements/22?geometry=contour",
|
||||||
|
"fields=contour,nom" => "https://geo.api.gouv.fr/departements/22?fields=contour,nom",
|
||||||
|
"fields=nom&geometry=contour" => "https://geo.api.gouv.fr/departements/22?fields=nom&geometry=contour",
|
||||||
|
];
|
||||||
|
|
||||||
|
$context = stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'timeout' => 10,
|
||||||
|
'header' => "User-Agent: Geosector/1.0\r\n"
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($urls as $type => $url) {
|
||||||
|
echo "Test : $type\n";
|
||||||
|
echo "URL : $url\n";
|
||||||
|
|
||||||
|
$response = @file_get_contents($url, false, $context);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
echo "✗ Erreur\n";
|
||||||
|
} else {
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
echo "✓ Réponse reçue\n";
|
||||||
|
|
||||||
|
// Afficher les clés du premier niveau
|
||||||
|
echo "Clés : " . implode(', ', array_keys($data)) . "\n";
|
||||||
|
|
||||||
|
// Vérifier la présence du contour
|
||||||
|
if (isset($data['contour'])) {
|
||||||
|
echo "→ Contour présent (type: " . ($data['contour']['type'] ?? 'inconnu') . ")\n";
|
||||||
|
if (isset($data['contour']['coordinates'])) {
|
||||||
|
$coords = $data['contour']['coordinates'];
|
||||||
|
$pointCount = is_array($coords[0]) ? count($coords[0]) : count($coords);
|
||||||
|
echo "→ Nombre de points : $pointCount\n";
|
||||||
|
}
|
||||||
|
} elseif (isset($data['geometry'])) {
|
||||||
|
echo "→ Geometry présent (type: " . ($data['geometry']['type'] ?? 'inconnu') . ")\n";
|
||||||
|
if (isset($data['geometry']['coordinates'])) {
|
||||||
|
$coords = $data['geometry']['coordinates'];
|
||||||
|
$pointCount = is_array($coords[0]) ? count($coords[0]) : count($coords);
|
||||||
|
echo "→ Nombre de points : $pointCount\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "→ Pas de contour ou geometry\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user