From 8c2251fabade99b67ed1fffa40827572b73023a2 Mon Sep 17 00:00:00 2001 From: Eric FELIXINE Date: Thu, 4 Jun 2026 02:05:32 -0400 Subject: [PATCH] =?UTF-8?q?TODO:=20mise=20a=20jour=202026-06-04=20-=20clea?= =?UTF-8?q?nup=20massif,=20helms=20ansible=20gener=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 24 + TODO.md | 185 +++-- smart-app-city/README.md | 196 ++++- .../backend/auth-service/Dockerfile | 6 + smart-app-city/backend/auth-service/server.js | 175 +++++ smart-app-city/docker-compose.web.yml | 35 + smart-app-city/frontend/dist/index.html | 705 ++++++++++++++++++ smart-app-city/frontend/nginx.conf | 26 + 8 files changed, 1237 insertions(+), 115 deletions(-) create mode 100644 .gitignore create mode 100644 smart-app-city/backend/auth-service/Dockerfile create mode 100644 smart-app-city/backend/auth-service/server.js create mode 100644 smart-app-city/docker-compose.web.yml create mode 100644 smart-app-city/frontend/dist/index.html create mode 100644 smart-app-city/frontend/nginx.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a99adf08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Dependencies +node_modules/ +*/node_modules/ + +# Build outputs +dist/ +build/ +*.pyc +__pycache__/ + +# Environment files +.env +.env.local +*.env + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/TODO.md b/TODO.md index 1117ceb4..9ffb982f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,100 +1,129 @@ # Smart City Digital Twin — TODO List -> Dernière mise à jour : 2026-06-01 22:00 (session continue - smart app + ditto) +> Dernière mise à jour : 2026-06-04 00:30 (finalisation documentation) -## ✅ Complété (session 2026-06-01) +## ✅ Complété (session 2026-06-03 / 06-04) | ID | Tâche | Détail | |----|-------|--------| -| jupyterhub-fix | JupyterHub DB path | `sqlite:////srv/jupyterhub/jupyterhub.sqlite` (absolute path, 4 slashes) | -| jupyterhub-rebuild | Rebuild Dockerfile | Supprimé double-nested `/srv/jupyterhub/srv/jupyterhub` | -| jupyterhub-spawner | Spawner config | `SimpleLocalProcessSpawner`, timeout 300s | -| jupyterhub-user | User eric | Créé id=2, admin, authorized | -| jupyterhub-sudo | sudo + eric user in container | Dockerfile modifié, spawn vérifié fonctionnel | -| hermes-dashboard | Dashboard WebUI+TUI | systemd service, localhost:9119, auto-boot | -| or-mbtiles-metadata | Bounds monde + center Martinique | `sqlite3` UPDATE sur metadata | -| or-map-settings | mapsettings.json vérifié | center=[-61,14.5], bounds=Martinique, minZoom=0 | -| or-mbtiles-location | mbtiles actif = /storage/map/ | PAS /opt/map/ (écrasé par volume) | -| trino-fix | node.properties créé | `node.environment=production`, `node.id=trino-lakehouse-01` | -| trino-config | config.properties nettoyé | `plugin.bundles` retiré (incompatible Trino 435) | -| kafka-fix | Kafka KRaft env vars | `KAFKA_CFG_*` → `KAFKA_*`, `CLUSTER_ID` ajouté, volumes recréés | -| git-push | Commits | Pushé sur Gitea (smart-city-digital-twin-martinique + lakehouse) | -| **smart-app-mvp** | **Smart App City MVP complet** | **Voir détail ci-dessous** | -| honcho-api | Honcho API déployée | `honcho-api-1` — Up sur `honcho.digitribe.fr`, workspace `hermes-agent` | -| honcho-plugin | Plugin mémoire Hermes ↔ Honcho | `~/.hermes/honcho.json` configuré, baseUrl `http://127.0.0.1:8089` | -| honcho-mémoire | Mémoire Honcho fonctionnelle | Stockage messages OK. Dialectic chat → nécessite clé LLM valide | -| cicd-pipeline | Gitea Actions CI/CD | Workflow lint + build + deploy, runner docker-runner-01 | -| ci-cd-secrets | Secrets Gitea Actions | SERVER_HOST, SERVER_USER, SSH_PRIVATE_KEY configurés | -| smart-app-docker | Dockerfile web + Traefik | Multi-stage node + nginx, SPA routing, smartapp.digitribe.fr | -| smart-app-deploy | Script de déploiement | `deploy.sh` — web/docker/api/all | -| localai-fix | LocalAI Bad Gateway | Container n'existe plus, config Traefik supprimée | -| ditto-mongodb-fix | MongoDB connection | `-Dditto.mongodb.uri` dans JAVA_TOOL_OPTIONS | -| ditto-secrets | Nouveaux secrets JWT/devops | Générés aléatoirement, sauvegardés `.env.ditto` | -| ditto-official-images | Gateway custom → latest | `eclipse/ditto-gateway:latest` officiel | +| airflow-deploy | Apache Airflow déployé | `airflow.digitribe.fr` — Python 3.11, LocalExecutor | +| openfn-cleanup | OpenFN supprimé | Race condition Cachex/Ecto non résolue | +| ditto-cleanup | Stack Ditto supprimée | API v2 non fonctionnelle (schema-versions) | +| openremote-cleanup | Stack OpenRemote supprimée | Patches bundle appliqués | +| gravitino-cleanup | Gravitino supprimé | Unhealthy | +| fiware-gis-cleanup | FIWARE GIS Quickstart supprimé | | +| contexus-cleanup | Contexus supprimé | Unhealthy | +| kafka-cleanup | Kafka supprimé | Unhealthy + sera redeployé via Helm | +| flink-cleanup | Flink supprimé | Dépendances kafka | +| bi-cleanup | Superset + Metabase supprimés | Seront redeployés via Helm | +| mindsdb-cleanup | MindsDB supprimé | Autoheal unhealthy | +| odk-cleanup | ODK Central supprimé | Sera redeployé via Helm | +| jupyterhub-cleanup | JupyterHub supprimé | Sera redeployé via Helm | +| zeppelin-cleanup | Zeppelin supprimé | Sera redeployé via Helm | +| gis-cleanup | MapStore + GeoServer + FROST supprimés | Seront redeployés via Helm | +| iot-cleanup | Node-RED + phpIPAM + EMQX + Mosquitto + BunkerM + ChirpStack supprimés | Seront redeployés via Helm | +| monitoring-cleanup | Grafana + Loki + Prometheus + InfluxDB + Telegraf supprimés | Seront redeployés via Helm | +| storage-cleanup | MinIO + PostgreSQL + PostGIS + Redis + Zookeeper supprimés | Seront redeployés via Helm | +| misc-cleanup | AgentGateway + Esperotech + Redpanda Console + Docker exporter + Simulator supprimés | | +| backups | Sauvegardes config | Fichiers sauvegardés dans /home/eric/backups/2026-06-03/ | +| helms-ansible | Fichiers Helm/Ansibles générés | 25+ rôles dans /home/eric/helms/ | +| helms-readme | README déploiement K8s | Architecture, installation, troubleshooting | +| helms-vault | Template vault.yml | Variables chiffrées pour le déploiement | ## 🔴 En cours | ID | Tâche | Raison | Prochaine action | |----|-------|--------|------------------| -| or-map-bounds | MapService retourne bounds Pays-Bas | Bug MapResourceImpl.java: mbtiles metadata bounds prioritaire sur mapsettings.json | Générer vrai mbtiles MVT Martinique OU patcher code source OR | +| (aucune) | — | — | — | -## ⏳ En attente +## ⏳ En attente (déploiement Kubernetes via Ansible) | ID | Tâche | |----|-------| -| or-mbtiles-martinique | Générer mbtiles MVT PBF pour Martinique (tippecanoe depuis GeoJSON filtré) | -| p1-or-map | Vérifier carte Martinique après fix bounds | -| p1-contexus-60 | Configurer les 60 devices Contexus | -| p3-analyse | GeoMesa + KeplerGL | -| p0-chirpstack | ChirpStack login API gRPC-REST | -| p1-thingsboard | Relancer ThingsBoard (si CPU dispo) | -| smart-app Phase 1 | MVP React Native | -| p2-geoserver | GeoServer + PostGIS couches Martinique | +| k8s-cluster | Créer le cluster Kubernetes (3 nœuds minimum) | +| nfs-server | Configurer le serveur NFS pour le storage | +| traefik-deploy | Déployer Traefik via Helm | +| cert-manager-deploy | Déployer cert-manager pour TLS | +| storage-deploy | Déployer NFS provisioner + StorageClass | +| monitoring-deploy | Déployer Prometheus + Grafana + Loki | +| databases-deploy | Déployer PostgreSQL HA + Redis + MinIO | +| kafka-deploy | Déployer Kafka (Strimzi) | +| flink-deploy | Déployer Apache Flink | +| airflow-deploy | Déployer Apache Airflow | +| iot-deploy | Déployer EMQX + Mosquitto + Node-RED + phpIPAM | +| gitea-deploy | Déployer Gitea | +| jupyterhub-deploy | Déployer JupyterHub | +| bi-deploy | Déployer Superset + Metabase | +| mindsdb-deploy | Déployer MindsDB | +| odk-deploy | Déployer ODK Central | +| gis-deploy | Déployer MapStore + GeoServer + FROST | +| clickhouse-deploy | Déployer ClickHouse (`clickhouse.digitribe.fr`) | +| starrocks-deploy | Déployer StarRocks (`starrocks.digitribe.fr`) | +| trino-deploy | Déployer Trino (`trino.digitribe.fr`) | +| deltalake-deploy | Déployer Delta Lake (`deltalake.digitribe.fr`) | +| streamlit-deploy | Déployer Streamlit (`streamlit.digitribe.fr`) | +| duckdb-deploy | Déployer DuckDB (`duckdb.digitribe.fr`) | +| smartapp-deploy | Déployer Smart App (`smartapp.digitribe.fr`) | +| backup-deploy | Déployer Velero pour les sauvegardes | -## 📝 Notes techniques 2026-06-01 +## 📁 Fichiers Helm / Ansible générés -### OpenRemote mbtiles — Points critiques -- Fichier actif : `/storage/map/mapdata.mbtiles` (volume Docker), PAS `/opt/map/` -- OR 1.24.0 ne sert que du **PBF vectoriel** — PNG raster = 404 -- Bug : MapService.java donne priorité aux bounds du mbtiles metadata sur mapsettings.json -- Fix : bounds mbtiles metadata = monde (`-180,-85,180,85`), bounds mapsettings = zone désirée -- Pour mettre à jour : `docker cp file.mbtiles openremote-manager:/storage/map/mapdata.mbtiles` +``` +helms/ +├── README.md # Documentation déploiement +├── deploy.yml # Playbook principal +├── undeploy.yml # Playbook de suppression +├── inventory/ +│ └── hosts.yml # Inventory des nœuds K8s +├── group_vars/ +│ ├── all.yml # Variables globales +│ └── vault.yml # Variables chiffrées (template) +└── roles/ # 25+ rôles Ansible + ├── prerequisites/ + ├── namespaces/ + ├── storage/ + ├── traefik/ + ├── cert-manager/ + ├── monitoring/ + ├── databases/ + ├── kafka/ + ├── flink/ + ├── airflow/ + ├── iot/ + ├── gitea/ + ├── jupyterhub/ + ├── bi/ + ├── mindsdb/ + ├── odk/ + ├── gis/ + ├── clickhouse/ + ├── starrocks/ + ├── trino/ + ├── deltalake/ + ├── streamlit/ + ├── duckdb/ + ├── nodered/ + ├── phpipam/ + ├── smartapp/ + └── backup/ +``` -### JupyterHub -- Port : 8000 (pas 8080) — accessible via https://jupyter.digitribe.fr -- User eric : id=2, admin, créé via NativeAuthenticator -- Config : `SimpleLocalProcessSpawner`, timeout 300s -- DB : `sqlite:////srv/jupyterhub/jupyterhub.sqlite` (absolute path, 4 slashes) -- `eric` OS user avec sudo NOPASSWD dans le container -- `jupyterhub-singleuser --version` = 5.3.0, `jupyter-lab --version` = 4.5.7 +## 📝 Infrastructure actuelle (10 containers Docker) -### Kafka (KRaft) -- `apache/kafka:3.9.0` utilise `KAFKA_*` (pas `KAFKA_CFG_*` qui est Bitnami) -- `CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk` requis pour storage formatting -- 2 brokers en mode KRaft (broker+controller), pas de ZooKeeper - -### Trino -- Config dans `/home/eric/lakehouse/docker-compose/config/trino/` -- `node.id=trino-lakehouse-01` (pas `_internal_`) -- `plugin.bundles` retiré de config.properties (incompatible Trino 435) - -### Infrastructure -- 86+ conteneurs Docker -- Kafka, Trino, JupyterHub = UP ✅ (fixes appliqués cette session) -- Tous les autres services principaux = UP ✅ +| Service | Image | Statut | +|---------|-------|--------| +| airflow-scheduler | apache/airflow:2.9.3-python3.11 | ✅ healthy | +| airflow-webserver | apache/airflow:2.9.3-python3.11 | ✅ healthy | +| airflow-init | apache/airflow:2.9.3-python3.11 | 🔄 restarting (one-shot) | +| airflow-postgres | postgres:16 | ✅ healthy | +| smartapp-api | smartapp-api:latest | ✅ Up 38h | +| smartapp-web | nginx:alpine | ✅ Up 38h | +| gitea-runner | gitea/act_runner:latest | ✅ Up 2 days | +| traefik | traefik:v3.1 | ✅ Up 2 days | +| smart-city-kepler | smart-city-kepler:latest | ✅ Up 2 weeks | +| gitea | gitea/gitea:latest | ✅ Up 2 jours | ## Credentials -- **Contexus**: iotevadmin / Digitribe972 -- **OpenRemote**: admin / Digitribe972 -- **PostgreSQL Contexus**: contexus / Digitribe972 -- **Redis Contexus**: Digitribe972 -- **Telegraf InfluxDB**: token=my-super-token, org=digitribe, bucket=smartcity -- **Grafana**: admin / Digitribe972 -- **Superset**: admin / Digitribe972 -- **Metabase**: admin@digitribe.fr / Digitribe972 -- **BunkerM MQTT**: bunker / bunker -- **ChirpStack**: admin / Digitribe972 -- **ODK Central**: efelixine@digitribe.fr / Digitribe972 -- **JupyterHub**: eric / admin (admin) — via NativeAuthenticator -- **MindsDB**: admin@digitribe.fr / Digitribe972 +- **Gitea** : eric / (voir config) +- **Airflow** : admin / (changé par Eric) diff --git a/smart-app-city/README.md b/smart-app-city/README.md index 84943ed3..8810ea66 100644 --- a/smart-app-city/README.md +++ b/smart-app-city/README.md @@ -1,61 +1,183 @@ -# Smart App City — Mobile Application +# Smart App City — Application Mobile -> Multi-platform mobile application for Smart City Digital Twin Martinique +> Application mobile multi-platforme pour le Smart City Digital Twin Martinique -## Quick Start +## 📱 Présentation -### Prerequisites +Smart App City est une application mobile (React Native + Expo) qui permet aux citoyens de : +- **Visualiser les données IoT** en temps réel (capteurs de température, humidité, qualité de l'air, bruit, trafic, énergie) +- **Accéder à une marketplace** de services et produits locaux +- **Interagir avec un assistant IA** (météo, transports, énergie, alertes) +- **Gérer ses notifications** et **profil utilisateur** + +## 🏗️ Architecture + +``` +smart-app-city/ +├── frontend/ # Application React Native + Expo (TypeScript) +│ ├── src/ +│ │ ├── screens/ # 15 écrans (Auth, Dashboard, Map, Marketplace, Chat, Profile, IoT, Notifications) +│ │ ├── components/ # Composants réutilisables (Card, Button, Charts, Maps) +│ │ ├── stores/ # State management (Zustand) — authStore, iotStore, notificationStore, uiStore +│ │ ├── hooks/ # Hooks personnalisés (useSensors, useAlerts, useNotifications, useLocation) +│ │ ├── services/ # Services API (auth, iot, notification, via Axios) +│ │ ├── i18n/ # Internationalisation (FR/EN/ES/DE) +│ │ ├── theme/ # Design system (Blue Ocean palette) +│ │ └── utils/ # Utilitaires (formatters, validators, constants) +│ ├── dist/ # Build web statique (nginx) +│ ├── Dockerfile # Multi-stage build (node + nginx) +│ ├── package.json # Dépendances (Expo 51, React Native 0.74) +│ └── app.json # Configuration Expo +├── backend/ # Backend microservices (Node.js) +├── ai/ # Services IA (RAG, Agent) +├── design/ # Maquettes HTML/CSS (28 screenshots, PDF, Figma JSON, Penpot SVG) +├── docs/ # Documentation +└── docker-compose.yml # Orchestration Docker +``` + +## 🚀 Développement + +### Prérequis - Node.js 20+ -- Expo CLI: `npm install -g expo-cli` -- Docker & Docker Compose +- Expo CLI : `npm install -g expo-cli` +- Docker & Docker Compose (pour le déploiement) -### Development +### Installation ```bash -# Install dependencies cd frontend -npm install - -# Start development server -npx expo start - -# Run on device -# Scan QR code with Expo Go app +npm install --legacy-peer-deps ``` -### Backend +### Lancement en mode développement ```bash -# Start all services -cd .. -docker-compose up -d +# Mode natif (iOS/Android) +npm start +# Scanner le QR code avec l'app Expo Go -# Start individual service -cd backend/auth-service -npm run start:dev +# Mode web +npm run web ``` -### AI Services +### Build web statique ```bash -# Start RAG service -cd ai/rag-service -pip install -r requirements.txt -uvicorn main:app --reload --port 8001 +# Sans Docker +cd frontend +npx expo export --platform web --output-dir dist -# Start Agent service -cd ai/agent-service -pip install -r requirements.txt -uvicorn main:app --reload --port 8002 +# Avec Docker +docker build -t smartapp-web:latest . ``` -## Documentation +### Déploiement -- [Architecture](docs/ARCHITECTURE.md) -- [Beckn Integration](docs/BECKN_INTEGRATION.md) -- [AI Architecture](docs/AI_ARCHITECTURE.md) -- [Internationalization](docs/I18N.md) +```bash +cd smart-app-city +docker compose -f docker-compose.web.yml up -d +``` -## License +L'application est accessible sur : **https://smartapp.digitribe.fr** + +## 🎨 Design System + +### Palette principale +- **Blue Ocean** : `#1565C0` (primaire) +- **Indigo** : `#3949AB` (accent) +- **Cyan** : `#00ACC1` (secondaire) +- **Deep Ocean** : `#0D1B2A` (dark mode) + +### Composants +Tous les composants UI sont dans `src/components/` : +- `common/Card.tsx` — Carte réutilisable (3 variants : default, elevated, outlined) +- `common/Button.tsx` — Bouton (5 variants, 3 tailles) +- `common/Header.tsx` — En-tête de section +- `common/LoadingSpinner.tsx` — Indicateur de chargement +- `common/ErrorBoundary.tsx` — Gestion d'erreurs React +- `cards/SensorCard.tsx` — Carte capteur IoT +- `cards/StatsCard.tsx` — Carte statistiques +- `cards/AlertCard.tsx` — Carte alerte +- `cards/ZoneCard.tsx` — Carte zone +- `charts/LineChart.tsx` — Graphique linéaire +- `charts/BarChart.tsx` — Graphique barres +- `charts/GaugeChart.tsx` — Jauge semi-circulaire +- `maps/MapView.tsx` — Vue carte (placeholder pour react-native-maps) +- `maps/MarkerPopup.tsx` — Popup marqueur + +## 📊 State Management (Zustand) + +### Stores +- **authStore** : Authentification utilisateur (login, register, logout, refresh token) +- **iotStore** : Données IoT (6 capteurs, 3 zones, 2 alertes mock) +- **notificationStore** : Notifications (5 mock notifications) +- **uiStore** : Thème + i18n (FR/EN/ES/DE) + +### Hooks +- **useSensors()** : Liste des capteurs, filtrage par type/zone +- **useAlerts()** : Alertes actives/critiques, acknowledge +- **useNotifications()** : CRUD notifications +- **useLocation()** : GPS avec expo-location (défaut : Fort-de-France) + +## 🌐 Internationalisation + +4 langues supportées : Français (défaut), English, Español, Deutsch. + +Fichier de traductions : `src/stores/uiStore.ts` (fonction `t(key, lang)`) + +## 🔌 API Backend + +L'application communique avec l'API Gateway : +- **Base URL** : `https://api-smartapp.digitribe.fr/api/v1` +- **Authentification** : JWT (Basic Auth pour les routes /admin) +- **Endpoints principaux** : + - `POST /api/auth/login` — Connexion + - `POST /api/auth/register` — Inscription + - `POST /api/auth/refresh` — Refresh token + - `GET /sensors` — Liste des capteurs + - `GET /zones` — Liste des zones + - `GET /alerts` — Liste des alertes + +## 📦 Déploiement CI/CD + +### Gitea Actions +Fichier : `.gitea/workflows/build-and-deploy.yml` + +Pipeline : +1. **Lint** : `tsc --noEmit` (TypeScript check) +2. **Build** : `npx expo export --platform web` +3. **Deploy** : SSH vers le serveur → copie les fichiers ou rebuild Docker + +### Secrets Gitea +- `SERVER_HOST` : `localhost` +- `SERVER_USER` : `eric` +- `SSH_PRIVATE_KEY` : Clé SSH pour le déploiement + +### Dockerfiles +- `frontend/Dockerfile` : Multi-stage build (node 20 alpine → nginx alpine) +- `docker-compose.web.yml` : Service nginx statique avec Traefik + +## 🌍 Services liés + +| Service | URL | Description | +|---------|-----|-------------| +| Gitea | https://gitea.digitribe.fr | Git + CI/CD | +| Honcho | https://honcho.digitribe.fr | Mémoire agent IA | +| Grafana | http://127.0.0.1:3088 | Métriques Prometheus | + +## 📝 Notes techniques importantes + +### WatermelonDB supprimé +Le package `@nozbe/watermelonDB` a été supprimé car la version `^0.27.0` n'existe plus. Pas de remplacement nécessaire — la mémoire locale utilise Zustand + AsyncStorage. + +### Build web Expo +- Le build `npx expo export:web` nécessite `@expo/webpack-config@~19.0.1` +- Si le build échoue avec "Lock compromised" : supprimer `package-lock.json` et `node_modules/`, puis `npm install --legacy-peer-deps` +- Le build Docker est plus fiable que le build local (environnement isolé) + +### Patch tool +Les fichiers contenant `//`, `=`, `+`, `{}` dans les strings peuvent corriger le tool `patch`. Utiliser `replace_all=true` ou échapper les caractères spéciaux. + +## 📄 Licence Smart City Digital Twin Martinique — Projet public diff --git a/smart-app-city/backend/auth-service/Dockerfile b/smart-app-city/backend/auth-service/Dockerfile new file mode 100644 index 00000000..c545e892 --- /dev/null +++ b/smart-app-city/backend/auth-service/Dockerfile @@ -0,0 +1,6 @@ +FROM node:20-alpine +WORKDIR /app +COPY server.js . +RUN npm init -y && npm install express bcryptjs jsonwebtoken cors +EXPOSE 3001 +CMD ["node", "server.js"] diff --git a/smart-app-city/backend/auth-service/server.js b/smart-app-city/backend/auth-service/server.js new file mode 100644 index 00000000..48ee35f8 --- /dev/null +++ b/smart-app-city/backend/auth-service/server.js @@ -0,0 +1,175 @@ +// Smart App City — Auth Service (Node.js + Express) +const express = require('express'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); + +const app = express(); +const PORT = 3001; +const JWT_SECRET = process.env.JWT_SECRET || 'smart-app-city-jwt-secret-2024'; +const DB_PATH = path.join(__dirname, 'users.json'); + +app.use(cors()); +app.use(express.json()); + +// ── Database helpers ── +function loadUsers() { + if (!fs.existsSync(DB_PATH)) { + fs.writeFileSync(DB_PATH, JSON.stringify({ users: [] }, null, 2)); + } + return JSON.parse(fs.readFileSync(DB_PATH, 'utf8')); +} + +function saveUsers(db) { + fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2)); +} + +// ── Create admin user on startup ── +function createAdminUser() { + const db = loadUsers(); + + // Admin user + if (!db.users.find(u => u.email === 'admin@digitribe.fr')) { + const hashedPassword = bcrypt.hashSync('Digitribe972', 10); + db.users.push({ + id: 'admin-001', + email: 'admin@digitribe.fr', + password: hashedPassword, + firstName: 'Admin', + lastName: 'Digitribe', + roles: ['admin', 'user'], + createdAt: new Date().toISOString() + }); + saveUsers(db); + console.log('✅ Admin user created: admin@digitribe.fr / Digitribe972'); + } else { + console.log('ℹ️ Admin user already exists'); + } + + // Eric user + if (!db.users.find(u => u.email === 'eric@digitribe.fr')) { + const hashedPassword = bcrypt.hashSync('Digitribe972', 10); + db.users.push({ + id: 'eric-001', + email: 'eric@digitribe.fr', + password: hashedPassword, + firstName: 'Eric', + lastName: 'Felixine', + roles: ['admin', 'user'], + createdAt: new Date().toISOString() + }); + saveUsers(db); + console.log('✅ Eric user created: eric@digitribe.fr / Digitribe972'); + } else { + console.log('ℹ️ Eric user already exists'); + } + + // Erol user + if (!db.users.find(u => u.email === 'erol@digitribe.fr')) { + const hashedPassword = bcrypt.hashSync('erol', 10); + db.users.push({ + id: 'erol-001', + email: 'erol@digitribe.fr', + password: hashedPassword, + firstName: 'Erol', + lastName: 'Digitribe', + roles: ['user'], + createdAt: new Date().toISOString() + }); + saveUsers(db); + console.log('✅ Erol user created: erol@digitribe.fr / erol'); + } else { + console.log('ℹ️ Erol user already exists'); + } +} + +// ── Routes ── + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'ok', service: 'auth-service' }); +}); + +// Register +app.post('/api/auth/register', async (req, res) => { + try { + const { email, password, firstName, lastName } = req.body; + if (!email || !password || !firstName || !lastName) { + return res.status(400).json({ message: 'Tous les champs sont requis' }); + } + if (password.length < 8) { + return res.status(400).json({ message: 'Le mot de passe doit contenir au moins 8 caractères' }); + } + const db = loadUsers(); + if (db.users.find(u => u.email === email)) { + return res.status(409).json({ message: 'Un compte avec cet email existe déjà' }); + } + const hashedPassword = await bcrypt.hash(password, 10); + const user = { + id: 'user-' + Date.now(), + email, + password: hashedPassword, + firstName, + lastName, + roles: ['user'], + createdAt: new Date().toISOString() + }; + db.users.push(user); + saveUsers(db); + const token = jwt.sign({ id: user.id, email: user.email, roles: user.roles }, JWT_SECRET, { expiresIn: '7d' }); + res.json({ accessToken: token, user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles: user.roles } }); + } catch (e) { + res.status(500).json({ message: 'Erreur serveur' }); + } +}); + +// Login +app.post('/api/auth/login', async (req, res) => { + try { + const { email, password } = req.body; + if (!email || !password) { + return res.status(400).json({ message: 'Email et mot de passe requis' }); + } + const db = loadUsers(); + const user = db.users.find(u => u.email === email); + if (!user) { + return res.status(401).json({ message: 'Identifiants incorrects' }); + } + const valid = await bcrypt.compare(password, user.password); + if (!valid) { + return res.status(401).json({ message: 'Identifiants incorrects' }); + } + const token = jwt.sign({ id: user.id, email: user.email, roles: user.roles }, JWT_SECRET, { expiresIn: '7d' }); + res.json({ accessToken: token, user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles: user.roles } }); + } catch (e) { + res.status(500).json({ message: 'Erreur serveur' }); + } +}); + +// Get current user +app.get('/api/auth/me', (req, res) => { + try { + const token = req.headers.authorization?.split(' ')[1]; + if (!token) return res.status(401).json({ message: 'Non authentifié' }); + const decoded = jwt.verify(token, JWT_SECRET); + const db = loadUsers(); + const user = db.users.find(u => u.id === decoded.id); + if (!user) return res.status(404).json({ message: 'Utilisateur non trouvé' }); + res.json({ id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, roles: user.roles }); + } catch (e) { + res.status(401).json({ message: 'Token invalide' }); + } +}); + +// Logout (client-side, but endpoint for completeness) +app.post('/api/auth/logout', (req, res) => { + res.json({ message: 'Déconnexion réussie' }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`🚀 Auth service running on port ${PORT}`); + createAdminUser(); +}); diff --git a/smart-app-city/docker-compose.web.yml b/smart-app-city/docker-compose.web.yml new file mode 100644 index 00000000..8d259999 --- /dev/null +++ b/smart-app-city/docker-compose.web.yml @@ -0,0 +1,35 @@ +services: + web: + image: nginx:alpine + container_name: smartapp-web + restart: unless-stopped + networks: + - traefik-public + volumes: + - /home/eric/smart-city-digital-twin-martinique/smart-app-city/frontend/dist:/usr/share/nginx/html + labels: + - "traefik.enable=false" + expose: + - "80" + + api: + build: + context: ./backend/auth-service + dockerfile: Dockerfile + container_name: smartapp-api + restart: unless-stopped + networks: + - traefik-public + volumes: + - /home/eric/smart-city-digital-twin-martinique/smart-app-city/backend/auth-service/users.json:/app/users.json + expose: + - "3001" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3001/health"] + interval: 10s + timeout: 5s + retries: 3 + +networks: + traefik-public: + external: true diff --git a/smart-app-city/frontend/dist/index.html b/smart-app-city/frontend/dist/index.html new file mode 100644 index 00000000..af8bfbf5 --- /dev/null +++ b/smart-app-city/frontend/dist/index.html @@ -0,0 +1,705 @@ + + + + + + Smart City Martinique + + + + + +
+
+ +

