deploy: notesfrais — 2026-04-29 09:57:19
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
# NotesFrais
|
||||
|
||||
PWA de gestion des notes de frais — capture photo, OCR, génération PDF, envoi par e-mail et export SharePoint.
|
||||
|
||||
## Stack technique
|
||||
|
||||
| Couche | Techno |
|
||||
|--------|--------|
|
||||
| Frontend | React 18 + Vite + TailwindCSS + TanStack Query v5 |
|
||||
| Backend | Node.js 20 + Express + TypeScript |
|
||||
| Base de données | PostgreSQL 16 |
|
||||
| Stockage fichiers | Volume Docker (`/app/uploads`) |
|
||||
| Authentification | JWT (access 15 min + refresh 30 j) |
|
||||
| PDF | pdf-lib |
|
||||
| E-mail | Nodemailer (SMTP par société) |
|
||||
| SharePoint | Microsoft Graph API (client_credentials) |
|
||||
| PWA / offline | vite-plugin-pwa + Workbox + IndexedDB queue |
|
||||
| Déploiement | Docker multi-stage + Coolify + Traefik (HTTPS) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Pré-requis
|
||||
|
||||
- Docker ≥ 24 et Docker Compose v2
|
||||
- Un domaine pointant sur le VPS (ex. `frais.domench.fr`)
|
||||
- Coolify installé sur le VPS (gère le routage HTTPS via Traefik)
|
||||
- Un **App Registration Azure / Entra ID** pour Microsoft Graph (voir §5)
|
||||
|
||||
---
|
||||
|
||||
## 2. Installation
|
||||
|
||||
### 2.1 Cloner le dépôt
|
||||
|
||||
```bash
|
||||
git clone <url-du-repo> notesfrais
|
||||
cd notesfrais
|
||||
```
|
||||
|
||||
### 2.2 Créer le fichier `.env`
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Éditer `.env` et remplir toutes les valeurs `CHANGE_ME` :
|
||||
|
||||
| Variable | Description | Commande de génération |
|
||||
|----------|-------------|------------------------|
|
||||
| `DOMAIN` | Domaine public (sans `https://`) | — |
|
||||
| `DB_PASSWORD` | Mot de passe PostgreSQL | `openssl rand -hex 32` |
|
||||
| `JWT_SECRET` | Secret de signature JWT | `openssl rand -hex 64` |
|
||||
| `APP_SECRET` | Clé AES-256 pour chiffrement BDD | `openssl rand -hex 32` |
|
||||
| `GREG_EMAIL` | E-mail du compte Greg (init uniquement) | — |
|
||||
| `GREG_PASSWORD` | Mot de passe du compte Greg | — |
|
||||
| `GAEL_EMAIL` | E-mail du compte Gaël (init uniquement) | — |
|
||||
| `GAEL_PASSWORD` | Mot de passe du compte Gaël | — |
|
||||
|
||||
### 2.3 Premier démarrage
|
||||
|
||||
```bash
|
||||
# Build et démarrage des conteneurs
|
||||
docker compose up -d --build
|
||||
|
||||
# Vérifier que tout est sain
|
||||
docker compose ps
|
||||
docker compose logs backend --tail=50
|
||||
```
|
||||
|
||||
Le script `docker-entrypoint.sh` attend automatiquement que PostgreSQL soit prêt, puis exécute la migration.
|
||||
|
||||
### 2.4 Créer les utilisateurs initiaux
|
||||
|
||||
Cette commande n'est à exécuter **qu'une seule fois**, juste après le premier démarrage :
|
||||
|
||||
```bash
|
||||
docker compose exec backend node dist/scripts/initUsers.js
|
||||
```
|
||||
|
||||
Une fois les comptes créés, vous pouvez supprimer les variables `GREG_*` et `GAEL_*` du `.env`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Déploiement avec Coolify
|
||||
|
||||
1. Dans Coolify, créer un nouveau service de type **Docker Compose**.
|
||||
2. Pointer sur le dépôt Git (branche `main`).
|
||||
3. Dans **Variables d'environnement**, renseigner les mêmes clés que dans `.env`.
|
||||
4. Dans **Domaines**, configurer `frais.domench.fr` → port `80` (Traefik gère le TLS).
|
||||
5. Lancer le build ; Coolify s'occupe du certificat Let's Encrypt.
|
||||
|
||||
> **Astuce** : le `docker-compose.yml` expose uniquement le port 80 du frontend.
|
||||
> Traefik route le trafic HTTPS → HTTP interne. Le backend n'est pas exposé directement.
|
||||
|
||||
---
|
||||
|
||||
## 4. Paramétrage in-app
|
||||
|
||||
Une fois connecté, aller dans **Réglages** :
|
||||
|
||||
### 4.1 Sociétés
|
||||
|
||||
Ajouter chaque société avec son nom et son adresse e-mail de contact.
|
||||
|
||||
### 4.2 Configuration e-mail (par société)
|
||||
|
||||
Renseigner les paramètres SMTP pour chaque société (hôte, port, TLS, utilisateur, mot de passe).
|
||||
Le mot de passe SMTP est chiffré en AES-256-GCM avant d'être stocké en base.
|
||||
|
||||
### 4.3 Microsoft Graph / SharePoint
|
||||
|
||||
Renseigner une seule fois pour **toutes les sociétés** :
|
||||
|
||||
| Champ | Description |
|
||||
|-------|-------------|
|
||||
| Tenant ID | ID du tenant Azure (voir §5) |
|
||||
| Client ID | ID de l'App Registration |
|
||||
| Client Secret | Secret de l'App Registration |
|
||||
| Site ID | ID du site SharePoint (voir §5.4) |
|
||||
| Item ID | ID du fichier Excel (voir §5.5) |
|
||||
| Nom de feuille | Nom de l'onglet Excel (ex. `App`) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Configuration Microsoft Graph (Azure / Entra ID)
|
||||
|
||||
### 5.1 Créer l'App Registration
|
||||
|
||||
1. Aller sur [portal.azure.com](https://portal.azure.com) → **Microsoft Entra ID** → **Inscriptions d'applications** → **Nouvelle inscription**.
|
||||
2. Nom : `NotesFrais` — Type de compte : **Locataire unique**.
|
||||
3. URI de redirection : aucune (flux client_credentials).
|
||||
4. **Créer**.
|
||||
|
||||
### 5.2 Créer un secret client
|
||||
|
||||
1. Dans l'app → **Certificats et secrets** → **Nouveau secret client**.
|
||||
2. Durée recommandée : 24 mois.
|
||||
3. **Copier la valeur immédiatement** (elle n'est visible qu'une fois).
|
||||
|
||||
### 5.3 Accorder les permissions API
|
||||
|
||||
1. **Autorisations API** → **Ajouter une autorisation** → **Microsoft Graph** → **Autorisations d'application**.
|
||||
2. Ajouter :
|
||||
- `Files.ReadWrite.All`
|
||||
- `Sites.ReadWrite.All`
|
||||
3. Cliquer **Accorder le consentement administrateur** (bouton vert) — indispensable pour les permissions d'application.
|
||||
|
||||
### 5.4 Obtenir le Site ID SharePoint
|
||||
|
||||
Dans [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) (connecté avec un compte admin) :
|
||||
|
||||
```
|
||||
GET https://graph.microsoft.com/v1.0/sites/<tenant>.sharepoint.com:/sites/<nom-du-site>
|
||||
```
|
||||
|
||||
Remplacer `<tenant>` par le nom du tenant (ex. `1dotech`) et `<nom-du-site>` par le chemin du site.
|
||||
Récupérer la valeur du champ `id` dans la réponse (format : `host,guid1,guid2`).
|
||||
|
||||
### 5.5 Obtenir l'Item ID du fichier Excel
|
||||
|
||||
```
|
||||
GET https://graph.microsoft.com/v1.0/sites/<site-id>/drive/root/children
|
||||
```
|
||||
|
||||
Trouver le fichier Excel dans la liste et copier son champ `id`.
|
||||
|
||||
> **Alternative** : naviguer dans l'arborescence avec
|
||||
> `GET /sites/<site-id>/drive/root:/<chemin/vers/fichier.xlsx>`
|
||||
|
||||
---
|
||||
|
||||
## 6. Mise à jour
|
||||
|
||||
```bash
|
||||
git pull
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Le `docker-entrypoint.sh` ré-applique la migration à chaque démarrage (toutes les instructions SQL utilisent `IF NOT EXISTS` / `ON CONFLICT DO NOTHING` — elles sont idempotentes).
|
||||
|
||||
---
|
||||
|
||||
## 7. Sauvegarde et restauration
|
||||
|
||||
### Sauvegarde de la base de données
|
||||
|
||||
```bash
|
||||
# Dump compressé horodaté
|
||||
docker compose exec db pg_dump -U notesfrais notesfrais \
|
||||
| gzip > "backup_notesfrais_$(date +%Y%m%d_%H%M%S).sql.gz"
|
||||
```
|
||||
|
||||
### Sauvegarde des fichiers uploadés
|
||||
|
||||
```bash
|
||||
# Copie locale du volume uploads
|
||||
docker cp $(docker compose ps -q backend):/app/uploads ./uploads_backup
|
||||
```
|
||||
|
||||
### Restauration de la base de données
|
||||
|
||||
```bash
|
||||
# Arrêter le backend le temps de la restauration
|
||||
docker compose stop backend
|
||||
|
||||
# Restaurer
|
||||
gunzip -c backup_notesfrais_YYYYMMDD_HHMMSS.sql.gz \
|
||||
| docker compose exec -T db psql -U notesfrais notesfrais
|
||||
|
||||
# Redémarrer
|
||||
docker compose start backend
|
||||
```
|
||||
|
||||
### Restauration des fichiers
|
||||
|
||||
```bash
|
||||
docker cp ./uploads_backup/. $(docker compose ps -q backend):/app/uploads/
|
||||
```
|
||||
|
||||
### Script de sauvegarde automatique (cron)
|
||||
|
||||
Créer `/etc/cron.daily/notesfrais-backup` :
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
BACKUP_DIR=/var/backups/notesfrais
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Garder 30 jours
|
||||
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
|
||||
|
||||
# Dump BDD
|
||||
cd /chemin/vers/notesfrais
|
||||
docker compose exec -T db pg_dump -U notesfrais notesfrais \
|
||||
| gzip > "$BACKUP_DIR/db_$(date +%Y%m%d_%H%M%S).sql.gz"
|
||||
```
|
||||
|
||||
```bash
|
||||
chmod +x /etc/cron.daily/notesfrais-backup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Logs et débogage
|
||||
|
||||
```bash
|
||||
# Tous les services
|
||||
docker compose logs -f
|
||||
|
||||
# Backend uniquement
|
||||
docker compose logs -f backend
|
||||
|
||||
# Vérifier l'état des conteneurs
|
||||
docker compose ps
|
||||
|
||||
# Accéder au shell backend
|
||||
docker compose exec backend sh
|
||||
|
||||
# Console PostgreSQL
|
||||
docker compose exec db psql -U notesfrais notesfrais
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Variables d'environnement — référence complète
|
||||
|
||||
### Fichier `.env` (racine, pour docker-compose)
|
||||
|
||||
| Variable | Obligatoire | Défaut | Description |
|
||||
|----------|-------------|--------|-------------|
|
||||
| `DOMAIN` | Oui | `frais.domench.fr` | Domaine public |
|
||||
| `DB_PASSWORD` | Oui | — | Mot de passe PostgreSQL |
|
||||
| `JWT_SECRET` | Oui | — | Secret JWT (≥ 64 octets hex) |
|
||||
| `APP_SECRET` | Oui | — | Clé AES-256 (32 octets hex) |
|
||||
| `GREG_EMAIL` | Init seult. | — | E-mail compte Greg |
|
||||
| `GREG_PASSWORD` | Init seult. | — | Mot de passe compte Greg |
|
||||
| `GAEL_EMAIL` | Init seult. | — | E-mail compte Gaël |
|
||||
| `GAEL_PASSWORD` | Init seult. | — | Mot de passe compte Gaël |
|
||||
|
||||
### Variables injectées par docker-compose (non à configurer dans `.env`)
|
||||
|
||||
| Variable | Valeur fixée dans docker-compose.yml |
|
||||
|----------|--------------------------------------|
|
||||
| `NODE_ENV` | `production` |
|
||||
| `PORT` | `3001` |
|
||||
| `DATABASE_URL` | construit depuis `DB_PASSWORD` |
|
||||
| `FRONTEND_URL` | construit depuis `DOMAIN` |
|
||||
| `UPLOADS_DIR` | `/app/uploads` |
|
||||
|
||||
---
|
||||
|
||||
## 10. Structure du projet
|
||||
|
||||
```
|
||||
notesfrais/
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # Point d'entrée Express
|
||||
│ │ ├── config.ts # Variables d'env
|
||||
│ │ ├── db.ts # Pool PostgreSQL
|
||||
│ │ ├── middleware/ # Auth JWT, error handler
|
||||
│ │ ├── routes/ # auth, invoices, companies, settings, guests
|
||||
│ │ ├── services/ # pdf, email, sharepoint
|
||||
│ │ ├── migrations/ # 001_init.sql
|
||||
│ │ └── scripts/ # migrate.ts, initUsers.ts
|
||||
│ ├── Dockerfile
|
||||
│ ├── docker-entrypoint.sh
|
||||
│ └── package.json
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── pages/ # Login, NewInvoice, MyInvoices, Settings…
|
||||
│ │ ├── components/ # Layout, modaux…
|
||||
│ │ ├── hooks/ # useOfflineQueue, useOnline…
|
||||
│ │ ├── utils/ # offlineQueue (IndexedDB), api…
|
||||
│ │ └── types/ # index.ts
|
||||
│ ├── Dockerfile
|
||||
│ ├── nginx.conf
|
||||
│ └── package.json
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
Reference in New Issue
Block a user