Compare commits
13 Commits
61cdec00e7
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 36c1fe805e | |||
| 96da68e18e | |||
| 59401b3c85 | |||
| 74f19b669b | |||
| 461d66a90f | |||
| ad9009afa1 | |||
| d82dc0578c | |||
| ec61ae6721 | |||
| 29a29a5074 | |||
| 6641da9a3d | |||
| 80cd96e006 | |||
| 54b343b2fb | |||
| 7c16e5d8cb |
@@ -1,210 +0,0 @@
|
||||
# Méthodes disponibles sur l'objet `Article` (Sequelize)
|
||||
|
||||
Ce document liste toutes les méthodes automatiquement générées par Sequelize pour le modèle `Article` en fonction des associations définies dans `src/database/relationships/index.js`.
|
||||
|
||||
---
|
||||
|
||||
## Associations Many-to-Many (belongsToMany)
|
||||
|
||||
### 1. **Tags** via `TagList`
|
||||
Association alias: `articleTags`
|
||||
Clés: `foreignKey: "articleId"`, `otherKey: "tagName"`
|
||||
|
||||
```javascript
|
||||
article.getArticleTags() // Récupère tous les tags
|
||||
article.setArticleTags([...]) // Définit les tags
|
||||
article.addArticleTag(tag) // Ajoute un tag
|
||||
article.addArticleTags([...]) // Ajoute plusieurs tags
|
||||
article.removeArticleTag(tag) // Retire un tag
|
||||
article.removeArticleTags([...]) // Retire plusieurs tags
|
||||
article.countArticleTags() // Compte les tags
|
||||
article.hasArticleTag(tag) // Vérifie si le tag est lié
|
||||
article.hasArticleTags([...]) // Vérifie si les tags sont liés
|
||||
|
||||
// Alias généré par Sequelize (correspond à belongsToMany):
|
||||
// Nom de la clé étrangère singularisée + pluralisé = "ArticleTag(s)" => "addTagList", "getTagList", etc.
|
||||
article.addTagList(tag) // Ajoute un tag via l'alias
|
||||
article.addTagLists([...]) // Ajoute plusieurs tags via l'alias
|
||||
article.getTagLists() // Récupère les tags via l'alias
|
||||
article.setTagLists([...]) // Définit les tags via l'alias
|
||||
article.removeTagList(tag) // Retire un tag via l'alias
|
||||
article.removeTagLists([...]) // Retire plusieurs tags via l'alias
|
||||
article.countTagLists() // Compte les tags via l'alias
|
||||
article.hasTagList(tag) // Vérifie si le tag est lié via l'alias
|
||||
article.hasTagLists([...]) // Vérifie si les tags sont liés via l'alias
|
||||
```
|
||||
|
||||
### 2. **Users (Favoris)** via `Favorite`
|
||||
Association alias: `articleFavoriteUsers`
|
||||
Clés: `foreignKey: "articleId"`, `otherKey: "userId"`
|
||||
|
||||
```javascript
|
||||
article.getArticleFavoriteUsers() // Récupère les utilisateurs qui ont favorisé
|
||||
article.setArticleFavoriteUsers([...]) // Définit les utilisateurs
|
||||
article.addUserIdUser(user) // Ajoute un utilisateur
|
||||
article.addArticleFavoriteUsers([...]) // Ajoute plusieurs utilisateurs
|
||||
article.removeUserIdUser(user) // Retire un utilisateur
|
||||
article.removeArticleFavoriteUsers([...]) // Retire plusieurs utilisateurs
|
||||
article.countArticleFavoriteUsers() // Compte les utilisateurs
|
||||
article.hasUserIdUser(user) // Vérifie si l'utilisateur a favorisé
|
||||
article.hasArticleFavoriteUsers([...]) // Vérifie si les utilisateurs ont favorisé
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Associations One-to-Many (hasMany)
|
||||
|
||||
### 3. **Comments**
|
||||
Association alias: `comments`
|
||||
Clé étrangère: `articleId`
|
||||
|
||||
```javascript
|
||||
article.getComments() // Récupère tous les commentaires
|
||||
article.setComments([...]) // Définit les commentaires
|
||||
article.addComment(comment) // Crée/ajoute un commentaire
|
||||
article.addComments([...]) // Ajoute plusieurs commentaires
|
||||
article.removeComment(comment) // Retire un commentaire
|
||||
article.removeComments([...]) // Retire plusieurs commentaires
|
||||
article.countComments() // Compte les commentaires
|
||||
article.hasComment(comment) // Vérifie si le commentaire existe
|
||||
article.hasComments([...]) // Vérifie si les commentaires existent
|
||||
article.createComment({...}) // Crée un nouveau commentaire
|
||||
```
|
||||
|
||||
### 4. **Favorites** (enregistrements de join)
|
||||
Association alias: `favorites`
|
||||
Clé étrangère: `articleId`
|
||||
|
||||
```javascript
|
||||
article.getFavorites() // Récupère les enregistrements Favorite
|
||||
article.setFavorites([...]) // Définit les favoris
|
||||
article.addFavorite(favorite) // Ajoute un enregistrement Favorite
|
||||
article.addFavorites([...]) // Ajoute plusieurs enregistrements
|
||||
article.removeFavorite(favorite) // Retire un enregistrement Favorite
|
||||
article.removeFavorites([...]) // Retire plusieurs enregistrements
|
||||
article.countFavorites() // Compte les enregistrements Favorite
|
||||
article.hasFavorite(favorite) // Vérifie si le Favorite existe
|
||||
article.hasFavorites([...]) // Vérifie si les Favorites existent
|
||||
article.createFavorite({...}) // Crée un nouveau Favorite
|
||||
```
|
||||
|
||||
### 5. **TagLists** (enregistrements de join)
|
||||
Association alias: `tagLists`
|
||||
Clé étrangère: `articleId`
|
||||
|
||||
```javascript
|
||||
article.getTagLists() // Récupère les enregistrements TagList
|
||||
article.setTagLists([...]) // Définit les TagList
|
||||
article.addTagList(tagList) // Ajoute un enregistrement TagList
|
||||
article.addTagLists([...]) // Ajoute plusieurs enregistrements
|
||||
article.removeTagList(tagList) // Retire un enregistrement TagList
|
||||
article.removeTagLists([...]) // Retire plusieurs enregistrements
|
||||
article.countTagLists() // Compte les enregistrements TagList
|
||||
article.hasTagList(tagList) // Vérifie si le TagList existe
|
||||
article.hasTagLists([...]) // Vérifie si les TagLists existent
|
||||
article.createTagList({...}) // Crée un nouveau TagList
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Associations Belongs-To (hasOne inverse)
|
||||
|
||||
### 6. **Author (User)**
|
||||
Association alias: `author`
|
||||
Clé étrangère: `authorId`
|
||||
|
||||
```javascript
|
||||
article.getAuthor() // Récupère l'auteur
|
||||
article.setAuthor(user) // Définit l'auteur
|
||||
article.createAuthor({...}) // Crée un nouvel auteur
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Méthodes personnalisées du modèle
|
||||
|
||||
### Méthodes d'instance définies dans `Article.js` :
|
||||
|
||||
```javascript
|
||||
article.toJSONFor() // Retourne un objet JSON formaté
|
||||
// Format: { id, slug, title, description, body }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooks (Triggers)
|
||||
|
||||
```javascript
|
||||
Article.beforeValidate() // Déclenché avant la validation
|
||||
// Génère automatiquement un slug basé sur le titre
|
||||
// Format: slug(`${article.title}`) + '-' + random
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exemples d'utilisation concrets
|
||||
|
||||
### Ajouter des tags à un article
|
||||
```javascript
|
||||
const article = await Article.findByPk(1);
|
||||
const tag = await Tag.findByPk('javascript');
|
||||
await article.addTagList(tag); // Via alias
|
||||
// ou
|
||||
await article.addArticleTag(tag); // Via alias généré automatiquement
|
||||
```
|
||||
|
||||
### Récupérer les commentaires d'un article
|
||||
```javascript
|
||||
const article = await Article.findByPk(1);
|
||||
const comments = await article.getComments();
|
||||
```
|
||||
|
||||
### Obtenir les utilisateurs qui ont favorisé cet article
|
||||
```javascript
|
||||
const article = await Article.findByPk(1);
|
||||
const fans = await article.getArticleFavoriteUsers();
|
||||
```
|
||||
|
||||
### Créer un commentaire pour un article
|
||||
```javascript
|
||||
const article = await Article.findByPk(1);
|
||||
const comment = await article.createComment({
|
||||
body: "Great article!",
|
||||
authorId: userId
|
||||
});
|
||||
```
|
||||
|
||||
### Compter les tags
|
||||
```javascript
|
||||
const article = await Article.findByPk(1);
|
||||
const tagCount = await article.countTagLists();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes importantes
|
||||
|
||||
1. **Noms des méthodes générées** : Sequelize génère les noms en fonction du nom de l'association (alias) ou du modèle.
|
||||
- Si alias est `articleTags` → `getArticleTags()`, `addArticleTag()`, etc.
|
||||
- Si alias est `tagLists` → `getTagLists()`, `addTagList()`, etc.
|
||||
|
||||
2. **Singulier/Pluriel** : Les méthodes `add*` acceptent soit un seul élément (singulier) soit un tableau (pluriel).
|
||||
|
||||
3. **Paramètres optionnels** : La plupart des méthodes `get*` acceptent un paramètre `options` pour personnaliser la requête :
|
||||
```javascript
|
||||
article.getComments({
|
||||
where: { createdAt: { [Op.gte]: someDate } },
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
```
|
||||
|
||||
4. **Transactions** : Toutes les opérations CRUD peuvent être encapsulées dans une transaction :
|
||||
```javascript
|
||||
const transaction = await sequelize.transaction();
|
||||
await article.addTagList(tag, { transaction });
|
||||
await transaction.commit();
|
||||
```
|
||||
|
||||
5. **Alias vs Nom généré** : L'alias défini dans la relation détermine le nom des méthodes générées. Dans ce projet, les alias comme `articleTags`, `tagLists` permettent d'appeler `getTagLists()`, `addTagList()`, etc.
|
||||
@@ -1,328 +0,0 @@
|
||||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ✅ MIGRATION AUTOMATIQUE RÉUSSIE - Structure Basée Domaines ║
|
||||
║ ║
|
||||
║ De: /api/v1, /api/v2, /api/v3 (confus) ║
|
||||
║ Vers: /api/skydive, /api/cms, /api/herowars (clair) ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
📊 STATISTIQUES DE LA MIGRATION
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✓ Fichiers de routes migrés: 19 fichiers
|
||||
✓ Domaines créés: 3 nouveaux domaines
|
||||
✓ Fichiers index.js: 4 créés/mis à jour
|
||||
✓ Documentation: 5 fichiers de rapport
|
||||
✓ Scripts de migration: 3 scripts npm
|
||||
✓ Commits Git: 2 commits
|
||||
✓ Fichiers Swagger mis à jour: 4 fichiers YAML
|
||||
|
||||
|
||||
🎯 STRUCTURE NOUVELLE CRÉÉE
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
src/routes/api/
|
||||
├── skydive/ ← Parachutisme
|
||||
│ ├── aeronefs.js
|
||||
│ ├── applications.js
|
||||
│ ├── canopies.js
|
||||
│ ├── dropzones.js
|
||||
│ ├── jumps.js
|
||||
│ ├── pages.js
|
||||
│ ├── profiles.js
|
||||
│ ├── qcm.js
|
||||
│ └── index.js
|
||||
│
|
||||
├── cms/ ← Gestion de contenu
|
||||
│ ├── articles.routes.js
|
||||
│ ├── comments.routes.js
|
||||
│ ├── product.routes.js
|
||||
│ ├── products.routes.js
|
||||
│ ├── profiles.routes.js
|
||||
│ ├── tags.routes.js
|
||||
│ ├── user.routes.js
|
||||
│ └── index.js
|
||||
│
|
||||
└── herowars/ ← Jeu HeroWars
|
||||
├── clans.routes.js
|
||||
├── members.routes.js
|
||||
├── herowars.routes.js
|
||||
└── index.js
|
||||
|
||||
|
||||
📝 FICHIERS DE DOCUMENTATION CRÉÉS
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
1. MIGRATION_REPORT.md
|
||||
└─ Rapport détaillé: structure ancienne → nouvelle, fichiers migrés
|
||||
|
||||
2. MIGRATION_COMPLETE_SUMMARY.md
|
||||
└─ Résumé exécutif avec instructions de test et frontend
|
||||
|
||||
3. POST_MIGRATION_CHECKLIST.md
|
||||
└─ Liste de vérification manuelle pour validation
|
||||
|
||||
4. SCRIPTS_MIGRATION_GUIDE.md
|
||||
└─ Guide complet d'utilisation des scripts (CE FICHIER)
|
||||
|
||||
5. MIGRATION_SWAGGER_TODO.md
|
||||
└─ Notes sur les mises à jour Swagger/OpenAPI
|
||||
|
||||
|
||||
⚙️ SCRIPTS DE MIGRATION CRÉÉS
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
1. scripts/migrate-to-domain-structure.js
|
||||
│
|
||||
├─ Phase 1: Crée les répertoires de domaines
|
||||
├─ Phase 2: Migre tous les fichiers de routes
|
||||
├─ Phase 3: Génère les fichiers index.js
|
||||
├─ Phase 4: Met à jour src/routes/api/index.js
|
||||
├─ Phase 5: Crée les notes Swagger
|
||||
├─ Phase 6: Gère les fichiers Swagger
|
||||
├─ Phase 7: Effectue les opérations Git
|
||||
├─ Phase 8: Valide la migration
|
||||
└─ Phase 9: Génère un rapport
|
||||
|
||||
[✅ EXÉCUTÉ: commit 9f73ed0]
|
||||
|
||||
2. scripts/update-references-post-migration.js
|
||||
│
|
||||
├─ Phase 1: Met à jour les fichiers YAML Swagger
|
||||
├─ Phase 2: Met à jour la collection/env Postman
|
||||
├─ Phase 3: Scanne les références v1/v2/v3 restantes
|
||||
└─ Phase 4: Génère une liste de vérification
|
||||
|
||||
[✅ EXÉCUTÉ]
|
||||
|
||||
3. scripts/finalize-migration.js
|
||||
│
|
||||
├─ Phase 1: Valide la structure des domaines
|
||||
├─ Phase 2: Liste les anciens répertoires
|
||||
├─ Phase 3: Optionnel - Nettoie les v1/v2/v3 (--cleanup)
|
||||
├─ Phase 4: Optionnel - Teste les endpoints (--test)
|
||||
└─ Phase 5: Génère un résumé final
|
||||
|
||||
[PRÊT À EXÉCUTER - voir instructions ci-dessous]
|
||||
|
||||
|
||||
🚀 PROCHAINES ÉTAPES IMMÉDIATES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ÉTAPE 1️⃣: VALIDER LA STRUCTURE
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
$ node scripts/finalize-migration.js
|
||||
|
||||
✓ Affichera: "skydive/: ✓ (8 route files)"
|
||||
✓ Affichera: "cms/: ✓ (7 route files)"
|
||||
✓ Affichera: "herowars/: ✓ (3 route files)"
|
||||
|
||||
|
||||
ÉTAPE 2️⃣: NETTOYER LES ANCIENS RÉPERTOIRES (OPTIONNEL)
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
OPTION A - Via script:
|
||||
$ node scripts/finalize-migration.js --cleanup
|
||||
|
||||
OPTION B - Manuellement:
|
||||
$ rm -rf src/routes/api/v1 src/routes/api/v2 src/routes/api/v3
|
||||
$ git add -A && git commit -m "chore: remove old v1/v2/v3 directories"
|
||||
|
||||
|
||||
ÉTAPE 3️⃣: TESTER LE SERVEUR
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
Terminal 1 - Démarrer l'API:
|
||||
$ npm run local
|
||||
|
||||
Terminal 2 - Tester les endpoints:
|
||||
$ curl http://localhost:3200/api-docs # Swagger UI
|
||||
$ curl http://localhost:3200/api/skydive/jumps # Skydive
|
||||
$ curl http://localhost:3200/api/cms/articles # CMS
|
||||
$ curl http://localhost:3200/api/herowars/clans # HeroWars
|
||||
|
||||
|
||||
ÉTAPE 4️⃣: METTRE À JOUR LE FRONTEND (IMPORTANT!) 🔴
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
A. Mettre à jour les environnements Angular:
|
||||
Fichiers à modifier:
|
||||
- src/environments/environment.ts
|
||||
- src/environments/environment.development.ts
|
||||
- src/environments/environment.local.ts
|
||||
|
||||
AVANT:
|
||||
export const environment = {
|
||||
apiUrl: '/api/v1',
|
||||
cmsUrl: '/api/v2',
|
||||
herowarsUrl: '/api/v3',
|
||||
};
|
||||
|
||||
APRÈS:
|
||||
export const environment = {
|
||||
apiUrl: '/api/skydive',
|
||||
cmsUrl: '/api/cms',
|
||||
herowarsUrl: '/api/herowars',
|
||||
};
|
||||
|
||||
B. Mettre à jour les services Angular:
|
||||
Cherchez et remplacez les chemins en dur:
|
||||
|
||||
sed -i '' "s|'/api/v1/|'/api/skydive/|g" src/**/*.ts
|
||||
sed -i '' "s|'/api/v2/|'/api/cms/|g" src/**/*.ts
|
||||
sed -i '' "s|'/api/v3/|'/api/herowars/|g" src/**/*.ts
|
||||
|
||||
C. Tester le frontend:
|
||||
$ npm start
|
||||
Vérifier dans DevTools que les appels API vont vers /api/skydive/*, /api/cms/*, etc.
|
||||
|
||||
|
||||
ÉTAPE 5️⃣: POUSSER LES CHANGEMENTS
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
$ git push origin develop
|
||||
ou
|
||||
$ git push --all --follow-tags
|
||||
|
||||
|
||||
📋 RÉSUMÉ DES COMMITS GIT
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Commit 1: 9f73ed0
|
||||
├─ Message: "refactor: restructure API routes from v1/v2/v3 to domain-based..."
|
||||
├─ Actions:
|
||||
│ ├─ Créé: src/routes/api/skydive/, cms/, herowars/
|
||||
│ ├─ Copié: 19 fichiers de routes
|
||||
│ └─ Mis à jour: src/routes/api/index.js
|
||||
└─ État: ✅ MERGED
|
||||
|
||||
Commit 2: 74df96c
|
||||
├─ Message: "docs: add migration documentation, finalization scripts, and update Swagger paths"
|
||||
├─ Actions:
|
||||
│ ├─ Créé: 4 fichiers de documentation
|
||||
│ ├─ Créé: 2 scripts de finalisation
|
||||
│ └─ Mis à jour: 4 fichiers Swagger YAML
|
||||
└─ État: ✅ CURRENT HEAD
|
||||
|
||||
|
||||
🔄 AVANT/APRÈS - COMPARAISON DES API
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
SAUT/PARACHUTISME:
|
||||
AVANT: GET /api/v1/jumps
|
||||
APRÈS: GET /api/skydive/jumps
|
||||
──────────────────────
|
||||
GET /api/v1/aeronefs → GET /api/skydive/aeronefs
|
||||
POST /api/v1/aeronefs → POST /api/skydive/aeronefs
|
||||
|
||||
CONTENU:
|
||||
AVANT: GET /api/v2/articles
|
||||
APRÈS: GET /api/cms/articles
|
||||
────────────────────────
|
||||
GET /api/v2/articles/123 → GET /api/cms/articles/123
|
||||
POST /api/v2/articles → POST /api/cms/articles
|
||||
GET /api/v2/comments → GET /api/cms/comments
|
||||
|
||||
JEU:
|
||||
AVANT: GET /api/v3/clans
|
||||
APRÈS: GET /api/herowars/clans
|
||||
────────────────────────
|
||||
GET /api/v3/members → GET /api/herowars/members
|
||||
POST /api/v3/members/123 → POST /api/herowars/members/123
|
||||
|
||||
|
||||
⚠️ IMPORTANT: ROLLBACK EN CAS DE PROBLÈME
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Si vous rencontrez des problèmes:
|
||||
|
||||
Option 1 - Peu de changements (rollback 2 commits):
|
||||
$ git reset --hard HEAD~2
|
||||
|
||||
Option 2 - Si vous avez poussé, utiliser revert:
|
||||
$ git revert 74df96c
|
||||
$ git revert 9f73ed0
|
||||
|
||||
Option 3 - Restaurer depuis le remote:
|
||||
$ git fetch origin
|
||||
$ git reset --hard origin/develop
|
||||
|
||||
|
||||
✅ CHECKLIST DE VALIDATION POST-MIGRATION
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Backend (adastra_api):
|
||||
☐ npm run local - serveur démarre sans erreur
|
||||
☐ curl http://localhost:3200/api-docs - Swagger UI accessible
|
||||
☐ Endpoints skydive fonctionnent: GET /api/skydive/jumps
|
||||
☐ Endpoints CMS fonctionnent: GET /api/cms/articles
|
||||
☐ Endpoints HeroWars fonctionnent: GET /api/herowars/clans
|
||||
|
||||
Frontend (adastra_app):
|
||||
☐ npm start - app démarre sans erreur
|
||||
☐ Appels API vont vers les nouveaux chemins
|
||||
☐ Authentification toujours fonctionnelle
|
||||
☐ Pages affichent les données correctement
|
||||
☐ Aucune erreur 404 sur les endpoints
|
||||
|
||||
Git:
|
||||
☐ Commits créés et visibles: git log --oneline -5
|
||||
☐ Prêt pour: git push origin develop
|
||||
|
||||
Documentation:
|
||||
☐ README.md mis à jour (optionnel)
|
||||
☐ Équipe notifiée du changement
|
||||
☐ Documentation technique mise à jour
|
||||
|
||||
|
||||
📚 RESSOURCES SUPPLÉMENTAIRES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Consultez ces fichiers pour plus d'informations:
|
||||
|
||||
1. SCRIPTS_MIGRATION_GUIDE.md
|
||||
└─ Guide détaillé de chaque script et options
|
||||
|
||||
2. POST_MIGRATION_CHECKLIST.md
|
||||
└─ Liste complète de vérification manuelle
|
||||
|
||||
3. MIGRATION_COMPLETE_SUMMARY.md
|
||||
└─ Résumé exécutif avec instructions
|
||||
|
||||
4. docs/*.md (depuis l'audit architectural)
|
||||
└─ Architecture complète et ADRs
|
||||
|
||||
|
||||
🎓 POINTS CLÉS À RETENIR
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✓ NON-DESTRUCTIF: Les anciens répertoires restent, vous pouvez les nettoyer
|
||||
progressivement après vérification
|
||||
|
||||
✓ AUTOMATISÉ: Scripts Python/Node qui gèrent la plupart des tâches
|
||||
Swagger, Postman, routes mis à jour automatiquement
|
||||
|
||||
⚠️ MANUEL: Frontend DOIT être mis à jour (recherche/remplace)
|
||||
Tests doivent être revérifiés
|
||||
Documentation peut nécessiter mise à jour
|
||||
|
||||
✓ GIT-SAFE: Chaque étape crée un commit
|
||||
Rollback facile si problème
|
||||
Toutes les modifications sont tracées
|
||||
|
||||
✓ BÉNÉFICES: Chemins d'API clairs et explicites
|
||||
Séparation nette des domaines métier
|
||||
Meilleure architecture et maintenabilité
|
||||
Préparation pour futures versions d'API
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✨ Migration complétée avec succès! ✨
|
||||
|
||||
Prêt à continuer avec les 20 ADRs d'architecture?
|
||||
(QUICK_START_WEEK1.md pour la Phase 1)
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Date: 24 avril 2026
|
||||
Statut: ✅ COMPLETE
|
||||
Prochaine étape: Mise à jour du frontend + tests
|
||||
@@ -1,111 +0,0 @@
|
||||
|
||||
╔════════════════════════════════════════════════════════════════════════════╗
|
||||
║ MIGRATION SUCCESSFULLY COMPLETED ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
NEW API STRUCTURE:
|
||||
├── /api/skydive/* ← Skydiving features (jumps, aeronefs, dropzones, etc.)
|
||||
├── /api/cms/* ← Content management (articles, tags, comments, etc.)
|
||||
└── /api/herowars/* ← Game features (clans, members, raids, etc.)
|
||||
|
||||
WHAT WAS CHANGED:
|
||||
✓ Routes directory: v1/v2/v3 → domain-based structure
|
||||
✓ Route definitions: Updated all index.js files
|
||||
✓ Swagger YAML: Updated path prefixes to new domains
|
||||
✓ Postman collection: Updated endpoint URLs
|
||||
|
||||
FILES MIGRATED:
|
||||
✓ Skydive domain (8 route files):
|
||||
- aeronefs.js, applications.js, canopies.js, dropzones.js
|
||||
- jumps.js, pages.js, profiles.js, qcm.js
|
||||
|
||||
✓ CMS domain (8 route files):
|
||||
- articles.routes.js, comments.routes.js, product.routes.js
|
||||
- products.routes.js, profiles.routes.js, tags.routes.js, user.routes.js
|
||||
|
||||
✓ Hero Wars domain (3 route files):
|
||||
- clans.routes.js, members.routes.js, herowars.routes.js
|
||||
|
||||
TESTING INSTRUCTIONS:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. Start the API server:
|
||||
npm run local
|
||||
|
||||
2. In another terminal, verify Swagger is updated:
|
||||
curl http://localhost:3200/api-docs
|
||||
|
||||
3. Test a sample endpoint:
|
||||
curl http://localhost:3200/api/skydive/jumps
|
||||
curl http://localhost:3200/api/cms/articles
|
||||
curl http://localhost:3200/api/herowars/clans
|
||||
|
||||
FRONTEND UPDATES REQUIRED:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
In your Angular frontend (adastra_app):
|
||||
|
||||
1. Update environment files:
|
||||
src/environments/environment.ts
|
||||
src/environments/environment.development.ts
|
||||
src/environments/environment.local.ts
|
||||
|
||||
Replace:
|
||||
- apiUrl: '/api/v1' → apiUrl: '/api/skydive'
|
||||
- cmUrl: '/api/v2' → cmUrl: '/api/cms'
|
||||
- hwUrl: '/api/v3' → hwUrl: '/api/herowars'
|
||||
|
||||
2. Update API service calls:
|
||||
// Before:
|
||||
http.get('/api/v1/jumps')
|
||||
http.get('/api/v2/articles')
|
||||
http.get('/api/v3/clans')
|
||||
|
||||
// After:
|
||||
http.get('/api/skydive/jumps')
|
||||
http.get('/api/cms/articles')
|
||||
http.get('/api/herowars/clans')
|
||||
|
||||
3. Search & replace in your services:
|
||||
sed -i 's|/api/v1/|/api/skydive/|g' src/**/*.ts
|
||||
sed -i 's|/api/v2/|/api/cms/|g' src/**/*.ts
|
||||
sed -i 's|/api/v3/|/api/herowars/|g' src/**/*.ts
|
||||
|
||||
GIT OPERATIONS:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
View migration commits:
|
||||
git log --oneline -5
|
||||
|
||||
If needed, view detailed changes:
|
||||
git show HEAD
|
||||
|
||||
Push to remote:
|
||||
git push origin develop
|
||||
|
||||
ROLLBACK (if issues):
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
If you need to rollback:
|
||||
git reset --hard HEAD~2
|
||||
|
||||
(Adjust the number of commits based on history)
|
||||
|
||||
ADDITIONAL RESOURCES:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Documentation files created:
|
||||
- MIGRATION_REPORT.md: Detailed migration information
|
||||
- MIGRATION_SWAGGER_TODO.md: Swagger update notes
|
||||
- POST_MIGRATION_CHECKLIST.md: Verification checklist
|
||||
|
||||
Scripts created:
|
||||
- scripts/migrate-to-domain-structure.js: Main migration script
|
||||
- scripts/update-references-post-migration.js: Post-migration updates
|
||||
- scripts/finalize-migration.js: Cleanup and testing
|
||||
|
||||
═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✓ Migration is complete! Follow the testing instructions above.
|
||||
|
||||
═════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
API STRUCTURE MIGRATION REPORT
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
MIGRATION: v1/v2/v3 → Domain-Based (skydive/cms/herowars)
|
||||
|
||||
OLD STRUCTURE:
|
||||
└── routes/api/
|
||||
├── v1/ (Skydive routes)
|
||||
├── v2/ (CMS + misc routes)
|
||||
└── v3/ (Hero Wars routes)
|
||||
|
||||
NEW STRUCTURE:
|
||||
└── routes/api/
|
||||
├── skydive/
|
||||
├── cms/
|
||||
└── herowars/
|
||||
|
||||
ROUTE MAPPINGS:
|
||||
✓ /api/v1/* → /api/skydive/*
|
||||
- applications, aeronefs, canopies, dropzones, jumps, pages, profiles, qcm
|
||||
|
||||
✓ /api/v2/* → /api/cms/*
|
||||
- user, articles, comments, product, products, profiles, tags
|
||||
|
||||
✓ /api/v3/* → /api/herowars/*
|
||||
- clans, members, herowars (moved from v2)
|
||||
|
||||
FILES CREATED/UPDATED:
|
||||
✓ src/routes/api/skydive/index.js
|
||||
✓ src/routes/api/cms/index.js
|
||||
✓ src/routes/api/herowars/index.js
|
||||
✓ src/routes/api/index.js
|
||||
✓ All route files copied to appropriate domains
|
||||
|
||||
MANUAL STEPS REQUIRED:
|
||||
|
||||
1. Update Swagger/OpenAPI documentation:
|
||||
- Review MIGRATION_SWAGGER_TODO.md
|
||||
- Update path prefixes in src/swagger/*.yaml files
|
||||
- Update paths in src/config/swagger.js if needed
|
||||
|
||||
2. Update app.js (if direct version references exist):
|
||||
- Search for 'v1', 'v2', 'v3' strings
|
||||
- Replace with domain names where appropriate
|
||||
|
||||
3. Update tests and client calls:
|
||||
- Replace /api/v1/ with /api/skydive/
|
||||
- Replace /api/v2/ with /api/cms/
|
||||
- Replace /api/v3/ with /api/herowars/
|
||||
|
||||
4. Update any documentation:
|
||||
- README.md API endpoints section
|
||||
- API documentation and guides
|
||||
|
||||
5. Test all endpoints:
|
||||
- Verify each domain routes work correctly
|
||||
- Check authentication/authorization still functions
|
||||
- Validate error handling
|
||||
|
||||
OPTIONAL: Old v1/v2/v3 directories can be removed after verification:
|
||||
rm -rf src/routes/api/v1
|
||||
rm -rf src/routes/api/v2
|
||||
rm -rf src/routes/api/v3
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
# Swagger Path Updates Required
|
||||
|
||||
The following Swagger YAML files need path updates:
|
||||
|
||||
## skydive.yaml
|
||||
- Change: /api/v1/ → /api/skydive/
|
||||
- Affected routes: /jumps, /aeronefs, /dropzones, /canopies, /pages, /qcm, /applications
|
||||
|
||||
## cms.yaml
|
||||
- Change: /api/v2/ → /api/cms/
|
||||
- Affected routes: /articles, /comments, /products, /tags, /profiles, /user
|
||||
|
||||
## herowars.yaml
|
||||
- Change: /api/v3/ → /api/herowars/
|
||||
- Affected routes: /clans, /members
|
||||
- Note: Add herowars endpoint from former /api/v2/herowars
|
||||
|
||||
Update in: src/config/swagger.js if paths are hardcoded.
|
||||
@@ -1,80 +0,0 @@
|
||||
|
||||
# Post-Migration Update Checklist
|
||||
|
||||
## Files Updated by Script ✓
|
||||
- [x] Swagger YAML files (/api/v1 → /api/skydive, etc.)
|
||||
- [x] Postman collection and environment files
|
||||
- [x] API routes structure (new domains created)
|
||||
|
||||
## Manual Verification Required
|
||||
|
||||
### 1. Test API Endpoints
|
||||
```bash
|
||||
npm run local # Start the server
|
||||
# In another terminal:
|
||||
curl http://localhost:3200/api-docs # Check Swagger UI
|
||||
```
|
||||
|
||||
### 2. Verify Routes
|
||||
- [ ] GET /api/skydive/jumps
|
||||
- [ ] GET /api/cms/articles
|
||||
- [ ] GET /api/herowars/clans
|
||||
- [ ] Other domain-specific endpoints
|
||||
|
||||
### 3. Frontend Updates Required (if applicable)
|
||||
- [ ] Update Angular environment files (environment.ts, environment.development.ts, etc.)
|
||||
- [ ] Replace all API calls from /api/v1, /api/v2, /api/v3 to new domains
|
||||
- [ ] Update service layer URLs
|
||||
- [ ] Update HTTP interceptors if needed
|
||||
|
||||
### 4. Documentation Updates
|
||||
- [ ] Update README.md with new API structure
|
||||
- [ ] Update API documentation (if any)
|
||||
- [ ] Update deployment/infrastructure docs
|
||||
- [ ] Update team wiki/documentation
|
||||
|
||||
### 5. CI/CD Pipeline (if applicable)
|
||||
- [ ] Update build/deployment scripts referencing v1/v2/v3
|
||||
- [ ] Update any health checks pointing to old paths
|
||||
- [ ] Update monitoring/alerting rules
|
||||
|
||||
### 6. Client Applications
|
||||
- [ ] Update all client libraries/SDKs
|
||||
- [ ] Update mobile app API endpoints
|
||||
- [ ] Update third-party integrations
|
||||
|
||||
### 7. Database/Caching (if applicable)
|
||||
- [ ] Clear any cached references to old endpoints
|
||||
- [ ] Update API documentation in databases
|
||||
- [ ] Check for hardcoded URLs in configuration
|
||||
|
||||
## Git Operations
|
||||
```bash
|
||||
# View the migration commits
|
||||
git log --oneline -5
|
||||
|
||||
# If rollback needed:
|
||||
git revert <commit-hash>
|
||||
|
||||
# Push changes
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
## Optional: Cleanup Old Directories
|
||||
```bash
|
||||
# After thorough testing, remove old v1/v2/v3 directories:
|
||||
rm -rf src/routes/api/v1
|
||||
rm -rf src/routes/api/v2
|
||||
rm -rf src/routes/api/v3
|
||||
git add -A
|
||||
git commit -m "chore: remove old v1/v2/v3 route directories"
|
||||
```
|
||||
|
||||
## Rollback Procedure
|
||||
If issues arise, rollback with:
|
||||
```bash
|
||||
git reset --hard HEAD~2 # Adjust commit count if needed
|
||||
```
|
||||
|
||||
---
|
||||
Document generated: 2026-04-23T22:48:52.497Z
|
||||
@@ -1,67 +1,153 @@
|
||||
# AdAstra_API
|
||||
# AdAstra API
|
||||
|
||||
API pour l'application Ad Astra
|
||||
Backend of the AdAstra platform — an Express REST API serving four feature domains: CMS, e-commerce, skydive club management, and Hero Wars guild analytics.
|
||||
|
||||
## Tech stack
|
||||
|
||||
| Layer | Choice |
|
||||
|---|---|
|
||||
| Runtime | Node.js `^16.20.2 \|\| ^18.19.1 \|\| ^20.11.1` |
|
||||
| Framework | Express 4 |
|
||||
| MySQL ORM | Sequelize 6 |
|
||||
| MongoDB ODM | Mongoose 6 |
|
||||
| Auth | Passport (local strategy) + JWT (express-jwt / jsonwebtoken) |
|
||||
| Validation | express-validator |
|
||||
| Security | Helmet, express-rate-limit, CORS |
|
||||
| Tests | Jest + Supertest, Postman/Newman |
|
||||
| Dev server | Nodemon |
|
||||
|
||||
## Related repository
|
||||
|
||||
The frontend lives in [`adastra_app`](../adastra_app) — an Angular 21 standalone app. It calls this API exclusively through relative paths prepended with `environment.apiBaseUrl`.
|
||||
|
||||
## Getting started
|
||||
|
||||
**Installation rapide**
|
||||
```bash
|
||||
# Initialisation (applique les migrations, seed roles → création admin interactive → seed articles)
|
||||
# La commande suivante applique d'abord les migrations nécessaires.
|
||||
npm install
|
||||
|
||||
# Copy the example env file for your environment
|
||||
cp config/env/.env.example config/env/.env.local
|
||||
# Edit .env.local: MYSQL_DBNAME, MYSQL_DBUSER, MYSQL_DBPASS, SECRET, …
|
||||
|
||||
# Initialize the database (migrate + seed roles + create admin user + seed articles)
|
||||
npm run setup:api
|
||||
|
||||
# Start the dev server
|
||||
npm run local
|
||||
```
|
||||
|
||||
**Installation manuelle**
|
||||
The server listens on `SERVER_PORT` (default **3200**).
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `npm start` | Production server (`APP_ENV=production`) |
|
||||
| `npm run dev` | Development server, uses `.env.development` |
|
||||
| `npm run local` | Local server with Nodemon, uses `.env.local` |
|
||||
| `npm test` | Jest unit + integration tests |
|
||||
| `npm run test:postman` | Newman / Postman collection run |
|
||||
| `npm run create:user` | Interactive admin user creation script |
|
||||
| `npm run stop` | Kill the process on port 3200 |
|
||||
|
||||
## Environment
|
||||
|
||||
Environment files live in `config/env/` and are loaded by `dotenv` at startup based on `APP_ENV`:
|
||||
|
||||
| File | Used when |
|
||||
|---|---|
|
||||
| `.env.local` | `npm run local` |
|
||||
| `.env.development` | `npm run dev` |
|
||||
| `.env.production` | `npm start` |
|
||||
|
||||
Copy `.env.example` to create a new environment file. Key variables:
|
||||
|
||||
```
|
||||
SERVER_PORT=3200
|
||||
SECRET= # JWT signing secret
|
||||
SESSION_SECRET=
|
||||
MYSQL_DBNAME=
|
||||
MYSQL_DBUSER=
|
||||
MYSQL_DBPASS=
|
||||
MONGODB_URI=
|
||||
SENDGRID_API_KEY= # optional — email sending
|
||||
SKYDIVERID_API_URL= # optional — skydiver ID external API
|
||||
# TRUST_PROXY=1 # uncomment when behind a reverse proxy
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Route domains
|
||||
|
||||
All routes are mounted under `/api` and split into four domains:
|
||||
|
||||
| Domain | Prefix | Resources |
|
||||
|---|---|---|
|
||||
| CMS | `/api/cms` | users, profiles, articles, comments, tags |
|
||||
| E-commerce | `/api/ecommerce` | products |
|
||||
| Skydive | `/api/skydive` | aeronefs, canopies, dropzones, jumps, pages, QCM, applications |
|
||||
| Hero Wars | `/api/herowars` | clans, members |
|
||||
|
||||
### Middleware pipeline
|
||||
|
||||
Requests flow through:
|
||||
|
||||
1. **Helmet** — security headers
|
||||
2. **CORS** — origin whitelist from `ALLOWED_ORIGINS`
|
||||
3. **Rate limiter** — `express-rate-limit`
|
||||
4. **Body parser** — JSON + URL-encoded
|
||||
5. **Passport** — initializes local + header-API-key strategies
|
||||
6. **Route handlers** — validators → `auth` middleware → controller → `asyncHandler`
|
||||
7. **Error handlers** — 404 catcher, log handler, final error handler
|
||||
|
||||
### Auth
|
||||
|
||||
- **Session-less JWT**: tokens are signed with `SECRET` and attached by the client as `Authorization: Token <jwt>`.
|
||||
- `auth.js` middleware verifies the token via `express-jwt` and populates `req.auth`.
|
||||
- Password storage: PBKDF2-SHA512 with a random 16-byte salt (no bcrypt dependency on the auth path).
|
||||
|
||||
### Databases
|
||||
|
||||
- **MySQL** (Sequelize): users, roles, skydive entities, e-commerce, CMS content.
|
||||
- **MongoDB** (Mongoose): Hero Wars guild/clan data and analytics snapshots.
|
||||
|
||||
## Database — migrations & seeders
|
||||
|
||||
**One-command setup:**
|
||||
```bash
|
||||
npm run setup:api
|
||||
# Runs: db:migrate → seed roles → create:user → seed articles
|
||||
```
|
||||
|
||||
**Manual steps:**
|
||||
```bash
|
||||
# Appliquer les migrations
|
||||
npx sequelize-cli db:migrate
|
||||
|
||||
# Exécuter le seeder des articles
|
||||
npx sequelize-cli db:seed --seed 20251126-add-roles.js
|
||||
|
||||
# Créer un utilisateur Admin
|
||||
npm run create:user
|
||||
|
||||
# Exécuter le seeder des articles
|
||||
npx sequelize-cli db:seed --seed 20251127-add-articles.js
|
||||
```
|
||||
|
||||
**Migration**
|
||||
**Migration helpers:**
|
||||
```bash
|
||||
# Appliquer les migrations
|
||||
npx sequelize-cli db:migrate
|
||||
|
||||
# Voir le statut des migrations
|
||||
npx sequelize-cli db:migrate:status
|
||||
|
||||
# Annuler les migrations
|
||||
npx sequelize-cli db:migrate:undo:all
|
||||
```
|
||||
|
||||
**Seeder**
|
||||
**Seeder helpers:**
|
||||
```bash
|
||||
# Exécuter tous les seeders
|
||||
npx sequelize-cli db:seed:all
|
||||
|
||||
# Exécuter un seeder spécifique
|
||||
npx sequelize-cli db:seed --seed 20251127-add-articles.js
|
||||
|
||||
# Annuler tous les seeders
|
||||
npx sequelize-cli db:seed:undo:all
|
||||
|
||||
# Annuler un seeder spécifique
|
||||
npx sequelize-cli db:seed:undo --seed 20251127-add-articles.js
|
||||
```
|
||||
|
||||
**Start 'local' API server**
|
||||
```bash
|
||||
# Appliquer les migrations
|
||||
npm run local
|
||||
## Documentation
|
||||
|
||||
# Exécuter le seeder des articles
|
||||
npx sequelize-cli db:seed --seed 20251126-add-roles.js
|
||||
Extended references are in [`docs/`](docs/):
|
||||
|
||||
# Créer un utilisateur Admin
|
||||
npm run create:user
|
||||
|
||||
# Exécuter le seeder des articles
|
||||
npx sequelize-cli db:seed --seed 20251127-add-articles.js
|
||||
```
|
||||
| File | Content |
|
||||
|---|---|
|
||||
| [DEPLOYMENT.md](docs/DEPLOYMENT.md) | Full VPS production deployment guide (Apache, PM2, SSL) |
|
||||
| [ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md) | All env variables documented |
|
||||
| [SWAGGER_GUIDE.md](docs/SWAGGER_GUIDE.md) | API documentation via Swagger UI |
|
||||
| [GITEA_INSTALL.md](docs/GITEA_INSTALL.md) | Self-hosted Gitea on Debian 11 + ISPConfig |
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
# Migration Scripts - Guide d'utilisation
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Cette migration restructure votre API Express de `/api/v1` `/api/v2` `/api/v3` (qui représentaient des domaines métier, pas des versions API) vers une architecture basée sur les domaines avec des chemins clairs et explicites.
|
||||
|
||||
### Ancien structure (confuse)
|
||||
```
|
||||
/api/v1/* → Skydive features
|
||||
/api/v2/* → CMS features
|
||||
/api/v3/* → Hero Wars features
|
||||
```
|
||||
|
||||
### Nouvelle structure (claire)
|
||||
```
|
||||
/api/skydive/* ← Toutes les fonctionnalités de parachutisme
|
||||
/api/cms/* ← Gestion de contenu (articles, tags, etc.)
|
||||
/api/herowars/* ← Gestion du jeu HeroWars (clans, membres, raids)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scripts de migration
|
||||
|
||||
### 1. `migrate-to-domain-structure.js` ✓ EXÉCUTÉ
|
||||
|
||||
**Rôle:** Crée la nouvelle structure et migre les fichiers
|
||||
|
||||
**Statut:** ✅ Completed
|
||||
- Créé les répertoires `/api/skydive`, `/api/cms`, `/api/herowars`
|
||||
- Copié tous les fichiers de routes vers les nouveaux emplacements
|
||||
- Généré les nouveaux fichiers `index.js` pour chaque domaine
|
||||
- Mis à jour `src/routes/api/index.js` pour utiliser les nouveaux chemins
|
||||
- Créé un commit Git
|
||||
|
||||
**Fichiers modifiés:**
|
||||
```
|
||||
✓ src/routes/api/skydive/ (8 fichiers)
|
||||
✓ src/routes/api/cms/ (8 fichiers)
|
||||
✓ src/routes/api/herowars/ (3 fichiers)
|
||||
✓ src/routes/api/index.js (mis à jour)
|
||||
```
|
||||
|
||||
**Rapports générés:**
|
||||
- `MIGRATION_REPORT.md` - Détails complets de la migration
|
||||
- `MIGRATION_SWAGGER_TODO.md` - Tâches pour Swagger
|
||||
|
||||
---
|
||||
|
||||
### 2. `update-references-post-migration.js` ✓ EXÉCUTÉ
|
||||
|
||||
**Rôle:** Met à jour les références aux chemins dans la documentation
|
||||
|
||||
**Statut:** ✅ Completed
|
||||
- Mis à jour les fichiers YAML Swagger (`/api/v1/` → `/api/skydive/`, etc.)
|
||||
- Mis à jour la collection et l'environnement Postman
|
||||
- Scanné les fichiers clés pour les références restantes
|
||||
|
||||
**Rapports générés:**
|
||||
- `POST_MIGRATION_CHECKLIST.md` - Liste de vérification manuelle
|
||||
|
||||
---
|
||||
|
||||
### 3. `finalize-migration.js`
|
||||
|
||||
**Rôle:** Valide, nettoie, et teste la migration
|
||||
|
||||
**Options disponibles:**
|
||||
```bash
|
||||
# Valider la structure sans rien faire
|
||||
node scripts/finalize-migration.js
|
||||
|
||||
# Valider et nettoyer les anciens répertoires
|
||||
node scripts/finalize-migration.js --cleanup
|
||||
|
||||
# Valider et tester les endpoints (nécessite le serveur en cours d'exécution)
|
||||
node scripts/finalize-migration.js --test
|
||||
|
||||
# Mode complet: validation, nettoyage et résumé
|
||||
node scripts/finalize-migration.js --full
|
||||
```
|
||||
|
||||
**Statut:** ✅ Ready to run
|
||||
|
||||
**Rapport généré:**
|
||||
- `MIGRATION_COMPLETE_SUMMARY.md` - Résumé final avec instructions
|
||||
|
||||
---
|
||||
|
||||
## Guide étape par étape
|
||||
|
||||
### Étape 1: Vérifier la migration ✅
|
||||
```bash
|
||||
ls -la src/routes/api/
|
||||
# Devrait afficher: cms/, herowars/, skydive/, v1/, v2/, v3/, index.js
|
||||
```
|
||||
|
||||
### Étape 2: Valider la structure
|
||||
```bash
|
||||
node scripts/finalize-migration.js
|
||||
```
|
||||
|
||||
**Résultat attendu:**
|
||||
```
|
||||
✓ skydive/: ✓ (8 route files)
|
||||
✓ cms/: ✓ (7 route files)
|
||||
✓ herowars/: ✓ (3 route files)
|
||||
```
|
||||
|
||||
### Étape 3: Nettoyer les anciens répertoires (OPTIONNEL)
|
||||
```bash
|
||||
# Option A: Via le script
|
||||
node scripts/finalize-migration.js --cleanup
|
||||
|
||||
# Option B: Manuellement
|
||||
rm -rf src/routes/api/v1 src/routes/api/v2 src/routes/api/v3
|
||||
git add -A
|
||||
git commit -m "chore: remove old v1/v2/v3 route directories"
|
||||
```
|
||||
|
||||
### Étape 4: Mettre à jour le frontend (IMPORTANT)
|
||||
|
||||
**Fichiers à modifier dans `adastra_app/src/environments/`:**
|
||||
|
||||
#### `environment.ts`
|
||||
```typescript
|
||||
// AVANT:
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: '/api/v1',
|
||||
cmsUrl: '/api/v2',
|
||||
herowarsUrl: '/api/v3',
|
||||
};
|
||||
|
||||
// APRÈS:
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: '/api/skydive',
|
||||
cmsUrl: '/api/cms',
|
||||
herowarsUrl: '/api/herowars',
|
||||
};
|
||||
```
|
||||
|
||||
#### `environment.development.ts` et `environment.local.ts`
|
||||
Applique les mêmes changements
|
||||
|
||||
**Mise à jour des services Angular:**
|
||||
```typescript
|
||||
// Dans vos services (par exemple, src/app/services/*.service.ts)
|
||||
|
||||
// AVANT:
|
||||
this.http.get(environment.apiUrl + '/jumps')
|
||||
this.http.get(environment.cmsUrl + '/articles')
|
||||
this.http.get(environment.herowarsUrl + '/clans')
|
||||
|
||||
// APRÈS:
|
||||
this.http.get(environment.apiUrl + '/jumps')
|
||||
this.http.get(environment.cmsUrl + '/articles')
|
||||
this.http.get(environment.herowarsUrl + '/clans')
|
||||
// Pas de changement si vous utilisez les variables d'environnement!
|
||||
// Sinon, cherchez/remplacez les chemins en dur
|
||||
```
|
||||
|
||||
**Automatiser la mise à jour du frontend (macOS/Linux):**
|
||||
```bash
|
||||
cd adastra_app
|
||||
sed -i '' "s|/api/v1|/api/skydive|g" src/**/*.ts
|
||||
sed -i '' "s|/api/v2|/api/cms|g" src/**/*.ts
|
||||
sed -i '' "s|/api/v3|/api/herowars|g" src/**/*.ts
|
||||
```
|
||||
|
||||
### Étape 5: Tester les endpoints
|
||||
|
||||
**Démarrer le serveur API:**
|
||||
```bash
|
||||
cd adastra_api
|
||||
npm run local
|
||||
```
|
||||
|
||||
**Dans un autre terminal, tester les nouveaux chemins:**
|
||||
```bash
|
||||
# Tester Swagger UI
|
||||
curl http://localhost:3200/api-docs
|
||||
|
||||
# Tester les endpoints des domaines
|
||||
curl http://localhost:3200/api/skydive/jumps
|
||||
curl http://localhost:3200/api/cms/articles
|
||||
curl http://localhost:3200/api/herowars/clans
|
||||
|
||||
# Avec authentification si nécessaire
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3200/api/skydive/jumps
|
||||
```
|
||||
|
||||
### Étape 6: Tester le frontend
|
||||
|
||||
```bash
|
||||
cd adastra_app
|
||||
npm start
|
||||
# Ou avec configuration spécifique
|
||||
npm run local # ng serve --configuration local
|
||||
```
|
||||
|
||||
Vérifier dans les DevTools que les appels API vont vers les nouveaux chemins.
|
||||
|
||||
---
|
||||
|
||||
## Statistiques de la migration
|
||||
|
||||
| Métrique | Valeur |
|
||||
|----------|--------|
|
||||
| Nouveaux domaines créés | 3 (skydive, cms, herowars) |
|
||||
| Fichiers de routes migrés | 19 |
|
||||
| Fichiers index.js créés/mis à jour | 4 |
|
||||
| Fichiers Swagger mis à jour | Oui |
|
||||
| Fichiers Postman mis à jour | Oui |
|
||||
| Commits Git créés | 1 (structure) + opt. 1 (nettoyage) |
|
||||
|
||||
---
|
||||
|
||||
## Fichiers de rapport créés
|
||||
|
||||
| Fichier | Contenu |
|
||||
|---------|---------|
|
||||
| `MIGRATION_REPORT.md` | Rapport détaillé de la migration |
|
||||
| `MIGRATION_SWAGGER_TODO.md` | Notes pour les mises à jour Swagger |
|
||||
| `POST_MIGRATION_CHECKLIST.md` | Liste de vérification manuelle |
|
||||
| `MIGRATION_COMPLETE_SUMMARY.md` | Résumé final et instructions |
|
||||
| `SCRIPTS_MIGRATION_GUIDE.md` | Ce fichier |
|
||||
|
||||
---
|
||||
|
||||
## Procédure de rollback
|
||||
|
||||
Si vous devez annuler la migration:
|
||||
|
||||
```bash
|
||||
# Voir l'historique
|
||||
git log --oneline -10
|
||||
|
||||
# Annuler les changements (ajuster le nombre de commits)
|
||||
git reset --hard HEAD~2
|
||||
|
||||
# Ou restaurer une branche spécifique
|
||||
git reset --hard origin/develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Points clés à retenir
|
||||
|
||||
✅ **La migration est non-destructive:**
|
||||
- Les anciens répertoires v1/v2/v3 existent toujours
|
||||
- Vous pouvez les supprimer après vérification
|
||||
|
||||
✅ **Mises à jour automatisées:**
|
||||
- Scripts: Oui ✓
|
||||
- Swagger: Oui ✓
|
||||
- Postman: Oui ✓
|
||||
|
||||
⚠️ **Mises à jour manuelles requises:**
|
||||
- Frontend Angular (environnement + services)
|
||||
- Tous les appels API en dur dans le code cliente
|
||||
- Documentation externe
|
||||
- Tests d'intégration
|
||||
|
||||
✅ **Git-safe:**
|
||||
- Chaque étape crée un commit
|
||||
- Rollback facile si problème
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Les endpoints retournent 404
|
||||
- ✓ Vérifier que le serveur a redémarré après la migration
|
||||
- ✓ Vérifier les chemins dans Swagger UI (http://localhost:3200/api-docs)
|
||||
- ✓ Vérifier que le backend a bien recompilé (avec nodemon)
|
||||
|
||||
### Les routes ne sont pas trouvées
|
||||
- ✓ Vérifier que `src/routes/api/index.js` a les bonnes références
|
||||
- ✓ Vérifier que chaque domaine a un `index.js`
|
||||
- ✓ Vérifier l'ordre des middlewares dans `app.js`
|
||||
|
||||
### Erreurs d'authentification
|
||||
- ✓ Les middlewares d'auth ne sont pas affectés
|
||||
- ✓ Les tokens JWT restent valides
|
||||
- ✓ Les headers restent les mêmes
|
||||
|
||||
### Le frontend ne peut pas accéder à l'API
|
||||
- ✓ Vérifier CORS dans `app.js`
|
||||
- ✓ Vérifier les URLs dans les environnements Angular
|
||||
- ✓ Vérifier les appels http dans les services
|
||||
|
||||
---
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
1. ✅ Tester tous les endpoints du backend
|
||||
2. ✅ Mettre à jour le frontend (environnement + services)
|
||||
3. ✅ Tester le frontend avec le backend modifié
|
||||
4. ✅ Mettre à jour la documentation
|
||||
5. ✅ Nettoyer les anciens répertoires v1/v2/v3 (optionnel)
|
||||
6. ✅ Déployer en staging/production
|
||||
|
||||
---
|
||||
|
||||
## Ressources supplémentaires
|
||||
|
||||
- Documentation architecture: `ARCHITECTURE_DIAGRAMS.md`
|
||||
- ADR (Architecture Decision Records): `ADR_INDEX.md`
|
||||
- Configuration Swagger: `src/config/swagger.js`
|
||||
- Routes configuration: `src/routes/api/index.js`
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
Pour des questions ou problèmes:
|
||||
|
||||
1. ✓ Consulter los fichiers de rapport (`POST_MIGRATION_CHECKLIST.md`, etc.)
|
||||
2. ✓ Vérifier les logs du serveur: `npm run local` (output)
|
||||
3. ✓ Vérifier Swagger UI: http://localhost:3200/api-docs
|
||||
4. ✓ Exécuter la validation: `node scripts/finalize-migration.js`
|
||||
|
||||
---
|
||||
|
||||
**Migration effectuée le:** 24 avril 2026
|
||||
**Version API:** v1/v2/v3 → domain-based
|
||||
**Status:** ✅ Complete
|
||||
@@ -1,762 +0,0 @@
|
||||
# Méthodes disponibles sur les modèles MySQL (Sequelize)
|
||||
|
||||
Ce document liste toutes les méthodes automatiquement générées par Sequelize pour chaque modèle en fonction des associations définies dans `src/database/relationships/index.js`.
|
||||
|
||||
---
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Article](#article)
|
||||
2. [Tag](#tag)
|
||||
3. [User](#user)
|
||||
4. [Comment](#comment)
|
||||
5. [Favorite](#favorite)
|
||||
6. [Follower](#follower)
|
||||
7. [Product](#product)
|
||||
8. [Brand](#brand)
|
||||
9. [Category](#category)
|
||||
10. [Packaging](#packaging)
|
||||
11. [TagList](#taglist)
|
||||
12. [Role](#role)
|
||||
13. [UserRoleXref](#userroleexref)
|
||||
14. [ProductCategoryXref](#productcategoryxref)
|
||||
15. [ProductTagXref](#producttagxref)
|
||||
|
||||
---
|
||||
|
||||
## Article
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Tags via `TagList`
|
||||
Alias: `articleTags`
|
||||
```javascript
|
||||
article.getArticleTags() // Récupère tous les tags
|
||||
article.setArticleTags([...]) // Définit les tags
|
||||
article.addArticleTag(tag) // Ajoute un tag
|
||||
article.removeArticleTag(tag) // Retire un tag
|
||||
article.countArticleTags() // Compte les tags
|
||||
article.hasArticleTag(tag) // Vérifie si le tag est lié
|
||||
```
|
||||
|
||||
#### Users (Favoris) via `Favorite`
|
||||
Alias: `articleFavoriteUsers` (cascade delete activé)
|
||||
```javascript
|
||||
article.getArticleFavoriteUsers() // Récupère les utilisateurs qui ont favorisé
|
||||
article.setArticleFavoriteUsers([...]) // Définit les utilisateurs
|
||||
article.addArticleFavoriteUser(user) // Ajoute un utilisateur
|
||||
article.removeArticleFavoriteUser(user) // Retire un utilisateur
|
||||
article.countArticleFavoriteUsers() // Compte les utilisateurs
|
||||
article.hasArticleFavoriteUser(user) // Vérifie si l'utilisateur a favorisé
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### Comments (cascade delete activé)
|
||||
```javascript
|
||||
article.getComments() // Récupère tous les commentaires
|
||||
article.setComments([...]) // Définit les commentaires
|
||||
article.addComment(comment) // Ajoute un commentaire
|
||||
article.removeComment(comment) // Retire un commentaire
|
||||
article.countComments() // Compte les commentaires
|
||||
article.hasComment(comment) // Vérifie si le commentaire existe
|
||||
article.createComment({...}) // Crée un commentaire
|
||||
```
|
||||
|
||||
#### Favorites (cascade delete activé)
|
||||
```javascript
|
||||
article.getFavorites() // Récupère les enregistrements Favorite
|
||||
article.setFavorites([...]) // Définit les favoris
|
||||
article.addFavorite(favorite) // Ajoute un Favorite
|
||||
article.removeFavorite(favorite) // Retire un Favorite
|
||||
article.countFavorites() // Compte les Favorites
|
||||
article.hasFavorite(favorite) // Vérifie si le Favorite existe
|
||||
article.createFavorite({...}) // Crée un Favorite
|
||||
```
|
||||
|
||||
#### TagLists (cascade delete activé)
|
||||
```javascript
|
||||
article.getTagLists() // Récupère les enregistrements TagList
|
||||
article.setTagLists([...]) // Définit les TagLists
|
||||
article.addTagList(tagList) // Ajoute un TagList
|
||||
article.removeTagList(tagList) // Retire un TagList
|
||||
article.countTagLists() // Compte les TagLists
|
||||
article.hasTagList(tagList) // Vérifie si le TagList existe
|
||||
article.createTagList({...}) // Crée un TagList
|
||||
```
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Author (User)
|
||||
```javascript
|
||||
article.getAuthor() // Récupère l'auteur
|
||||
article.setAuthor(user) // Définit l'auteur
|
||||
article.createAuthor({...}) // Crée un auteur
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
article.toJSONFor() // Formate en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tag
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Articles via `TagList`
|
||||
Alias: `articleIdArticleTagLists`
|
||||
```javascript
|
||||
tag.getArticleIdArticleTagLists() // Récupère les articles
|
||||
tag.setArticleIdArticleTagLists([...])
|
||||
tag.addArticleIdArticleTagList(article)
|
||||
tag.removeArticleIdArticleTagList(article)
|
||||
tag.countArticleIdArticleTagLists()
|
||||
tag.hasArticleIdArticleTagList(article)
|
||||
```
|
||||
|
||||
#### Products via `ProductTagXref`
|
||||
Alias: `productIdProductProductTagXrefs`
|
||||
```javascript
|
||||
tag.getProductIdProductProductTagXrefs()
|
||||
tag.setProductIdProductProductTagXrefs([...])
|
||||
tag.addProductIdProductProductTagXref(product)
|
||||
tag.removeProductIdProductProductTagXref(product)
|
||||
tag.countProductIdProductProductTagXrefs()
|
||||
tag.hasProductIdProductProductTagXref(product)
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### TagLists
|
||||
```javascript
|
||||
tag.getTagLists() // Récupère les enregistrements TagList
|
||||
tag.setTagLists([...])
|
||||
tag.addTagList(tagList)
|
||||
tag.removeTagList(tagList)
|
||||
tag.countTagLists()
|
||||
tag.hasTagList(tagList)
|
||||
tag.createTagList({...})
|
||||
```
|
||||
|
||||
#### ProductTagXrefs
|
||||
```javascript
|
||||
tag.getProductTagXrefs() // Récupère les ProductTagXrefs
|
||||
tag.setProductTagXrefs([...])
|
||||
tag.addProductTagXref(xref)
|
||||
tag.removeProductTagXref(xref)
|
||||
tag.countProductTagXrefs()
|
||||
tag.hasProductTagXref(xref)
|
||||
tag.createProductTagXref({...})
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
tag.toJSONFor() // Formate en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Articles (Favoris) via `Favorite`
|
||||
Alias: `articleIdArticles`
|
||||
```javascript
|
||||
user.getArticleIdArticles() // Récupère les articles favorisés
|
||||
user.setArticleIdArticles([...])
|
||||
user.addArticleIdArticle(article)
|
||||
user.removeArticleIdArticle(article)
|
||||
user.countArticleIdArticles()
|
||||
user.hasArticleIdArticle(article)
|
||||
```
|
||||
|
||||
#### Followers (ceux qui vous suivent) via `Follower`
|
||||
Alias: `followerIdUsers`
|
||||
```javascript
|
||||
user.getFollowerIdUsers() // Récupère les followers
|
||||
user.setFollowerIdUsers([...])
|
||||
user.addFollowerIdUser(user)
|
||||
user.removeFollowerIdUser(user)
|
||||
user.countFollowerIdUsers()
|
||||
user.hasFollowerIdUser(user)
|
||||
```
|
||||
|
||||
#### Following (que vous suivez) via `Follower`
|
||||
Alias: `userIdUserFollowers`
|
||||
```javascript
|
||||
user.getUserIdUserFollowers() // Récupère ceux que vous suivez
|
||||
user.setUserIdUserFollowers([...])
|
||||
user.addUserIdUserFollower(user)
|
||||
user.removeUserIdUserFollower(user)
|
||||
user.countUserIdUserFollowers()
|
||||
user.hasUserIdUserFollower(user)
|
||||
```
|
||||
|
||||
#### Roles via `UserRoleXref`
|
||||
Alias: `roleIdRoles`
|
||||
```javascript
|
||||
user.getRoleIdRoles() // Récupère les rôles
|
||||
user.setRoleIdRoles([...])
|
||||
user.addRoleIdRole(role)
|
||||
user.removeRoleIdRole(role)
|
||||
user.countRoleIdRoles()
|
||||
user.hasRoleIdRole(role)
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### Articles (créés par l'utilisateur)
|
||||
```javascript
|
||||
user.getArticles() // Récupère les articles créés
|
||||
user.setArticles([...])
|
||||
user.addArticle(article)
|
||||
user.removeArticle(article)
|
||||
user.countArticles()
|
||||
user.hasArticle(article)
|
||||
user.createArticle({...})
|
||||
```
|
||||
|
||||
#### Comments (créés par l'utilisateur)
|
||||
```javascript
|
||||
user.getComments() // Récupère les commentaires créés
|
||||
user.setComments([...])
|
||||
user.addComment(comment)
|
||||
user.removeComment(comment)
|
||||
user.countComments()
|
||||
user.hasComment(comment)
|
||||
user.createComment({...})
|
||||
```
|
||||
|
||||
#### Favorites (enregistrements)
|
||||
```javascript
|
||||
user.getFavorites() // Récupère les Favorites
|
||||
user.setFavorites([...])
|
||||
user.addFavorite(favorite)
|
||||
user.removeFavorite(favorite)
|
||||
user.countFavorites()
|
||||
user.hasFavorite(favorite)
|
||||
user.createFavorite({...})
|
||||
```
|
||||
|
||||
#### Followers (enregistrements)
|
||||
```javascript
|
||||
user.getFollowers() // Récupère les Followers
|
||||
user.setFollowers([...])
|
||||
user.addFollower(follower)
|
||||
user.removeFollower(follower)
|
||||
user.countFollowers()
|
||||
user.hasFollower(follower)
|
||||
user.createFollower({...})
|
||||
```
|
||||
|
||||
#### FollowerFollowers (ceux qui vous suivent via enregistrement)
|
||||
```javascript
|
||||
user.getFollowerFollowers() // Récupère les FollowerFollowers
|
||||
user.setFollowerFollowers([...])
|
||||
user.addFollowerFollower(follower)
|
||||
user.removeFollowerFollower(follower)
|
||||
user.countFollowerFollowers()
|
||||
user.hasFollowerFollower(follower)
|
||||
user.createFollowerFollower({...})
|
||||
```
|
||||
|
||||
#### Products (créés/possédés)
|
||||
```javascript
|
||||
user.getProducts() // Récupère les produits
|
||||
user.setProducts([...])
|
||||
user.addProduct(product)
|
||||
user.removeProduct(product)
|
||||
user.countProducts()
|
||||
user.hasProduct(product)
|
||||
user.createProduct({...})
|
||||
```
|
||||
|
||||
#### UserRoleXrefs
|
||||
```javascript
|
||||
user.getUserRoleXrefs() // Récupère les UserRoleXrefs
|
||||
user.setUserRoleXrefs([...])
|
||||
user.addUserRoleXref(xref)
|
||||
user.removeUserRoleXref(xref)
|
||||
user.countUserRoleXrefs()
|
||||
user.hasUserRoleXref(xref)
|
||||
user.createUserRoleXref({...})
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
user.validPassword(password) // Valide le mot de passe
|
||||
user.setPassword(password) // Définit le mot de passe (hash)
|
||||
user.generateJWT() // Génère un JWT
|
||||
user.toAuthJSON() // Retourne objet auth
|
||||
user.toJSONFor() // Formate en JSON
|
||||
user.toProfileJSONFor() // Formate profil en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comment
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Article
|
||||
```javascript
|
||||
comment.getArticle() // Récupère l'article
|
||||
comment.setArticle(article) // Définit l'article
|
||||
comment.createArticle({...}) // Crée un article
|
||||
```
|
||||
|
||||
#### Author (User)
|
||||
```javascript
|
||||
comment.getAuthor() // Récupère l'auteur
|
||||
comment.setAuthor(user) // Définit l'auteur
|
||||
comment.createAuthor({...}) // Crée un auteur
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
comment.toJSONFor() // Formate en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Favorite
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### User
|
||||
```javascript
|
||||
favorite.getUser() // Récupère l'utilisateur
|
||||
favorite.setUser(user) // Définit l'utilisateur
|
||||
favorite.createUser({...}) // Crée un utilisateur
|
||||
```
|
||||
|
||||
#### Article
|
||||
```javascript
|
||||
favorite.getArticle() // Récupère l'article
|
||||
favorite.setArticle(article) // Définit l'article
|
||||
favorite.createArticle({...}) // Crée un article
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
favorite.toJSONFor() // Formate en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Follower
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### User (l'utilisateur suivi)
|
||||
```javascript
|
||||
follower.getUser() // Récupère l'utilisateur suivi
|
||||
follower.setUser(user) // Définit l'utilisateur suivi
|
||||
follower.createUser({...}) // Crée un utilisateur
|
||||
```
|
||||
|
||||
#### Follower (l'utilisateur qui suit) - alias 'follower'
|
||||
```javascript
|
||||
follower.getFollower() // Récupère celui qui suit
|
||||
follower.setFollower(user) // Définit celui qui suit
|
||||
follower.createFollower({...}) // Crée un utilisateur qui suit
|
||||
```
|
||||
|
||||
### Instance Methods
|
||||
```javascript
|
||||
follower.toJSONFor() // Formate en JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Product
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Categories via `ProductCategoryXref`
|
||||
Alias: `productCategories`
|
||||
```javascript
|
||||
product.getProductCategories() // Récupère les catégories
|
||||
product.setProductCategories([...])
|
||||
product.addProductCategory(category)
|
||||
product.removeProductCategory(category)
|
||||
product.countProductCategories()
|
||||
product.hasProductCategory(category)
|
||||
```
|
||||
|
||||
#### Tags via `ProductTagXref`
|
||||
Alias: `productTags`
|
||||
```javascript
|
||||
product.getProductTags() // Récupère les tags
|
||||
product.setProductTags([...])
|
||||
product.addProductTag(tag)
|
||||
product.removeProductTag(tag)
|
||||
product.countProductTags()
|
||||
product.hasProductTag(tag)
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### ProductCategoryXrefs
|
||||
```javascript
|
||||
product.getProductCategoryXrefs() // Récupère les liens catégories
|
||||
product.setProductCategoryXrefs([...])
|
||||
product.addProductCategoryXref(xref)
|
||||
product.removeProductCategoryXref(xref)
|
||||
product.countProductCategoryXrefs()
|
||||
product.hasProductCategoryXref(xref)
|
||||
product.createProductCategoryXref({...})
|
||||
```
|
||||
|
||||
#### ProductTagXrefs
|
||||
```javascript
|
||||
product.getProductTagXrefs() // Récupère les liens tags
|
||||
product.setProductTagXrefs([...])
|
||||
product.addProductTagXref(xref)
|
||||
product.removeProductTagXref(xref)
|
||||
product.countProductTagXrefs()
|
||||
product.hasProductTagXref(xref)
|
||||
product.createProductTagXref({...})
|
||||
```
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Brand
|
||||
```javascript
|
||||
product.getBrand() // Récupère la marque
|
||||
product.setBrand(brand) // Définit la marque
|
||||
product.createBrand({...}) // Crée une marque
|
||||
```
|
||||
|
||||
#### Owner (User)
|
||||
```javascript
|
||||
product.getOwner() // Récupère le propriétaire
|
||||
product.setOwner(user) // Définit le propriétaire
|
||||
product.createOwner({...}) // Crée un propriétaire
|
||||
```
|
||||
|
||||
#### Packaging
|
||||
```javascript
|
||||
product.getPackaging() // Récupère l'emballage
|
||||
product.setPackaging(packaging) // Définit l'emballage
|
||||
product.createPackaging({...}) // Crée un emballage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Brand
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### Products
|
||||
```javascript
|
||||
brand.getProducts() // Récupère les produits
|
||||
brand.setProducts([...])
|
||||
brand.addProduct(product)
|
||||
brand.removeProduct(product)
|
||||
brand.countProducts()
|
||||
brand.hasProduct(product)
|
||||
brand.createProduct({...})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Products via `ProductCategoryXref`
|
||||
Alias: `productIdProducts`
|
||||
```javascript
|
||||
category.getProductIdProducts() // Récupère les produits
|
||||
category.setProductIdProducts([...])
|
||||
category.addProductIdProduct(product)
|
||||
category.removeProductIdProduct(product)
|
||||
category.countProductIdProducts()
|
||||
category.hasProductIdProduct(product)
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### ProductCategoryXrefs
|
||||
```javascript
|
||||
category.getProductCategoryXrefs() // Récupère les liens produits
|
||||
category.setProductCategoryXrefs([...])
|
||||
category.addProductCategoryXref(xref)
|
||||
category.removeProductCategoryXref(xref)
|
||||
category.countProductCategoryXrefs()
|
||||
category.hasProductCategoryXref(xref)
|
||||
category.createProductCategoryXref({...})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Packaging
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### Products
|
||||
```javascript
|
||||
packaging.getProducts() // Récupère les produits
|
||||
packaging.setProducts([...])
|
||||
packaging.addProduct(product)
|
||||
packaging.removeProduct(product)
|
||||
packaging.countProducts()
|
||||
packaging.hasProduct(product)
|
||||
packaging.createProduct({...})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TagList
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Article
|
||||
```javascript
|
||||
tagList.getArticle() // Récupère l'article
|
||||
tagList.setArticle(article) // Définit l'article
|
||||
tagList.createArticle({...}) // Crée un article
|
||||
```
|
||||
|
||||
#### Tag
|
||||
Alias: `tagNameTag`
|
||||
```javascript
|
||||
tagList.getTagNameTag() // Récupère le tag
|
||||
tagList.setTagNameTag(tag) // Définit le tag
|
||||
tagList.createTagNameTag({...}) // Crée un tag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Role
|
||||
|
||||
### Many-to-Many (belongsToMany)
|
||||
|
||||
#### Users via `UserRoleXref`
|
||||
Alias: `userIdUserUserRoleXrefs`
|
||||
```javascript
|
||||
role.getUserIdUserUserRoleXrefs() // Récupère les utilisateurs
|
||||
role.setUserIdUserUserRoleXrefs([...])
|
||||
role.addUserIdUserUserRoleXref(user)
|
||||
role.removeUserIdUserUserRoleXref(user)
|
||||
role.countUserIdUserUserRoleXrefs()
|
||||
role.hasUserIdUserUserRoleXref(user)
|
||||
```
|
||||
|
||||
### One-to-Many (hasMany)
|
||||
|
||||
#### UserRoleXrefs
|
||||
```javascript
|
||||
role.getUserRoleXrefs() // Récupère les UserRoleXrefs
|
||||
role.setUserRoleXrefs([...])
|
||||
role.addUserRoleXref(xref)
|
||||
role.removeUserRoleXref(xref)
|
||||
role.countUserRoleXrefs()
|
||||
role.hasUserRoleXref(xref)
|
||||
role.createUserRoleXref({...})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UserRoleXref
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### User
|
||||
```javascript
|
||||
xref.getUser() // Récupère l'utilisateur
|
||||
xref.setUser(user) // Définit l'utilisateur
|
||||
xref.createUser({...}) // Crée un utilisateur
|
||||
```
|
||||
|
||||
#### Role
|
||||
```javascript
|
||||
xref.getRole() // Récupère le rôle
|
||||
xref.setRole(role) // Définit le rôle
|
||||
xref.createRole({...}) // Crée un rôle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ProductCategoryXref
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Product
|
||||
```javascript
|
||||
xref.getProduct() // Récupère le produit
|
||||
xref.setProduct(product) // Définit le produit
|
||||
xref.createProduct({...}) // Crée un produit
|
||||
```
|
||||
|
||||
#### Category
|
||||
```javascript
|
||||
xref.getCategory() // Récupère la catégorie
|
||||
xref.setCategory(category) // Définit la catégorie
|
||||
xref.createCategory({...}) // Crée une catégorie
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ProductTagXref
|
||||
|
||||
### Belongs-To
|
||||
|
||||
#### Product
|
||||
```javascript
|
||||
xref.getProduct() // Récupère le produit
|
||||
xref.setProduct(product) // Définit le produit
|
||||
xref.createProduct({...}) // Crée un produit
|
||||
```
|
||||
|
||||
#### Tag
|
||||
Alias: `tagNameTag`
|
||||
```javascript
|
||||
xref.getTagNameTag() // Récupère le tag
|
||||
xref.setTagNameTag(tag) // Définit le tag
|
||||
xref.createTagNameTag({...}) // Crée un tag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes générales
|
||||
|
||||
1. **Paramètres `options`** : Presque toutes les méthodes `get*()` acceptent un objet `options` pour personnaliser la requête :
|
||||
```javascript
|
||||
model.getAssociated({
|
||||
where: { createdAt: { [Op.gte]: someDate } },
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
```
|
||||
|
||||
2. **Transactions** : Encapsulez les opérations dans une transaction :
|
||||
```javascript
|
||||
const transaction = await sequelize.transaction();
|
||||
await model.addAssociated(associated, { transaction });
|
||||
await transaction.commit();
|
||||
```
|
||||
|
||||
3. **Eager Loading** : Utilisez `include` pour charger les associations :
|
||||
```javascript
|
||||
const article = await Article.findByPk(1, {
|
||||
include: [
|
||||
{ model: Tag, as: 'articleTags' },
|
||||
{ model: User, as: 'author' },
|
||||
{ model: Comment, as: 'comments' }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
4. **Alias vs Nom généré** : Le nom des méthodes dépend de l'alias défini. Exemple :
|
||||
- Alias `articleTags` → `getArticleTags()`, `addArticleTag()`
|
||||
- Alias `tagLists` → `getTagLists()`, `addTagList()`
|
||||
|
||||
5. **Toutes les méthodes de manipulation** acceptent un paramètre `options` final :
|
||||
```javascript
|
||||
await article.addTagList(tag, { transaction, validate: true });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Seeders
|
||||
|
||||
### Seeder Articles (20251127-add-articles.js)
|
||||
|
||||
Ce seeder ajoute automatiquement 3 articles avec du contenu Lorem Ipsum et les associe à des tags d'animaux.
|
||||
|
||||
#### Exécution
|
||||
```bash
|
||||
# Exécuter tous les seeders
|
||||
npx sequelize-cli db:seed:all
|
||||
|
||||
# Annuler un seeder spécifique
|
||||
npx sequelize-cli db:seed:undo --seed 20251127-add-articles.js
|
||||
|
||||
# Annuler tous les seeders
|
||||
npx sequelize-cli db:seed:undo:all
|
||||
```
|
||||
|
||||
#### Articles créés
|
||||
|
||||
| Titre | Tags | Slug |
|
||||
|-------|------|------|
|
||||
| Introduction aux écosystèmes marins | Dolphin, Eagle, Wolf | introduction-aux-ecosystemes-marins |
|
||||
| Les prédateurs de la savane africaine | Lion, Tiger, Wolf | les-predateurs-de-la-savane-africaine |
|
||||
| L'intelligence animale au-delà de nos attentes | Eagle, Dolphin, Lion | lintelligence-animale-au-dela-de-nos-attentes |
|
||||
|
||||
#### Tags disponibles
|
||||
- Lion
|
||||
- Eagle
|
||||
- Dolphin
|
||||
- Tiger
|
||||
- Wolf
|
||||
|
||||
#### Fonctionnalités du seeder
|
||||
- ✅ Crée automatiquement les 5 tags s'ils n'existent pas
|
||||
- ✅ Génère des slugs uniques pour chaque article
|
||||
- ✅ Associe 3 tags à chaque article via la table `tagList`
|
||||
- ✅ Utilise les transactions pour l'intégrité des données
|
||||
- ✅ Support du rollback via `db:seed:undo`
|
||||
|
||||
---
|
||||
|
||||
## Migrations et Schéma
|
||||
|
||||
### Migration initiale (20251127000000-initial-schema.js)
|
||||
|
||||
Crée tous les 15 tables avec les contraintes CASCADE appropriées :
|
||||
|
||||
**Tables avec CASCADE delete activé :**
|
||||
- `article` → `comment` (foreignKey: `articleId`)
|
||||
- `article` → `favorite` (foreignKey: `articleId`)
|
||||
- `article` → `tagList` (foreignKey: `articleId`)
|
||||
- `user` → `article` (foreignKey: `authorId`)
|
||||
- `user` → `comment` (foreignKey: `authorId`)
|
||||
- `tag` → `tagList` (foreignKey: `tagName`)
|
||||
|
||||
**Exécution**
|
||||
```bash
|
||||
# Appliquer les migrations
|
||||
npx sequelize-cli db:migrate
|
||||
|
||||
# Voir le statut des migrations
|
||||
npx sequelize-cli db:migrate:status
|
||||
|
||||
# Annuler les migrations
|
||||
npx sequelize-cli db:migrate:undo:all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration de la base de données
|
||||
|
||||
### Variables d'environnement
|
||||
|
||||
Le fichier `.env.development` doit contenir :
|
||||
```
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASS=password
|
||||
DB_NAME=adastra
|
||||
DB_DIALECT=mysql
|
||||
```
|
||||
|
||||
### Validation du schéma au démarrage
|
||||
|
||||
L'application valide automatiquement la présence du schéma au démarrage :
|
||||
- ✅ Si le schéma est absent, l'app échoue avec un message d'erreur
|
||||
- ✅ Si le schéma est présent, les associations sont créées
|
||||
- ✅ Message d'aide fourni pour exécuter les migrations manquantes
|
||||
|
||||
```bash
|
||||
# Démarrer l'app (validera le schéma)
|
||||
npm run local # Développement avec nodemon
|
||||
npm run dev # Développement
|
||||
npm start # Production
|
||||
```
|
||||
@@ -1,210 +0,0 @@
|
||||
# Guide Swagger - Documentation OpenAPI pour Adastra API
|
||||
|
||||
## Accès à la documentation
|
||||
Une fois le serveur lancé, accédez à : **http://localhost:3200/api-docs**
|
||||
|
||||
## Exemple de documentation pour une route
|
||||
|
||||
Voici un exemple de comment documenter une GET route avec des paramètres de query :
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/articles:
|
||||
* get:
|
||||
* summary: Récupère la liste des articles
|
||||
* description: Retourne une liste paginée de tous les articles
|
||||
* tags:
|
||||
* - Articles
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: Numéro de page
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: Nombre d'articles par page
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Liste des articles récupérée avec succès
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* title:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* total:
|
||||
* type: integer
|
||||
* 400:
|
||||
* description: Erreur de validation
|
||||
* 401:
|
||||
* description: Non authentifié
|
||||
* 500:
|
||||
* description: Erreur serveur
|
||||
*/
|
||||
router.get('/', async (req, res) => {
|
||||
// ... logique du contrôleur
|
||||
});
|
||||
```
|
||||
|
||||
## Exemple avec POST et authentification
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/articles:
|
||||
* post:
|
||||
* summary: Crée un nouvel article
|
||||
* description: Crée un nouvel article avec les données fournies
|
||||
* tags:
|
||||
* - Articles
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - title
|
||||
* - content
|
||||
* properties:
|
||||
* title:
|
||||
* type: string
|
||||
* example: "Mon Article"
|
||||
* content:
|
||||
* type: string
|
||||
* example: "Contenu de l'article"
|
||||
* tags:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* example: ["tag1", "tag2"]
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Article créé avec succès
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* title:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: Données invalides
|
||||
* 401:
|
||||
* description: Non authentifié
|
||||
*/
|
||||
router.post('/', authenticate, async (req, res) => {
|
||||
// ... logique du contrôleur
|
||||
});
|
||||
```
|
||||
|
||||
## Exemple avec paramètres URL
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* @swagger
|
||||
* /api/v1/articles/{id}:
|
||||
* get:
|
||||
* summary: Récupère un article par ID
|
||||
* tags:
|
||||
* - Articles
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: ID de l'article
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Article récupéré avec succès
|
||||
* 404:
|
||||
* description: Article non trouvé
|
||||
* 500:
|
||||
* description: Erreur serveur
|
||||
*/
|
||||
router.get('/:id', async (req, res) => {
|
||||
// ... logique du contrôleur
|
||||
});
|
||||
```
|
||||
|
||||
## Types de schémas courants
|
||||
|
||||
### Objets simples
|
||||
```javascript
|
||||
schema: {
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
name: { type: string }
|
||||
email: { type: string, format: email }
|
||||
age: { type: integer, minimum: 0 }
|
||||
isActive: { type: boolean }
|
||||
}
|
||||
```
|
||||
|
||||
### Arrays
|
||||
```javascript
|
||||
schema: {
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
name: { type: string }
|
||||
}
|
||||
```
|
||||
|
||||
### Enums
|
||||
```javascript
|
||||
schema: {
|
||||
type: string
|
||||
enum: ["pending", "approved", "rejected"]
|
||||
}
|
||||
```
|
||||
|
||||
## Tags disponibles
|
||||
Les tags disponibles dans la config Swagger sont :
|
||||
- **Articles** - Gestion des articles
|
||||
- **Users** - Gestion des utilisateurs
|
||||
- **Products** - Gestion des produits
|
||||
- **Clans** - Gestion des clans
|
||||
- **Hero Wars** - Données Hero Wars
|
||||
|
||||
## Instructions générales
|
||||
|
||||
1. **Placez les commentaires JSDoc directement au-dessus de la définition de la route**
|
||||
2. **Utilisez le tag `@swagger`** pour indiquer que c'est de la documentation OpenAPI
|
||||
3. **Commencez par le chemin d'accès** : `/api/v1/articles`, `/api/v1/articles/{id}`, etc.
|
||||
4. **Spécifiez la méthode HTTP** : `get`, `post`, `put`, `delete`, `patch`
|
||||
5. **Incluez toujours** : `summary`, `description`, `tags`, `responses`
|
||||
6. **Pour les routes protégées**, utilisez `security: [{ bearerAuth: [] }]` ou `security: [{ apiKeyAuth: [] }]`
|
||||
|
||||
## Pour plus de détails
|
||||
Consultez la documentation officielle : https://swagger.io/specification/
|
||||
|
||||
---
|
||||
|
||||
**Note** : Une fois que vous aurez ajouté les commentaires Swagger à vos routes, redémarrez le serveur et accédez à `/api-docs` pour voir la documentation mise à jour.
|
||||
@@ -0,0 +1,387 @@
|
||||
# Guide de mise en production — Adastra
|
||||
|
||||
Serveur cible : VPS Debian 11, Apache, MySQL déjà installés.
|
||||
Stack : Node.js/Express (API) + Angular 18 (frontend).
|
||||
|
||||
---
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Prérequis serveur](#1-prérequis-serveur)
|
||||
2. [Base de données MySQL](#2-base-de-données-mysql)
|
||||
3. [Base de données MongoDB](#3-base-de-données-mongodb)
|
||||
4. [Déploiement de l'API](#4-déploiement-de-lapi)
|
||||
5. [Déploiement du frontend](#5-déploiement-du-frontend)
|
||||
6. [Configuration Apache](#6-configuration-apache)
|
||||
7. [HTTPS avec Let's Encrypt](#7-https-avec-lets-encrypt)
|
||||
8. [PM2 — démarrage automatique](#8-pm2--démarrage-automatique)
|
||||
9. [Procédure de mise à jour](#9-procédure-de-mise-à-jour)
|
||||
|
||||
---
|
||||
|
||||
## 1. Prérequis serveur
|
||||
|
||||
### Node.js (via nvm — recommandé)
|
||||
|
||||
```bash
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||
source ~/.bashrc
|
||||
nvm install 20
|
||||
nvm use 20
|
||||
nvm alias default 20
|
||||
node -v # doit afficher v20.x.x
|
||||
```
|
||||
|
||||
### PM2 (gestionnaire de process Node)
|
||||
|
||||
```bash
|
||||
npm install -g pm2
|
||||
```
|
||||
|
||||
### MongoDB
|
||||
|
||||
L'API utilise à la fois MySQL et MongoDB. MongoDB n'est pas fourni par les dépôts Debian 11 par défaut.
|
||||
|
||||
```bash
|
||||
# Clé GPG et dépôt MongoDB 7
|
||||
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
|
||||
gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
|
||||
|
||||
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/debian bullseye/mongodb-org/7.0 main" \
|
||||
| tee /etc/apt/sources.list.d/mongodb-org-7.0.list
|
||||
|
||||
apt update && apt install -y mongodb-org
|
||||
systemctl enable --now mongod
|
||||
mongod --version
|
||||
```
|
||||
|
||||
### Modules Apache nécessaires
|
||||
|
||||
```bash
|
||||
a2enmod proxy proxy_http rewrite headers ssl
|
||||
systemctl restart apache2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Base de données MySQL
|
||||
|
||||
MySQL est déjà installé. Créer la base et l'utilisateur dédié.
|
||||
|
||||
```sql
|
||||
-- En tant que root MySQL
|
||||
CREATE DATABASE adastra_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'adastra'@'localhost' IDENTIFIED BY 'MOT_DE_PASSE_FORT';
|
||||
GRANT ALL PRIVILEGES ON adastra_prod.* TO 'adastra'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
Les migrations Sequelize (voir [section 4](#4-déploiement-de-lapi)) créeront le schéma.
|
||||
|
||||
---
|
||||
|
||||
## 3. Base de données MongoDB
|
||||
|
||||
```bash
|
||||
# Créer la base et l'utilisateur applicatif
|
||||
mongosh <<'EOF'
|
||||
use adastradb
|
||||
db.createUser({
|
||||
user: "adastra",
|
||||
pwd: "MOT_DE_PASSE_FORT",
|
||||
roles: [{ role: "readWrite", db: "adastradb" }]
|
||||
})
|
||||
EOF
|
||||
```
|
||||
|
||||
Activer l'authentification MongoDB si elle n'est pas encore activée :
|
||||
|
||||
```bash
|
||||
# /etc/mongod.conf
|
||||
security:
|
||||
authorization: enabled
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl restart mongod
|
||||
```
|
||||
|
||||
La `MONGODB_URI` dans le `.env` devient alors :
|
||||
```
|
||||
MONGODB_URI=mongodb://adastra:MOT_DE_PASSE_FORT@localhost:27017/adastradb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Déploiement de l'API
|
||||
|
||||
### Récupérer le code
|
||||
|
||||
```bash
|
||||
mkdir -p /var/www
|
||||
cd /var/www
|
||||
git clone <url-du-repo> adastra_api
|
||||
cd adastra_api
|
||||
git checkout master # ou la branche de production
|
||||
npm install --omit=dev
|
||||
```
|
||||
|
||||
### Fichier d'environnement
|
||||
|
||||
```bash
|
||||
cp config/env/.env.example config/env/.env.production
|
||||
nano config/env/.env.production
|
||||
```
|
||||
|
||||
Valeurs à renseigner obligatoirement :
|
||||
|
||||
```dotenv
|
||||
APP_ENV=production
|
||||
NODE_ENV=production
|
||||
SERVER_PORT=3200
|
||||
|
||||
# Secrets — générer avec :
|
||||
# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
||||
SECRET=<64-octets-hex>
|
||||
SESSION_SECRET=<64-octets-hex>
|
||||
|
||||
# MySQL
|
||||
MYSQL_DBNAME=adastra_prod
|
||||
MYSQL_DBUSER=adastra
|
||||
MYSQL_DBPASS=MOT_DE_PASSE_FORT
|
||||
MYSQL_DBHOST=localhost
|
||||
MYSQL_DBPORT=3306
|
||||
MYSQL_DBTYPE=mysql
|
||||
|
||||
# MongoDB
|
||||
MONGODB_URI=mongodb://adastra:MOT_DE_PASSE_FORT@localhost:27017/adastradb
|
||||
|
||||
# CORS — domaine exact du frontend (sans slash final)
|
||||
ALLOWED_ORIGINS=https://adastra.example.com
|
||||
|
||||
# Proxy Apache → Node = 1 saut
|
||||
TRUST_PROXY=1
|
||||
|
||||
# Sendgrid (si les emails sont utilisés)
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_FROM_MAIL=
|
||||
SENDGRID_TO_MAIL=
|
||||
```
|
||||
|
||||
### Migrations et seeds initiaux
|
||||
|
||||
À exécuter **une seule fois** lors du premier déploiement :
|
||||
|
||||
```bash
|
||||
cd /var/www/adastra_api
|
||||
APP_ENV=production npx sequelize-cli db:migrate
|
||||
APP_ENV=production npx sequelize-cli db:seed --seed 20251126-add-roles.js
|
||||
APP_ENV=production npm run create:user # crée le premier compte admin
|
||||
APP_ENV=production npx sequelize-cli db:seed --seed 20251127-add-articles.js
|
||||
```
|
||||
|
||||
### Démarrer avec PM2
|
||||
|
||||
```bash
|
||||
cd /var/www/adastra_api
|
||||
pm2 start app.js --name adastra-api
|
||||
pm2 save
|
||||
```
|
||||
|
||||
Vérifier que l'API répond :
|
||||
|
||||
```bash
|
||||
curl http://localhost:3200/api/health # ou tout endpoint public
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Déploiement du frontend
|
||||
|
||||
Le build se fait **en local** (ou sur un serveur de CI), puis le répertoire `dist/` est transféré sur le serveur.
|
||||
|
||||
### Build en local
|
||||
|
||||
```bash
|
||||
cd /chemin/local/adastra_app
|
||||
npm install
|
||||
npm run build # production build → dist/adastra_angular/browser/
|
||||
```
|
||||
|
||||
> **Important :** vérifier que `src/environments/environment.ts` pointe vers l'URL de l'API de production avant de builder.
|
||||
> ```typescript
|
||||
> apiBaseUrl: 'https://api.adastra.example.com/api'
|
||||
> ```
|
||||
|
||||
### Transfert sur le serveur
|
||||
|
||||
```bash
|
||||
# Depuis la machine locale
|
||||
rsync -avz --delete dist/adastra_angular/browser/ \
|
||||
user@serveur:/var/www/adastra_app/
|
||||
```
|
||||
|
||||
Ou avec scp :
|
||||
|
||||
```bash
|
||||
scp -r dist/adastra_angular/browser/* user@serveur:/var/www/adastra_app/
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
```bash
|
||||
# Sur le serveur
|
||||
chown -R www-data:www-data /var/www/adastra_app
|
||||
chmod -R 755 /var/www/adastra_app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Configuration Apache
|
||||
|
||||
Architecture recommandée : deux sous-domaines séparés.
|
||||
|
||||
| Sous-domaine | Rôle |
|
||||
|---|---|
|
||||
| `adastra.example.com` | Frontend Angular (fichiers statiques) |
|
||||
| `api.adastra.example.com` | API Node.js (reverse proxy) |
|
||||
|
||||
### VirtualHost frontend — `/etc/apache2/sites-available/adastra-app.conf`
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName adastra.example.com
|
||||
|
||||
DocumentRoot /var/www/adastra_app
|
||||
|
||||
<Directory /var/www/adastra_app>
|
||||
Options -Indexes
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
|
||||
# Angular HTML5 routing — renvoie tout vers index.html
|
||||
FallbackResource /index.html
|
||||
</Directory>
|
||||
|
||||
# Compression et cache navigateur
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json
|
||||
</IfModule>
|
||||
|
||||
<FilesMatch "\.(js|css|woff2|png|jpg|svg)$">
|
||||
Header set Cache-Control "max-age=31536000, public, immutable"
|
||||
</FilesMatch>
|
||||
<FilesMatch "index\.html$">
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
||||
</FilesMatch>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/adastra-app-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/adastra-app-access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### VirtualHost API — `/etc/apache2/sites-available/adastra-api.conf`
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName api.adastra.example.com
|
||||
|
||||
ProxyPreserveHost On
|
||||
ProxyPass / http://127.0.0.1:3200/
|
||||
ProxyPassReverse / http://127.0.0.1:3200/
|
||||
|
||||
# Transmet l'IP réelle du client à Node (requis pour TRUST_PROXY=1)
|
||||
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
|
||||
RequestHeader set X-Forwarded-Proto "http"
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/adastra-api-error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/adastra-api-access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### Activer les vhosts
|
||||
|
||||
```bash
|
||||
a2ensite adastra-app.conf
|
||||
a2ensite adastra-api.conf
|
||||
apache2ctl configtest # doit afficher "Syntax OK"
|
||||
systemctl reload apache2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. HTTPS avec Let's Encrypt
|
||||
|
||||
```bash
|
||||
apt install -y certbot python3-certbot-apache
|
||||
certbot --apache -d adastra.example.com -d api.adastra.example.com
|
||||
```
|
||||
|
||||
Certbot modifie automatiquement les vhosts pour le HTTPS et programme le renouvellement.
|
||||
|
||||
Vérifier le renouvellement automatique :
|
||||
|
||||
```bash
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
Après activation du HTTPS, penser à mettre à jour dans le `.env.production` :
|
||||
```dotenv
|
||||
ALLOWED_ORIGINS=https://adastra.example.com
|
||||
```
|
||||
|
||||
Et dans `environment.ts` avant de rebuilder le frontend :
|
||||
```typescript
|
||||
apiBaseUrl: 'https://api.adastra.example.com/api'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. PM2 — démarrage automatique
|
||||
|
||||
Pour que l'API redémarre automatiquement après un reboot :
|
||||
|
||||
```bash
|
||||
pm2 startup # affiche une commande à exécuter en root — la copier/coller
|
||||
pm2 save
|
||||
```
|
||||
|
||||
Commandes PM2 utiles :
|
||||
|
||||
```bash
|
||||
pm2 list # état des process
|
||||
pm2 logs adastra-api # logs en temps réel
|
||||
pm2 restart adastra-api # redémarrage manuel
|
||||
pm2 stop adastra-api # arrêt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Procédure de mise à jour
|
||||
|
||||
### Mettre à jour l'API
|
||||
|
||||
```bash
|
||||
cd /var/www/adastra_api
|
||||
git pull origin master
|
||||
npm install --omit=dev
|
||||
|
||||
# Si des migrations ont été ajoutées
|
||||
APP_ENV=production npx sequelize-cli db:migrate
|
||||
|
||||
pm2 restart adastra-api
|
||||
```
|
||||
|
||||
### Mettre à jour le frontend
|
||||
|
||||
En local :
|
||||
|
||||
```bash
|
||||
cd /chemin/local/adastra_app
|
||||
git pull origin master
|
||||
npm install
|
||||
npm run build
|
||||
rsync -avz --delete dist/adastra_angular/browser/ user@serveur:/var/www/adastra_app/
|
||||
```
|
||||
|
||||
Aucun redémarrage Apache nécessaire — les fichiers statiques sont servis directement.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Variables d'environnement — Adastra API
|
||||
|
||||
Référence pour les variables sensibles ou non-triviales. Pour les valeurs par défaut et la liste exhaustive, voir `config/env/.env.example`.
|
||||
|
||||
---
|
||||
|
||||
## TRUST_PROXY
|
||||
|
||||
**Fichier concerné :** `src/createApp.js`
|
||||
**Utilisé par :** `express-rate-limit` (IP réelle du client), `req.ip`
|
||||
|
||||
### Principe
|
||||
|
||||
Indique à Express combien de proxies se trouvent entre internet et le process Node. Sans cette valeur, `req.ip` retourne l'IP du proxy (`127.0.0.1`) au lieu de l'IP du client réel, ce qui rend le rate limiting inefficace (toutes les requêtes semblent venir de la même IP).
|
||||
|
||||
### Valeurs selon la topologie
|
||||
|
||||
| Environnement | Topologie | Valeur |
|
||||
|---|---|---|
|
||||
| Dev local | Node exposé directement, pas de proxy | ne pas setter (variable absente) |
|
||||
| Prod — serveur seul | nginx → Node sur le même serveur | `1` |
|
||||
| Prod — cloud avec LB | nginx → load balancer → Node | `2` |
|
||||
| Confiance totale | Tous les proxies de la chaîne `X-Forwarded-For` | `true` (déconseillé) |
|
||||
|
||||
> **Règle générale :** `TRUST_PROXY` = nombre de proxies entre internet et Node.
|
||||
|
||||
### Vérification empirique
|
||||
|
||||
Ajouter temporairement cette route et appeler `/debug-ip` depuis l'extérieur :
|
||||
|
||||
```js
|
||||
app.get('/debug-ip', (req, res) => {
|
||||
res.json({
|
||||
ip: req.ip,
|
||||
ips: req.ips,
|
||||
xForwardedFor: req.headers['x-forwarded-for']
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- `req.ip` retourne l'IP réelle du client → valeur correcte
|
||||
- `req.ip` retourne `127.0.0.1` ou l'IP du proxy → augmenter de 1
|
||||
- `req.ips` est vide malgré un proxy → idem
|
||||
|
||||
**Supprimer la route après vérification.**
|
||||
|
||||
### Implémentation actuelle
|
||||
|
||||
```js
|
||||
if (process.env.TRUST_PROXY) {
|
||||
app.set('trust proxy', parseInt(process.env.TRUST_PROXY, 10) || process.env.TRUST_PROXY);
|
||||
}
|
||||
```
|
||||
|
||||
Le cast `parseInt(...) || process.env.TRUST_PROXY` permet de passer soit un entier (`"1"` → `1`) soit la chaîne `"true"`.
|
||||
|
||||
---
|
||||
|
||||
## SECRET
|
||||
|
||||
**Fichier concerné :** `src/config/index.js`
|
||||
**Utilisé par :** signature JWT, HMAC-SHA256 des API keys, secret de session Express
|
||||
|
||||
Valeur obligatoire en production — le process refuse de démarrer si elle est absente. Générer avec :
|
||||
|
||||
```bash
|
||||
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SESSION_SECRET
|
||||
|
||||
**Fichier concerné :** `src/createApp.js`
|
||||
**Utilisé par :** `express-session`
|
||||
|
||||
Indépendant de `SECRET` pour limiter la surface d'impact si l'un des deux est compromis. Même commande de génération que `SECRET`. Fallback sur `SECRET` si absent (rétrocompatibilité).
|
||||
|
||||
---
|
||||
|
||||
## ALLOWED_ORIGINS
|
||||
|
||||
**Fichier concerné :** `src/createApp.js`
|
||||
**Utilisé par :** CORS
|
||||
|
||||
Liste d'origines autorisées, séparées par des virgules. Si absente ou vide, CORS refuse toutes les origines cross-domain.
|
||||
|
||||
```
|
||||
ALLOWED_ORIGINS=http://localhost:4200,http://localhost:4300
|
||||
```
|
||||
|
||||
En production, indiquer uniquement le domaine du frontend (ex: `https://adastra.example.com`).
|
||||
@@ -0,0 +1,338 @@
|
||||
# Installation de Gitea sur Debian 11 avec Apache/ISPConfig
|
||||
|
||||
Gitea sera accessible sur `https://git.unespace.com` en reverse proxy devant Apache.
|
||||
Les repos existants dans `/opt/git/` seront migrés à la fin.
|
||||
|
||||
---
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Prérequis Apache](#1-prérequis-apache)
|
||||
2. [Créer l'utilisateur système Gitea](#2-créer-lutilisateur-système-gitea)
|
||||
3. [Télécharger et installer le binaire](#3-télécharger-et-installer-le-binaire)
|
||||
4. [Créer les répertoires et la configuration](#4-créer-les-répertoires-et-la-configuration)
|
||||
5. [Créer le service systemd](#5-créer-le-service-systemd)
|
||||
6. [Démarrer Gitea et finaliser l'installation](#6-démarrer-gitea-et-finaliser-linstallation)
|
||||
7. [Configurer le VHost dans ISPConfig](#7-configurer-le-vhost-dans-ispconfig)
|
||||
8. [Migrer les repos existants](#8-migrer-les-repos-existants)
|
||||
9. [Mettre à jour les remotes locaux](#9-mettre-à-jour-les-remotes-locaux)
|
||||
|
||||
---
|
||||
|
||||
## 1. Prérequis Apache
|
||||
|
||||
Activer les modules proxy si ce n'est pas déjà fait :
|
||||
|
||||
```bash
|
||||
a2enmod proxy proxy_http headers
|
||||
systemctl reload apache2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Créer l'utilisateur système Gitea
|
||||
|
||||
Gitea a besoin de son propre utilisateur Unix. On le crée **sans home** et **sans shell** pour limiter la surface d'attaque, mais on lui donne accès au répertoire des repos existants :
|
||||
|
||||
```bash
|
||||
adduser --system --shell /bin/bash --gecos 'Gitea' --group --home /opt/gitea gitea
|
||||
```
|
||||
|
||||
Pour que Gitea puisse lire les repos appartenant à l'utilisateur `git`, on ajoute `gitea` au groupe `git` :
|
||||
|
||||
```bash
|
||||
usermod -aG git gitea
|
||||
```
|
||||
|
||||
> Si tes repos dans `/opt/git/` appartiennent à `git:git`, cette étape est nécessaire pour la migration.
|
||||
|
||||
---
|
||||
|
||||
## 3. Télécharger et installer le binaire
|
||||
|
||||
Récupérer la dernière version stable depuis la page des releases Gitea. À adapter avec le numéro de version actuel :
|
||||
|
||||
```bash
|
||||
# Vérifier la dernière version sur https://github.com/go-gitea/gitea/releases
|
||||
GITEA_VERSION="1.26.1"
|
||||
|
||||
wget -O /tmp/gitea https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64
|
||||
chmod +x /tmp/gitea
|
||||
mv /tmp/gitea /usr/local/bin/gitea
|
||||
|
||||
# Vérifier
|
||||
gitea --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Créer les répertoires et la configuration
|
||||
|
||||
```bash
|
||||
# Répertoires de données
|
||||
mkdir -p /opt/gitea/{custom,data,log,repositories}
|
||||
chown -R gitea:gitea /opt/gitea
|
||||
chmod -R 750 /opt/gitea
|
||||
|
||||
# Répertoire de configuration
|
||||
mkdir -p /etc/gitea
|
||||
chown root:gitea /etc/gitea
|
||||
chmod 770 /etc/gitea
|
||||
```
|
||||
|
||||
Créer le fichier de configuration `/etc/gitea/app.ini` :
|
||||
|
||||
```bash
|
||||
cat > /etc/gitea/app.ini << 'EOF'
|
||||
APP_NAME = Adastra Git
|
||||
RUN_USER = gitea
|
||||
RUN_MODE = prod
|
||||
WORK_PATH = /opt/gitea
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 3030
|
||||
DOMAIN = git.unespace.com
|
||||
ROOT_URL = https://git.unespace.com/
|
||||
DISABLE_SSH = false
|
||||
SSH_PORT = 22
|
||||
SSH_DOMAIN = git.unespace.com
|
||||
START_SSH_SERVER = false
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /opt/gitea/data/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = /opt/gitea/repositories
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = false
|
||||
SECRET_KEY =
|
||||
INTERNAL_TOKEN =
|
||||
|
||||
[log]
|
||||
ROOT_PATH = /opt/gitea/log
|
||||
MODE = file
|
||||
LEVEL = warn
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
EOF
|
||||
|
||||
chown root:gitea /etc/gitea/app.ini
|
||||
chmod 660 /etc/gitea/app.ini
|
||||
```
|
||||
|
||||
> **Note :** `INSTALL_LOCK = false` permet à l'installeur web de fonctionner au premier démarrage. Il passera à `true` automatiquement après.
|
||||
> Le port `3030` est choisi pour éviter tout conflit avec l'API Node.js (3200) et d'autres services éventuels.
|
||||
|
||||
---
|
||||
|
||||
## 5. Créer le service systemd
|
||||
|
||||
```bash
|
||||
cat > /etc/systemd/system/gitea.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Gitea (Git with a cup of tea)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=gitea
|
||||
Group=gitea
|
||||
WorkingDirectory=/opt/gitea
|
||||
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
|
||||
Restart=always
|
||||
RestartSec=2s
|
||||
Environment=USER=gitea HOME=/opt/gitea GITEA_WORK_DIR=/opt/gitea
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable gitea
|
||||
systemctl start gitea
|
||||
systemctl status gitea
|
||||
```
|
||||
|
||||
Vérifier que Gitea écoute bien sur le bon port :
|
||||
|
||||
```bash
|
||||
ss -tlnp | grep 3030
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Démarrer Gitea et finaliser l'installation
|
||||
|
||||
À ce stade Gitea tourne mais l'interface d'installation web n'est pas encore accessible publiquement (le VHost n'est pas créé). On peut tester depuis le serveur lui-même :
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:3030 | head -5
|
||||
# doit retourner du HTML
|
||||
```
|
||||
|
||||
L'installation web sera finalisée **après** la configuration du VHost (étape 7).
|
||||
|
||||
---
|
||||
|
||||
## 7. Configurer le VHost dans ISPConfig
|
||||
|
||||
### 7.1 Créer le site web
|
||||
|
||||
Dans ISPConfig → **Sites → Ajouter un site web** :
|
||||
|
||||
| Champ | Valeur |
|
||||
|---|---|
|
||||
| Domain | `git.unespace.com` |
|
||||
| Document Root | `/var/www/gitea-placeholder` (peu importe, ne sera pas utilisé) |
|
||||
| Auto-subdomain | Aucun |
|
||||
| Redirect type | Laisser vide |
|
||||
|
||||
Sauvegarder.
|
||||
|
||||
### 7.2 Activer le SSL Let's Encrypt
|
||||
|
||||
Dans l'onglet **SSL** du site que tu viens de créer :
|
||||
|
||||
- Cocher **SSL**
|
||||
- Cocher **Let's Encrypt**
|
||||
- Sauvegarder
|
||||
|
||||
ISPConfig demande le certificat automatiquement. Attendre que le statut passe à "actif" (peut prendre 1-2 minutes).
|
||||
|
||||
### 7.3 Ajouter les directives reverse proxy
|
||||
|
||||
Dans l'onglet **Options** (ou **Directives Apache** selon ta version d'ISPConfig), dans le champ **Apache Directives**, coller :
|
||||
|
||||
```apache
|
||||
ProxyPreserveHost On
|
||||
ProxyPass / http://127.0.0.1:3030/
|
||||
ProxyPassReverse / http://127.0.0.1:3030/
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
|
||||
```
|
||||
|
||||
Répéter l'opération dans le champ **Apache Directives SSL** (le champ équivalent pour le VHost HTTPS) avec les mêmes lignes.
|
||||
|
||||
Sauvegarder. ISPConfig recharge Apache automatiquement.
|
||||
|
||||
### 7.4 Vérifier
|
||||
|
||||
```bash
|
||||
curl -I https://git.unespace.com
|
||||
# HTTP/2 200 ou redirection vers l'installeur Gitea
|
||||
```
|
||||
|
||||
### 7.5 Finaliser l'installation Gitea via le navigateur
|
||||
|
||||
Ouvre `https://git.unespace.com` dans ton navigateur. L'installeur Gitea s'affiche.
|
||||
|
||||
Paramètres recommandés :
|
||||
|
||||
| Champ | Valeur |
|
||||
|---|---|
|
||||
| Base URL | `https://git.unespace.com/` |
|
||||
| SSH Server Domain | `git.unespace.com` |
|
||||
| SSH Port | `22` |
|
||||
| HTTP Port | `3030` |
|
||||
| Database | SQLite3 (déjà configuré) |
|
||||
| Compte administrateur | Remplir nom/email/mot de passe |
|
||||
|
||||
Cliquer **Installer Gitea**. L'installeur met à jour `app.ini` et redirige vers la page d'accueil.
|
||||
|
||||
### 7.6 Ajouter ta clé SSH publique
|
||||
|
||||
Pour pouvoir pousser via SSH, ta clé publique doit être enregistrée dans Gitea.
|
||||
|
||||
Depuis ta machine locale, récupère ta clé publique :
|
||||
|
||||
```bash
|
||||
cat ~/.ssh/id_rsa.pub
|
||||
# ou si tu utilises ed25519 :
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
```
|
||||
|
||||
Dans Gitea : **ton avatar → Paramètres → Clés SSH/GPG → Ajouter une clé** — colle le contenu et valide.
|
||||
|
||||
---
|
||||
|
||||
## 8. Migrer les repos existants
|
||||
|
||||
Tes repos actuels sont dans `/opt/git/` sous forme de repos bare (ex. `/opt/git/adastra_api.git`).
|
||||
|
||||
### Option A — Via l'interface Gitea (recommandé)
|
||||
|
||||
Pour chaque repo :
|
||||
|
||||
1. Dans Gitea, créer un nouveau dépôt vide (même nom, sans initialisation)
|
||||
2. Depuis ta machine locale, ajouter le nouveau remote et pousser :
|
||||
|
||||
```bash
|
||||
cd /chemin/local/adastra_api
|
||||
git remote add gitea gitea@git.unespace.com:TON_USERNAME/adastra_api.git
|
||||
git push gitea --all
|
||||
git push gitea --tags
|
||||
```
|
||||
|
||||
> **Note :** l'utilisateur SSH est `gitea` (le user système sous lequel tourne Gitea), pas `git`.
|
||||
|
||||
### Option B — Copie directe côté serveur
|
||||
|
||||
Si tu veux éviter de tout repousser depuis le local, tu peux cloner les repos bare existants directement dans le répertoire de Gitea :
|
||||
|
||||
```bash
|
||||
# Pour chaque repo — adapter le nom d'utilisateur Gitea
|
||||
su - gitea -s /bin/bash
|
||||
|
||||
cd /opt/gitea/repositories/TON_USERNAME/
|
||||
|
||||
# Clone bare du repo existant
|
||||
git clone --mirror /opt/git/adastra_api.git adastra_api.git
|
||||
git clone --mirror /opt/git/adastra_app.git adastra_app.git
|
||||
```
|
||||
|
||||
Puis dans Gitea → Admin panel → **Git Repositories** → **Git Resync** pour que Gitea indexe les repos copiés.
|
||||
|
||||
---
|
||||
|
||||
## 9. Mettre à jour les remotes locaux
|
||||
|
||||
Une fois les repos migrés, mettre à jour les remotes dans tes clones locaux :
|
||||
|
||||
```bash
|
||||
# Vérifier les remotes actuels
|
||||
git remote -v
|
||||
|
||||
# Remplacer l'ancien remote origin
|
||||
git remote set-url origin gitea@git.unespace.com:TON_USERNAME/adastra_api.git
|
||||
git remote set-url origin gitea@git.unespace.com:TON_USERNAME/adastra_app.git
|
||||
|
||||
# Vérifier
|
||||
git remote -v
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
> L'ancien serveur Git continue de fonctionner en parallèle jusqu'à ce que tu sois certain que tout est migré.
|
||||
|
||||
---
|
||||
|
||||
## Commandes d'administration utiles
|
||||
|
||||
```bash
|
||||
# Statut du service
|
||||
systemctl status gitea
|
||||
|
||||
# Logs en temps réel
|
||||
journalctl -u gitea -f
|
||||
|
||||
# Mise à jour de Gitea (remplacer le binaire puis redémarrer)
|
||||
systemctl stop gitea
|
||||
wget -O /usr/local/bin/gitea https://dl.gitea.com/gitea/NEW_VERSION/gitea-NEW_VERSION-linux-amd64
|
||||
chmod +x /usr/local/bin/gitea
|
||||
systemctl start gitea
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
# Use express-validator for input validation at the route level
|
||||
|
||||
* Status: accepted
|
||||
* Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
API endpoints accepted user input without validating format, length, or type. A `fieldValidation` helper existed in several controllers but only checked for empty values, was duplicated across three controllers (users, articles, products), and had a critical bug: it called `next(error)` without `return`, so execution continued even when a field was invalid.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* Input validation must happen before business logic and must halt execution on failure.
|
||||
* Validation rules should be declared at the route level, not buried inside controller logic.
|
||||
* The solution must be consistent and reusable across the codebase without adding a framework wrapper.
|
||||
|
||||
## Considered Options
|
||||
|
||||
* Keep and fix the `fieldValidation` helper
|
||||
* Joi with a validation middleware wrapper
|
||||
* express-validator with route-level chains
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "express-validator", because it integrates natively as Express middleware, allows validation rules to be declared directly in route definitions, and requires no wrapper function. A shared `validate.js` middleware reads `validationResult` and returns HTTP 422 with a structured error body if any rule fails.
|
||||
|
||||
Structure:
|
||||
- `src/middlewares/validate.js` — shared middleware that checks `validationResult` and short-circuits on errors
|
||||
- `src/middlewares/validators/users.validators.js` — rule sets for user endpoints
|
||||
- `src/middlewares/validators/articles.validators.js` — rule sets for article endpoints
|
||||
- Rules applied at route level: `router.post('/', rules, validate, controller)`
|
||||
|
||||
Initial coverage: CMS user endpoints (register, login, update profile/email/username/role) and article endpoints (create, update). Products and tags endpoints retain the old `fieldValidation` pattern pending a separate ecommerce validation pass.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
* Validation always runs before the controller — no risk of continuing on invalid input.
|
||||
* Rules are co-located with routes, making the contract of each endpoint visible at a glance.
|
||||
* Reusable rule sets can be composed and shared across routes.
|
||||
* `fieldValidation` removed from users and articles controllers — no more duplicated logic.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
* Products and tags controllers still use the old `fieldValidation` pattern — inconsistency until the ecommerce pass is done.
|
||||
* Validation rules must be maintained in sync with model constraints (e.g. if a column length changes, the validator must be updated manually).
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Keep and fix fieldValidation
|
||||
|
||||
* Good, because no new dependency.
|
||||
* Bad, because it is duplicated in every controller that needs it.
|
||||
* Bad, because it belongs in controllers, not at the boundary where input arrives.
|
||||
|
||||
### Joi
|
||||
|
||||
* Good, because schema-based validation with rich type coercion.
|
||||
* Bad, because requires a wrapper middleware to integrate with Express — adds boilerplate not present in the existing codebase.
|
||||
|
||||
### express-validator
|
||||
|
||||
* Good, because native Express middleware — no wrapper needed.
|
||||
* Good, because rule sets are plain arrays, easy to compose and test independently.
|
||||
* Good, because actively maintained with wide adoption in Express projects.
|
||||
* Bad, because rules are imperative chains rather than declarative schemas, which can be verbose for complex objects.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Upgrade Express from v4 to v5 and remove redundant middleware
|
||||
|
||||
* Status: accepted
|
||||
* Date: 2026-04-28
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The API ran on Express 4.18, with two additional middleware packages (`method-override`, `errorhandler`) and an `asyncHandler` wrapper used across all controllers to forward async errors to Express. Express 5 was released as stable in September 2024 and addresses the async error handling gap natively.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* Express 5 propagates promise rejections in route handlers to error middleware automatically — the `asyncHandler` wrapper is redundant noise.
|
||||
* `method-override` allows clients to tunnel `PUT`/`DELETE` via `POST` using a `_method` parameter. The API is consumed exclusively by Angular's `HttpClient`, which sends correct HTTP verbs directly. The middleware has never been exercised.
|
||||
* `errorhandler` provides verbose error pages in development, but a custom `exceptions.handler.js` already handles error formatting for all environments.
|
||||
* Removing unused middleware reduces the attack surface and the number of dependencies to keep up to date.
|
||||
|
||||
## Considered Options
|
||||
|
||||
* Stay on Express 4, keep existing middleware
|
||||
* Upgrade to Express 5, keep `method-override` and `errorhandler` for safety
|
||||
* Upgrade to Express 5 and remove all three redundant pieces
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "upgrade to Express 5 and remove all three redundant pieces", because the migration has zero breaking changes for this codebase (no wildcard routes, no removed APIs in use) and each removed package was demonstrably unused or superseded.
|
||||
|
||||
Changes made:
|
||||
- `express` bumped to `^5` (installed 5.2.1)
|
||||
- `method-override` removed from `package.json` and `createApp.js`
|
||||
- `errorhandler` removed from `package.json` and `createApp.js`
|
||||
- `asyncHandler` wrapper removed from all 8 controllers and its middleware file deleted
|
||||
- Controller exports changed from `asyncHandler(async (req, res, next) => { ... })` to `async (req, res, next) => { ... }`
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
* Unhandled async errors now reach the error handler without any wrapper — less boilerplate in every controller.
|
||||
* Two dependencies removed from the production bundle (`method-override`, `errorhandler`).
|
||||
* Codebase is on a maintained major version with long-term support.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
* Express 5 is a major version bump — future middleware additions must be verified for Express 5 compatibility before adoption.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Stay on Express 4
|
||||
|
||||
* Good, because no migration effort.
|
||||
* Bad, because the async error gap requires the `asyncHandler` wrapper indefinitely.
|
||||
* Bad, because `method-override` and `errorhandler` remain as unused surface area.
|
||||
|
||||
### Upgrade to Express 5, keep existing middleware
|
||||
|
||||
* Good, because conservative — no behavioural changes beyond async propagation.
|
||||
* Bad, because `method-override` and `errorhandler` are still dead weight.
|
||||
|
||||
### Upgrade to Express 5 and remove all three
|
||||
|
||||
* Good, because every removed package is one fewer dependency to audit and update.
|
||||
* Good, because controller code is simpler without the wrapper.
|
||||
* Bad, because a future Express 4-only middleware would need to be replaced — acceptable trade-off given the ecosystem has largely moved to v5.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Extract user and application routes into a dedicated auth domain
|
||||
|
||||
* Status: accepted
|
||||
* Date: 2026-05-01
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
User authentication/registration routes (`/api/cms/user`) were grouped under the CMS domain, and API key application routes (`/api/skydive/applications`) were grouped under the Skydive domain. Neither resource is domain-specific content: users and applications are identity and access management concerns shared across all domains.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* The domain prefix should reflect what a resource *is*, not which feature first needed it.
|
||||
* `/api/cms/user` implies users are CMS content; `/api/skydive/applications` implies API keys are skydive data — both are misleading.
|
||||
* A dedicated `auth` domain makes the security boundary explicit and easier to apply targeted middleware (rate limiting, stricter CORS, etc.) in the future.
|
||||
|
||||
## Considered Options
|
||||
|
||||
* Keep routes in their current domains
|
||||
* Move only users out of CMS, leave applications in skydive
|
||||
* Create a dedicated `auth` domain for both
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "dedicated `auth` domain", because both resources are identity/access concerns and grouping them together makes the API surface self-documenting.
|
||||
|
||||
Changes made:
|
||||
- `src/routes/api/auth/` created with `users.routes.js`, `applications.routes.js`, and `index.js`
|
||||
- `/user` removed from `src/routes/api/cms/index.js`
|
||||
- `/applications` removed from `src/routes/api/skydive/index.js`
|
||||
- `/auth` domain registered in `src/routes/api/index.js`
|
||||
- Frontend `user.service.ts` and `applications.service.ts` updated: `_apiDomain` changed from `/cms` and `/skydive` to `/auth`
|
||||
|
||||
Resulting routes: `/api/auth/user/*` and `/api/auth/applications/*`.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
* API domain structure matches resource semantics — auth concerns are isolated from content and feature domains.
|
||||
* A single place to tighten auth-specific middleware (rate limiting, IP allowlists, audit logging) without touching other domains.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
* Breaking change on the API surface — any client other than the Angular frontend calling the old paths must be updated.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Keep routes in current domains
|
||||
|
||||
* Good, because no migration effort.
|
||||
* Bad, because the domain prefix actively misleads — users are not CMS content, applications are not skydive data.
|
||||
|
||||
### Move only users out of CMS
|
||||
|
||||
* Good, because smaller change.
|
||||
* Bad, because applications in skydive remains wrong, and two related resources end up in different places.
|
||||
|
||||
### Dedicated auth domain
|
||||
|
||||
* Good, because both resources land where their semantics say they belong.
|
||||
* Good, because the auth boundary is explicit and ready for future hardening.
|
||||
* Bad, because it is a breaking change requiring a coordinated update of all consumers.
|
||||
Generated
+997
-680
File diff suppressed because it is too large
Load Diff
+19
-19
@@ -27,43 +27,43 @@
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"ejs": "^3.1.9",
|
||||
"errorhandler": "^1.5.1",
|
||||
"express": "^4.18.2",
|
||||
"express-jwt": "^8.4.1",
|
||||
"express-session": "^1.18.0",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^16.6.1",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^5",
|
||||
"express-jwt": "^8.5.1",
|
||||
"express-rate-limit": "^8.4.1",
|
||||
"express-session": "^1.19.0",
|
||||
"express-validator": "^7.3.2",
|
||||
"helmet": "^8.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"method-override": "3.0.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"methods": "1.1.2",
|
||||
"mongoose": "^6.12.6",
|
||||
"mongoose-unique-validator": "^3.1.0",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.14.3",
|
||||
"morgan": "^1.10.1",
|
||||
"mysql2": "^3.22.3",
|
||||
"node-fetch": "^3.3.2",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"request": "^2.88.2",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize": "^6.37.8",
|
||||
"sequelize-auto": "^0.8.8",
|
||||
"slug": "^8.2.3",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"underscore": "^1.13.6",
|
||||
"underscore": "^1.13.8",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"globals": "^15.0.0",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"eslint": "^9.39.4",
|
||||
"globals": "^15.15.0",
|
||||
"jest": "^30.3.0",
|
||||
"newman": "^5.3.2",
|
||||
"nodemon": "^2.0.22",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"nodemon": "^3.1.14",
|
||||
"sequelize-cli": "^6.6.5",
|
||||
"supertest": "^7.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
},
|
||||
{
|
||||
"num": 21,
|
||||
"libelle": "La vitesse de largage d’un Cessna (206 ; 207) est d ‘environ :",
|
||||
"libelle": "La vitesse de largage d’un Cessna (206 ; 207) est d’environ :",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "50 kts.", "correct": false},
|
||||
{"index": "B", "libelle": "80 kts.", "correct": true},
|
||||
@@ -189,7 +189,7 @@
|
||||
},
|
||||
{
|
||||
"num": 22,
|
||||
"libelle": "La vitesse de largage d’un Cessna Caravan est d ‘environ :",
|
||||
"libelle": "La vitesse de largage d’un Cessna Caravan est d’environ :",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "100 kts.", "correct": false},
|
||||
{"index": "B", "libelle": "80 kts.", "correct": true},
|
||||
@@ -839,7 +839,7 @@
|
||||
},
|
||||
{
|
||||
"num": 94,
|
||||
"libelle": "Au moment du largage, s‘assurer que l’espace aérien est dégagé en dessous :",
|
||||
"libelle": "Au moment du largage, s’assurer que l’espace aérien est dégagé en dessous :",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "Est de la responsabilité du pilote, du directeur de séance et du largueur.", "correct": true},
|
||||
{"index": "B", "libelle": "Est de la responsabilité du directeur de séance uniquement.", "correct": false},
|
||||
@@ -1519,7 +1519,7 @@
|
||||
},
|
||||
{
|
||||
"num": 167,
|
||||
"libelle": "Pour faire un virage ‘’à plat’’ :",
|
||||
"libelle": "Pour faire un virage ’à plat’ :",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "Il faut manœuvrer très rapidement à pleine vitesse.", "correct": false},
|
||||
{"index": "B", "libelle": "Il faut manœuvrer doucement et en ½ frein.", "correct": true}
|
||||
@@ -1738,7 +1738,7 @@
|
||||
},
|
||||
{
|
||||
"num": 191,
|
||||
"libelle": "Qu‘appelle-t-on angle d’incidence ?",
|
||||
"libelle": "Qu’appelle-t-on angle d’incidence ?",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "L’angle entre l’aile et l’axe du cône de suspension.", "correct": false},
|
||||
{"index": "B", "libelle": "L’angle entre la trajectoire et l’horizontale.", "correct": false},
|
||||
@@ -3260,7 +3260,7 @@
|
||||
"libelle": "La présence d’une couche nuageuse en altitude (cirrocumulus ou cirrostratus) :",
|
||||
"answers": [
|
||||
{"index": "A", "libelle": "Indique que le mauvais temps est passé.", "correct": false},
|
||||
{"index": "B", "libelle": "Annonce l ‘arrivée d’une perturbation.", "correct": true}
|
||||
{"index": "B", "libelle": "Annonce l’arrivée d’une perturbation.", "correct": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+14
-31
@@ -34,28 +34,34 @@ const question = (prompt) => {
|
||||
const questionHidden = (prompt) => {
|
||||
return new Promise((resolve) => {
|
||||
process.stdout.write(prompt);
|
||||
|
||||
// Suppress readline's own software echo so only '*' appears
|
||||
const originalWrite = rl._writeToOutput;
|
||||
rl._writeToOutput = () => {};
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
|
||||
|
||||
let password = '';
|
||||
|
||||
|
||||
const dataListener = (char) => {
|
||||
char = char.toString();
|
||||
|
||||
|
||||
switch (char) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\u0004':
|
||||
case '':
|
||||
process.stdin.setRawMode(false);
|
||||
process.stdin.pause();
|
||||
process.stdout.write('\n');
|
||||
process.stdin.removeListener('data', dataListener);
|
||||
rl._writeToOutput = originalWrite;
|
||||
resolve(password);
|
||||
break;
|
||||
case '\u0003':
|
||||
case '':
|
||||
process.exit();
|
||||
break;
|
||||
case '\u007F':
|
||||
case '':
|
||||
case '\b':
|
||||
password = password.slice(0, -1);
|
||||
process.stdout.write('\b \b');
|
||||
@@ -65,7 +71,7 @@ const questionHidden = (prompt) => {
|
||||
process.stdout.write('*');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
process.stdin.on('data', dataListener);
|
||||
});
|
||||
};
|
||||
@@ -116,31 +122,8 @@ const createUser = async () => {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ Connexion établie\n');
|
||||
|
||||
// Initialize all models by calling their functions
|
||||
const rawDB = require('../src/database/models/mysql');
|
||||
|
||||
// Call each model function to initialize it
|
||||
rawDB.User();
|
||||
rawDB.Role();
|
||||
rawDB.Article();
|
||||
rawDB.Tag();
|
||||
rawDB.TagList();
|
||||
rawDB.UserRoleXref();
|
||||
rawDB.Follower();
|
||||
rawDB.Favorite();
|
||||
rawDB.Comment();
|
||||
rawDB.Brand();
|
||||
rawDB.Category();
|
||||
rawDB.Packaging();
|
||||
rawDB.Product();
|
||||
rawDB.ProductCategoryXref();
|
||||
rawDB.ProductTagXref();
|
||||
|
||||
// Also initialize relationships
|
||||
const DB = require('../src/database/mysql');
|
||||
require('../src/database/relationships')();
|
||||
|
||||
// Get the initialized models from sequelize
|
||||
const DB = sequelize.models;
|
||||
|
||||
// Hash password with same algorithm as UserService
|
||||
const { salt, hash } = hashPassword(password);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const { ArticleService, TagService, UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
const includeOptions = ArticleService.getArticleIncludeAssoc();
|
||||
|
||||
module.exports.createArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createArticle = async (req, res, next) => {
|
||||
try {
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
|
||||
@@ -47,9 +46,9 @@ module.exports.createArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while creating article.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteArticle = async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
@@ -72,9 +71,9 @@ module.exports.deleteArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while deleting article.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getArticle = async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
|
||||
@@ -93,9 +92,9 @@ module.exports.getArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching article.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getArticles = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getArticles = async (req, res, next) => {
|
||||
try {
|
||||
let articles = { rows: [], count: 0 };
|
||||
const { tag, author, favorited, limit = 20, offset = 0 } = req.query;
|
||||
@@ -131,9 +130,9 @@ module.exports.getArticles = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching articles.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateArticle = async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
@@ -174,9 +173,9 @@ module.exports.updateArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating article.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.articlesFeed = asyncHandler(async (req, res, next) => {
|
||||
module.exports.articlesFeed = async (req, res, next) => {
|
||||
try {
|
||||
const { limit = 3, offset = 0 } = req.query;
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
@@ -207,9 +206,9 @@ module.exports.articlesFeed = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching articles feed.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.addFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.addFavoriteArticle = async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
@@ -227,9 +226,9 @@ module.exports.addFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while adding a favorite.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteFavoriteArticle = async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const loggedUser = await UserService.getLoggedUserById(req.payload.id);
|
||||
@@ -248,5 +247,5 @@ module.exports.deleteFavoriteArticle = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while removing a favorite.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const { ClanService, UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
module.exports.createClan = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createClan = async (req, res, next) => {
|
||||
try {
|
||||
const user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -27,9 +26,9 @@ module.exports.createClan = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while creating clan.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteClan = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteClan = async (req, res, next) => {
|
||||
try {
|
||||
const { clanId } = req.params;
|
||||
let clan = await ClanService.getClanById(clanId);
|
||||
@@ -45,9 +44,9 @@ module.exports.deleteClan = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while deleting clan.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllClans = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getAllClans = async (req, res, next) => {
|
||||
try {
|
||||
let query = {};
|
||||
let { limit = 25, offset = 0 } = req.query;
|
||||
@@ -73,9 +72,9 @@ module.exports.getAllClans = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while fetching clans.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getClan = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getClan = async (req, res, next) => {
|
||||
try {
|
||||
const { clanId } = req.params;
|
||||
let clan = await ClanService.getClanById(clanId);
|
||||
@@ -87,9 +86,9 @@ module.exports.getClan = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while fetching clan.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateClan = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateClan = async (req, res, next) => {
|
||||
try {
|
||||
const { clanId } = req.params;
|
||||
let clan = await ClanService.getClanById(clanId);
|
||||
@@ -110,4 +109,4 @@ module.exports.updateClan = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while updating clan.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const { CommentService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
module.exports.createComment = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createComment = async (req, res, next) => {
|
||||
try {
|
||||
const { slug, title, description, body } = req.body;
|
||||
const comment = await CommentService.createComment({
|
||||
@@ -20,9 +19,9 @@ module.exports.createComment = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while creating comment.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllComments = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getAllComments = async (req, res, next) => {
|
||||
try {
|
||||
const comments = await CommentService.getAllComments();
|
||||
|
||||
@@ -32,4 +31,4 @@ module.exports.getAllComments = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching comments.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const { MemberService, UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
module.exports.createMember = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createMember = async (req, res, next) => {
|
||||
try {
|
||||
const user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -26,9 +25,9 @@ module.exports.createMember = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while creating member.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteMember = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteMember = async (req, res, next) => {
|
||||
try {
|
||||
const { memberId } = req.params;
|
||||
let member = await MemberService.getMemberById(memberId);
|
||||
@@ -44,9 +43,9 @@ module.exports.deleteMember = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while deleting member.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllMembers = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getAllMembers = async (req, res, next) => {
|
||||
try {
|
||||
let query = {};
|
||||
let { limit = 25, offset = 0 } = req.query;
|
||||
@@ -72,9 +71,9 @@ module.exports.getAllMembers = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while fetching members.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getMember = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getMember = async (req, res, next) => {
|
||||
try {
|
||||
const { memberId } = req.params;
|
||||
let member = await MemberService.getMemberById(memberId);
|
||||
@@ -86,9 +85,9 @@ module.exports.getMember = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while updating member.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateMember = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateMember = async (req, res, next) => {
|
||||
try {
|
||||
const { memberId } = req.params;
|
||||
let member = await MemberService.getMemberById(memberId);
|
||||
@@ -109,4 +108,4 @@ module.exports.updateMember = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse(`Something went wrong while updating member.`, 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const { ProductService } = require('../services');
|
||||
//const { where } = require("sequelize");
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const Product = require("../database/models/mysql/Product");
|
||||
const Tag = require("../database/models/mysql/Tag");
|
||||
const User = require("../database/models/mysql/User");
|
||||
@@ -23,7 +22,7 @@ const includeOptions = [
|
||||
{ model: User, as: "author", attributes: { exclude: ["email", "password"] } },
|
||||
];
|
||||
|
||||
module.exports.getProducts = asyncHandler(async (req, res) => {
|
||||
module.exports.getProducts = async (req, res) => {
|
||||
const { tag, author, favorited, limit = 20, offset = 0 } = req.query;
|
||||
const { loggedUser } = req;
|
||||
|
||||
@@ -75,9 +74,9 @@ module.exports.getProducts = asyncHandler(async (req, res) => {
|
||||
res
|
||||
.status(200)
|
||||
.json({ products: products.rows, productsCount: products.count });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getProductsByCategory = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getProductsByCategory = async (req, res, next) => {
|
||||
const { category } = req.params;
|
||||
//console.log(category);
|
||||
|
||||
@@ -88,9 +87,9 @@ module.exports.getProductsByCategory = asyncHandler(async (req, res, next) => {
|
||||
return next(new ErrorResponse("Products not found", 404));
|
||||
}
|
||||
res.status(200).json({ category: category, count: products.count, products: products.rows });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.productsFeed = asyncHandler(async (req, res) => {
|
||||
module.exports.productsFeed = async (req, res) => {
|
||||
const { loggedUser } = req;
|
||||
|
||||
const { limit = 3, offset = 0 } = req.query;
|
||||
@@ -114,9 +113,9 @@ module.exports.productsFeed = asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
res.json({ products: products.rows, productsCount: products.count });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getProduct = async (req, res, next) => {
|
||||
const { slug: productSlug } = req.params;
|
||||
|
||||
let product = await ProductService.getProductBySlug(productSlug);
|
||||
@@ -129,19 +128,15 @@ module.exports.getProduct = asyncHandler(async (req, res, next) => {
|
||||
product.tags = tags;
|
||||
|
||||
res.status(200).json({ product: product });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.createProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createProduct = async (req, res, next) => {
|
||||
const { loggedUser } = req;
|
||||
|
||||
fieldValidation(req.body.product.title, next);
|
||||
fieldValidation(req.body.product.description, next);
|
||||
fieldValidation(req.body.product.body, next);
|
||||
|
||||
const { title, description, body, tagList } = req.body.product;
|
||||
const slugInDB = await Product.findOne({ where: { slug: slug(`${title}`) } });
|
||||
if (slugInDB) {
|
||||
next(new ErrorResponse("Title already exists", 400));
|
||||
return next(new ErrorResponse("Title already exists", 400));
|
||||
}
|
||||
|
||||
const product = await Product.create({
|
||||
@@ -169,9 +164,9 @@ module.exports.createProduct = asyncHandler(async (req, res, next) => {
|
||||
await appendFavorites(loggedUser, product);
|
||||
|
||||
res.status(201).json({ product });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteProduct = async (req, res, next) => {
|
||||
const { slug: productSlug } = req.params;
|
||||
const { loggedUser } = req;
|
||||
|
||||
@@ -181,7 +176,7 @@ module.exports.deleteProduct = asyncHandler(async (req, res, next) => {
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
next(new ErrorResponse("Product not found", 404));
|
||||
return next(new ErrorResponse("Product not found", 404));
|
||||
}
|
||||
|
||||
if (product.authorId !== loggedUser.id) {
|
||||
@@ -191,9 +186,9 @@ module.exports.deleteProduct = asyncHandler(async (req, res, next) => {
|
||||
await product.destroy();
|
||||
|
||||
res.status(200).json({ product });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateProduct = async (req, res, next) => {
|
||||
const { slug: productSlug } = req.params;
|
||||
const { loggedUser } = req;
|
||||
|
||||
@@ -203,7 +198,7 @@ module.exports.updateProduct = asyncHandler(async (req, res, next) => {
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
next(new ErrorResponse("Product not found", 404));
|
||||
return next(new ErrorResponse("Product not found", 404));
|
||||
}
|
||||
if (product.authorId !== loggedUser.id) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "Authentication required."));
|
||||
@@ -229,9 +224,9 @@ module.exports.updateProduct = asyncHandler(async (req, res, next) => {
|
||||
await appendFavorites(loggedUser, product);
|
||||
|
||||
res.status(200).json({ product });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.addFavoriteProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.addFavoriteProduct = async (req, res, next) => {
|
||||
const { loggedUser } = req;
|
||||
const { slug: productSlug } = req.params;
|
||||
|
||||
@@ -252,9 +247,9 @@ module.exports.addFavoriteProduct = asyncHandler(async (req, res, next) => {
|
||||
await appendFavorites(loggedUser, product);
|
||||
|
||||
res.status(200).json({ product });
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteFavoriteProduct = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteFavoriteProduct = async (req, res, next) => {
|
||||
const { loggedUser } = req;
|
||||
const { slug: productSlug } = req.params;
|
||||
|
||||
@@ -275,10 +270,5 @@ module.exports.deleteFavoriteProduct = asyncHandler(async (req, res, next) => {
|
||||
await appendFavorites(loggedUser, product);
|
||||
|
||||
res.status(200).json({ product });
|
||||
});
|
||||
|
||||
const fieldValidation = (field, next) => {
|
||||
if (!field) {
|
||||
return next(new ErrorResponse(`Missing fields`, 400));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const { UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.addFollowUser = async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is now being followed.'
|
||||
@@ -10,9 +9,9 @@ module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteFollowUser = async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is no longer being followed.',
|
||||
@@ -21,9 +20,9 @@ module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getProfile = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getProfile = async (req, res, next) => {
|
||||
try {
|
||||
/*const user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -35,7 +34,7 @@ module.exports.getProfile = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getParamUsername = async (req, res, next, value) => {
|
||||
try {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
const { TagService, UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
|
||||
module.exports.createTag = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createTag = async (req, res, next) => {
|
||||
try {
|
||||
|
||||
fieldValidation(req.body.tag.name, next);
|
||||
const { name } = req.body.tag;
|
||||
const tagInDB = await TagService.tagIsInDB(name);
|
||||
if (tagInDB) {
|
||||
@@ -21,9 +18,9 @@ module.exports.createTag = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while creating tag.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.deleteTag = asyncHandler(async (req, res, next) => {
|
||||
module.exports.deleteTag = async (req, res, next) => {
|
||||
try {
|
||||
const { name } = req.params;
|
||||
const tag = await TagService.getTagByName(name);
|
||||
@@ -43,9 +40,9 @@ module.exports.deleteTag = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching tags.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllTags = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getAllTags = async (req, res, next) => {
|
||||
try {
|
||||
const tags = await TagService.getAllTags();
|
||||
|
||||
@@ -58,10 +55,5 @@ module.exports.getAllTags = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching tags.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
const fieldValidation = (field, next) => {
|
||||
if (!field) {
|
||||
return next(new ErrorResponse(`Missing fields`, 400));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const { UserService, SkydiverProfileService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
var crypto = require('crypto');
|
||||
const passport = require('passport');
|
||||
|
||||
module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
module.exports.authenticate = async (req, res, next) => {
|
||||
try {
|
||||
passport.authenticate('headerapikey', { session: false }, function (err, application, info) {
|
||||
if (err) {
|
||||
@@ -28,9 +27,9 @@ module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
//res.status(500).json({ message: 'Something went wrong while authenticating.', error: err.message });
|
||||
return next(new ErrorResponse('Something went wrong while authenticating.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.createUser = async (req, res, next) => {
|
||||
try {
|
||||
const { username, firstname, lastname, phone, email, password } = req.body.user;
|
||||
const userSlug = UserService.slugify(username);
|
||||
@@ -57,9 +56,9 @@ module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while creating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getAllUsers = async (req, res, next) => {
|
||||
try {
|
||||
const users = await UserService.getAllUsers();
|
||||
|
||||
@@ -69,24 +68,29 @@ module.exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.getUser = async (req, res, next) => {
|
||||
try {
|
||||
const user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "Authentication required."));
|
||||
}
|
||||
|
||||
const profile = await SkydiverProfileService.getByUserId(req.payload.id);
|
||||
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON()
|
||||
user: {
|
||||
...user.toAuthJSON(),
|
||||
...(profile ? profile.toJSONFor() : { poids: null, licence: null, bg_image: null }),
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.loginAsUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.loginAsUser = async (req, res, next) => {
|
||||
try {
|
||||
passport.authenticate('local', { session: false }, function (err, user, info) {
|
||||
if (err) {
|
||||
@@ -102,9 +106,9 @@ module.exports.loginAsUser = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while processing login.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUser = async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -142,9 +146,9 @@ module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateUserEmail = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserEmail = async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -162,9 +166,9 @@ module.exports.updateUserEmail = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateUserPassword = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserPassword = async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -182,9 +186,9 @@ module.exports.updateUserPassword = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateUserRole = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserRole = async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -205,9 +209,9 @@ module.exports.updateUserRole = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.updateUserUsername = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserUsername = async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
@@ -225,5 +229,5 @@ module.exports.updateUserUsername = asyncHandler(async (req, res, next) => {
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+1
-7
@@ -1,8 +1,7 @@
|
||||
var express = require('express'),
|
||||
session = require('express-session'),
|
||||
cors = require('cors'),
|
||||
helmet = require('helmet'),
|
||||
errorhandler = require('errorhandler');
|
||||
helmet = require('helmet');
|
||||
|
||||
const config = require('./config'),
|
||||
swaggerUi = require('swagger-ui-express'),
|
||||
@@ -23,7 +22,6 @@ function createApp() {
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(require('method-override')());
|
||||
app.use(express.static(__dirname + '/../public'));
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || config.secret,
|
||||
@@ -32,10 +30,6 @@ function createApp() {
|
||||
saveUninitialized: false
|
||||
}));
|
||||
|
||||
if (process.env.APP_ENV !== 'production') {
|
||||
app.use(errorhandler());
|
||||
}
|
||||
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
|
||||
swaggerOptions: { persistAuthorization: true },
|
||||
}));
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const asyncHandler = (fn) => (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = asyncHandler;
|
||||
@@ -0,0 +1,27 @@
|
||||
const { body } = require('express-validator');
|
||||
|
||||
const createProductRules = [
|
||||
body('product.title')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isLength({ max: 255 }).withMessage('must be at most 255 characters')
|
||||
.trim(),
|
||||
body('product.description')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isLength({ max: 1000 }).withMessage('must be at most 1000 characters')
|
||||
.trim(),
|
||||
body('product.body')
|
||||
.notEmpty().withMessage("can't be blank"),
|
||||
];
|
||||
|
||||
const updateProductRules = [
|
||||
body('product.title')
|
||||
.optional()
|
||||
.isLength({ max: 255 }).withMessage('must be at most 255 characters')
|
||||
.trim(),
|
||||
body('product.description')
|
||||
.optional()
|
||||
.isLength({ max: 1000 }).withMessage('must be at most 1000 characters')
|
||||
.trim(),
|
||||
];
|
||||
|
||||
module.exports = { createProductRules, updateProductRules };
|
||||
@@ -0,0 +1,10 @@
|
||||
const { body } = require('express-validator');
|
||||
|
||||
const createTagRules = [
|
||||
body('tag.name')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isLength({ max: 50 }).withMessage('must be at most 50 characters')
|
||||
.trim(),
|
||||
];
|
||||
|
||||
module.exports = { createTagRules };
|
||||
@@ -4,7 +4,7 @@ const createUserRules = [
|
||||
body('user.email')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isEmail().withMessage('must be a valid email address')
|
||||
.normalizeEmail(),
|
||||
.toLowerCase(),
|
||||
body('user.username')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isLength({ min: 3, max: 50 }).withMessage('must be between 3 and 50 characters')
|
||||
@@ -18,7 +18,7 @@ const loginRules = [
|
||||
body('user.email')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isEmail().withMessage('must be a valid email address')
|
||||
.normalizeEmail(),
|
||||
.toLowerCase(),
|
||||
body('user.password')
|
||||
.notEmpty().withMessage("can't be blank"),
|
||||
];
|
||||
@@ -44,7 +44,7 @@ const updateEmailRules = [
|
||||
body('user.email')
|
||||
.notEmpty().withMessage("can't be blank")
|
||||
.isEmail().withMessage('must be a valid email address')
|
||||
.normalizeEmail(),
|
||||
.toLowerCase(),
|
||||
];
|
||||
|
||||
const updateUsernameRules = [
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
var routes = require('express').Router();
|
||||
|
||||
routes.use('/user', require('./users.routes'));
|
||||
routes.use('/applications', require('./applications.routes'));
|
||||
|
||||
module.exports = routes;
|
||||
@@ -1,6 +1,5 @@
|
||||
var routes = require('express').Router();
|
||||
|
||||
routes.use('/user', require('./users.routes'));
|
||||
routes.use('/articles', require('./articles.routes'));
|
||||
routes.use('/comments', require('./comments.routes'));
|
||||
routes.use('/profiles', require('./profiles.routes'));
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const express = require('express');
|
||||
const { createTag, deleteTag, getAllTags } = require('../../../controllers/tags.controller');
|
||||
const auth = require('../../../middlewares/auth');
|
||||
const validate = require('../../../middlewares/validate');
|
||||
const { createTagRules } = require('../../../middlewares/validators/tags.validators');
|
||||
|
||||
const tagRouter = express.Router();
|
||||
|
||||
tagRouter.post('/', auth.required, createTag);
|
||||
tagRouter.post('/', auth.required, createTagRules, validate, createTag);
|
||||
tagRouter.get('/', auth.optional, getAllTags);
|
||||
tagRouter.delete('/:name', auth.required, deleteTag);
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ const {
|
||||
updateProduct,
|
||||
} = require('../../../controllers/products.controller');
|
||||
const auth = require('../../../middlewares/auth');
|
||||
const validate = require('../../../middlewares/validate');
|
||||
const { createProductRules, updateProductRules } = require('../../../middlewares/validators/products.validators');
|
||||
|
||||
const productRouter = express.Router();
|
||||
|
||||
productRouter.get('/', auth.optional, getProducts);
|
||||
productRouter.post('/', auth.required, createProduct);
|
||||
productRouter.post('/', auth.required, createProductRules, validate, createProduct);
|
||||
productRouter.get('/:slug', auth.optional, getProduct);
|
||||
productRouter.put('/:slug', auth.required, updateProduct);
|
||||
productRouter.put('/:slug', auth.required, updateProductRules, validate, updateProduct);
|
||||
productRouter.delete('/:slug', auth.required, deleteProduct);
|
||||
productRouter.post('/:slug/favorite', auth.required, addFavoriteProduct);
|
||||
productRouter.delete('/:slug/favorite', auth.required, deleteFavoriteProduct);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var routes = require('express').Router();
|
||||
|
||||
routes.use('/auth', require('./auth'));
|
||||
routes.use('/skydive', require('./skydive'));
|
||||
routes.use('/cms', require('./cms'));
|
||||
routes.use('/ecommerce', require('./ecommerce'));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
var routes = require('express').Router();
|
||||
|
||||
routes.use('/applications', require('./applications.routes'));
|
||||
routes.use('/aeronefs', require('./aeronefs.routes'));
|
||||
routes.use('/canopies', require('./canopies.routes'));
|
||||
routes.use('/dropzones', require('./dropzones.routes'));
|
||||
|
||||
Reference in New Issue
Block a user