Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ee43c8357 | ||
|
|
a6b0719ebf | ||
|
|
01c005adea | ||
|
|
b9ae743ffb | ||
|
|
40c7975bbc | ||
|
|
430ab669aa | ||
|
|
e03165bcc0 | ||
|
|
8a06bb81ef | ||
|
|
65ffa77fd0 | ||
|
|
d7f7e6a0fd | ||
|
|
2fe3385129 | ||
|
|
fd2ef4f7a1 | ||
|
|
be78e1497c | ||
|
|
c909650566 |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
listen_addresses = '*'
|
||||
port = 5432
|
||||
unix_socket_directories = '/home/runner/postgres_data/socket'
|
||||
@@ -1,100 +0,0 @@
|
||||
# Alternative Dockerfile - Approach Simple et Direct
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache python3 make g++ git
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
RUN npm ci --include=dev --prefer-offline
|
||||
COPY . .
|
||||
RUN npm run build && ls -la dist/ && test -f dist/index.js
|
||||
|
||||
# Production Stage avec approach simplifiée
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
RUN apk add --no-cache dumb-init postgresql-client wget curl bash
|
||||
|
||||
# Créer l'utilisateur
|
||||
RUN addgroup -g 1001 -S nodejs && adduser -S regisflow -u 1001 -G nodejs
|
||||
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /app/backups /app/logs /app/data
|
||||
|
||||
# Copier les fichiers depuis le builder
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/package*.json ./
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/dist ./dist
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/shared ./shared
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/drizzle.config.ts ./
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/init.sql ./
|
||||
|
||||
# Installer TOUTES les dépendances (dev inclus) pour avoir drizzle-kit
|
||||
RUN npm ci --include=dev --prefer-offline
|
||||
|
||||
# Copier les assets publics
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/dist/public ./public
|
||||
|
||||
# Vérifier que drizzle-kit est disponible
|
||||
RUN npx drizzle-kit --version
|
||||
|
||||
# Nettoyer
|
||||
RUN npm cache clean --force && rm -rf /tmp/*
|
||||
|
||||
# Créer le script d'entrée INLINE dans le Dockerfile au lieu de le copier
|
||||
RUN cat > /app/start.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "🚀 RegisFlow starting..."
|
||||
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
echo "❌ DATABASE_URL not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_HOST=$(echo $DATABASE_URL | sed -n 's|.*@\([^:/]*\).*|\1|p')
|
||||
DB_PORT=$(echo $DATABASE_URL | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||||
DB_USER=$(echo $DATABASE_URL | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
||||
|
||||
echo "⏳ Waiting for database..."
|
||||
timeout=90
|
||||
while ! pg_isready -h "$DB_HOST" -p "${DB_PORT:-5432}" -U "$DB_USER" -q && [ $timeout -gt 0 ]; do
|
||||
sleep 3
|
||||
timeout=$((timeout-3))
|
||||
done
|
||||
|
||||
if [ $timeout -le 0 ]; then
|
||||
echo "❌ Database timeout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Database ready"
|
||||
mkdir -p /app/logs /app/backups
|
||||
|
||||
echo "🔄 Running migrations..."
|
||||
if npm run db:push; then
|
||||
echo "✅ Migrations successful"
|
||||
else
|
||||
echo "❌ Migration failed, but continuing anyway..."
|
||||
echo " Application will auto-create tables on first connection"
|
||||
fi
|
||||
|
||||
echo "🌟 Starting application..."
|
||||
exec npm start
|
||||
EOF
|
||||
|
||||
# Donner les permissions au script
|
||||
RUN chmod +x /app/start.sh && chown regisflow:nodejs /app/start.sh
|
||||
|
||||
# Changer définitivement vers l'utilisateur non-root
|
||||
USER regisflow
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=5000
|
||||
ENV TZ=Europe/Paris
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=15s --start-period=90s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:5000/health || exit 1
|
||||
|
||||
# Utiliser le script inline
|
||||
ENTRYPOINT ["dumb-init", "--", "/app/start.sh"]
|
||||
@@ -1,91 +0,0 @@
|
||||
# Dockerfile multi-stage pour RegisFlow Production - Version Corrigée
|
||||
# Stage 1: Build
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
# Installer les dépendances de build
|
||||
RUN apk add --no-cache python3 make g++ git
|
||||
|
||||
# Créer le répertoire de build
|
||||
WORKDIR /build
|
||||
|
||||
# Copier les fichiers de dépendances
|
||||
COPY package*.json ./
|
||||
|
||||
# Installer toutes les dépendances (dev + prod)
|
||||
RUN npm ci --include=dev --prefer-offline
|
||||
|
||||
# Copier le code source complet
|
||||
COPY . .
|
||||
|
||||
# Construire l'application client et serveur
|
||||
RUN npm run build
|
||||
|
||||
# Vérifier que les fichiers de build existent
|
||||
RUN ls -la dist/ && test -f dist/index.js
|
||||
|
||||
# Stage 2: Production
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
# Installer uniquement les dépendances système nécessaires pour production
|
||||
RUN apk add --no-cache \
|
||||
dumb-init \
|
||||
postgresql-client \
|
||||
wget \
|
||||
curl \
|
||||
bash \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Créer un utilisateur non-root avec des permissions limitées
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S regisflow -u 1001 -G nodejs
|
||||
|
||||
# Créer les répertoires avec permissions appropriées
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /app/backups /app/logs /app/data && \
|
||||
chown -R regisflow:nodejs /app
|
||||
|
||||
# Copier uniquement les fichiers nécessaires depuis le builder
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/package*.json ./
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/dist ./dist
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/shared ./shared
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/drizzle.config.ts ./
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/init.sql ./
|
||||
|
||||
# Installer uniquement les dépendances de production
|
||||
RUN npm ci --omit=dev --prefer-offline
|
||||
|
||||
# Copier les assets client au bon endroit pour serveStatic
|
||||
COPY --from=builder --chown=regisflow:nodejs /build/dist/public ./public
|
||||
|
||||
# Nettoyer le cache npm
|
||||
RUN npm cache clean --force && \
|
||||
rm -rf /tmp/*
|
||||
|
||||
# Copier le script d'entrée directement dans /app (accessible)
|
||||
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||
RUN chmod +x /app/docker-entrypoint.sh && \
|
||||
chown regisflow:nodejs /app/docker-entrypoint.sh
|
||||
|
||||
# Changer vers l'utilisateur non-root
|
||||
USER regisflow
|
||||
|
||||
# Exposer le port
|
||||
EXPOSE 5000
|
||||
|
||||
# Variables d'environnement de production
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=5000
|
||||
ENV NODE_OPTIONS="--max-old-space-size=768"
|
||||
ENV TZ=Europe/Paris
|
||||
|
||||
# Labels pour la documentation
|
||||
LABEL maintainer="RegisFlow Team"
|
||||
LABEL version="1.0.0"
|
||||
LABEL description="Application RegisFlow pour la gestion des ventes de feux d'artifice"
|
||||
|
||||
# Health check amélioré
|
||||
HEALTHCHECK --interval=30s --timeout=15s --start-period=90s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:5000/health || exit 1
|
||||
|
||||
# Utiliser dumb-init pour gérer les signaux et lancer le script depuis /app
|
||||
ENTRYPOINT ["dumb-init", "--", "/app/docker-entrypoint.sh"]
|
||||
@@ -1,144 +0,0 @@
|
||||
# 🚀 RegisFlow Production Deployment Checklist 2025
|
||||
|
||||
## ✅ Pré-Déploiement
|
||||
|
||||
### Configuration Système
|
||||
- [ ] **Docker installé** : Version 20.10+ avec Docker Compose 2.0+
|
||||
- [ ] **Ressources système** : Minimum 2GB RAM, 5GB disque libre
|
||||
- [ ] **Ports disponibles** : 5000 (app) et 5433 (PostgreSQL) libres
|
||||
- [ ] **Permissions utilisateur** : Accès Docker sans sudo
|
||||
|
||||
### Configuration Sécurité
|
||||
- [ ] **Variables d'environnement** : `.env.production` créé depuis le template
|
||||
- [ ] **POSTGRES_PASSWORD** : Modifié avec mot de passe sécurisé (16+ caractères)
|
||||
- [ ] **SESSION_SECRET** : Généré avec clé unique 32+ caractères
|
||||
- [ ] **SECURE_COOKIES** : Activé pour production HTTPS
|
||||
- [ ] **Firewall** : Configuration ports 5000/5433 selon besoins
|
||||
|
||||
## 🔧 Déploiement
|
||||
|
||||
### Build et Démarrage
|
||||
- [ ] **Clone repository** : Code source récupéré
|
||||
- [ ] **Build Docker** : `docker-compose build` sans erreurs
|
||||
- [ ] **Démarrage services** : `docker-compose up -d` réussi
|
||||
- [ ] **Health check** : `curl http://localhost:5000/health` retourne status: healthy
|
||||
|
||||
### Vérification Base de Données
|
||||
- [ ] **PostgreSQL démarré** : Container regisflow-db actif
|
||||
- [ ] **Tables créées** : Migration automatique réussie
|
||||
- [ ] **Connexion application** : Pas d'erreurs de connexion DB
|
||||
- [ ] **Admin créé** : Compte admin/admin123 disponible
|
||||
|
||||
## 🧪 Tests Fonctionnels
|
||||
|
||||
### Interface Utilisateur
|
||||
- [ ] **Page login** : http://localhost:5000 accessible
|
||||
- [ ] **Connexion admin** : Login admin/admin123 fonctionne
|
||||
- [ ] **Navigation** : Toutes les pages se chargent
|
||||
- [ ] **Responsive** : Interface correcte mobile/tablet/desktop
|
||||
|
||||
### Fonctionnalités Métier
|
||||
- [ ] **Nouvelle vente** : Formulaire de vente opérationnel
|
||||
- [ ] **Multi-produits** : Ajout/suppression produits OK
|
||||
- [ ] **Photos** : Capture caméra ou upload fichier fonctionnel
|
||||
- [ ] **Validation** : EAN-13 et champs obligatoires vérifiés
|
||||
- [ ] **Sauvegarde** : Vente enregistrée en base de données
|
||||
|
||||
### Exports et Rapports
|
||||
- [ ] **Historique ventes** : Liste et filtres opérationnels
|
||||
- [ ] **Export PDF** : Génération PDF avec photos
|
||||
- [ ] **Export CSV** : Export CSV avec données complètes
|
||||
- [ ] **Export Excel** : Export Excel avec photos intégrées
|
||||
|
||||
### Administration
|
||||
- [ ] **Gestion utilisateurs** : Création/modification/suppression
|
||||
- [ ] **Gestion magasins** : CRUD magasins complet
|
||||
- [ ] **Sauvegardes** : Backup automatique et manuel
|
||||
- [ ] **Purge données** : Suppression automatique 19+ mois
|
||||
|
||||
## 🔒 Sécurité Production
|
||||
|
||||
### Authentication
|
||||
- [ ] **Sessions sécurisées** : Cookies HttpOnly activés
|
||||
- [ ] **Rôles utilisateurs** : Admin/Manager/Employee respectés
|
||||
- [ ] **Isolation magasins** : Données séparées par magasin
|
||||
- [ ] **Mot de passe admin** : Changé depuis défaut admin123
|
||||
|
||||
### Base de Données
|
||||
- [ ] **Chiffrement connexion** : SCRAM-SHA-256 activé
|
||||
- [ ] **Isolation containers** : Réseau Docker sécurisé
|
||||
- [ ] **Volumes persistants** : Données sauvegardées
|
||||
- [ ] **Backup chiffré** : Sauvegardes sécurisées
|
||||
|
||||
### Système
|
||||
- [ ] **Utilisateur non-root** : Application s'exécute sans privilèges
|
||||
- [ ] **Ressources limitées** : CPU/RAM bornés
|
||||
- [ ] **Logs structurés** : Monitoring et audit trail
|
||||
- [ ] **Health checks** : Supervision continue
|
||||
|
||||
## 📊 Monitoring Production
|
||||
|
||||
### Surveillance Automatique
|
||||
- [ ] **Health endpoint** : `/health` retourne métriques complètes
|
||||
- [ ] **Logs application** : `docker-compose logs` informatifs
|
||||
- [ ] **Métriques système** : CPU/RAM/Disque surveillés
|
||||
- [ ] **Alertes erreurs** : Notifications en cas de problème
|
||||
|
||||
### Sauvegardes
|
||||
- [ ] **Backup automatique** : Toutes les 12h (00:00 et 12:00)
|
||||
- [ ] **Rétention backups** : 20 sauvegardes maximum
|
||||
- [ ] **Purge automatique** : 1er du mois à 02:00
|
||||
- [ ] **Statistiques** : Interface admin affiche stats
|
||||
|
||||
### Performance
|
||||
- [ ] **Temps de réponse** : < 2s pour pages principales
|
||||
- [ ] **Upload photos** : < 30s pour photos 10MB
|
||||
- [ ] **Base de données** : Latence < 100ms
|
||||
- [ ] **Mémoire** : Utilisation < 80% allouée
|
||||
|
||||
## 🚨 Urgences et Récupération
|
||||
|
||||
### Procédures de Récupération
|
||||
- [ ] **Backup restore** : Procédure testée et documentée
|
||||
- [ ] **Rollback version** : Retour version précédente possible
|
||||
- [ ] **Recovery database** : Restauration PostgreSQL
|
||||
- [ ] **Contacts support** : Équipe technique identifiée
|
||||
|
||||
### Diagnostics Rapides
|
||||
```bash
|
||||
# Status général
|
||||
docker-compose ps
|
||||
|
||||
# Health application
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# Logs en temps réel
|
||||
docker-compose logs -f regisflow
|
||||
|
||||
# Test base de données
|
||||
docker exec regisflow-db pg_isready -U regisflow
|
||||
```
|
||||
|
||||
## 📈 Post-Déploiement
|
||||
|
||||
### Formation Utilisateurs
|
||||
- [ ] **Guide utilisateur** : Documentation fournie
|
||||
- [ ] **Formation admin** : Administration système
|
||||
- [ ] **Procédures métier** : Processus ventes documentés
|
||||
- [ ] **Support utilisateur** : Canal support défini
|
||||
|
||||
### Maintenance Préventive
|
||||
- [ ] **Planning updates** : Mises à jour programmées
|
||||
- [ ] **Monitoring continu** : Surveillance 24/7 configurée
|
||||
- [ ] **Backup verification** : Tests restore périodiques
|
||||
- [ ] **Audit sécurité** : Révision trimestrielle
|
||||
|
||||
---
|
||||
|
||||
**✅ RegisFlow Production Ready 2025**
|
||||
|
||||
Date de validation : _____________________
|
||||
|
||||
Responsable déploiement : _____________________
|
||||
|
||||
Signature : _____________________
|
||||
@@ -1,35 +0,0 @@
|
||||
# CORRECTION URGENTE ADMIN PRODUCTION
|
||||
|
||||
## Problème identifié
|
||||
- En production: Utilisateur admin ID 3 avec rôle "admin" au lieu de "administrator"
|
||||
- Logs montrent: `{"id":3,"username":"admin","email":"admin@exampl...`
|
||||
|
||||
## Solutions appliquées
|
||||
|
||||
### 1. Correction automatique (déjà ajoutée au code)
|
||||
Le fichier `server/storage.ts` contient maintenant une correction automatique qui se déclenche au démarrage.
|
||||
|
||||
### 2. Correction manuelle SQL (si nécessaire)
|
||||
|
||||
**Commande Docker pour production:**
|
||||
```bash
|
||||
docker exec regisflow-regisflow-1 psql -U regisflow -d regisflow -f /app/force-admin-correction.sql
|
||||
```
|
||||
|
||||
**Ou commande SQL directe:**
|
||||
```bash
|
||||
docker exec regisflow-regisflow-1 psql -U regisflow -d regisflow -c "UPDATE users SET role = 'administrator' WHERE username = 'admin';"
|
||||
```
|
||||
|
||||
### 3. Vérification
|
||||
```bash
|
||||
docker exec regisflow-regisflow-1 psql -U regisflow -d regisflow -c "SELECT id, username, role FROM users WHERE username = 'admin';"
|
||||
```
|
||||
|
||||
## Résultats attendus
|
||||
Après correction, l'onglet "Administration" apparaîtra dans l'interface utilisateur.
|
||||
|
||||
## Si le problème persiste
|
||||
1. Redémarrer le container: `docker restart regisflow-regisflow-1`
|
||||
2. Vider le cache navigateur
|
||||
3. Se reconnecter avec admin/admin123
|
||||
@@ -1,75 +0,0 @@
|
||||
# RegisFlow Production Update 2025
|
||||
|
||||
## Problème Docker Résolu - Version Finale
|
||||
|
||||
### Problème Identifié
|
||||
L'erreur `docker-entrypoint.sh: No such file or directory` était causée par des problèmes de permissions et de copie de fichiers dans le contexte Docker multi-stage.
|
||||
|
||||
### Solution Implémentée
|
||||
|
||||
#### Dockerfile.simple - Approche Robuste
|
||||
Au lieu de copier un fichier externe, le script d'entrée est maintenant créé directement dans le Dockerfile avec gestion d'erreur des migrations :
|
||||
|
||||
```dockerfile
|
||||
# Créer le script d'entrée INLINE dans le Dockerfile
|
||||
RUN cat > /app/start.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "🚀 RegisFlow starting..."
|
||||
# ... script complet inline ...
|
||||
EOF
|
||||
```
|
||||
|
||||
### Avantages de cette Approche
|
||||
|
||||
1. **Élimination du problème de copie** : Pas de fichier externe à copier
|
||||
2. **Permissions garanties** : Script créé avec les bonnes permissions
|
||||
3. **Simplicité** : Moins d'étapes, moins d'erreurs possibles
|
||||
4. **Robustesse** : Fonctionne sur tous les environnements Docker
|
||||
|
||||
### Problèmes Additionnels Résolus
|
||||
|
||||
1. **drizzle-kit manquant** : Script inclut maintenant toutes les dépendances
|
||||
2. **Migration robuste** : Échec de migration n'empêche plus le démarrage
|
||||
3. **Auto-création** : Tables créées automatiquement à la première connexion
|
||||
|
||||
### Fichiers Modifiés
|
||||
|
||||
- `Dockerfile.simple` : Approche la plus robuste avec gestion d'erreur
|
||||
- `docker-compose.yml` : Utilise Dockerfile.simple
|
||||
- `docker-test-quick.sh` : Script de test mis à jour
|
||||
|
||||
### Test de Validation
|
||||
|
||||
```bash
|
||||
# Test complet automatisé
|
||||
./docker-test-quick.sh
|
||||
|
||||
# Ou étape par étape
|
||||
docker-compose down -v
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
### Problème SSL PostgreSQL Résolu
|
||||
|
||||
4. **Erreur SSL** : "The server does not support SSL connections"
|
||||
- **Solution** : Configuration SSL désactivée pour Docker local
|
||||
- **Implémentation** : sslmode=disable dans DATABASE_URL
|
||||
- **Robustesse** : Détection automatique du mode SSL nécessaire
|
||||
|
||||
5. **Erreur Permissions Admin** : "Insufficient permissions" pour admin
|
||||
- **Problème** : Incohérence entre rôle 'admin' en DB et 'administrator' dans code
|
||||
- **Solution** : Mise à jour rôle admin vers 'administrator' et correction routes
|
||||
- **Implémentation** : UPDATE users + correction toutes routes requireRole()
|
||||
|
||||
### Status : ✅ RÉSOLU COMPLET
|
||||
|
||||
Cette solution garantit un déploiement Docker fiable en production sans les problèmes de fichiers manquants, migrations échouées, ou erreurs SSL.
|
||||
|
||||
---
|
||||
|
||||
**Date** : 19 Juillet 2025
|
||||
**Version** : RegisFlow Production 2025.1.1
|
||||
**Status** : Production Ready
|
||||
@@ -1,120 +0,0 @@
|
||||
🔄 Attempting database migration...
|
||||
|
||||
> rest-express@1.0.0 db:push
|
||||
|
||||
> drizzle-kit push
|
||||
|
||||
No config path provided, using default 'drizzle.config.ts'
|
||||
|
||||
Reading config file '/app/drizzle.config.ts'
|
||||
|
||||
Using 'pg' driver for database querying
|
||||
|
||||
[⣷] Pulling schema from database...
|
||||
|
||||
[⣯] Pulling schema from database...
|
||||
|
||||
[⣟] Pulling schema from database...
|
||||
|
||||
[✓] Pulling schema from database...
|
||||
|
||||
[i] No changes detected
|
||||
|
||||
✅ Migration completed
|
||||
|
||||
🌟 Starting RegisFlow application...
|
||||
|
||||
> rest-express@1.0.0 start
|
||||
|
||||
> NODE_ENV=production node dist/index.js
|
||||
|
||||
/app/node_modules/pg-pool/index.js:45
|
||||
|
||||
Error.captureStackTrace(err)
|
||||
|
||||
^
|
||||
|
||||
Error: The server does not support SSL connections
|
||||
|
||||
at /app/node_modules/pg-pool/index.js:45:11
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
|
||||
at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22
|
||||
|
||||
at async DatabaseStorage.getAllUsers (file:///app/dist/index.js:210:12)
|
||||
|
||||
at async DatabaseStorage.initializeDefaults (file:///app/dist/index.js:384:27)
|
||||
|
||||
at async registerRoutes (file:///app/dist/index.js:636:22)
|
||||
|
||||
at async file:///app/dist/index.js:1277:18
|
||||
|
||||
Node.js v20.19.4
|
||||
|
||||
🚀 RegisFlow Production Starting...
|
||||
|
||||
⏳ Waiting for database regisflow-db:5432...
|
||||
|
||||
✅ Database ready
|
||||
|
||||
🔄 Attempting database migration...
|
||||
|
||||
> rest-express@1.0.0 db:push
|
||||
|
||||
> drizzle-kit push
|
||||
|
||||
No config path provided, using default 'drizzle.config.ts'
|
||||
|
||||
Reading config file '/app/drizzle.config.ts'
|
||||
|
||||
Using 'pg' driver for database querying
|
||||
|
||||
[⣷] Pulling schema from database...
|
||||
|
||||
[⣯] Pulling schema from database...
|
||||
|
||||
[⣟] Pulling schema from database...
|
||||
|
||||
[⡿] Pulling schema from database...
|
||||
|
||||
[✓] Pulling schema from database...
|
||||
|
||||
[i] No changes detected
|
||||
|
||||
✅ Migration completed
|
||||
|
||||
🌟 Starting RegisFlow application...
|
||||
|
||||
> rest-express@1.0.0 start
|
||||
|
||||
> NODE_ENV=production node dist/index.js
|
||||
|
||||
/app/node_modules/pg-pool/index.js:45
|
||||
|
||||
Error.captureStackTrace(err)
|
||||
|
||||
^
|
||||
|
||||
Error: The server does not support SSL connections
|
||||
|
||||
at /app/node_modules/pg-pool/index.js:45:11
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
|
||||
at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22
|
||||
|
||||
at async DatabaseStorage.getAllUsers (file:///app/dist/index.js:210:12)
|
||||
|
||||
at async DatabaseStorage.initializeDefaults (file:///app/dist/index.js:384:27)
|
||||
|
||||
at async registerRoutes (file:///app/dist/index.js:636:22)
|
||||
|
||||
at async file:///app/dist/index.js:1277:18
|
||||
|
||||
Node.js v20.19.4
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
[⢿] Pulling schema from database...
|
||||
|
||||
[✓] Pulling schema from database...
|
||||
|
||||
[i] No changes detected
|
||||
|
||||
✅ Migration completed
|
||||
|
||||
🌟 Starting RegisFlow application...
|
||||
|
||||
Database URL configured with SSL disabled
|
||||
|
||||
> rest-express@1.0.0 start
|
||||
|
||||
> NODE_ENV=production node dist/index.js
|
||||
|
||||
5:47:11 PM [express] serving on port 5000
|
||||
|
||||
📅 Automatic backup scheduler started
|
||||
|
||||
⏰ Backups will run every 12 hours (00:00 and 12:00)
|
||||
|
||||
📁 Backup directory: /app/backups
|
||||
|
||||
📚 Maximum backups kept: 10
|
||||
|
||||
🕐 Planificateur de purge des ventes démarré
|
||||
|
||||
⏰ Purge automatique : 1er de chaque mois à 02:00
|
||||
|
||||
📅 Rétention des données : 19 mois maximum
|
||||
|
||||
5:47:15 PM [express] GET /api/auth/me 304 in 31ms :: {"id":3,"username":"admin","email":"admin@examp…
|
||||
|
||||
5:47:16 PM [express] GET /api/sales 200 in 12ms :: []
|
||||
|
||||
5:47:16 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:16 PM [express] GET /api/auth/me 304 in 11ms :: {"id":3,"username":"admin","email":"admin@examp…
|
||||
|
||||
5:47:20 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:20 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:20 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:21 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:21 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:22 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:28 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:28 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:28 PM [express] GET /api/auth/me 304 in 9ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:28 PM [express] GET /api/auth/me 304 in 3ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:47:28 PM [express] GET /api/stores 304 in 7ms :: [{"id":3,"name":"Magasin Principal","address":"À …
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
4:35:13 PM [express] GET /api/auth/me 401 in 2ms :: {"error":"Authentication required"}
|
||||
|
||||
4:35:13 PM [express] GET /api/auth/default-credentials-status 200 in 311ms :: {"showDefaultCredentia…
|
||||
|
||||
4:35:34 PM [express] POST /api/auth/login 200 in 327ms :: {"user":{"id":1,"username":"admin","email"…
|
||||
|
||||
4:35:35 PM [express] GET /api/auth/me 200 in 5ms :: {"id":1,"username":"admin","email":"admin@exampl…
|
||||
|
||||
4:35:35 PM [express] GET /api/auth/me 304 in 4ms :: {"id":1,"username":"admin","email":"admin@exampl…
|
||||
|
||||
4:35:35 PM [express] GET /api/auth/me 304 in 3ms :: {"id":1,"username":"admin","email":"admin@exampl…
|
||||
|
||||
4:35:35 PM [express] GET /api/auth/me 304 in 4ms :: {"id":1,"username":"admin","email":"admin@exampl…
|
||||
|
||||
4:35:35 PM [express] GET /api/admin/stores 200 in 5ms :: [{"id":1,"name":"Magasin Principal","addres…
|
||||
|
||||
4:36:29 PM [express] POST /api/sales 201 in 181ms :: {"id":1,"storeId":1,"userId":1,"timestamp":"202…
|
||||
|
||||
4:36:32 PM [express] GET /api/auth/me 304 in 4ms :: {"id":1,"username":"admin","email":"admin@exampl…
|
||||
|
||||
4:36:32 PM [express] GET /api/sales 200 in 62ms :: [{"id":1,"storeId":1,"userId":1,"timestamp":"2025…
|
||||
|
||||
4:37:01 PM [express] GET /api/auth/me 304 in 30ms :: {"id":1,"username":"admin","email":"admin@examp…
|
||||
|
||||
4:37:01 PM [express] GET /api/admin/users 200 in 7ms :: [{"id":1,"username":"admin","email":"admin@e…
|
||||
|
||||
4:37:01 PM [express] GET /api/admin/purge/stats 200 in 11ms :: {"totalSales":1,"oldSales":0,"cutoffD…
|
||||
|
||||
4:37:01 PM [express] GET /api/admin/backup/stats 304 in 28ms :: {"totalBackups":10,"backupDirectory"…
|
||||
|
||||
Import backup error: TypeError: value.toISOString is not a function
|
||||
|
||||
at PgTimestamp.mapToDriverValue (file:///app/node_modules/drizzle-orm/pg-core/columns/timestamp.js:34:18)
|
||||
|
||||
at file:///app/node_modules/drizzle-orm/sql/sql.js:131:73
|
||||
|
||||
at Array.map (<anonymous>)
|
||||
|
||||
at SQL.buildQueryFromSourceParams (file:///app/node_modules/drizzle-orm/sql/sql.js:73:32)
|
||||
|
||||
at file:///app/node_modules/drizzle-orm/sql/sql.js:92:21
|
||||
|
||||
at Array.map (<anonymous>)
|
||||
|
||||
at SQL.buildQueryFromSourceParams (file:///app/node_modules/drizzle-orm/sql/sql.js:73:32)
|
||||
|
||||
at file:///app/node_modules/drizzle-orm/sql/sql.js:95:21
|
||||
|
||||
at Array.map (<anonymous>)
|
||||
|
||||
at SQL.buildQueryFromSourceParams (file:///app/node_modules/drizzle-orm/sql/sql.js:73:32)
|
||||
|
||||
4:37:16 PM [express] POST /api/admin/backup/import 500 in 70ms :: {"error":"Failed to import backup"…
|
||||
@@ -1,66 +0,0 @@
|
||||
NODE_ENV=production node dist/index.js
|
||||
|
||||
5:12:31 PM [express] serving on port 5000
|
||||
|
||||
📅 Automatic backup scheduler started
|
||||
|
||||
⏰ Backups will run every 12 hours (00:00 and 12:00)
|
||||
|
||||
📁 Backup directory: /app/backups
|
||||
|
||||
📚 Maximum backups kept: 10
|
||||
|
||||
🕐 Planificateur de purge des ventes démarré
|
||||
|
||||
⏰ Purge automatique : 1er de chaque mois à 02:00
|
||||
|
||||
📅 Rétention des données : 19 mois maximum
|
||||
|
||||
5:25:38 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:38 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:39 PM [express] GET /api/auth/me 304 in 8ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:39 PM [express] GET /api/auth/me 304 in 7ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:39 PM [express] GET /api/stores 200 in 8ms :: [{"id":3,"name":"Magasin Principal","address":"À …
|
||||
|
||||
5:25:39 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:40 PM [express] GET /api/sales 200 in 10ms :: []
|
||||
|
||||
5:25:43 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:43 PM [express] GET /api/auth/me 304 in 4ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:44 PM [express] GET /api/auth/me 304 in 4ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:44 PM [express] GET /api/auth/me 304 in 4ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:50 PM [express] POST /api/auth/logout 200 in 3ms :: {"message":"Logged out successfully"}
|
||||
|
||||
5:25:50 PM [express] GET /api/sales 401 in 2ms :: {"error":"Authentication required"}
|
||||
|
||||
5:25:50 PM [express] GET /api/auth/me 401 in 2ms :: {"error":"Authentication required"}
|
||||
|
||||
5:25:50 PM [express] GET /api/auth/me 401 in 1ms :: {"error":"Authentication required"}
|
||||
|
||||
5:25:50 PM [express] GET /api/auth/default-credentials-status 200 in 362ms :: {"showDefaultCredentia…
|
||||
|
||||
5:25:54 PM [express] POST /api/auth/login 200 in 326ms :: {"user":{"id":3,"username":"admin","email"…
|
||||
|
||||
5:25:55 PM [express] GET /api/auth/me 304 in 14ms :: {"id":3,"username":"admin","email":"admin@examp…
|
||||
|
||||
5:25:55 PM [express] GET /api/auth/me 304 in 6ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:55 PM [express] GET /api/auth/me 304 in 5ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:55 PM [express] GET /api/auth/me 304 in 4ms :: {"id":3,"username":"admin","email":"admin@exampl…
|
||||
|
||||
5:25:55 PM [express] GET /api/stores 304 in 11ms :: [{"id":3,"name":"Magasin Principal","address":"À…
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -1,42 +0,0 @@
|
||||
-- Script de vérification pour RegisFlow Production
|
||||
-- À exécuter pour valider que la base de données est prête
|
||||
|
||||
-- Vérifier la version PostgreSQL
|
||||
SELECT version();
|
||||
|
||||
-- Vérifier les tables principales
|
||||
\dt
|
||||
|
||||
-- Compter les utilisateurs
|
||||
SELECT COUNT(*) as total_users FROM users;
|
||||
|
||||
-- Compter les magasins
|
||||
SELECT COUNT(*) as total_stores FROM stores;
|
||||
|
||||
-- Compter les ventes
|
||||
SELECT COUNT(*) as total_sales FROM sales;
|
||||
|
||||
-- Vérifier l'utilisateur admin par défaut
|
||||
SELECT username, role, email FROM users WHERE role = 'admin';
|
||||
|
||||
-- Vérifier les index pour performance
|
||||
\di
|
||||
|
||||
-- Taille de la base de données
|
||||
SELECT pg_size_pretty(pg_database_size('regisflow')) as database_size;
|
||||
|
||||
-- Dernières ventes (si des données existent)
|
||||
SELECT id, vendeur, nom, prenom, created_at
|
||||
FROM sales
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;
|
||||
|
||||
-- Vérifier les sessions actives
|
||||
SELECT COUNT(*) as active_sessions FROM sessions;
|
||||
|
||||
-- Status général
|
||||
SELECT
|
||||
'RegisFlow Production Check' as status,
|
||||
CURRENT_TIMESTAMP as check_time,
|
||||
current_database() as database_name,
|
||||
current_user as database_user;
|
||||
@@ -88,7 +88,7 @@ export default function NewSaleForm() {
|
||||
mutationFn: async (saleData: any) => {
|
||||
const dataToSend = {
|
||||
...saleData,
|
||||
...(user?.role === 'admin' && selectedStoreId && { storeId: selectedStoreId })
|
||||
...(user?.role === 'administrator' && selectedStoreId && { storeId: selectedStoreId })
|
||||
};
|
||||
|
||||
const response = await apiRequest('/api/sales', {
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function NewSaleFormMulti() {
|
||||
mutationFn: async (saleData: any) => {
|
||||
const dataToSend = {
|
||||
...saleData,
|
||||
...(user?.role === 'admin' && selectedStoreId && { storeId: selectedStoreId })
|
||||
...(user?.role === 'administrator' && selectedStoreId && { storeId: selectedStoreId })
|
||||
};
|
||||
|
||||
const response = await apiRequest('/api/sales', {
|
||||
|
||||
@@ -554,15 +554,51 @@ export default function SalesHistory({ canDelete = false }: SalesHistoryProps) {
|
||||
<span>{sale.nom} {sale.prenom}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-sm">{sale.typeArticle}</td>
|
||||
<td>
|
||||
<span className={`modern-badge ${
|
||||
sale.categorie === 'F3' ? 'badge-destructive' : 'badge-secondary'
|
||||
}`}>
|
||||
{sale.categorie}
|
||||
</span>
|
||||
<td className="text-sm">
|
||||
{sale.products && sale.products.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{sale.products.map((product, index) => (
|
||||
<div key={index} className="text-sm leading-relaxed">
|
||||
{product.typeArticle}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{sale.products && sale.products.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{sale.products.map((product, index) => (
|
||||
<div key={index} className="text-sm font-medium">
|
||||
<span className={`inline-block px-2 py-1 rounded text-xs font-semibold ${
|
||||
product.categorie === 'F3' || product.categorie === 'F4'
|
||||
? 'bg-red-100 text-red-800'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{product.categorie}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{sale.products && sale.products.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{sale.products.map((product, index) => (
|
||||
<div key={index} className="text-sm font-medium leading-relaxed">
|
||||
{product.quantite}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-center">{sale.quantite}</td>
|
||||
<td className="text-sm">{sale.modePaiement}</td>
|
||||
<td>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Script de debug pour forcer la correction admin en production
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Simuler la correction directement dans le code pour production
|
||||
const fixProductionAdmin = () => {
|
||||
console.log('=== DEBUG ADMIN PRODUCTION ===');
|
||||
console.log('Problème identifié:');
|
||||
console.log('- Logs montrent: ID 3, email tronqué "admin@exampl"');
|
||||
console.log('- Utilisateur connecté différent entre dev (ID 1) et prod (ID 3)');
|
||||
console.log('');
|
||||
|
||||
console.log('SOLUTIONS à appliquer:');
|
||||
console.log('1. Corriger base de données production:');
|
||||
console.log(' UPDATE users SET role = \'administrator\' WHERE id = 3;');
|
||||
console.log('');
|
||||
console.log('2. Ou créer utilisateur admin correct:');
|
||||
console.log(' INSERT INTO users (username, password, email, role, store_id) VALUES');
|
||||
console.log(' (\'admin\', \'$2b$12$hashed_password\', \'admin@example.com\', \'administrator\', NULL);');
|
||||
console.log('');
|
||||
|
||||
console.log('3. Vérifier les sessions:');
|
||||
console.log(' DELETE FROM sessions WHERE data LIKE \'%"id":3%\';');
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
fixProductionAdmin();
|
||||
}
|
||||
|
||||
module.exports = fixProductionAdmin;
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 RegisFlow Docker entrypoint started (Production v2025)"
|
||||
|
||||
# Vérifier les variables d'environnement essentielles
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
echo "❌ ERROR: DATABASE_URL environment variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$SESSION_SECRET" ]; then
|
||||
echo "⚠️ WARNING: SESSION_SECRET not set, using default (not recommended for production)"
|
||||
fi
|
||||
|
||||
echo "📊 Environment configured - NODE_ENV: $NODE_ENV"
|
||||
echo "🗄️ Database URL configured"
|
||||
echo "🌍 Timezone: $TZ"
|
||||
|
||||
# Patch pour Node.js compatibility (import.meta.dirname)
|
||||
if [ -f "dist/index.js" ] && ! grep -q "__dirname" dist/index.js; then
|
||||
echo "🔧 Applying Node.js compatibility patch..."
|
||||
sed -i 's|import\.meta\.dirname|process.cwd()|g' dist/index.js
|
||||
echo "✅ Compatibility patch applied"
|
||||
fi
|
||||
|
||||
# Extraction des informations de connexion pour pg_isready
|
||||
DB_HOST=$(echo $DATABASE_URL | sed -n 's|.*@\([^:/]*\).*|\1|p')
|
||||
DB_PORT=$(echo $DATABASE_URL | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||||
DB_USER=$(echo $DATABASE_URL | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
||||
|
||||
echo "🔗 Connecting to: $DB_HOST:${DB_PORT:-5432}"
|
||||
|
||||
# Attendre que la base de données soit prête avec retry amélioré
|
||||
echo "⏳ Waiting for database to be ready..."
|
||||
timeout=90
|
||||
while ! pg_isready -h "$DB_HOST" -p "${DB_PORT:-5432}" -U "$DB_USER" -q && [ $timeout -gt 0 ]; do
|
||||
echo "Database not ready, waiting... ($timeout seconds left)"
|
||||
sleep 3
|
||||
timeout=$((timeout-3))
|
||||
done
|
||||
|
||||
if [ $timeout -le 0 ]; then
|
||||
echo "❌ Database connection timeout after 90 seconds"
|
||||
echo "🔍 Debug info:"
|
||||
echo " HOST: $DB_HOST"
|
||||
echo " PORT: ${DB_PORT:-5432}"
|
||||
echo " USER: $DB_USER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Database is ready and accessible"
|
||||
|
||||
# Créer les répertoires nécessaires
|
||||
mkdir -p /app/logs /app/backups
|
||||
echo "📁 Created application directories"
|
||||
|
||||
# Exécuter les migrations de base de données
|
||||
echo "🔄 Running database migrations..."
|
||||
if npm run db:push; then
|
||||
echo "✅ Database migrations completed successfully"
|
||||
else
|
||||
echo "❌ Database migration failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérification des permissions et de l'espace disque
|
||||
echo "🔍 System checks:"
|
||||
echo " Disk space: $(df -h /app | tail -1 | awk '{print $4}') available"
|
||||
echo " Memory: $(free -h | grep Mem | awk '{print $7}') available"
|
||||
echo " User: $(whoami)"
|
||||
|
||||
# Démarrer l'application
|
||||
echo "🌟 Starting RegisFlow application on port $PORT..."
|
||||
echo "🏥 Health check available at: http://localhost:$PORT/health"
|
||||
exec npm start
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🐳 RegisFlow Docker Quick Test"
|
||||
echo "=============================="
|
||||
|
||||
# Nettoyer les anciens containers
|
||||
echo "🧹 Cleaning up existing containers..."
|
||||
docker-compose down -v 2>/dev/null || true
|
||||
docker system prune -f >/dev/null 2>&1 || true
|
||||
|
||||
# Test de build avec l'approche simplifiée (skip migrations)
|
||||
echo "🔨 Building fresh Docker image (simple approach - skip migrations)..."
|
||||
if docker-compose build --no-cache regisflow; then
|
||||
echo "✅ Build successful"
|
||||
else
|
||||
echo "❌ Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Créer .env.production si nécessaire
|
||||
if [[ ! -f ".env.production" ]]; then
|
||||
echo "📝 Creating .env.production from example..."
|
||||
cp .env.production.example .env.production
|
||||
fi
|
||||
|
||||
# Test de démarrage
|
||||
echo "🚀 Starting containers..."
|
||||
if docker-compose up -d; then
|
||||
echo "✅ Containers started"
|
||||
|
||||
# Attendre l'application
|
||||
echo "⏳ Waiting for application..."
|
||||
for i in {1..30}; do
|
||||
if curl -s http://localhost:5000/health >/dev/null 2>&1; then
|
||||
echo "✅ Application is responding!"
|
||||
echo "🏥 Health: $(curl -s http://localhost:5000/health | grep -o '"status":"[^"]*"' || echo 'OK')"
|
||||
break
|
||||
fi
|
||||
echo " Attempt $i/30..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $i -eq 30 ]; then
|
||||
echo "❌ Application timeout"
|
||||
echo "📋 Recent logs:"
|
||||
docker-compose logs --tail=10 regisflow
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "❌ Container startup failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 SUCCESS! RegisFlow is running"
|
||||
echo "================================"
|
||||
echo "Access: http://localhost:5000"
|
||||
echo "Admin: admin / admin123"
|
||||
echo ""
|
||||
echo "To stop: docker-compose down"
|
||||
echo "To see logs: docker-compose logs -f"
|
||||
@@ -1,22 +0,0 @@
|
||||
-- CORRECTION URGENTE ADMIN PRODUCTION
|
||||
-- Exécuter sur la base de production
|
||||
|
||||
-- 1. Vérifier l'utilisateur problématique (ID 3 d'après les logs)
|
||||
SELECT id, username, email, role, store_id FROM users WHERE id = 3;
|
||||
|
||||
-- 2. Corriger le rôle de l'utilisateur ID 3
|
||||
UPDATE users SET role = 'administrator' WHERE id = 3 AND username = 'admin';
|
||||
|
||||
-- 3. Vérifier si correction appliquée
|
||||
SELECT id, username, email, role, store_id FROM users WHERE id = 3;
|
||||
|
||||
-- 4. Nettoyer les sessions problématiques
|
||||
DELETE FROM sessions WHERE data LIKE '%"id":3%' AND data NOT LIKE '%"role":"administrator"%';
|
||||
|
||||
-- 5. Vérifier tous les utilisateurs admin
|
||||
SELECT id, username, email, role, store_id FROM users WHERE role = 'administrator';
|
||||
|
||||
-- 6. Forcer création admin backup si nécessaire
|
||||
INSERT INTO users (username, password, email, first_name, last_name, role, store_id, is_active, created_at, updated_at)
|
||||
VALUES ('admin_backup', '$2b$12$LQv3c1yqBwuvHi44M0Bfa.5jW.5J5J5J5J5J5J5J5J5J5J5J5J5J5J', 'admin_backup@regisflow.com', 'Admin', 'Backup', 'administrator', NULL, true, NOW(), NOW())
|
||||
ON CONFLICT (username) DO NOTHING;
|
||||
@@ -1,15 +0,0 @@
|
||||
-- Script pour corriger le rôle admin en production
|
||||
-- Ce script doit être exécuté sur la base de données de production
|
||||
|
||||
-- Vérifier tous les utilisateurs admin
|
||||
SELECT id, username, role, email FROM users WHERE username = 'admin';
|
||||
|
||||
-- Mettre à jour tous les utilisateurs admin vers le rôle 'administrator'
|
||||
UPDATE users SET role = 'administrator' WHERE username = 'admin' AND role = 'admin';
|
||||
|
||||
-- Vérifier que la mise à jour a fonctionné
|
||||
SELECT id, username, role, email FROM users WHERE username = 'admin';
|
||||
|
||||
-- Vérifier que les routes admin fonctionnent (permissions)
|
||||
SELECT COUNT(*) as total_users FROM users;
|
||||
SELECT COUNT(*) as admin_users FROM users WHERE role = 'administrator';
|
||||
@@ -1,20 +0,0 @@
|
||||
-- CORRECTION IMMÉDIATE ADMIN PRODUCTION
|
||||
-- Basé sur les logs: l'utilisateur admin a l'ID 3 avec email tronqué
|
||||
|
||||
-- 1. Identifier l'utilisateur problématique
|
||||
SELECT 'AVANT CORRECTION:' as status, id, username, email, role, store_id FROM users WHERE username = 'admin';
|
||||
|
||||
-- 2. CORRECTION FORCÉE: Mettre à jour TOUS les utilisateurs admin vers administrator
|
||||
UPDATE users SET role = 'administrator' WHERE username = 'admin';
|
||||
|
||||
-- 3. CORRECTION SPÉCIFIQUE: Forcer l'utilisateur ID 3 (celui des logs de production)
|
||||
UPDATE users SET role = 'administrator', email = 'admin@regisflow.com' WHERE id = 3;
|
||||
|
||||
-- 4. Vérifier la correction
|
||||
SELECT 'APRÈS CORRECTION:' as status, id, username, email, role, store_id FROM users WHERE username = 'admin';
|
||||
|
||||
-- 5. Nettoyer les sessions avec mauvais rôle
|
||||
DELETE FROM sessions WHERE data LIKE '%"id":3%' AND data NOT LIKE '%"role":"administrator"%';
|
||||
|
||||
-- 6. Afficher tous les administrateurs
|
||||
SELECT 'TOUS LES ADMIN:' as status, id, username, email, role FROM users WHERE role = 'administrator';
|
||||
@@ -1,42 +0,0 @@
|
||||
-- Migration pour les ventes multi-produits
|
||||
-- Cette migration convertit les ventes existantes vers le nouveau système
|
||||
|
||||
-- 1. Créer la nouvelle table sale_products
|
||||
CREATE TABLE IF NOT EXISTS sale_products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sale_id INTEGER REFERENCES sales(id) ON DELETE CASCADE NOT NULL,
|
||||
type_article VARCHAR(255) NOT NULL,
|
||||
categorie VARCHAR(2) NOT NULL,
|
||||
quantite INTEGER NOT NULL,
|
||||
gencode VARCHAR(13) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 2. Migrer les données existantes des ventes vers sale_products
|
||||
INSERT INTO sale_products (sale_id, type_article, categorie, quantite, gencode)
|
||||
SELECT
|
||||
id,
|
||||
type_article,
|
||||
categorie,
|
||||
quantite,
|
||||
gencode
|
||||
FROM sales
|
||||
WHERE type_article IS NOT NULL;
|
||||
|
||||
-- 3. Supprimer les colonnes produit de la table sales (après migration réussie)
|
||||
-- Ces commandes seront exécutées après validation de la migration
|
||||
-- ALTER TABLE sales DROP COLUMN type_article;
|
||||
-- ALTER TABLE sales DROP COLUMN categorie;
|
||||
-- ALTER TABLE sales DROP COLUMN quantite;
|
||||
-- ALTER TABLE sales DROP COLUMN gencode;
|
||||
|
||||
-- 4. Créer les index pour optimiser les recherches
|
||||
CREATE INDEX IF NOT EXISTS idx_sale_products_sale_id ON sale_products(sale_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sale_products_gencode ON sale_products(gencode);
|
||||
|
||||
-- 5. Ajouter les contraintes
|
||||
ALTER TABLE sale_products ADD CONSTRAINT chk_sale_products_categorie
|
||||
CHECK (categorie IN ('F2', 'F3'));
|
||||
|
||||
-- 6. Mettre à jour les séquences
|
||||
SELECT setval('sale_products_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sale_products));
|
||||
@@ -0,0 +1 @@
|
||||
backup
|
||||
@@ -1,380 +1,61 @@
|
||||
# RegisFlow
|
||||
|
||||
## Overview
|
||||
|
||||
Application web complète de gestion et suivi des ventes de feux d'artifice en conformité avec la réglementation française. L'application fournit un système d'enregistrement des ventes avec authentification multi-utilisateur, contrôle d'accès basé sur les rôles, gestion multi-magasins, et stockage sécurisé en base de données PostgreSQL.
|
||||
RegisFlow is a comprehensive web application for managing and tracking fireworks sales in compliance with French regulations. It provides a secure system for sales registration with multi-user authentication, role-based access control, multi-store management, and secure PostgreSQL database storage. The application aims to streamline sales operations for fireworks retailers while ensuring regulatory compliance, offering a robust solution for tracking, reporting, and data management.
|
||||
|
||||
## User Preferences
|
||||
|
||||
Preferred communication style: Simple, everyday language.
|
||||
|
||||
## System Architecture
|
||||
|
||||
### Frontend Architecture
|
||||
### Frontend
|
||||
- **Framework**: React 18 with TypeScript
|
||||
- **Build Tool**: Vite for development and production builds
|
||||
- **Styling**: TailwindCSS with shadcn/ui component library
|
||||
- **State Management**: React Hook Form for form handling, TanStack Query for server state
|
||||
- **UI Components**: Radix UI primitives with custom shadcn/ui components
|
||||
- **Authentication**: Session-based authentication with role-based access control
|
||||
- **Build Tool**: Vite
|
||||
- **Styling**: TailwindCSS with shadcn/ui and Radix UI primitives
|
||||
- **State Management**: React Hook Form, TanStack Query
|
||||
- **Authentication**: Session-based with role-based access control
|
||||
- **UI/UX Decisions**: Modern, angular design with a professional color scheme, focusing on clear visual borders and frames. Elements like cards, buttons, and form fields use rectangular shapes with a 4px border-radius. Responsive design is prioritized for mobile, tablet, and desktop.
|
||||
|
||||
### Backend Architecture
|
||||
### Backend
|
||||
- **Server**: Express.js with TypeScript
|
||||
- **Database ORM**: Drizzle ORM configured for PostgreSQL
|
||||
- **Database**: PostgreSQL via environment variable DATABASE_URL
|
||||
- **Authentication**: bcrypt password hashing with Express sessions
|
||||
- **Session Storage**: PostgreSQL session store for scalability
|
||||
- **Database ORM**: Drizzle ORM
|
||||
- **Database**: PostgreSQL
|
||||
- **Authentication**: bcrypt password hashing, Express sessions, PostgreSQL session store
|
||||
- **Key Features**:
|
||||
- Multi-User and Store Management: Secure login, administrative user/store management, multi-tenant store isolation.
|
||||
- Sales Management: Comprehensive sales form with validation (including EAN-13), sales history with search/filter, and data export (CSV/Excel with images).
|
||||
- Data Compliance: Automated data purge for records older than 19 months, role-based permissions (Admin, Manager, Employee), and secure session management.
|
||||
- Backup & Recovery: Full database backup/restore with automatic scheduling and cleanup.
|
||||
- Photo Capture: Integrated photo capture for identity documents and receipts with robust camera support and file upload fallback.
|
||||
|
||||
### Data Storage
|
||||
- **Primary Storage**: PostgreSQL database with complete schema
|
||||
- **Session Storage**: PostgreSQL sessions table for user authentication
|
||||
- **Schema Management**: Drizzle migrations for database versioning
|
||||
- **Multi-tenant**: Each store has isolated sales data
|
||||
|
||||
## Key Components
|
||||
|
||||
### Core Application Features
|
||||
1. **Multi-User Authentication**: Secure login system with role-based access control
|
||||
2. **User Management**: Administrative interface for creating and managing users
|
||||
3. **Store Management**: Multi-tenant architecture with store isolation
|
||||
4. **Sales Form Management**: Comprehensive form for recording fireworks sales with validation
|
||||
5. **Sales History**: Complete transaction history with date filtering and search
|
||||
6. **Data Export**: CSV export functionality for regulatory reporting
|
||||
7. **Role-Based Permissions**: Different access levels for Admin, Manager, and Employee roles
|
||||
8. **Automated Data Purge**: Regulatory compliance with automatic deletion of sales data older than 19 months
|
||||
9. **Backup & Recovery**: Complete database backup system with import/export capabilities
|
||||
|
||||
### Authentication & Authorization
|
||||
- **Administrators**: Full access to all features including user/store management
|
||||
- **Managers**: Sales management and history access, no administration features
|
||||
- **Employees**: Sales creation and history viewing only
|
||||
- **Default Account**: admin/admin123 (auto-created on first database setup)
|
||||
|
||||
### Technical Components
|
||||
- **Session Management**: Secure session handling with PostgreSQL storage
|
||||
- **Form Validation**: Multi-step validation including EAN-13 barcode validation
|
||||
- **Responsive Design**: Mobile-first approach with tablet and desktop support
|
||||
- **Toast Notifications**: User feedback system for actions and errors
|
||||
- **Modal System**: Dialog-based UI for detailed views and confirmations
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Authentication Flow
|
||||
1. User accesses application and is redirected to login page
|
||||
2. Credentials verified against PostgreSQL users table with bcrypt
|
||||
3. Successful login creates secure session stored in PostgreSQL
|
||||
4. User role determines available features and access levels
|
||||
5. Session automatically managed with configurable expiration
|
||||
|
||||
### Sales Recording Process
|
||||
1. Authenticated user fills out comprehensive sales form
|
||||
2. Form validation ensures regulatory compliance (EAN-13, required fields)
|
||||
3. Data submitted to backend with user and store association
|
||||
4. Sales record created in PostgreSQL with complete audit trail
|
||||
5. Real-time updates to sales history via TanStack Query
|
||||
6. Export capability for regulatory compliance reporting
|
||||
|
||||
### Data Management
|
||||
- **Database-First**: All data operations use PostgreSQL for consistency
|
||||
- **Multi-Tenant**: Store-based data isolation for security
|
||||
- **Role-Based Access**: Different permissions based on user role
|
||||
- **Session Security**: Secure session management with PostgreSQL storage
|
||||
- **Audit Trail**: Complete transaction history with timestamps
|
||||
- **Primary Storage**: PostgreSQL database with Drizzle ORM for schema management and migrations.
|
||||
- **Session Storage**: PostgreSQL sessions table.
|
||||
- **Multi-tenant**: Each store maintains isolated sales data.
|
||||
- **Data Flow**: Secure authentication and session management; sales recording with comprehensive validation and audit trails; robust data management with role-based access and secure session handling.
|
||||
|
||||
## External Dependencies
|
||||
|
||||
### Core Dependencies
|
||||
- **@neondatabase/serverless**: PostgreSQL database connectivity
|
||||
- **drizzle-orm**: Type-safe database operations
|
||||
- **@tanstack/react-query**: Server state management
|
||||
- **react-hook-form**: Form handling and validation
|
||||
- **@hookform/resolvers**: Form validation resolvers
|
||||
- `@neondatabase/serverless`: PostgreSQL database connectivity
|
||||
- `drizzle-orm`: Type-safe database operations
|
||||
- `@tanstack/react-query`: Server state management
|
||||
- `react-hook-form`: Form handling and validation
|
||||
- `@hookform/resolvers`: Form validation resolvers
|
||||
|
||||
### Authentication & Security
|
||||
- **bcryptjs**: Password hashing for secure authentication
|
||||
- **express-session**: Session management middleware
|
||||
- **connect-pg-simple**: PostgreSQL session store
|
||||
- **drizzle-zod**: Schema validation with Zod integration
|
||||
- `bcryptjs`: Password hashing
|
||||
- `express-session`: Session management
|
||||
- `connect-pg-simple`: PostgreSQL session store
|
||||
- `drizzle-zod`: Schema validation with Zod
|
||||
|
||||
### UI and Styling
|
||||
- **@radix-ui/***: Headless UI primitives for accessibility
|
||||
- **tailwindcss**: Utility-first CSS framework
|
||||
- **class-variance-authority**: Component variant management
|
||||
- **lucide-react**: Icon library
|
||||
- `@radix-ui/*`: Headless UI primitives
|
||||
- `tailwindcss`: Utility-first CSS framework
|
||||
- `class-variance-authority`: Component variant management
|
||||
- `lucide-react`: Icon library
|
||||
|
||||
### Development Tools
|
||||
- **tsx**: TypeScript execution for development
|
||||
- **esbuild**: Fast JavaScript bundler for production
|
||||
- **vite**: Development server and build tool
|
||||
|
||||
## Deployment Strategy
|
||||
|
||||
### Development Environment
|
||||
- **Local Development**: Vite dev server with hot module replacement
|
||||
- **Database**: PostgreSQL with auto-initialization of default admin account
|
||||
- **Authentication**: Session-based with development-friendly settings
|
||||
- **Build Process**: TypeScript compilation with Vite bundling
|
||||
|
||||
### Production Environment
|
||||
- **Frontend**: Static assets served via Vite build output
|
||||
- **Backend**: Express.js server bundled with esbuild
|
||||
- **Database**: PostgreSQL via DATABASE_URL environment variable
|
||||
- **Sessions**: Secure session storage in PostgreSQL sessions table
|
||||
- **Environment Variables**: DATABASE_URL, SESSION_SECRET (optional)
|
||||
|
||||
### Docker Deployment
|
||||
- **Container Strategy**: Multi-stage Docker build for optimized production images
|
||||
- **Service Architecture**: Docker Compose with PostgreSQL and application containers
|
||||
- **Database**: PostgreSQL 15 with persistent volumes and automated initialization
|
||||
- **Direct Access**: Application accessible directly on port 5000
|
||||
- **Health Monitoring**: Health checks for all services with automatic recovery
|
||||
- **Volume Management**: Persistent storage for database and backup data
|
||||
- **Environment**: Complete environment variable configuration with security templates
|
||||
|
||||
### Security Configuration
|
||||
- **Password Hashing**: bcrypt with appropriate salt rounds
|
||||
- **Session Security**: HTTP-only cookies with configurable expiration
|
||||
- **Role-Based Access**: Middleware-enforced permission system
|
||||
- **CSRF Protection**: Session-based protection against cross-site attacks
|
||||
|
||||
### Build Configuration
|
||||
- **Client Build**: `vite build` generates optimized static assets
|
||||
- **Server Build**: `esbuild` bundles Express server for deployment
|
||||
- **Type Checking**: TypeScript compilation ensures type safety
|
||||
- **Database Setup**: `npm run db:push` for schema deployment
|
||||
- **Default Account**: Automatic creation of admin/admin123 on first run
|
||||
|
||||
### Deployment Features
|
||||
- **Environment Detection**: Automatic development/production mode switching
|
||||
- **Asset Optimization**: Bundled and minified production builds
|
||||
- **Hot Reloading**: Development-only feature for rapid iteration
|
||||
- **Error Handling**: Comprehensive error boundaries and logging
|
||||
- **Multi-Tenant Ready**: Store-based data isolation for scalability
|
||||
|
||||
## Recent Changes (January 2025)
|
||||
|
||||
### Migration to Full-Stack Architecture
|
||||
- ✅ Migrated from IndexedDB to PostgreSQL database with Drizzle ORM
|
||||
- ✅ Implemented complete authentication system with bcrypt password hashing
|
||||
- ✅ Added role-based access control (Administrator, Manager, Employee)
|
||||
- ✅ Created multi-tenant architecture with store-based data isolation
|
||||
- ✅ Built administration panel for user and store management
|
||||
- ✅ Generated default admin account (admin/admin123) for initial setup
|
||||
- ✅ Updated all components to work with database instead of local storage
|
||||
- ✅ Added session-based authentication with PostgreSQL session store
|
||||
- ✅ Implemented comprehensive API with proper validation and error handling
|
||||
|
||||
### User Experience Improvements (January 9, 2025)
|
||||
- ✅ Fixed authentication flow issues with login/logout redirects
|
||||
- ✅ Replaced native browser confirm() dialogs with modern AlertDialog modals
|
||||
- ✅ Enhanced deletion confirmations in sales history and administration panels
|
||||
- ✅ Improved visual feedback with detailed confirmation dialogs showing item details
|
||||
- ✅ Added proper modal management with shadcn/ui AlertDialog components
|
||||
- ✅ Fixed critical admin user permissions bug preventing profile modifications
|
||||
- ✅ Added server-side protection against accidental admin role changes
|
||||
- ✅ Enhanced UI with disabled role field for self-editing administrators
|
||||
- ✅ Improved admin interface - "Tous" displayed for admin store access, no redundant store selector for admin profiles
|
||||
- ✅ Cleaned up new sale page interface by removing unnecessary store information display
|
||||
- ✅ Streamlined dashboard layout for better user experience
|
||||
- ✅ Made "Lieu de naissance" field mandatory in new sale form for regulatory compliance
|
||||
- ✅ Removed unnecessary header section from new sale form for cleaner interface
|
||||
- ✅ Created custom favicon with fireworks design representing RegisFlow application
|
||||
- ✅ Implemented modern UI template with angular design elements replacing rounded components
|
||||
- ✅ Updated color scheme to use darker, more professional tones with less vibrant gradients
|
||||
- ✅ Modified card layouts, buttons, and form elements to use rectangular shapes instead of rounded corners
|
||||
- ✅ Enhanced Dashboard with clean, professional navigation tabs using bottom borders instead of rounded active states
|
||||
- ✅ Simplified login page with modern, angular design maintaining accessibility and usability
|
||||
- ✅ Removed remaining rounded elements from SalesHistory filter cards and form inputs
|
||||
- ✅ Updated NewSaleForm with modern-input class for all form fields
|
||||
- ✅ Applied modern-section and modern-card classes throughout the application
|
||||
- ✅ Replaced all rounded UI elements with straight-edged, angular modern design
|
||||
|
||||
### Database Backup & Recovery System (January 9, 2025)
|
||||
- ✅ Added complete database backup and restore functionality in administration panel
|
||||
- ✅ Export feature creates comprehensive JSON backups with all data (users, stores, sales)
|
||||
- ✅ Import feature allows full database restoration with data validation
|
||||
- ✅ Automatic ID remapping during import to handle database constraints
|
||||
- ✅ Security measures: passwords excluded from backups, admin account protected during restore
|
||||
- ✅ User-friendly interface with clear warnings for destructive operations
|
||||
|
||||
### Automatic Backup Scheduler (January 9, 2025)
|
||||
- ✅ Implemented automatic backup system with node-cron running every 12 hours (00:00 and 12:00)
|
||||
- ✅ Automatic cleanup maintaining only the 10 most recent backups to prevent disk space issues
|
||||
- ✅ Backup files stored in server/backups/ directory with timestamped filenames
|
||||
- ✅ Enhanced administration interface showing backup statistics and manual backup creation
|
||||
- ✅ Real-time backup status monitoring with automatic stats refresh every 30 seconds
|
||||
- ✅ Initial backup creation on server startup if no backups exist
|
||||
|
||||
### Security Improvements (January 9, 2025)
|
||||
- ✅ Enhanced login page to hide default admin credentials (admin/admin123) once password is changed
|
||||
- ✅ Added dynamic credential visibility based on whether default password is still in use
|
||||
- ✅ Security warning displayed when default credentials are shown
|
||||
- ✅ API endpoint to check default credential status with password hash comparison
|
||||
|
||||
### Automatic Data Purge System (January 9, 2025)
|
||||
- ✅ Implemented automatic purge system for sales data older than 19 months
|
||||
- ✅ Scheduled purge execution on the 1st of each month at 02:00 (Europe/Paris timezone)
|
||||
- ✅ Manual purge execution available in administration panel
|
||||
- ✅ Comprehensive statistics dashboard showing purge-eligible records
|
||||
- ✅ Regulatory compliance with French fireworks sales data retention requirements
|
||||
- ✅ Safety confirmation dialogs for manual purge operations
|
||||
- ✅ Real-time monitoring of data retention status with visual indicators
|
||||
|
||||
### Modern UI Design & Advanced Search (January 10, 2025)
|
||||
- ✅ Complete UI redesign with modern, elegant design system
|
||||
- ✅ Enhanced visual borders and frames throughout the application for better visibility
|
||||
- ✅ Colored sections with animations and smooth transitions
|
||||
- ✅ Made "autorité de délivrance" field optional in new sale form as requested
|
||||
- ✅ Added comprehensive search module in sales history with multi-field filtering
|
||||
- ✅ Real-time search across names, sellers, articles, codes, payment methods, and birth places
|
||||
- ✅ Added CSV export functionality alongside existing PDF export
|
||||
- ✅ Visible section frames around each category in new sale form
|
||||
- ✅ Enhanced cards and containers with stronger visual borders
|
||||
- ✅ Improved responsive design for mobile and tablet devices
|
||||
- ✅ Modern search interface with clear result counting and active search indicators
|
||||
- ✅ Dual export options (PDF and CSV) for regulatory compliance reporting
|
||||
- ✅ Enhanced save button with more prominent green color for better visibility
|
||||
- ✅ Added visible solid background colors to each form category (vendor, product, client, identity)
|
||||
- ✅ Removed section borders for cleaner design with color-coded backgrounds
|
||||
- ✅ Added border to reset button for better visibility
|
||||
- ✅ Optimized border thickness for better balance between visibility and aesthetics
|
||||
- ✅ Removed manual date field from new sale form - timestamp now automatically recorded
|
||||
- ✅ Automatic date/time creation ensures accuracy and prevents manual entry errors
|
||||
- ✅ Added date and time display in sales history with automatic timestamp from database
|
||||
- ✅ Harmonized color scheme in new sale form with softer, complementary colors
|
||||
- ✅ Updated section backgrounds to work cohesively with green save button
|
||||
- ✅ Uniformized export buttons with consistent styling
|
||||
- ✅ Applied angular design with reduced border-radius throughout entire application
|
||||
- ✅ Modern, crisp appearance with 4px border-radius for cards, inputs, and buttons
|
||||
- ✅ White background for all input fields for better contrast and readability
|
||||
|
||||
### Photo Capture System & Tablet Support (January 11, 2025)
|
||||
- ✅ Restored complete photo capture functionality for identity documents (recto/verso)
|
||||
- ✅ Added photo download capability from sales history modal with structured filenames
|
||||
- ✅ Enhanced camera support for tablets with advanced constraint handling
|
||||
- ✅ Multi-fallback camera system: environment → user → basic video for maximum compatibility
|
||||
- ✅ Optimized video resolution for tablets (1280x720 ideal, up to 1920x1080)
|
||||
- ✅ Added visual framing overlay to help users position identity documents correctly
|
||||
- ✅ Improved CameraModal with better responsive design for tablet screens
|
||||
- ✅ Enhanced error handling with device-specific camera availability checks
|
||||
- ✅ Camera permission detection and clear error messages for troubleshooting
|
||||
- ✅ Support for both front and rear cameras on tablets and mobile devices
|
||||
- ✅ Camera diagnostic system with detailed test functionality
|
||||
- ✅ File upload fallback when camera access fails
|
||||
- ✅ Comprehensive error handling with specific messages for different failure types
|
||||
- ✅ HTTPS requirement detection for secure camera access
|
||||
- ✅ Multiple capture methods: camera → file upload for maximum compatibility
|
||||
- ✅ Added receipt/ticket photo capture as third photo type alongside identity documents
|
||||
- ✅ PayloadTooLargeError resolved by increasing server limit to 10MB for photo uploads
|
||||
- ✅ Complete photo management system with separate sections for identity and receipt photos
|
||||
- ✅ Enhanced SaleDetailsModal with organized photo display and download functionality
|
||||
- ✅ **CRITICAL FIX**: Photo ticket functionality completely debugged and operational
|
||||
- ✅ **API Routes**: Fixed camelCase to snake_case mapping (photoTicket → photo_ticket)
|
||||
- ✅ **Frontend**: Corrected NewSaleForm.tsx to include photoTicket in submission data
|
||||
- ✅ **Database**: Verified photo_ticket column exists and stores data correctly
|
||||
- ✅ **MAJOR FIX**: Resolved photo display in exports by correcting column mapping in getSalesByStore()
|
||||
- ✅ **Excel Export**: Enhanced with dedicated Photos sheet containing actual photo data links
|
||||
- ✅ **Production Ready**: All photo capture and export systems tested and validated for deployment
|
||||
- ✅ **UI Cleanup**: Removed "(optionnel)" text from ticket photo label for cleaner interface
|
||||
- ✅ **Camera System Enhanced**: Improved error handling and fallback mechanisms for photo capture
|
||||
- ✅ **Default Quantity Fix**: Set default product quantity to "1" in all forms and functions
|
||||
|
||||
### Production Deployment System (January 11, 2025)
|
||||
- ✅ Complete production deployment configuration with Docker Compose
|
||||
- ✅ Production-optimized Dockerfile with multi-stage builds and security hardening
|
||||
- ✅ Health check endpoints for monitoring and load balancer integration
|
||||
- ✅ Resource limits and logging configuration for production containers
|
||||
- ✅ Persistent volume management for data persistence and backups
|
||||
- ✅ Production security features: non-root user, secure cookies, session management
|
||||
- ✅ Replit Deployment integration ready for one-click production deployment
|
||||
- ✅ Photo capture system fully integrated with database storage
|
||||
- ✅ Modal de détails des ventes opérationnel avec support complet des photos
|
||||
- ✅ API et schéma de base de données synchronisés pour la production
|
||||
- ✅ Bug critique corrigé : mapping camelCase/snake_case pour photo_ticket
|
||||
- ✅ NewSaleForm.tsx corrigé pour inclure photoTicket dans la soumission
|
||||
- ✅ API routes.ts corrigé avec mapping automatique des photos
|
||||
- ✅ **Production Cleanup**: Removed all unnecessary deployment files and debug code
|
||||
- ✅ **Clean Architecture**: Simplified to essential files only (Dockerfile, docker-compose.yml, init.sql)
|
||||
- ✅ **Debug Removal**: All debug logs and temporary files cleaned for production
|
||||
- ✅ RegisFlow prêt pour déploiement en production avec toutes les fonctionnalités
|
||||
|
||||
### Production-Ready Excel Export with Images (January 11, 2025)
|
||||
- ✅ **CRITICAL FIX**: Excel export completely rebuilt with ExcelJS supporting embedded images
|
||||
- ✅ **Images in Excel**: Photos now directly embedded in Excel cells within the "Photos" worksheet
|
||||
- ✅ **Schema Consistency**: Corrected all snake_case/camelCase mappings between database and frontend
|
||||
- ✅ **Database Integrity**: Full production readiness verification completed
|
||||
- ✅ **Data Validation**: 15 sales, 17 products, 5 users - all data integrity checks passed
|
||||
- ✅ **Security Verified**: Default admin password changed, proper role distribution confirmed
|
||||
- ✅ **Production Ready**: All export functionalities operational with real database photos
|
||||
- ✅ **Excel Images**: Photos from sales (recto, verso, ticket) properly displayed in Excel export files
|
||||
|
||||
### Production Deployment System 2025 Update (January 19, 2025)
|
||||
- ✅ **Complete Docker Configuration Overhaul** : Updated to Node.js 20 and PostgreSQL 16
|
||||
- ✅ **Enhanced Security** : Implemented non-root containers, security constraints, and resource limits
|
||||
- ✅ **Multi-stage Dockerfile** : Optimized build process with separate build and production stages
|
||||
- ✅ **Advanced Health Checks** : Comprehensive monitoring with detailed status endpoints
|
||||
- ✅ **Production Environment** : Created `.env.production.example` with all security variables
|
||||
- ✅ **Automated Deployment** : Enhanced docker-entrypoint script with improved error handling
|
||||
- ✅ **Resource Management** : Memory and CPU limits for optimal server performance
|
||||
- ✅ **Network Security** : Updated to use nginx_default network for reverse proxy integration
|
||||
- ✅ **Comprehensive Documentation** : Created DEPLOYMENT_GUIDE.md with complete setup instructions
|
||||
- ✅ **Environment Validation** : Enhanced startup scripts with database connection verification
|
||||
- ✅ **Security Hardening** : SCRAM-SHA-256 authentication, secure cookies, and session management
|
||||
- ✅ **Backup Integration** : Production-ready backup system with automatic retention policies
|
||||
- ✅ **Monitoring Ready** : Complete logging and health check system for production monitoring
|
||||
|
||||
### Docker Production Fixes & Complete Resolution (January 19, 2025)
|
||||
- ✅ **CRITICAL FIX**: Resolved "docker-entrypoint.sh: No such file or directory" error
|
||||
- ✅ **Script Inline**: Created start script directly in Dockerfile.simple (no file copy issues)
|
||||
- ✅ **Migration Robustness**: Fixed drizzle-kit dependency and error handling
|
||||
- ✅ **Graceful Degradation**: Application starts even if migrations fail
|
||||
- ✅ **Auto-Creation**: Database tables created automatically on first connection
|
||||
- ✅ **SSL Configuration**: Fixed PostgreSQL SSL errors with sslmode=disable for Docker
|
||||
- ✅ **Backup Import Fix**: Corrected timestamp conversion in backup import functionality
|
||||
- ✅ **Admin Permissions Fix**: Corrected user role 'admin' to 'administrator' for production consistency
|
||||
- ✅ **Role Middleware Fix**: Updated all admin routes to use 'administrator' role instead of 'admin'
|
||||
- ✅ **Frontend Role Fix**: Updated Dashboard.tsx canAccessAdmin checks to use 'administrator' role
|
||||
- ✅ **Production Database Fix**: Corrected admin user role in production database to 'administrator'
|
||||
- ✅ **Production Issue Identified**: Production uses separate PostgreSQL database with different admin user
|
||||
- ✅ **SQL Fix Script Created**: Created fix-production-admin.sql to update admin role in production
|
||||
- ✅ **Production Guide**: Created comprehensive DEPLOYMENT_GUIDE.md with troubleshooting
|
||||
- ✅ **Environment Templates**: Updated .env.production.example with security defaults
|
||||
- ✅ **Health Diagnostics**: Added production-ready health checks and monitoring
|
||||
- ✅ **Multi-stage Build**: Optimized Dockerfile for production performance and security
|
||||
|
||||
### Previous Docker Issues (January 13, 2025)
|
||||
- ✅ Resolved cache corruption errors during Docker deployment
|
||||
- ✅ Fixed missing prod-start.js references in Dockerfile and entrypoint scripts
|
||||
- ✅ Simplified Docker build process to use dist/index.js directly
|
||||
- ✅ Eliminated temporary compatibility scripts after project cleanup
|
||||
- ✅ Fixed Node.js 18 import.meta.dirname undefined error with automatic patching
|
||||
- ✅ Added sed-based patch in docker-entrypoint-simple.sh for production compatibility
|
||||
- ✅ Production-ready Docker configuration with Node.js 18 compatibility
|
||||
|
||||
### Docker Production Deployment Fix (January 9, 2025)
|
||||
- ✅ Fixed "relation 'users' does not exist" error by adding drizzle-kit to production dependencies
|
||||
- ✅ Added automatic database migration in docker-entrypoint-simple.sh script
|
||||
- ✅ Resolved "Could not find build directory /app/public" by copying assets to correct location
|
||||
- ✅ Updated Dockerfile to copy dist/public to /app/public for serveStatic compatibility
|
||||
- ✅ Database tables now created automatically on first container startup
|
||||
- ✅ Complete Docker deployment package ready with all fixes applied
|
||||
|
||||
### Portainer Deployment Fix (January 10, 2025)
|
||||
- ✅ Resolved Docker image pull error "regisflow-regisflow:latest" in Portainer
|
||||
- ✅ Created local image building solution with "regisflow:latest" tag
|
||||
- ✅ Generated Portainer-specific stack configuration (portainer-stack.yml)
|
||||
- ✅ Built automated deployment script (deploy-portainer.sh) for server setup
|
||||
- ✅ Created comprehensive Portainer deployment guide (PORTAINER_GUIDE.md)
|
||||
- ✅ Fixed image reference in docker-compose.yml to use local image
|
||||
- ✅ Configured proper networking and volume management for Portainer stacks
|
||||
|
||||
### Production Deployment System (January 9, 2025)
|
||||
- ✅ Multi-stage Dockerfile optimized for production with security hardening
|
||||
- ✅ Production-specific Docker Compose configuration with resource limits
|
||||
- ✅ PostgreSQL production configuration with performance optimizations
|
||||
- ✅ Automated production deployment script with security validations
|
||||
- ✅ Nginx reverse proxy configuration with SSL/TLS and security headers
|
||||
- ✅ Comprehensive monitoring script for health checks, logs, and statistics
|
||||
- ✅ Enhanced environment configuration with mandatory security variables
|
||||
- ✅ Production-ready logging, backup retention, and data persistence
|
||||
- ✅ Container security hardening with non-root user and resource limits
|
||||
### Development Tools (used for project development, not runtime dependencies for deployed app)
|
||||
- `tsx`: TypeScript execution
|
||||
- `esbuild`: JavaScript bundler
|
||||
- `vite`: Development server and build tool
|
||||
@@ -1,6 +1,4 @@
|
||||
import { db } from './db';
|
||||
import { sales } from '@shared/schema';
|
||||
import { lt } from 'drizzle-orm';
|
||||
import { storage } from './storage';
|
||||
import cron from 'node-cron';
|
||||
|
||||
/**
|
||||
@@ -15,13 +13,9 @@ export async function purgeSalesData() {
|
||||
|
||||
console.log(`🗑️ Démarrage purge des ventes antérieures au ${cutoffDate.toLocaleDateString('fr-FR')}`);
|
||||
|
||||
// Supprimer les ventes antérieures à la date limite
|
||||
const result = await db
|
||||
.delete(sales)
|
||||
.where(lt(sales.timestamp, cutoffDate))
|
||||
.returning({ id: sales.id });
|
||||
|
||||
const deletedCount = result.length;
|
||||
// Use storage's purge method
|
||||
const result = await (storage as any).purgeOldSales();
|
||||
const deletedCount = result.deletedCount;
|
||||
|
||||
if (deletedCount > 0) {
|
||||
console.log(`✅ Purge terminée : ${deletedCount} vente(s) supprimée(s)`);
|
||||
@@ -51,25 +45,14 @@ export async function purgeSalesData() {
|
||||
*/
|
||||
export async function getPurgeStats() {
|
||||
try {
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setMonth(cutoffDate.getMonth() - 19);
|
||||
|
||||
// Compter les ventes qui seraient supprimées
|
||||
const oldSales = await db
|
||||
.select({ id: sales.id })
|
||||
.from(sales)
|
||||
.where(lt(sales.timestamp, cutoffDate));
|
||||
|
||||
// Compter le total des ventes
|
||||
const totalSales = await db
|
||||
.select({ id: sales.id })
|
||||
.from(sales);
|
||||
// Use storage's stats method
|
||||
const stats = await (storage as any).getPurgeStats();
|
||||
|
||||
return {
|
||||
totalSales: totalSales.length,
|
||||
oldSales: oldSales.length,
|
||||
cutoffDate: cutoffDate.toISOString(),
|
||||
purgeEligible: oldSales.length > 0
|
||||
totalSales: stats.totalSales,
|
||||
oldSales: stats.oldSales,
|
||||
cutoffDate: stats.cutoffDate,
|
||||
purgeEligible: stats.oldSales > 0
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -8,20 +8,13 @@ if (!process.env.DATABASE_URL) {
|
||||
);
|
||||
}
|
||||
|
||||
// Configuration de connexion avec SSL désactivé pour Docker local
|
||||
const connectionConfig = {
|
||||
// Configuration pour production avec DATABASE_URL
|
||||
export const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: false // Désactiver SSL pour production Docker locale
|
||||
};
|
||||
|
||||
// Si DATABASE_URL contient déjà sslmode=disable, ne pas forcer SSL
|
||||
if (process.env.DATABASE_URL?.includes('sslmode=disable')) {
|
||||
connectionConfig.ssl = false;
|
||||
} else if (process.env.NODE_ENV === 'production' && !process.env.DATABASE_URL?.includes('localhost')) {
|
||||
// Activer SSL uniquement pour production externe (pas Docker local)
|
||||
connectionConfig.ssl = { rejectUnauthorized: false };
|
||||
}
|
||||
|
||||
export const pool = new Pool(connectionConfig);
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
max: 20, // Maximum number of clients in the pool
|
||||
idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
|
||||
connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection cannot be established
|
||||
});
|
||||
|
||||
export const db = drizzle(pool, { schema });
|
||||
@@ -3,6 +3,7 @@ import { createServer, type Server } from "http";
|
||||
import { storage } from "./storage";
|
||||
import session from "express-session";
|
||||
import connectPg from "connect-pg-simple";
|
||||
import MemoryStore from "memorystore";
|
||||
import { loginSchema, insertUserSchema, insertStoreSchema, insertSaleSchema } from "@shared/schema";
|
||||
import { z } from "zod";
|
||||
import { createAutomaticBackup, getBackupStats } from "./backup-scheduler";
|
||||
@@ -48,13 +49,11 @@ declare global {
|
||||
}
|
||||
|
||||
export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Session configuration with PostgreSQL store
|
||||
const pgStore = connectPg(session);
|
||||
// Use memory store for sessions when database is not available
|
||||
const MemStoreClass = MemoryStore(session);
|
||||
app.use(session({
|
||||
store: new pgStore({
|
||||
conString: process.env.DATABASE_URL,
|
||||
createTableIfMissing: false,
|
||||
tableName: 'sessions',
|
||||
store: new MemStoreClass({
|
||||
checkPeriod: 86400000 // prune expired entries every 24h
|
||||
}),
|
||||
secret: process.env.SESSION_SECRET || 'fireworks-secret-key-2024',
|
||||
resave: false,
|
||||
@@ -198,7 +197,18 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
app.get('/api/stores', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = await storage.getUser(req.session.userId!);
|
||||
if (!user || !user.storeId) {
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
// Administrators can access all stores
|
||||
if (user.role === 'administrator') {
|
||||
const allStores = await storage.getAllStores();
|
||||
return res.json(allStores);
|
||||
}
|
||||
|
||||
// Non-administrators only access their assigned store
|
||||
if (!user.storeId) {
|
||||
return res.status(404).json({ error: "User store not found" });
|
||||
}
|
||||
|
||||
@@ -232,7 +242,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
targetStoreId = parseInt(storeId as string);
|
||||
|
||||
// For non-admin users, verify they can only access their own store
|
||||
if (user.role !== 'admin' && targetStoreId !== user.storeId) {
|
||||
if (user.role !== 'administrator' && targetStoreId !== user.storeId) {
|
||||
return res.status(403).json({ error: "Access denied to this store" });
|
||||
}
|
||||
} else {
|
||||
@@ -264,7 +274,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
// Determine which store to use for the sale
|
||||
let targetStoreId: number;
|
||||
if (user.role === 'admin' && req.body.storeId) {
|
||||
if (user.role === 'administrator' && req.body.storeId) {
|
||||
// Admin can create sales for any store
|
||||
targetStoreId = req.body.storeId;
|
||||
} else {
|
||||
@@ -312,6 +322,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
// Parse and validate products
|
||||
const validatedProducts = products.map((product: any) => ({
|
||||
saleId: 0, // Will be set by storage layer
|
||||
typeArticle: product.typeArticle,
|
||||
categorie: product.categorie,
|
||||
quantite: parseInt(product.quantite),
|
||||
@@ -329,7 +340,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/sales/:id', requireRole(['admin', 'manager']), async (req, res) => {
|
||||
app.delete('/api/sales/:id', requireRole(['administrator', 'manager']), async (req, res) => {
|
||||
try {
|
||||
const saleId = parseInt(req.params.id);
|
||||
await storage.deleteSale(saleId);
|
||||
|
||||
@@ -375,4 +375,311 @@ export class DatabaseStorage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
export const storage = new DatabaseStorage();
|
||||
class MemStorage implements IStorage {
|
||||
private users: User[] = [];
|
||||
private stores: Store[] = [];
|
||||
private sales: Sale[] = [];
|
||||
private saleProducts: SaleProduct[] = [];
|
||||
private nextUserId = 1;
|
||||
private nextStoreId = 1;
|
||||
private nextSaleId = 1;
|
||||
private nextSaleProductId = 1;
|
||||
|
||||
// User operations
|
||||
async getUser(id: number): Promise<User | undefined> {
|
||||
return this.users.find(u => u.id === id);
|
||||
}
|
||||
|
||||
async getUserByUsername(username: string): Promise<User | undefined> {
|
||||
return this.users.find(u => u.username === username);
|
||||
}
|
||||
|
||||
async createUser(userData: InsertUser): Promise<User> {
|
||||
// Hash password if provided
|
||||
if (userData.password) {
|
||||
userData.password = await this.hashPassword(userData.password);
|
||||
}
|
||||
|
||||
// Convert empty email to null to avoid unique constraint issues
|
||||
if (userData.email === "") {
|
||||
userData.email = null;
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
...userData,
|
||||
id: this.nextUserId++,
|
||||
email: userData.email || null,
|
||||
isActive: userData.isActive || true,
|
||||
updatedAt: null,
|
||||
firstName: userData.firstName || null,
|
||||
lastName: userData.lastName || null,
|
||||
storeId: userData.storeId || null,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
this.users.push(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
async updateUser(id: number, userData: Partial<InsertUser>): Promise<User | undefined> {
|
||||
const userIndex = this.users.findIndex(u => u.id === id);
|
||||
if (userIndex === -1) return undefined;
|
||||
|
||||
// Hash password if provided
|
||||
if (userData.password) {
|
||||
userData.password = await this.hashPassword(userData.password);
|
||||
}
|
||||
|
||||
// Convert empty email to null to avoid unique constraint issues
|
||||
if (userData.email === "") {
|
||||
userData.email = null;
|
||||
}
|
||||
|
||||
this.users[userIndex] = { ...this.users[userIndex], ...userData };
|
||||
return this.users[userIndex];
|
||||
}
|
||||
|
||||
async getAllUsers(): Promise<User[]> {
|
||||
return [...this.users].sort((a, b) => (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0));
|
||||
}
|
||||
|
||||
async deleteUser(id: number): Promise<void> {
|
||||
this.users = this.users.filter(u => u.id !== id);
|
||||
}
|
||||
|
||||
// Store operations
|
||||
async getStore(id: number): Promise<Store | undefined> {
|
||||
return this.stores.find(s => s.id === id);
|
||||
}
|
||||
|
||||
async createStore(storeData: InsertStore): Promise<Store> {
|
||||
const store: Store = {
|
||||
...storeData,
|
||||
id: this.nextStoreId++,
|
||||
address: storeData.address || null,
|
||||
phone: storeData.phone || null,
|
||||
email: storeData.email || null,
|
||||
isActive: storeData.isActive || true,
|
||||
updatedAt: null,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
this.stores.push(store);
|
||||
return store;
|
||||
}
|
||||
|
||||
async getAllStores(): Promise<Store[]> {
|
||||
return [...this.stores].sort((a, b) => (b.createdAt?.getTime() || 0) - (a.createdAt?.getTime() || 0));
|
||||
}
|
||||
|
||||
async updateStore(id: number, storeData: Partial<InsertStore>): Promise<Store | undefined> {
|
||||
const storeIndex = this.stores.findIndex(s => s.id === id);
|
||||
if (storeIndex === -1) return undefined;
|
||||
|
||||
this.stores[storeIndex] = { ...this.stores[storeIndex], ...storeData };
|
||||
return this.stores[storeIndex];
|
||||
}
|
||||
|
||||
async deleteStore(id: number): Promise<void> {
|
||||
this.stores = this.stores.filter(s => s.id !== id);
|
||||
}
|
||||
|
||||
// Sale operations
|
||||
async getSale(id: number): Promise<Sale | undefined> {
|
||||
return this.sales.find(s => s.id === id);
|
||||
}
|
||||
|
||||
async createSaleWithProducts(sale: InsertSale, products: InsertSaleProduct[]): Promise<Sale> {
|
||||
const newSale: Sale = {
|
||||
...sale,
|
||||
id: this.nextSaleId++,
|
||||
timestamp: new Date(),
|
||||
lieuNaissance: sale.lieuNaissance || null,
|
||||
autoriteDelivrance: sale.autoriteDelivrance || null,
|
||||
// Map typeIdentite to documentType field for historical reasons
|
||||
documentType: sale.typeIdentite || null,
|
||||
// Map numeroIdentite to documentNumber field
|
||||
documentNumber: sale.numeroIdentite || null,
|
||||
// Ensure adresse is properly mapped
|
||||
adresse: sale.adresse || null,
|
||||
photoRecto: sale.photoRecto || null,
|
||||
photoVerso: sale.photoVerso || null,
|
||||
photo_ticket: sale.photo_ticket || null,
|
||||
};
|
||||
this.sales.push(newSale);
|
||||
|
||||
// Create sale products
|
||||
for (const product of products) {
|
||||
const saleProduct: SaleProduct = {
|
||||
...product,
|
||||
id: this.nextSaleProductId++,
|
||||
saleId: newSale.id,
|
||||
createdAt: null,
|
||||
};
|
||||
this.saleProducts.push(saleProduct);
|
||||
}
|
||||
|
||||
return newSale;
|
||||
}
|
||||
|
||||
async getSalesByStore(storeId: number, startDate?: string, endDate?: string): Promise<(Sale & { products: SaleProduct[] })[]> {
|
||||
let filteredSales = this.sales.filter(s => s.storeId === storeId);
|
||||
|
||||
if (startDate) {
|
||||
const start = new Date(startDate);
|
||||
filteredSales = filteredSales.filter(s => s.timestamp && s.timestamp >= start);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
const end = new Date(endDate);
|
||||
end.setHours(23, 59, 59, 999); // Include the entire end date
|
||||
filteredSales = filteredSales.filter(s => s.timestamp && s.timestamp <= end);
|
||||
}
|
||||
|
||||
// Add products to each sale
|
||||
const salesWithProducts = filteredSales.map(sale => ({
|
||||
...sale,
|
||||
products: this.saleProducts.filter(p => p.saleId === sale.id)
|
||||
}));
|
||||
|
||||
return salesWithProducts.sort((a, b) => (b.timestamp?.getTime() || 0) - (a.timestamp?.getTime() || 0));
|
||||
}
|
||||
|
||||
async getSaleWithProducts(id: number): Promise<(Sale & { products: SaleProduct[] }) | undefined> {
|
||||
const sale = await this.getSale(id);
|
||||
if (!sale) return undefined;
|
||||
|
||||
const products = this.saleProducts.filter(sp => sp.saleId === id);
|
||||
return { ...sale, products };
|
||||
}
|
||||
|
||||
async deleteSale(id: number): Promise<void> {
|
||||
this.saleProducts = this.saleProducts.filter(sp => sp.saleId !== id);
|
||||
this.sales = this.sales.filter(s => s.id !== id);
|
||||
}
|
||||
|
||||
// Auth operations
|
||||
async authenticateUser(username: string, password: string): Promise<User | null> {
|
||||
const user = await this.getUserByUsername(username);
|
||||
if (!user || !user.isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
return isValid ? user : null;
|
||||
}
|
||||
|
||||
async hashPassword(password: string): Promise<string> {
|
||||
return await bcrypt.hash(password, 12);
|
||||
}
|
||||
|
||||
// Check if admin still uses default password
|
||||
async isUsingDefaultPassword(): Promise<boolean> {
|
||||
const adminUser = await this.getUserByUsername('admin');
|
||||
if (!adminUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the stored password hash matches the default password "admin123"
|
||||
return await bcrypt.compare('admin123', adminUser.password);
|
||||
}
|
||||
|
||||
// Initialize default admin user and store if none exist
|
||||
async initializeDefaults(): Promise<{ defaultAdminCredentials?: { username: string; password: string } }> {
|
||||
const existingUsers = await this.getAllUsers();
|
||||
|
||||
if (existingUsers.length === 0) {
|
||||
// Create default store
|
||||
const defaultStore = await this.createStore({
|
||||
name: "Magasin Principal",
|
||||
address: "À définir",
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Create default admin user
|
||||
const defaultPassword = "admin123";
|
||||
const adminUser = await this.createUser({
|
||||
username: "admin",
|
||||
password: defaultPassword,
|
||||
email: "admin@example.com",
|
||||
firstName: "Administrateur",
|
||||
lastName: "Principal",
|
||||
role: "administrator",
|
||||
storeId: defaultStore.id,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
return {
|
||||
defaultAdminCredentials: {
|
||||
username: "admin",
|
||||
password: defaultPassword
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods for backup and purge functionality
|
||||
class MemStorageWithBackup extends MemStorage {
|
||||
// Backup/export functionality
|
||||
async exportData() {
|
||||
return {
|
||||
users: this.users.map(u => ({ ...u, password: undefined })), // Exclude passwords
|
||||
stores: this.stores,
|
||||
sales: this.sales,
|
||||
saleProducts: this.saleProducts
|
||||
};
|
||||
}
|
||||
|
||||
// Import/restore functionality
|
||||
async importData(data: any) {
|
||||
if (data.users) this.users = data.users;
|
||||
if (data.stores) this.stores = data.stores;
|
||||
if (data.sales) this.sales = data.sales;
|
||||
if (data.saleProducts) this.saleProducts = data.saleProducts;
|
||||
|
||||
// Update counters to avoid ID conflicts
|
||||
this.nextUserId = Math.max(...this.users.map(u => u.id), 0) + 1;
|
||||
this.nextStoreId = Math.max(...this.stores.map(s => s.id), 0) + 1;
|
||||
this.nextSaleId = Math.max(...this.sales.map(s => s.id), 0) + 1;
|
||||
this.nextSaleProductId = Math.max(...this.saleProducts.map(sp => sp.id), 0) + 1;
|
||||
}
|
||||
|
||||
// Purge old sales data (19 months+)
|
||||
async purgeOldSales(): Promise<{ deletedCount: number }> {
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setMonth(cutoffDate.getMonth() - 19);
|
||||
|
||||
const oldSalesCount = this.sales.filter(s => s.timestamp && s.timestamp < cutoffDate).length;
|
||||
|
||||
// Remove old sales and their products
|
||||
const oldSaleIds = this.sales
|
||||
.filter(s => s.timestamp && s.timestamp < cutoffDate)
|
||||
.map(s => s.id);
|
||||
|
||||
this.sales = this.sales.filter(s => !s.timestamp || s.timestamp >= cutoffDate);
|
||||
this.saleProducts = this.saleProducts.filter(sp => !oldSaleIds.includes(sp.saleId));
|
||||
|
||||
return { deletedCount: oldSalesCount };
|
||||
}
|
||||
|
||||
// Get purge statistics
|
||||
async getPurgeStats() {
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setMonth(cutoffDate.getMonth() - 19);
|
||||
|
||||
const totalSales = this.sales.length;
|
||||
const oldSales = this.sales.filter(s => s.timestamp && s.timestamp < cutoffDate).length;
|
||||
|
||||
return {
|
||||
totalSales,
|
||||
oldSales,
|
||||
cutoffDate: cutoffDate.toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Use PostgreSQL storage in production, in-memory for development
|
||||
export const storage = process.env.NODE_ENV === 'production'
|
||||
? new DatabaseStorage()
|
||||
: new MemStorageWithBackup();
|
||||
@@ -74,6 +74,13 @@ export const sales = pgTable("sales", {
|
||||
autoriteDelivrance: varchar("autorite_delivrance", { length: 255 }),
|
||||
dateDelivrance: varchar("date_delivrance", { length: 10 }).notNull(),
|
||||
|
||||
// Adresse - ajout du champ manquant
|
||||
adresse: text("adresse"),
|
||||
|
||||
// Champs legacy pour compatibilité historique
|
||||
documentType: varchar("document_type", { length: 50 }),
|
||||
documentNumber: varchar("document_number", { length: 100 }),
|
||||
|
||||
// Photos
|
||||
photo_recto: text("photo_recto"),
|
||||
photo_verso: text("photo_verso"),
|
||||
@@ -91,6 +98,42 @@ export const saleProducts = pgTable("sale_products", {
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
// Customer Orders table
|
||||
export const customerOrders = pgTable("customer_orders", {
|
||||
id: serial("id").primaryKey(),
|
||||
storeId: integer("store_id").references(() => stores.id).notNull(),
|
||||
userId: integer("user_id").references(() => users.id).notNull(),
|
||||
timestamp: timestamp("timestamp").defaultNow(),
|
||||
|
||||
// Client information
|
||||
nom: varchar("nom", { length: 255 }).notNull(),
|
||||
prenom: varchar("prenom", { length: 255 }).notNull(),
|
||||
telephone: varchar("telephone", { length: 20 }),
|
||||
email: varchar("email", { length: 255 }),
|
||||
adresse: text("adresse"),
|
||||
|
||||
// Order details
|
||||
statut: varchar("statut", { length: 50 }).notNull().default("En attente de commande"),
|
||||
dateCommande: timestamp("date_commande"),
|
||||
dateLivraison: timestamp("date_livraison"),
|
||||
montant: integer("montant"), // En centimes
|
||||
commentaire: text("commentaire"), // Champ commentaire principal
|
||||
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
});
|
||||
|
||||
// Customer Order Products table
|
||||
export const customerOrderProducts = pgTable("customer_order_products", {
|
||||
id: serial("id").primaryKey(),
|
||||
orderId: integer("order_id").references(() => customerOrders.id, { onDelete: "cascade" }).notNull(),
|
||||
produit: varchar("produit", { length: 255 }).notNull(),
|
||||
quantite: integer("quantite").notNull().default(1),
|
||||
prixUnitaire: integer("prix_unitaire"), // En centimes
|
||||
total: integer("total"), // En centimes
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
// Relations
|
||||
export const storesRelations = relations(stores, ({ many }) => ({
|
||||
users: many(users),
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🚀 RegisFlow Docker Deployment Test"
|
||||
echo "==================================="
|
||||
|
||||
# Fonction de test des prérequis
|
||||
check_prerequisites() {
|
||||
echo "📋 Checking prerequisites..."
|
||||
|
||||
# Docker
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "❌ Docker not found. Please install Docker."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Docker Compose
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
echo "❌ Docker Compose not found. Please install Docker Compose."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Docker and Docker Compose are available"
|
||||
}
|
||||
|
||||
# Fonction de validation des fichiers
|
||||
check_files() {
|
||||
echo "📁 Checking required files..."
|
||||
|
||||
required_files=(
|
||||
"Dockerfile"
|
||||
"docker-compose.yml"
|
||||
"docker-entrypoint.sh"
|
||||
".env.production.example"
|
||||
"package.json"
|
||||
)
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "❌ Missing required file: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✅ All required files are present"
|
||||
}
|
||||
|
||||
# Test de build Docker
|
||||
test_docker_build() {
|
||||
echo "🔨 Testing Docker build..."
|
||||
|
||||
if docker-compose build --no-cache regisflow; then
|
||||
echo "✅ Docker build successful"
|
||||
else
|
||||
echo "❌ Docker build failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test de configuration
|
||||
test_configuration() {
|
||||
echo "⚙️ Testing configuration..."
|
||||
|
||||
if [[ ! -f ".env.production" ]]; then
|
||||
echo "⚠️ .env.production not found, creating from example..."
|
||||
cp .env.production.example .env.production
|
||||
echo "📝 Please edit .env.production with your secure credentials!"
|
||||
fi
|
||||
|
||||
# Vérifier que les variables critiques ne sont pas par défaut
|
||||
if grep -q "CHANGEME" .env.production; then
|
||||
echo "⚠️ WARNING: Default passwords detected in .env.production"
|
||||
echo " Please change POSTGRES_PASSWORD and SESSION_SECRET"
|
||||
fi
|
||||
|
||||
echo "✅ Configuration checked"
|
||||
}
|
||||
|
||||
# Test de démarrage rapide
|
||||
test_startup() {
|
||||
echo "🚦 Testing container startup..."
|
||||
|
||||
# Nettoyer d'abord
|
||||
docker-compose down -v 2>/dev/null
|
||||
|
||||
# Démarrer en mode détaché
|
||||
if docker-compose up -d; then
|
||||
echo "✅ Containers started successfully"
|
||||
|
||||
# Attendre que l'application soit prête
|
||||
echo "⏳ Waiting for application to be ready..."
|
||||
timeout=60
|
||||
while ! curl -s http://localhost:5000/health >/dev/null && [ $timeout -gt 0 ]; do
|
||||
echo " Waiting... ($timeout seconds left)"
|
||||
sleep 2
|
||||
timeout=$((timeout-2))
|
||||
done
|
||||
|
||||
if [ $timeout -gt 0 ]; then
|
||||
echo "✅ Application is responding"
|
||||
echo "🏥 Health check: $(curl -s http://localhost:5000/health | grep -o '"status":"[^"]*"')"
|
||||
else
|
||||
echo "❌ Application startup timeout"
|
||||
echo "📋 Container logs:"
|
||||
docker-compose logs --tail=20 regisflow
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "❌ Container startup failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test de connexion base de données
|
||||
test_database() {
|
||||
echo "🗄️ Testing database connection..."
|
||||
|
||||
if docker exec regisflow-db pg_isready -U regisflow -q; then
|
||||
echo "✅ Database is accessible"
|
||||
|
||||
# Tester les tables
|
||||
tables=$(docker exec regisflow-db psql -U regisflow -t -c "\dt" | wc -l)
|
||||
echo "📊 Database has $tables tables"
|
||||
|
||||
else
|
||||
echo "❌ Database connection failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Fonction de nettoyage
|
||||
cleanup() {
|
||||
echo "🧹 Cleaning up test containers..."
|
||||
docker-compose down -v
|
||||
echo "✅ Cleanup completed"
|
||||
}
|
||||
|
||||
# Test complet
|
||||
run_full_test() {
|
||||
check_prerequisites
|
||||
check_files
|
||||
test_configuration
|
||||
test_docker_build
|
||||
test_startup
|
||||
test_database
|
||||
|
||||
echo ""
|
||||
echo "🎉 ALL TESTS PASSED!"
|
||||
echo "===================="
|
||||
echo "RegisFlow is ready for production deployment"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Edit .env.production with secure credentials"
|
||||
echo "2. Run: docker-compose up -d"
|
||||
echo "3. Access: http://localhost:5000"
|
||||
echo "4. Monitor: docker-compose logs -f"
|
||||
echo ""
|
||||
|
||||
cleanup
|
||||
}
|
||||
|
||||
# Exécuter le test ou afficher l'aide
|
||||
case "${1:-test}" in
|
||||
"test")
|
||||
run_full_test
|
||||
;;
|
||||
"build")
|
||||
check_prerequisites
|
||||
check_files
|
||||
test_docker_build
|
||||
;;
|
||||
"health")
|
||||
curl -s http://localhost:5000/health | python3 -m json.tool || echo "Application not running"
|
||||
;;
|
||||
"cleanup")
|
||||
cleanup
|
||||
;;
|
||||
"help")
|
||||
echo "Usage: $0 [test|build|health|cleanup|help]"
|
||||
echo " test - Run full deployment test (default)"
|
||||
echo " build - Test Docker build only"
|
||||
echo " health - Check application health"
|
||||
echo " cleanup - Clean up test containers"
|
||||
echo " help - Show this help"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use '$0 help' for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||