feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
81
docs/COMPTES-ACCES.md
Normal file
81
docs/COMPTES-ACCES.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Comptes et accès (référence locale)
|
||||
|
||||
> **Sécurité** : les mots de passe et secrets **réels** ne doivent **pas** être commités dans ce dépôt. Ils vivent dans ton fichier **`.env`** à la racine (non versionné). Ce document décrit les **rôles** et les **valeurs d’exemple / défauts documentés** dans le code ou les fichiers d’exemple.
|
||||
|
||||
---
|
||||
|
||||
## 1. Compte administrateur (API / tableau de bord admin)
|
||||
|
||||
L’admin n’est **pas** un utilisateur en base : l’identifiant et le mot de passe viennent des **variables d’environnement**.
|
||||
|
||||
| Champ | Variable | Où c’est défini |
|
||||
|--------|-----------|------------------|
|
||||
| Identifiant (login) | `ADMIN_USERNAME` | `.env` (exemple dans `.env.example` : `admin`) |
|
||||
| Mot de passe | `ADMIN_PASSWORD` **ou** hash `ADMIN_PASSWORD_HASH` | `.env` uniquement |
|
||||
| Secours dev (si les deux absents) | `ADMIN_DEV_DEFAULT` | `.env` (voir `routes/admin_routes.py`) |
|
||||
|
||||
**Comment connaître *ton* mot de passe admin :**
|
||||
|
||||
- Si `ADMIN_PASSWORD` est renseigné en clair dans `.env`, c’est cette valeur.
|
||||
- Si seul `ADMIN_PASSWORD_HASH` est renseigné, le mot de passe en clair **n’apparaît nulle part** dans le dépôt : c’est celui que **tu** as choisi lors de la configuration (bcrypt). Il n’y a pas de liste centralisée à extraire automatiquement.
|
||||
|
||||
**Placeholder documenté** (fichier `.env.example`, pas un secret réel) :
|
||||
|
||||
| Compte | Mot de passe (exemple fichier exemple) |
|
||||
|--------|------------------------------------------|
|
||||
| `admin` | `your_admin_password_here` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Comptes utilisateurs de l’application (inscription)
|
||||
|
||||
Les utilisateurs finaux (**email + mot de passe**) sont créés via l’API d’**inscription** (`/api/v1/auth/register` ou équivalent selon version). Il **n’existe pas** de liste de comptes utilisateurs livrée avec le projet : chaque environnement a ses propres comptes, stockés en base (PostgreSQL / SQLite selon config) avec mot de passe **haché**.
|
||||
|
||||
**Liste des comptes utilisateurs avec mots de passe en clair :** inexistante dans le code ; à consulter uniquement via ta base ou les personnes qui se sont inscrites.
|
||||
|
||||
---
|
||||
|
||||
## 3. PostgreSQL (Docker **développement** uniquement)
|
||||
|
||||
Fichier `docker-compose.dev.yml` — valeurs **par défaut** si tu ne les surcharges pas dans `.env` :
|
||||
|
||||
| Rôle | Utilisateur | Mot de passe | Base |
|
||||
|------|-------------|--------------|------|
|
||||
| PostgreSQL | `translate` (ou `POSTGRES_USER`) | `translate_secret_123` (ou `POSTGRES_PASSWORD`) | `translate_db` (ou `POSTGRES_DB`) |
|
||||
|
||||
En **production** (`docker-compose.yml`), le mot de passe PostgreSQL **doit** être défini dans `.env` ; il n’y a pas de défaut documenté dans le compose prod.
|
||||
|
||||
---
|
||||
|
||||
## 4. Grafana (si le service est activé dans `docker-compose.yml`)
|
||||
|
||||
D’après les variables du compose :
|
||||
|
||||
| Compte | Utilisateur par défaut | Mot de passe par défaut |
|
||||
|--------|------------------------|-------------------------|
|
||||
| Grafana | `GRAFANA_USER` ou `admin` | `GRAFANA_PASSWORD` ou `admin` |
|
||||
|
||||
À remplacer impérativement en production.
|
||||
|
||||
---
|
||||
|
||||
## 5. Redis
|
||||
|
||||
Pas de « compte » utilisateur classique pour l’app en dev simple ; l’URL est `REDIS_URL` dans `.env` si configurée.
|
||||
|
||||
---
|
||||
|
||||
## Synthèse
|
||||
|
||||
| Type de compte | Liste + mots de passe dans le repo ? |
|
||||
|----------------|----------------------------------------|
|
||||
| Admin | **Non** — tout est dans `.env` (éventuellement hash seulement). |
|
||||
| Utilisateurs app | **Non** — inscription + base de données. |
|
||||
| Postgres (dev compose) | **Oui** — défauts ci-dessus (`translate` / `translate_secret_123`). |
|
||||
| Grafana (optionnel) | **Oui** — défaut `admin` / `admin` si non surchargé. |
|
||||
|
||||
Pour un inventaire **personnel** incluant ton vrai mot de passe admin, ouvre ton **`.env` local** (ne le commite pas) ou garde une copie chiffrée / hors dépôt.
|
||||
|
||||
|
||||
mot de passe admin
|
||||
ChangeMeAfterReset!
|
||||
51
docs/GESTION-UTILISATEURS.md
Normal file
51
docs/GESTION-UTILISATEURS.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Gestion des utilisateurs — référence technique
|
||||
|
||||
## 1. Deux emplacements possibles pour un compte
|
||||
|
||||
| Emplacement | Fichier / système | Quand c’est utilisé |
|
||||
|-------------|-------------------|---------------------|
|
||||
| **Base SQLAlchemy** | Table `users` dans `data/translate.db` (SQLite par défaut) ou PostgreSQL | Inscription et authentification lorsque le module base est chargé (`services/auth_service.py`). |
|
||||
| **Fichier JSON** | `data/users.json` | Ancien mode « fichier seul », ou compte créé avant migration, ou utilisateur présent uniquement ici (legacy). |
|
||||
|
||||
L’authentification (`get_user_by_email`, etc.) lit **d’abord la base**, puis **repli** sur `users.json` si l’utilisateur n’y est pas (`services/auth_service.py`).
|
||||
|
||||
## 2. Bug corrigé : liste admin vide alors qu’un compte existe
|
||||
|
||||
**Cause** : `GET /api/v1/admin/users` ne lisait **que** la table SQL `users`. Un compte présent **uniquement** dans `data/users.json` n’apparaissait **jamais**.
|
||||
|
||||
**Correction** : la route agrège :
|
||||
|
||||
1. Tous les utilisateurs de la base (ordre `created_at` décroissant).
|
||||
2. Les entrées de `users.json` dont l’`id` **et** l’e-mail (normalisé) ne sont **pas** déjà couverts par la base (évite les doublons).
|
||||
|
||||
Chaque ligne comporte un champ **`storage`** : `database` ou `json_file`.
|
||||
|
||||
## 3. Endpoints admin concernés
|
||||
|
||||
| Méthode | Chemin | Rôle |
|
||||
|---------|--------|------|
|
||||
| `GET` | `/api/v1/admin/users` | Liste fusionnée (base + JSON-only). |
|
||||
| `PATCH` | `/api/v1/admin/users/{user_id}` | Changement de plan (`plan`). |
|
||||
| `POST` | `/api/v1/admin/users/{user_id}/password` | Corps JSON `{ "new_password": "..." }` (min. 8 caractères). Définit le hash en base **ou** dans `users.json` selon où vit le compte. |
|
||||
|
||||
Authentification : en-tête `Authorization: Bearer <token admin>` (pas le JWT utilisateur).
|
||||
|
||||
## 4. Réinitialisation du mot de passe (compte existant, mot de passe oublié)
|
||||
|
||||
1. Se connecter à l’**admin**.
|
||||
2. Aller dans **Utilisateurs**.
|
||||
3. Repérer la ligne (colonne **Stockage** : **Base** ou **JSON**).
|
||||
4. Cliquer **MDP**, saisir le nouveau mot de passe deux fois, valider.
|
||||
|
||||
Il n’existe pas encore de flux « mot de passe oublié » par **e-mail** côté utilisateur final (pas d’envoi SMTP dans ce flux).
|
||||
|
||||
## 5. Statistiques admin (`GET /api/v1/admin/stats`)
|
||||
|
||||
Le total d’utilisateurs et la répartition par plan incluent désormais **base + utilisateurs JSON-only**, de façon cohérente avec la liste utilisateurs.
|
||||
|
||||
## 6. Fichiers de code principaux
|
||||
|
||||
- `routes/admin_routes.py` — `get_admin_users`, `admin_reset_user_password`, `get_admin_stats`.
|
||||
- `services/auth_service.py` — `admin_set_user_password`, `load_users`, `get_user_by_email`.
|
||||
- `database/repositories.py` — `UserRepository`.
|
||||
- `frontend/src/app/admin/users/` — tableau, dialogue mot de passe, hooks.
|
||||
116
docs/PRIX-ABONNEMENTS.md
Normal file
116
docs/PRIX-ABONNEMENTS.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Prix et abonnements — référence exacte
|
||||
|
||||
Ce document décrit **uniquement** le comportement actuel du code (chemins, formules, endpoints). Aucune phrase générique : chaque affirmation correspond à une implémentation précise.
|
||||
|
||||
---
|
||||
|
||||
## 1. Où sont stockés les données tarifaires
|
||||
|
||||
| Rôle | Emplacement | Contenu |
|
||||
|------|-------------|---------|
|
||||
| Surcharges (prix modifiables par l’admin) | Fichier **`data/pricing_overrides.json`** à la racine du dépôt backend (répertoire de travail du processus Python) | Pour chaque clé `starter`, `pro`, `business` : `price_monthly`, `price_yearly` (recalculé par le serveur), `stripe_price_id_monthly`, `stripe_price_id_yearly` (optionnel, chaînes vides possibles). |
|
||||
| Variables d’environnement | Fichier **`.env`** à la racine du backend | Clés `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`, et `STRIPE_PRICE_STARTER_MONTHLY`, `STRIPE_PRICE_STARTER_YEARLY`, idem pour `PRO` et `BUSINESS`. L’admin peut mettre à jour ce fichier lors d’une sauvegarde. |
|
||||
| Valeurs par défaut du code | **`models/subscription.py`**, dictionnaire **`PLANS`** | Utilisé si une valeur n’existe pas dans `pricing_overrides.json` pour le **prix mensuel** de chaque plan. Les quotas (documents/mois, pages, etc.) et les textes de fonctionnalités viennent **toujours** de `PLANS`, pas du JSON d’override. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Qui peut modifier les prix
|
||||
|
||||
- **Interface admin** : page **Pricing & Stripe** (`frontend/src/app/admin/pricing/page.tsx`).
|
||||
- **API** : uniquement avec un **jeton administrateur** valide.
|
||||
- **Lecture** : `GET /api/v1/admin/pricing`
|
||||
- **Écriture** : `PUT /api/v1/admin/pricing`
|
||||
- **Création automatique Stripe** : `POST /api/v1/admin/pricing/setup-stripe`
|
||||
|
||||
Les utilisateurs non admin **ne** peuvent **pas** modifier les tarifs. Ils ne voient que la liste publique (voir section 5).
|
||||
|
||||
---
|
||||
|
||||
## 3. Règle de calcul du prix annuel (pas de choix libre)
|
||||
|
||||
Le serveur impose une seule règle :
|
||||
|
||||
```
|
||||
prix_annuel = arrondi(prix_mensuel × 12 × 0,8 ; 2 décimales)
|
||||
```
|
||||
|
||||
- **Constante dans le code** : `YEARLY_DISCOUNT_FACTOR = 0.8` dans `services/pricing_config.py`.
|
||||
- **Interprétation** : le total annuel payé d’un coup = **80 %** de ce que coûteraient **12 mois** au tarif mensuel. C’est **équivalent** à une réduction de **20 %** sur ce total « 12 × mensuel ».
|
||||
- **Constante affichée** : `ANNUAL_DISCOUNT_PERCENT = 20` (même valeur, autre formulation).
|
||||
|
||||
**Conséquence** : tu ne définis pas un annuel arbitraire. Tu définis le **mensuel** ; le serveur **recalcule** l’annuel et l’enregistre dans `pricing_overrides.json` lors d’un `PUT`. Un champ `price_yearly` envoyé par un client est **ignoré** pour la logique métier (le serveur normalise).
|
||||
|
||||
**Plage autorisée pour le prix mensuel** (forfaits payants `starter` / `pro` / `business`) : **entre 0,01 € et 50 000 €** (validation dans `validate_monthly_price_eur`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Ordre de résolution pour les prix affichés et le paiement
|
||||
|
||||
### 4.1 Montants en euros (mensuel / annuel)
|
||||
|
||||
Pour un plan `starter`, `pro` ou `business` :
|
||||
|
||||
1. Lire `price_monthly` dans **`data/pricing_overrides.json`** pour ce plan.
|
||||
2. Si absent, utiliser `price_monthly` dans **`PLANS`** (`models/subscription.py`).
|
||||
3. Calculer `price_yearly` avec la formule de la section 3.
|
||||
|
||||
Pour les plans **gratuit** (`free`) et **entreprise** (`enterprise`), les montants spéciaux (0 ou « sur devis ») viennent de **`PLANS`** ; **pas** de même logique de remise annuelle.
|
||||
|
||||
### 4.2 Identifiants Stripe Price (`price_…`)
|
||||
|
||||
Pour savoir quel Stripe Price utiliser au checkout :
|
||||
|
||||
1. **D’abord** : champs `stripe_price_id_monthly` and `stripe_price_id_yearly` dans **`data/pricing_overrides.json`** pour le plan.
|
||||
2. **Sinon** : variables d’environnement `STRIPE_PRICE_<PLAN>_MONTHLY` et `STRIPE_PRICE_<PLAN>_YEARLY` (ex. `STRIPE_PRICE_STARTER_MONTHLY`), après **rechargement** du fichier `.env` en mémoire pour le processus (voir `reload_dotenv_from_dotenv_file` dans `services/pricing_config.py`).
|
||||
|
||||
Il n’y a **plus** de repli sur les `os.getenv` figés dans le dictionnaire `PLANS` au moment de l’import du module pour ces identifiants.
|
||||
|
||||
### 4.3 Comportement du checkout Stripe (`services/payment_service.py`)
|
||||
|
||||
1. Si un **identifiant** `price_…` valide est trouvé pour la période **mensuelle** ou **annuelle** : la session Checkout utilise **ce** Stripe Price (montant défini **dans Stripe**).
|
||||
2. Sinon : création d’une ligne avec **`price_data`** (montant en centimes) calculé à partir des **mêmes** règles euros que l’affichage (`get_subscription_line_amount_eur`), **sans** redémarrage du serveur tant que `pricing_overrides.json` est à jour.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce que voit le site public (sans être admin)
|
||||
|
||||
- **Endpoint HTTP** : `GET /api/v1/auth/plans` (sans authentification).
|
||||
- **Réponse** : liste des plans avec `price_monthly`, `price_yearly`, `annual_discount_percent`, et les limites (documents, pages, etc.) issues de **`PLANS`**.
|
||||
- **Métadonnées** : `meta.annual_discount_percent` (20) et `meta.yearly_discount_factor` (0,8).
|
||||
|
||||
La page **`/pricing`** du frontend charge cet endpoint et remplace les données statiques de secours si la réponse est valide.
|
||||
|
||||
---
|
||||
|
||||
## 6. Packs de crédits (hors admin)
|
||||
|
||||
Les **prix des packs de crédits** (nombre de crédits, prix en euros) sont définis dans **`models/subscription.py`**, structure **`CREDIT_PACKAGES`**. Ils **ne** sont **pas** modifiables via l’écran **Pricing & Stripe** actuel. Ils apparaissent dans `GET /api/v1/auth/plans` sous `credit_packages`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Bouton « Créer automatiquement » (Stripe)
|
||||
|
||||
- **Endpoint** : `POST /api/v1/admin/pricing/setup-stripe`.
|
||||
- **Condition** : `STRIPE_SECRET_KEY` présente dans l’environnement et commençant par `sk_`.
|
||||
- **Effet** : le backend crée ou retrouve des produits Stripe et des **prix mensuels et annuels** en **euros** dont les montants **en centimes** sont calculés à partir des **mêmes** règles euros que ci-dessus (mensuel effectif + annuel = `mensuel × 12 × 0,8`). Les identifiants `price_…` sont enregistrés dans **`.env`** et dans **`data/pricing_overrides.json`**.
|
||||
|
||||
---
|
||||
|
||||
## 8. Redémarrage du serveur
|
||||
|
||||
- **Pas requis** pour que les **montants** (JSON) soient pris en compte : le fichier est lu à chaque requête qui appelle `load_pricing_overrides()`.
|
||||
- Après modification du **`.env`** par l’admin, le code appelle **`apply_runtime_config_after_admin_write()`** pour recharger les variables dans le processus courant.
|
||||
|
||||
En cas de **plusieurs processus** (plusieurs workers), chaque processus recharge les variables lorsqu’il en a besoin ou après une écriture admin sur le même worker ; le fichier **`data/pricing_overrides.json`** est partagé sur le disque.
|
||||
|
||||
---
|
||||
|
||||
## 9. Résumé opérationnel (ce que tu fais concrètement)
|
||||
|
||||
1. Te connecter en **admin**.
|
||||
2. Ouvrir **Pricing & Stripe**.
|
||||
3. Saisir le **prix mensuel** pour Starter, Pro, Business (l’annuel affiché est calculé ; il n’est pas saisi librement).
|
||||
4. Optionnel : renseigner les **Price ID** Stripe ou utiliser **Créer automatiquement** si la clé secrète Stripe est configurée.
|
||||
5. Cliquer sur **Sauvegarder la configuration**.
|
||||
|
||||
Les changements sont persistés dans **`data/pricing_overrides.json`** et, le cas échéant, dans **`.env`**. Les pages publiques qui consomment **`GET /api/v1/auth/plans`** reflètent les nouveaux montants sans redémarrage obligatoire du serveur.
|
||||
Reference in New Issue
Block a user