01 · Projet
Vue d'ensemble
Wolff Immobilier est une plateforme web pour une agence immobiliere de luxe. Elle propose deux interfaces distinctes : une zone client personnalisee par dossier, et un dashboard pour les agents.
Identite visuelle
- Theme
- Noir & Or (luxe immobilier)
- Couleurs cles
- Noir
#0a0a0a · Or #c9a84c · Creme #faf8f3
- Typographie
- Cormorant Garamond (titres) · DM Sans (texte)
Deux interfaces
Page client (publique, par client) — chaque client a sa propre URL avec ses biens, ses visites, ses statuts (a visiter / coup de coeur / retire), et ses documents.
Dashboard agent (avec login) — gestion des clients, des biens par recherche, du planning de visites, et de tous les documents.
URLs
- Site public
https://wolff-immobilier.com
- Page client
https://wolff-immobilier.com/clients/<slug>/
- Dashboard agent
https://agent.wolff-immobilier.com
02 · Infrastructure
Stack technique
Hebergement
- Serveur
- Hetzner Cloud · Ubuntu 24.04 LTS
- IP publique
77.42.95.132
- Acces
- SSH (port 22, root)
- Localisation
- Allemagne
Runtime
- Node.js
- v20 LTS (sans dependance npm externe)
- Gestionnaire
- PM2 (process name :
wolff)
- Port interne
3000 (HTTP localhost)
- Stockage
- JSON local
/root/wolff-immobilier/data/db.json
Reverse proxy & SSL
- Nginx
- v1.24, deux sites enabled :
agent-wolff, wolff-immobilier
- SSL
- Let's Encrypt via Certbot (renouvellement automatique)
- Config sites
/etc/nginx/sites-enabled/
DNS
- Registrar
- Infomaniak
- Domaines
wolff-immobilier.com · agent.wolff-immobilier.com
Securite
- UFW : ports ouverts 22 (SSH), 80 (HTTP), 443 (HTTPS)
- JWT : authentification dashboard (HMAC-SHA256, 8h)
- Passwords : hash PBKDF2-SHA512 + sel
- Indexation : desactivee (robots.txt + header X-Robots-Tag)
03 · Structure
Architecture des fichiers
Tout le projet vit dans /root/wolff-immobilier/ sur le serveur.
/root/wolff-immobilier/
├── server.js # Serveur HTTP principal (routeur, API, fichiers statiques)
├── db.js # Acces a la base JSON + crypto (JWT, hash)
├── generator.js # Genere les pages HTML clients au demarrage
├── README.md # Documentation
├── data/
│ └── db.json # Base de donnees JSON (seule source de verite)
├── public/
│ └── dashboard.html # Interface dashboard agent (1 fichier autonome)
├── clients/ # Pages HTML clients generees
│ ├── vincent/index.html
│ └── jeremy-barnier/index.html
└── uploads/ # Fichiers uploades (PDF, images des biens)
Roles des fichiers
- server.js
- Le coeur. Routeur HTTP, toutes les routes API, sert les fichiers statiques. ~715 lignes.
- db.js
- Fonctions
load(), save(), hash(), verifyJWT(). Lit/ecrit data/db.json.
- generator.js
- Au demarrage du serveur, regenere
clients/<slug>/index.html pour chaque client en base.
- dashboard.html
- App mono-fichier (HTML+CSS+JS inline). Aucun framework. ~1424 lignes.
- clients/<slug>/
- Page autonome generee pour chaque client. Lit l'API publique sans auth.
Important
Le tableau data.biens (legacy, vide) doit rester present dans db.json car regenerateAll() dans server.js y accede au demarrage. Ne pas le supprimer.
04 · Backend
Routes API
Toutes les routes utilisent le pattern /api/.... Les routes protegees attendent un header Authorization: Bearer <jwt>.
Authentification
| Methode | Route | Description |
| POST | /api/auth/login | Login agent, retourne JWT |
| GET | /api/auth/me | Info agent connecte (token requis) |
Dashboard
| Methode | Route | Description |
| GET | /api/dashboard/stats | KPI : clients actifs, visites semaine, compromis... |
| GET | /api/dashboard/activites | 20 dernieres activites |
| GET | /api/dashboard/agenda | Toutes les visites enrichies (avec client_nom) |
Clients
| Methode | Route | Description |
| GET | /api/clients | Liste tous les clients |
| GET | /api/clients/:slug | Detail d'un client |
| POST | /api/clients | Creer un client (cree aussi /clients/<slug>/) |
| PUT | /api/clients/:slug | Modifier un client |
| DELETE | /api/clients/:slug | Supprimer un client (et son dossier) |
Biens (routes generiques par slug)
| Methode | Route | Description |
| GET | /api/biens-:slug | Tous les biens d'un client (avec auth) |
| POST | /api/biens-:slug | Creer un bien (ordre = max+1 auto) |
| PUT | /api/biens-:slug/:id | Modifier un bien |
| DELETE | /api/biens-:slug/:id | Supprimer un bien |
| PUT | /api/biens-:slug-ordre | Reorder (drag/drop) |
| GET | /api/biens-public/:slug | Liste publique (sans auth, page client) |
| GET | /api/biens-tous | Agregation tous clients (KPI/agenda) |
| GET | /api/biens-client/:slug | Statuts des biens d'un client |
| GET | /api/biens-tous-clients | Statuts tous clients |
Recherches
| Methode | Route | Description |
| GET | /api/recherches/:slug | Recherches d'un client |
| POST | /api/recherches/:slug | Creer recherche |
| PUT | /api/recherches/:slug/:rid | Modifier recherche |
| DELETE | /api/recherches/:slug/:rid | Supprimer recherche |
| GET | /api/recherches/:slug/:rid | Biens d'une recherche (filtres) |
Visites
| Methode | Route | Description |
| GET | /api/visites | Toutes les visites (auth) |
| POST | /api/visites | Creer une visite |
| PUT | /api/visites/:id | Modifier (date, heure, statut) |
| DELETE | /api/visites/:id | Supprimer |
| GET | /api/visites-public?slug=X | Visites publiques (page client) |
Documents
| Methode | Route | Description |
| GET | /api/documents-bien/:id | Liste docs d'un bien |
| POST | /api/documents-bien/:id/upload | Upload (multipart, champ agent_only) |
| DELETE | /api/documents-bien/:id/:docId | Supprimer un document |
05 · Donnees
Structure de la base de donnees
Un seul fichier JSON : /root/wolff-immobilier/data/db.json. Lu/ecrit a chaque requete via db.load() / db.save().
Cles principales
| Cle | Type | Role |
agents | array | Comptes agents (login dashboard) |
clients | array | Clients (Vincent, Jeremy...) avec slug, dossier, etape |
etapes | array | Catalogue des 14 etapes possibles d'un dossier |
activites | array | Journal d'activites du dashboard |
biens_<slug> | array | Biens d'un client (un tableau par client : biens_vincent, biens_jeremy_barnier...) |
biens_client_statuts | object | Statuts (coup-de-coeur, retire) par client puis par bien_id |
recherches | object | Recherches par client : { "vincent": [...], "jeremy-barnier": [...] } |
statuts_recherches | object | Filtres avances de chaque recherche |
visites | array | Toutes les visites (tous clients) |
documents_biens | object | Documents par bien_id (cle = bien_id, valeur = array) |
biens / documents / messages | array (vide) | Legacy, NE PAS SUPPRIMER (utilises par regenerateAll) |
Schema d'un bien
{
"id": 1772548615726, // timestamp Date.now()
"ordre": 0, // unique par client, sert au tri et numerotage (#ordre+1)
"recherche_id": "r1", // appartient a quelle recherche
"titre": "Appartement 3.5p",
"prix": "920'000",
"secteur": "Montreux",
"pieces": "3.5",
"surface": "82",
"etage": "2e",
"annee": "1985",
"travaux": "non",
"agence": "iad",
"contact": "+41 79 XXX",
"lien": "https://...",
"note_agent": "..."
}
Schema d'une visite
{
"id": 1773575578649,
"client_slug": "vincent",
"bien_id": "1800000000005",
"date": "2026-03-16", // ISO YYYY-MM-DD
"heure": "11:30", // HH:MM
"adresse_rdv": "Route de ...",
"statut_visite": "programmee", // programmee | visitee | retenu | controle
"note_agent": "",
"created_at": "2026-03-15T11:52:58.649Z"
}
06 · Logique
Regles metier
Numerotation des biens
Chaque bien a un champ ordre (entier). Le numero affiche partout est ordre + 1 (ex : #5).
- Unique globalement par client — pas par recherche. Vincent a 55 biens numerotes #1 a #55, toutes recherches confondues.
- Calcule a la creation :
ordre = max(ordres existants) + 1 dans la route POST.
- Affichage : utiliser
b.ordre + 1 partout (jamais indexOf ni i+1).
- Reorder : drag/drop met a jour le champ
ordre via PUT /api/biens-:slug-ordre.
Multi-clients / multi-recherches
- Architecture 100% slug-based. Aucun client n'est hardcode dans le code.
- Dashboard utilise
_clientBiensActif (variable JS globale) pour savoir quel client est selectionne.
- Un client peut avoir plusieurs recherches (ex : Vincent a "Riviera vaudoise" et "Nyon/Rolle").
- L'onglet Biens du dashboard a un selecteur de client + onglets de recherches.
Statuts d'un bien (cote client)
| Statut | Description |
a-visiter | Par defaut (non choisi) |
coup-de-coeur | Le client a marque ce bien comme favori |
retire | Le client a ecarte ce bien |
Statuts d'une visite
| Statut | Description |
programmee | Visite future planifiee |
visitee | Visite effectuee |
retenu | Coup de coeur confirme apres visite |
controle | Visite de controle a replanifier |
Visibilite des documents
- Un document a un champ
agent_only (boolean).
agent_only: true = visible uniquement dans le dashboard, jamais sur la page client.
agent_only: false = visible client (telechargeable depuis la fiche bien).
07 · Operations
SSH & operations courantes
Se connecter au serveur
# Connexion SSH (mot de passe demande, non documente ici)
ssh root@77.42.95.132
# Aller dans le projet
cd /root/wolff-immobilier
Bonne pratique
Utiliser une cle SSH plutot que le mot de passe. Generer une cle avec ssh-keygen -t ed25519 et l'ajouter via ssh-copy-id root@77.42.95.132.
Backup AVANT chaque modification
Obligatoire
Toujours faire un backup avant de toucher au code ou a la base. Une commande, une seconde, peut sauver des heures.
tar -czf /root/wolff-backup-$(date +%Y%m%d-%H%M).tar.gz /root/wolff-immobilier
Lister les backups
ls -lh /root/wolff-backup-*.tar.gz
Restaurer un backup
# Renommer l'actuel avant de restaurer
mv /root/wolff-immobilier /root/wolff-immobilier.broken
# Extraire le backup
tar -xzf /root/wolff-backup-20260512-1647.tar.gz -C /
# Restart
pm2 restart wolff
Restart du serveur applicatif
pm2 restart wolff
pm2 status
pm2 logs wolff --lines 10 --nostream
Restart Nginx
nginx -t # tester la config
systemctl reload nginx # appliquer sans coupure
systemctl restart nginx # redemarrer franchement
Update du systeme
apt update
DEBIAN_FRONTEND=noninteractive apt upgrade -y -o Dpkg::Options::="--force-confold"
apt autoremove -y
# Si reboot requis
[ -f /var/run/reboot-required ] && echo "REBOOT REQUIS"
reboot
Verifier le port et le service
# Le port 3000 doit etre en ecoute par node
ss -tlnp | grep 3000
# Le service systemd PM2 doit etre actif
systemctl is-active pm2-root
08 · Diagnostic
Commandes de depannage
Logs PM2
# Logs en direct
pm2 logs wolff
# Dernieres lignes seulement
pm2 logs wolff --lines 20 --nostream
# Vider les logs
pm2 flush wolff
Verifier la syntaxe du code
# server.js
node --check /root/wolff-immobilier/server.js
# JS dans dashboard.html (extraire avant)
SS=$(grep -n "^<script>" /root/wolff-immobilier/public/dashboard.html | head -1 | cut -d: -f1)
SE=$(grep -n "^</script>" /root/wolff-immobilier/public/dashboard.html | tail -1 | cut -d: -f1)
sed -n "$((SS+1)),$((SE-1))p" /root/wolff-immobilier/public/dashboard.html > /tmp/dash.js
node --check /tmp/dash.js
Tester l'API en live
# Obtenir un token
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
-d '{"email":"agent@wollf.fr","password":"PASSWORD"}' \
http://localhost:3000/api/auth/login | grep -oP '"token":"[^"]+' | cut -d'"' -f4)
# Tester une route protegee
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/biens-vincent
Inspecter la base
# Voir toutes les cles + tailles
node -e "
const db = require('/root/wolff-immobilier/data/db.json');
Object.keys(db).forEach(k => {
const v = db[k];
console.log(k, Array.isArray(v) ? v.length+' items' : typeof v);
});"
# Voir les visites
node -e "
const db = require('/root/wolff-immobilier/data/db.json');
db.visites.forEach(v => console.log(v.date, v.heure, v.client_slug, v.bien_id));"
Modifier la base proprement
node << 'EOF'
const fs = require('fs');
const path = '/root/wolff-immobilier/data/db.json';
const db = JSON.parse(fs.readFileSync(path, 'utf8'));
// Modification ici, par exemple :
// db.visites = db.visites.filter(v => v.date);
fs.writeFileSync(path, JSON.stringify(db, null, 2));
console.log('OK');
EOF
Disque, memoire, processus
df -h / # espace disque
free -h # memoire
pm2 monit # monitor interactif
htop # vue d'ensemble
09 · Attention
Pieges a eviter (FAQ developpeur)
Piege 1 — Ne JAMAIS supprimer data.biens / data.documents / data.messages
Meme si ces tableaux sont vides (legacy), regenerateAll() dans server.js y accede au demarrage. Les supprimer = le serveur crashe au boot.
Piege 2 — Routes biens : toujours genericiser sur le slug
Ne JAMAIS hardcoder un client dans une route. Utiliser '/api/biens-' + slug. La cle DB associee est 'biens_' + slug.replace(/-/g,'_') (Jeremy-Barnier devient biens_jeremy_barnier).
Piege 3 — Numero de bien : ordre+1, jamais indexOf ni i+1
Le numero affiche doit toujours etre b.ordre + 1. Utiliser indexOf(bien)+1 ou (b, i) => i+1 casse la coherence entre dashboard et page client, et entre recherches.
Piege 4 — Creer un bien : ordre = max+1 (pas length)
Dans la route POST, calculer ordre = Math.max(ordres existants) + 1. Utiliser data[key].length cree des doublons d'ordre des qu'on supprime un bien.
Piege 5 — KPI dashboard : utiliser data.visites, pas data.biens
Le KPI visites_semaine dans /api/dashboard/stats doit filtrer data.visites. Filtrer data.biens (legacy vide) retourne toujours 0.
Piege 6 — Python regex multiligne sur server.js
Les tirets unicode ── (U+2500) dans les commentaires posent souci en regex Python multiline. Preferer la suppression par numero de ligne (lines[:N] + lines[M:]) pour les gros blocs.
Piege 7 — Python : verifier la taille apres ecriture
Toujours valider len(s) > N avant d'ecrire un fichier. Une regex qui rate peut produire un fichier vide. Faire sys.exit(1) si la taille est suspecte.
Piege 8 — Visites sans date
Une visite avec date: "" casse les tris (localeCompare) et n'apparait nulle part. Toujours valider la date a la creation. Nettoyer la base si besoin : db.visites = db.visites.filter(v => v.date && v.heure).
Piege 9 — Generator.js utilise /api/biens-public/:slug
Si la route /api/biens-public/:slug est retiree, tous les nouveaux clients crees auront une page HTML cassee. Ne pas la supprimer.
Outils utiles
- Toujours backup avant modif (commande en section 07)
- Verifier syntaxe :
node --check fichier.js
- Lire les logs apres chaque restart :
pm2 logs wolff --lines 10 --nostream
- Tester routes avec curl avant de modifier le front
Conventions de code
- Routes API : pattern slug-based (
/api/<ressource>-:slug ou /api/<ressource>/:slug)
- Dashboard : variables d'etat globales prefixees par
_ (ex : _clientBiensActif, _visites)
- Style : Noir & Or, typographie Cormorant + DM Sans, jamais d'emoji dans l'UI (utiliser des symboles unicode discrets :
◈ ◎ ⌂ ◷ ◻ ⚙)
- Numero de bien : toujours
#<ordre+1>, dans une div en font-size:10px, color:gris, letter-spacing:1px