Smart City Martinique

+

Martinique

+
+
+
+
Connexion
+
+ +
+ 📧 + +
+
+
+ +
+ 🔒 + + +
+
+ + +
ou
+
+ + +
+
+ Pas encore de compte ? + S'inscrire +
+
+
+ + +
+
+
← Retour
+

Créer un compte

+

Rejoignez Smart City Martinique

+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+ +
+ 📧 + +
+
+
+ +
+ 🔒 + + +
+
+
+ +
+ 🔒 + +
+
+
+ + J'accepte les conditions d'utilisation +
+ +
+ Déjà un compte ? + Se connecter +
+
+
+ + +
+
+
+
+
Bonjour 👋
+
Utilisateur
+
+ +
+ +
+
+
+
28°
C
Temp.
+
72%
Humidité
+
42
AQI
Air
+
55
dB
Bruit
+
+
+
Actions rapides
+ +
+
+ +
+
+
+
28°C
+
Fort-de-France
+
Ensoleillé • Humidité 72%
+
+
☀️
+
+
+
+
+
Capteurs en direct
Voir tout →
+
+
+
+
Alertes
+
+
+ ⚠️ +
+
Qualité air — Schoelcher
+
AQI élevé détecté. Évitez les efforts prolongés.
+
+
+
+
+
+ +
+ + +
+
+
Carte Interactive
Martinique
+
+
+
+
🗺️
+
Carte OpenStreetMap
+
14.6°N, 61.0°W
+
+
+
Couches
+
+ 📡 Capteurs + 🌬️ Qualité Air + 🔊 Bruit + 🚗 Trafic + 🌤️ Météo + 🎉 Événements +
+
+
+
Capteurs à proximité
+
+
+
+ +
+ + +
+
+
Marketplace
Services & Produits
+ +
+
+
+
+ Tous + 🍎 Alimentation + 🚌 Transport + ⚡ Énergie + 🏥 Santé +
+
+
+
+ +
+ + +
+
+
+
🤖
+
Smart City IA
En ligne
+
+
+
+
Bonjour ! Je suis Smart City IA 🤖

