{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "project": {
    "name": "Xrencontre",
    "version": "1.0.0",
    "description": "Site de rencontre francophone — architecture complète, NoSQL, durcie contre squat serveur et faux profils.",
    "language": "fr-CA",
    "license": "Proprietary",
    "stack": {
      "runtime": "Node.js >= 20 LTS",
      "framework": "Express 4",
      "database": "MongoDB 7 (NoSQL)",
      "odm": "Mongoose 8",
      "auth": "JWT (HS256) + refresh tokens en cookie httpOnly SameSite=Strict",
      "frontend": "HTML5 + CSS3 + JS vanilla (pas de framework lourd)",
      "tls": "Let's Encrypt via certbot",
      "reverse_proxy": "Nginx (TLS, gzip, rate-limit, headers)"
    }
  },
  "principes_securite": {
    "objectifs": [
      "Empêcher tout accès direct au serveur (squat SSH, panneau admin exposé, services orphelins).",
      "Empêcher la création et la persistance de faux profils.",
      "Garantir la confidentialité des conversations privées.",
      "Limiter la surface d'attaque API (rate-limit, CORS strict, tokens signés)."
    ],
    "protection_serveur": {
      "firewall": "UFW: ports ouverts uniquement 22 (admin), 80, 443. Tous les autres droppés.",
      "ssh": {
        "port": 22,
        "PermitRootLogin": "no",
        "PasswordAuthentication": "no",
        "AllowUsers": "[utilisateur dédié, non-root]",
        "cle": "ed25519 minimum, passphrase obligatoire"
      },
      "fail2ban": {
        "jails": ["sshd", "nginx-http-auth", "nginx-botsearch", "nginx-limit-req"],
        "bantime": "1h",
        "findtime": "10m",
        "maxretry": 5
      },
      "mongodb": {
        "bind_ip": "127.0.0.1",
        "auth": "obligatoire",
        "tls": "interne",
        "utilisateurs": ["app_user (lecture/écriture)", "backup_user (lecture seule)"]
      },
      "nginx_headers": [
        "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload",
        "X-Frame-Options: DENY",
        "X-Content-Type-Options: nosniff",
        "Referrer-Policy: strict-origin-when-cross-origin",
        "Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'",
        "Permissions-Policy: geolocation=(), microphone=(), camera=()"
      ],
      "rate_limit_zones": [
        {"nom": "login", "limite": "5r/m", "burst": 3},
        {"nom": "register", "limite": "3r/m", "burst": 2},
        {"nom": "api", "limite": "60r/m", "burst": 20}
      ],
      "mises_a_jour": "unattended-upgrades activé",
      "audit": "auditd + logrotate + sauvegarde quotidienne chiffrée hors site",
      "panneau_admin": "Accessible uniquement via VPN WireGuard"
    },
    "anti_faux_profils": {
      "verification_email": {
        "obligatoire": true,
        "expiration_token": "24h",
        "domaines_jetables_bloques": true
      },
      "verification_telephone": {
        "obligatoire_pour_actions": ["envoyer message", "voir photos privées"],
        "fournisseur": "Twilio Verify",
        "tentatives_max": 3
      },
      "captcha": {
        "fournisseur": "hCaptcha ou Cloudflare Turnstile",
        "pages": ["inscription", "connexion après 2 échecs", "récupération mot de passe"]
      },
      "detection_bots": {
        "honeypot": "champ caché 'website' (rempli = rejet)",
        "delai_minimum_formulaire": "3 secondes",
        "fingerprint": "User-Agent + IP + canvas hash"
      },
      "moderation": {
        "photos": "Classifier NSFW + revue humaine",
        "texte_profil": "Filtrage liens/numéros (anti-spam)",
        "signalement": "Bouton sur chaque profil et message",
        "seuil_auto_suspension": 3
      },
      "score_confiance": {
        "facteurs": ["email vérifié", "téléphone vérifié", "photo modérée", "ancienneté", "signalements"],
        "valeur": "0 à 100",
        "seuil_visible": 40
      }
    }
  },
  "modeles_donnees": {
    "User": {
      "_id": "ObjectId",
      "email": {"type": "string", "unique": true, "verifie": true},
      "telephone": {"type": "string", "verifie": false},
      "passwordHash": "bcrypt cost=12",
      "role": {"enum": ["user", "moderator", "admin"], "defaut": "user"},
      "statut": {"enum": ["actif", "suspendu", "supprime"], "defaut": "actif"},
      "scoreConfiance": {"min": 0, "max": 100, "defaut": 10},
      "twoFactor": {"active": false, "secret": "chiffré AES-256"},
      "creeLe": "Date",
      "derniereConnexion": "Date",
      "ipDerniereConnexion_hash": "SHA-256"
    },
    "Profile": {
      "userId": "ref User",
      "pseudo": {"unique": true, "min": 3, "max": 24},
      "dateNaissance": {"min_age": 18},
      "genre": ["homme", "femme", "non-binaire", "autre"],
      "orientation": ["hetero", "gay", "lesbienne", "bi", "pan", "autre"],
      "ville": "string",
      "geo": "GeoJSON Point",
      "bio": "max 500 caractères",
      "interets": "max 10 tags",
      "photos": "tableau (url, moderée, principale)",
      "preferences": {"genreRecherche": [], "ageMin": 18, "ageMax": 99, "distanceKm": 50}
    },
    "Match": {
      "users": "array<ObjectId> longueur 2",
      "score": "0-100",
      "statut": ["en_attente", "mutuel", "rejete"]
    },
    "Message": {
      "conversationId": "ObjectId",
      "expediteurId": "ref User",
      "destinataireId": "ref User",
      "contenu_chiffre": "AES-256-GCM",
      "envoyeLe": "Date",
      "luLe": "Date | null"
    },
    "Report": {
      "rapporteurId": "ObjectId",
      "cibleId": "ObjectId",
      "type": ["faux_profil", "harcelement", "contenu_inapproprie", "spam"],
      "statut": ["ouvert", "en_revue", "ferme_valide", "ferme_invalide"]
    },
    "AuditLog": {
      "action": "string",
      "userId": "ObjectId | null",
      "ip_hash": "string",
      "succes": "boolean",
      "horodatage": "Date"
    }
  },
  "endpoints_api": {
    "base": "/api/v1",
    "routes": [
      {"methode": "POST", "chemin": "/auth/register", "auth": false, "rate_limit": "3/min/IP"},
      {"methode": "POST", "chemin": "/auth/verify-email", "auth": false},
      {"methode": "POST", "chemin": "/auth/login", "auth": false, "rate_limit": "5/min/IP"},
      {"methode": "POST", "chemin": "/auth/refresh", "auth": "cookie"},
      {"methode": "POST", "chemin": "/auth/logout", "auth": true},
      {"methode": "GET",  "chemin": "/profile/me", "auth": true},
      {"methode": "PUT",  "chemin": "/profile/me", "auth": true},
      {"methode": "POST", "chemin": "/profile/photo", "auth": true},
      {"methode": "GET",  "chemin": "/profile/:pseudo", "auth": true},
      {"methode": "POST", "chemin": "/match/search", "auth": true},
      {"methode": "POST", "chemin": "/match/like/:userId", "auth": true},
      {"methode": "POST", "chemin": "/match/pass/:userId", "auth": true},
      {"methode": "GET",  "chemin": "/match/mutuels", "auth": true},
      {"methode": "GET",  "chemin": "/conversations", "auth": true},
      {"methode": "GET",  "chemin": "/conversations/:id", "auth": true},
      {"methode": "POST", "chemin": "/conversations/:id/messages", "auth": true},
      {"methode": "POST", "chemin": "/reports", "auth": true},
      {"methode": "GET",  "chemin": "/admin/reports", "auth": "role:moderator+"},
      {"methode": "POST", "chemin": "/admin/users/:id/suspendre", "auth": "role:admin"}
    ]
  },
  "pages_frontend": [
    {"chemin": "/", "fichier": "index.html"},
    {"chemin": "/inscription", "fichier": "register.html"},
    {"chemin": "/connexion", "fichier": "login.html"},
    {"chemin": "/profil", "fichier": "profile.html", "auth": true},
    {"chemin": "/recherche", "fichier": "search.html", "auth": true},
    {"chemin": "/messages", "fichier": "messages.html", "auth": true}
  ],
  "deploiement": {
    "variables_env": [
      "NODE_ENV", "PORT", "MONGO_URI",
      "JWT_SECRET", "JWT_REFRESH_SECRET",
      "BCRYPT_COST",
      "SMTP_HOST", "SMTP_USER", "SMTP_PASS",
      "TWILIO_SID", "TWILIO_TOKEN", "TWILIO_VERIFY_SERVICE",
      "HCAPTCHA_SECRET",
      "AES_KEY",
      "CORS_ORIGIN", "COOKIE_DOMAIN"
    ],
    "commandes": {
      "install": "npm ci",
      "dev": "npm run dev",
      "prod": "pm2 start backend/server.js --name xrencontre",
      "seed_admin": "node backend/scripts/seed-admin.js"
    }
  }
}