Je peux vous aider avec :
• 🌤️ Météo en temps réel
• 🚌 Transports
• ⚡ Énergie
• 🚨 Alertes

Comment puis-je vous aider ?
+
+
+ 🌤️ Météo + 🚌 Bus + ⚡ Énergie + 🚨 Alertes +
+
+ + +
+
+ + +
+
+
+
👤
+
Utilisateur
+
-
+
+ 🏙️ Citoyen + ✅ Vérifié +
+
+
+
+
12
Signalements
+
45
Points
+
5
Abonnements
+
+
+
+
+ +
+ + +
+
+
+
Notifications
+ +
+
+
+
+
+ +
+ + +
🚨

Signalements

Écran en cours de développement

+
📡

Capteurs IoT

Écran en cours de développement

+
🌤️

Météo

Écran en cours de développement

+
🏥

Santé

Écran en cours de développement

+ + + + diff --git a/smart-app-city/frontend/nginx.conf b/smart-app-city/frontend/nginx.conf new file mode 100644 index 00000000..12ec0ed9 --- /dev/null +++ b/smart-app-city/frontend/nginx.conf @@ -0,0 +1,26 @@ +server { + listen 80; + server_name smartapp.digitribe.fr; + root /usr/share/nginx/html; + index index.html; + + # Gzip + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; + + # SPA routing - must be first + location / { + try_files $uri $uri/ /index.html; + } + + # Static assets caching + location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Health check + location /health { + return 200 'OK'; + } +}