fix(user): persist updateUser correctly; add setup:api script and README update; seeders tweaks
This commit is contained in:
@@ -4,7 +4,7 @@ DEBUG=false
|
||||
SERVER_PORT=3201
|
||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||
SESSION_LIFETIME=3600
|
||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
||||
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||
SENDGRID_API_KEY="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"
|
||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
||||
SERVER_PORT=3201
|
||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||
SESSION_LIFETIME=86400
|
||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
||||
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||
SENDGRID_API_KEY="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"
|
||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
||||
SERVER_PORT=3201
|
||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||
SESSION_LIFETIME=3600
|
||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
||||
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||
SENDGRID_API_KEY="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"
|
||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
||||
SERVER_PORT=3201
|
||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||
SESSION_LIFETIME=3600
|
||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
||||
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||
SENDGRID_API_KEY="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"
|
||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# Instructions pour les agents Copilot / AI
|
||||
|
||||
But : fournir à un agent IA les informations essentielles pour être immédiatement productif dans ce workspace.
|
||||
|
||||
## Vue d'ensemble des projets
|
||||
|
||||
- **adastra_api/** : API Express/Node.js avec Sequelize (MySQL) + Mongoose (Mongo)
|
||||
- **adastra_app/** : Frontend Angular 17
|
||||
|
||||
## Entrées principales de l'application
|
||||
|
||||
### API (adastra_api)
|
||||
|
||||
- **Point d'entrée :** `app.js` (racine du projet)
|
||||
- Charge les variables d'environnement via `APP_ENV` (`.env.${APP_ENV}`)
|
||||
- Initialise les middlewares (CORS, Morgan, session, etc.)
|
||||
- Connecte MongoDB et MySQL
|
||||
- Lance le serveur sur le port (défaut : 3200)
|
||||
|
||||
- **Routage Express :** `src/app.js` et `src/routes/`
|
||||
- Toutes les routes API sont sous `/api/v1`
|
||||
- Exemple : `GET /api/v1/articles` → `src/controllers/article.controller.js`
|
||||
|
||||
- **Contrôleurs :** `src/controllers/`
|
||||
- Gèrent la logique métier, validations, réponses JSON
|
||||
- Suivent le pattern : controller → service → utils → database
|
||||
|
||||
### Frontend (adastra_app)
|
||||
|
||||
- **Point d'entrée :** `src/main.ts` (bootstrap Angular)
|
||||
- **Configuration :** `angular.json` (builds, configurations, assets)
|
||||
- **Routes :** `src/app/app.routes.ts`
|
||||
- **Composants :** `src/app/components/`
|
||||
- **Styles globaux :** `src/styles/`
|
||||
|
||||
## Architecture et patterns clés
|
||||
|
||||
### Structure des données
|
||||
|
||||
- **Sequelize (MySQL)** : modèles dans `src/database/models/`
|
||||
- Utilise `sequelize-cli` pour les migrations (si applicable)
|
||||
- Auto-généré avec `sequelize-auto`
|
||||
|
||||
- **Mongoose (MongoDB)** : modèles dans `src/database/models/mongo`
|
||||
- Initialisé avec `require('./src/database/models/mongo')`
|
||||
|
||||
### Gestion des erreurs
|
||||
|
||||
Middleware centralisé dans `src/middlewares/exceptions.handler` :
|
||||
- `notFoundErrorHandler` : capture 404
|
||||
- `logErrorHandler` : log des erreurs
|
||||
- `fianlErrorHandler` : réponse finale au client
|
||||
|
||||
### Authentification & configuration
|
||||
|
||||
- **Passport :** `src/config/passport-local.js`, `passport-headerapikey.js`
|
||||
- **Config générale :** `src/config/index.js` (secrets, session lifetime, DB)
|
||||
- **Variables d'environnement :** `.env.${APP_ENV}` (local, development, production)
|
||||
|
||||
## Commandes essentielles
|
||||
|
||||
### API (adastra_api)
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
cd adastra_api && npm install
|
||||
|
||||
# Développement (avec hot reload via nodemon)
|
||||
npm run local # APP_ENV=local nodemon ./app.js (port 3200)
|
||||
|
||||
# Production/Staging
|
||||
npm run dev # APP_ENV=development node ./app.js
|
||||
npm run start # APP_ENV=production node ./app.js
|
||||
|
||||
# Tests
|
||||
npm test # Newman : exécute tests/adastra-api-tests.postman_collection.json
|
||||
|
||||
# Arrêter le serveur
|
||||
npm run stop # Tue le processus sur le port 3200
|
||||
|
||||
# MongoDB (Docker)
|
||||
npm run mongo:start # Lance le conteneur Mongo (port 27017)
|
||||
npm run mongo:stop # Arrête et nettoie le conteneur
|
||||
```
|
||||
|
||||
### Frontend (adastra_app)
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
cd adastra_app && npm install
|
||||
|
||||
# Développement
|
||||
npm run local # ng serve --configuration local --port 4400
|
||||
npm start # ng serve (port 4200 par défaut)
|
||||
npm run dev # ng serve --configuration local --port 4201
|
||||
|
||||
# Build
|
||||
npm run build # Production build (dist/)
|
||||
npm run watch # Watch mode
|
||||
|
||||
# Linting
|
||||
npm run lint # ESLint
|
||||
```
|
||||
|
||||
## Conventions et bonnes pratiques
|
||||
|
||||
### Ajout d'une nouvelle route API
|
||||
|
||||
1. Créer/modifier la route dans `src/routes/` (ex. `src/routes/api/v2/articles.routes.js`)
|
||||
2. Implémenter le contrôleur dans `src/controllers/article.controller.js`
|
||||
3. Ajouter validation dans `src/validations/` si nécessaire
|
||||
4. Tester via Postman → exporter en `tests/adastra-api-tests.postman_collection.json`
|
||||
|
||||
### Ajout d'un modèle de données
|
||||
|
||||
- **Sequelize :** créer le fichier dans `src/database/models/`, utiliser `sequelize-cli` si migrations
|
||||
- **Mongoose :** créer le schéma dans `src/database/models/mongo/`, importer dans `src/database/models/mongo/index.js`
|
||||
|
||||
### Structure de réponse API
|
||||
|
||||
Vérifier `src/controllers/` pour le format des réponses — généralement :
|
||||
```javascript
|
||||
res.status(200).json({ data: [...], message: 'Success' });
|
||||
res.status(400).json({ error: 'Validation failed', details: [...] });
|
||||
```
|
||||
|
||||
## Variables d'environnement essentielles
|
||||
|
||||
À définir dans `.env.local`, `.env.development`, `.env.production` :
|
||||
- `SERVER_PORT` : port du serveur (défaut 3200)
|
||||
- `APP_ENV` : `local`, `development`, ou `production`
|
||||
- `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_NAME` : configuration MySQL
|
||||
- `MONGO_URI` : URI de MongoDB
|
||||
- Clés API externes (SendGrid, etc.)
|
||||
|
||||
## Points d'attention
|
||||
|
||||
- **Ne pas modifier** directement l'ordre des middlewares dans `app.js` sans tester l'intégralité du flow
|
||||
- **Versions Node :** respecter `engines` dans `package.json` (`^16.20.2 || ^18.19.1 || ^20.11.1`)
|
||||
- **Dépendances critiques :** `express`, `sequelize`, `mongoose`, `passport`, `jsonwebtoken`
|
||||
|
||||
## Ressources utiles pour un agent
|
||||
|
||||
Si tu dois ajouter/modifier du code, consulte d'abord :
|
||||
1. **Contrôleur similaire existant** : ex. `src/controllers/member.controller.js` pour le pattern
|
||||
2. **Route similaire existante** : `src/routes/` pour voir comment router
|
||||
3. **Config/Middleware** : vérifier si logique centralisée existe déjà (`src/middlewares/`, `src/config/`)
|
||||
4. **Tests Postman** : `tests/adastra-api-tests.postman_collection.json` pour valider les endpoints et `tests/adastra-api-tests.postman_environment.json` pour les variables d'environnement
|
||||
|
||||
---
|
||||
|
||||
**Questions manquantes ?** Demande-moi les détails sur :
|
||||
- Structure exacte des schémas Sequelize/Mongoose
|
||||
- Authentification et système d'autorisations
|
||||
- Workflows de déploiement ou CI/CD
|
||||
- Intégration frontend/backend (headers, tokens, etc.)
|
||||
@@ -0,0 +1,210 @@
|
||||
# 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.
|
||||
@@ -2,7 +2,42 @@
|
||||
|
||||
API pour l'application Ad Astra
|
||||
|
||||
**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 run setup:api
|
||||
```
|
||||
|
||||
**Installation manuelle**
|
||||
```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**
|
||||
```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**
|
||||
```bash
|
||||
# Exécuter tous les seeders
|
||||
npx sequelize-cli db:seed:all
|
||||
|
||||
@@ -14,4 +49,19 @@ 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
|
||||
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,762 @@
|
||||
# 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
-1
@@ -6,7 +6,7 @@
|
||||
"SERVER_PORT": 3201,
|
||||
"SECRET": "$2b$10$aEsAUG7Dy8dcTORi1vaQle",
|
||||
"SESSION_LIFETIME": 3600,
|
||||
"MONGODB_URI": "mongodb://localhost:27017/headupdb",
|
||||
"MONGODB_URI": "mongodb://localhost:27017/adastradb",
|
||||
"SENDGRID_API_KEY": "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",
|
||||
"SENDGRID_FROM_MAIL": "contact@rampeur.fr",
|
||||
"SENDGRID_TO_MAIL": "rampeur@gmail.com",
|
||||
|
||||
+3
-1
@@ -14,7 +14,9 @@
|
||||
"test": "newman run ./tests/adastra-api-tests.postman_collection.json -e ./tests/adastra-api-tests.postman_environment.json",
|
||||
"stop": "lsof -ti :3200 | xargs kill",
|
||||
"mongo:start": "docker run --name hu-mongo -p 27017:27017 mongo & sleep 5",
|
||||
"mongo:stop": "docker stop hu-mongo && docker rm hu-mongo"
|
||||
"mongo:stop": "docker stop hu-mongo && docker rm hu-mongo",
|
||||
"create:user": "node scripts/create-user.js",
|
||||
"setup:api": "npx sequelize-cli db:migrate && npx sequelize-cli db:seed --seed 20251126-add-roles.js && npm run create:user && npx sequelize-cli db:seed --seed 20251127-add-articles.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.20.2 || ^18.19.1 || ^20.11.1"
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script pour créer un utilisateur Admin interactivement
|
||||
* Usage: npm run create:user
|
||||
*/
|
||||
|
||||
const readline = require('readline');
|
||||
const crypto = require('crypto');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// Load environment
|
||||
const appEnv = process.env.APP_ENV || 'development';
|
||||
require('dotenv').config({ path: `.env.${appEnv}` });
|
||||
|
||||
const sequelize = require('../src/database/config/sequelize');
|
||||
|
||||
// Create readline interface
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// Helper function for prompts
|
||||
const question = (prompt) => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(prompt, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Hash password
|
||||
const hashPassword = (password) => {
|
||||
const salt = crypto.randomBytes(16).toString('hex');
|
||||
const hash = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
|
||||
return { salt, hash };
|
||||
};
|
||||
|
||||
// Main function
|
||||
const createUser = async () => {
|
||||
try {
|
||||
console.log('\n🔐 Création d\'un utilisateur Admin\n');
|
||||
console.log('Veuillez fournir les informations suivantes:\n');
|
||||
|
||||
// Get user input
|
||||
const email = await question('Email: ');
|
||||
const username = await question('Nom d\'utilisateur: ');
|
||||
const firstname = await question('Prénom (optionnel): ');
|
||||
const lastname = await question('Nom (optionnel): ');
|
||||
const phone = await question('Téléphone (optionnel): ');
|
||||
const password = await question('Mot de passe: ');
|
||||
const passwordConfirm = await question('Confirmer le mot de passe: ');
|
||||
|
||||
// Validation
|
||||
if (!email || !username || !password) {
|
||||
console.error('\n❌ Email, nom d\'utilisateur et mot de passe sont obligatoires\n');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
console.error('\n❌ Les mots de passe ne correspondent pas\n');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
console.error('\n❌ Le mot de passe doit contenir au moins 6 caractères\n');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Connect to database and load models
|
||||
console.log('\n📡 Connexion à la base de données...');
|
||||
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
|
||||
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);
|
||||
|
||||
// Create user
|
||||
console.log('👤 Création de l\'utilisateur...');
|
||||
const user = await DB.User.create({
|
||||
id: uuidv4(),
|
||||
email: email.toLowerCase(),
|
||||
username,
|
||||
firstname: firstname || null,
|
||||
lastname: lastname || null,
|
||||
phone: phone || null,
|
||||
image: null,
|
||||
role: 'Admin',
|
||||
salt,
|
||||
hash
|
||||
});
|
||||
|
||||
// Find and assign Admin role
|
||||
const adminRole = await DB.Role.findOne({ where: { name: 'Admin' } });
|
||||
if (adminRole) {
|
||||
await user.addRoleIdRole(adminRole);
|
||||
console.log('✅ Rôle Admin assigné\n');
|
||||
} else {
|
||||
console.log('⚠️ Rôle Admin non trouvé, création en tant qu\'utilisateur uniquement\n');
|
||||
}
|
||||
|
||||
// Success message
|
||||
console.log('✅ Utilisateur créé avec succès!\n');
|
||||
console.log('Détails:');
|
||||
console.log(` 📧 Email: ${user.email}`);
|
||||
console.log(` 👤 Nom d'utilisateur: ${user.username}`);
|
||||
console.log(` 🔐 Rôle: ${user.role}`);
|
||||
console.log('');
|
||||
|
||||
rl.close();
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('\n❌ Erreur lors de la création de l\'utilisateur:\n');
|
||||
console.error(error.message);
|
||||
console.error(error.stack);
|
||||
console.log('');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Run script
|
||||
createUser();
|
||||
@@ -1,8 +1,21 @@
|
||||
const { UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
var crypto = require('crypto');
|
||||
const passport = require('passport');
|
||||
|
||||
|
||||
module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is now being followed.',
|
||||
user: true
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
passport.authenticate('headerapikey', { session: false }, function (err, application, info) {
|
||||
@@ -31,12 +44,27 @@ module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
|
||||
module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
const { username, firstname, lastname, email } = req.body.user;
|
||||
|
||||
fieldValidation(req.body.user.username, next);
|
||||
fieldValidation(req.body.user.email, next);
|
||||
fieldValidation(req.body.user.password, next);
|
||||
|
||||
const { username, firstname, lastname, phone, email, password } = req.body.user;
|
||||
const userSlug = UserService.slugify(username);
|
||||
const slugInDB = await UserService.isUsernameUsed(userSlug);
|
||||
if (slugInDB) {
|
||||
return next(new ErrorResponse("Username already exists", 400));
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID()
|
||||
const user = await UserService.createUser({
|
||||
id: uuid,
|
||||
username,
|
||||
firstname,
|
||||
lastname,
|
||||
email
|
||||
phone,
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
@@ -48,6 +76,17 @@ module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is no longer being followed.',
|
||||
user: true
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
const users = await UserService.getAllUsers();
|
||||
@@ -69,6 +108,7 @@ module.exports.getUser = asyncHandler(async (req, res, next) => {
|
||||
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
uuid: crypto.randomUUID()
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
||||
@@ -105,12 +145,6 @@ module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
if (typeof req.body.user.username !== 'undefined') {
|
||||
user.username = req.body.user.username;
|
||||
}
|
||||
if (typeof req.body.user.email !== 'undefined') {
|
||||
user.email = req.body.user.email;
|
||||
}
|
||||
if (typeof req.body.user.firstname !== 'undefined') {
|
||||
user.firstname = req.body.user.firstname;
|
||||
}
|
||||
@@ -132,46 +166,111 @@ module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
if (typeof req.body.user.bg_image !== 'undefined') {
|
||||
user.bg_image = req.body.user.bg_image;
|
||||
}
|
||||
if (typeof req.body.user.password !== 'undefined') {
|
||||
user.setPassword(req.body.user.password);
|
||||
}
|
||||
if (typeof req.body.user.role !== 'undefined') {
|
||||
user.role = req.body.user.role;
|
||||
return next(new ErrorResponse("Forbidden", 403, "A suspected attempt to usurp rights has been detected. The suspicious activity has been reported and xill be investigated."));
|
||||
}
|
||||
|
||||
/*user = await UserService.updateUser(req.body.user);
|
||||
// Persist changes using the service (service will save instance or update+fetch)
|
||||
const updatedUser = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});*/
|
||||
return user.save().then(function () {
|
||||
user: updatedUser.toAuthJSON()
|
||||
});
|
||||
/*return user.save().then(function () {
|
||||
return res.json({
|
||||
message: 'User updated successfully.',
|
||||
user: user.toAuthJSON()
|
||||
});
|
||||
});*/
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.updateUserEmail = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.email, next);
|
||||
if (typeof req.body.user.email !== 'undefined') {
|
||||
user.email = req.body.user.email;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserPassword = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is now being followed.',
|
||||
user: true
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
if (typeof req.body.user.password !== 'undefined') {
|
||||
const {salt, hash} = UserService.generateSaltHash(req.body.user.password);
|
||||
user.salt = salt;
|
||||
user.hash = hash;
|
||||
}
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message));
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserRole = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is no longer being followed.',
|
||||
user: true
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.role, next);
|
||||
if (typeof req.body.user.role !== 'undefined') {
|
||||
user.role = req.body.user.role;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message));
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.updateUserUsername = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.username, next);
|
||||
if (typeof req.body.user.username !== 'undefined') {
|
||||
user.username = req.body.user.username;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
const fieldValidation = (field, next) => {
|
||||
if (!field) {
|
||||
return next(new ErrorResponse(`Missing fields`, 400));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
// Create User table
|
||||
await queryInterface.createTable('user', {
|
||||
id: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
@@ -202,7 +202,7 @@ module.exports = {
|
||||
allowNull: false
|
||||
},
|
||||
authorId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -236,7 +236,7 @@ module.exports = {
|
||||
allowNull: false
|
||||
},
|
||||
authorId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -306,7 +306,7 @@ module.exports = {
|
||||
// Create Favorite table (junction table for User-Article)
|
||||
await queryInterface.createTable('favorite', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -342,7 +342,7 @@ module.exports = {
|
||||
// Create Follower table (junction table for User-User)
|
||||
await queryInterface.createTable('follower', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -353,7 +353,7 @@ module.exports = {
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
followerId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -437,7 +437,7 @@ module.exports = {
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
ownerId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -471,7 +471,7 @@ module.exports = {
|
||||
// Create UserRoleXref table (junction table for User-Role)
|
||||
await queryInterface.createTable('userRoleXref', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
|
||||
@@ -11,7 +11,7 @@ const UserModel = () => {
|
||||
User.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.STRING(24),
|
||||
type: DataTypes.UUID(),
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
@@ -87,16 +87,16 @@ const UserModel = () => {
|
||||
}
|
||||
);
|
||||
|
||||
User.beforeCreate((user) => {
|
||||
/*User.beforeCreate((user) => {
|
||||
if (typeof user.username === 'undefined') {
|
||||
user.username = `${user.firstname}_${user.lastname}`;
|
||||
}
|
||||
user.setPassword(user.password);
|
||||
user.role = 'User';
|
||||
});
|
||||
});*/
|
||||
|
||||
User.prototype.validPassword = function (password) {
|
||||
let hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
const hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
return this.hash === hash;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Create roles
|
||||
await queryInterface.bulkInsert('role', [
|
||||
{
|
||||
name: 'Admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
], {});
|
||||
|
||||
console.log('✅ Rôles créés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Delete roles
|
||||
await queryInterface.bulkDelete('role', { name: { [Sequelize.Op.in]: ['Admin', 'User'] } }, {});
|
||||
|
||||
console.log('✅ Rôles supprimés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la suppression des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@ const slug = require('slug');
|
||||
|
||||
const ANIMAL_TAGS = ['Lion', 'Eagle', 'Dolphin', 'Tiger', 'Wolf'];
|
||||
|
||||
const AUTHOR_ID = '642c4459666702637dcb5066';
|
||||
// AUTHOR_ID will be resolved at runtime to the oldest user with role 'Admin'
|
||||
|
||||
const ARTICLES = [
|
||||
{
|
||||
@@ -46,6 +46,40 @@ module.exports = {
|
||||
// Get transaction for consistency
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
// Resolve AUTHOR_ID: oldest user who has role 'Admin'
|
||||
let AUTHOR_ID = null;
|
||||
try {
|
||||
const adminQuery = await queryInterface.sequelize.query(
|
||||
`SELECT u.id FROM \`user\` u
|
||||
JOIN userRoleXref ur ON ur.userId = u.id
|
||||
JOIN role r ON r.id = ur.roleId
|
||||
WHERE r.name = ?
|
||||
ORDER BY u.createdAt ASC
|
||||
LIMIT 1`,
|
||||
{ replacements: ['Admin'], transaction, raw: true }
|
||||
);
|
||||
|
||||
if (adminQuery && adminQuery[0] && adminQuery[0][0] && adminQuery[0][0].id) {
|
||||
AUTHOR_ID = adminQuery[0][0].id;
|
||||
} else {
|
||||
// Fallback: pick the oldest user in `user` table
|
||||
const fallback = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM \`user\` ORDER BY createdAt ASC LIMIT 1`,
|
||||
{ transaction, raw: true }
|
||||
);
|
||||
if (fallback && fallback[0] && fallback[0][0] && fallback[0][0].id) {
|
||||
AUTHOR_ID = fallback[0][0].id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AUTHOR_ID) {
|
||||
throw new Error('Aucun utilisateur trouvé pour être utilisé comme auteur (Admin absent).');
|
||||
}
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create articles
|
||||
for (const articleData of ARTICLES) {
|
||||
@@ -127,7 +161,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
async down(queryInterface) {
|
||||
try {
|
||||
// Delete in correct order due to foreign keys
|
||||
await queryInterface.sequelize.query('DELETE FROM tagList WHERE 1=1');
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Create roles
|
||||
await queryInterface.bulkInsert('role', [
|
||||
{
|
||||
name: 'Admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
], {});
|
||||
|
||||
console.log('✅ Rôles créés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Delete roles
|
||||
await queryInterface.bulkDelete('role', { name: { [Sequelize.Op.in]: ['Admin', 'User'] } }, {});
|
||||
|
||||
console.log('✅ Rôles supprimés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la suppression des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
// Seeders managed by individual migration files
|
||||
// See seeders directory for specific seeder implementations
|
||||
@@ -1,5 +1,8 @@
|
||||
const express = require('express');
|
||||
const { authenticate, createUser, getUser, loginAsUser, updateUser, addFollowUser, deleteFollowUser } = require('../../../controllers/user.controller');
|
||||
const {
|
||||
addFollowUser, authenticate, createUser, deleteFollowUser, getUser, loginAsUser,
|
||||
updateUser, updateUserEmail, updateUserPassword, updateUserRole, updateUserUsername
|
||||
} = require('../../../controllers/user.controller');
|
||||
const auth = require('../../../middlewares/auth');
|
||||
|
||||
const userRouter = express.Router();
|
||||
@@ -11,6 +14,10 @@ userRouter.get('/authenticate', auth.optional, authenticate);
|
||||
userRouter.post('/login', auth.optional, loginAsUser);
|
||||
//userRouter.post('/register', auth.optional, createUser);
|
||||
//userRouter.get('/users', auth.required, getAllUsers);
|
||||
userRouter.put('/update/email', auth.required, updateUserEmail);
|
||||
userRouter.put('/update/password', auth.required, updateUserPassword);
|
||||
userRouter.put('/update/role', auth.required, updateUserRole);
|
||||
userRouter.put('/update/username', auth.required, updateUserUsername);
|
||||
|
||||
userRouter.post('/:username/follow', auth.required, addFollowUser);
|
||||
userRouter.delete('/:username/follow', auth.required, deleteFollowUser);
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
const slug = require("slug");
|
||||
const DB = require('../../database/mysql');
|
||||
var crypto = require('crypto');
|
||||
|
||||
const { User } = DB;
|
||||
|
||||
class UserService {
|
||||
static async createUser(data) {
|
||||
if (typeof data.username === 'undefined') {
|
||||
data.username = UserService.slugify(`${data.firstname.charAt(0)}${data.lastname.charAt(0)}`);
|
||||
}
|
||||
const {salt, hash} = await UserService.generateSaltHash(data.password);
|
||||
data.salt = salt;
|
||||
data.hash = hash;
|
||||
data.role = 'User';
|
||||
return User.create(data);
|
||||
}
|
||||
|
||||
static async generateSaltHash(password) {
|
||||
const salt = crypto.randomBytes(16).toString('hex');
|
||||
const hash = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
|
||||
return {salt, hash};
|
||||
}
|
||||
|
||||
static async getAllUsers() {
|
||||
return User.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
@@ -36,14 +51,19 @@ class UserService {
|
||||
return user.getArticleIdArticles(searchOptions);
|
||||
}
|
||||
|
||||
static async getFavoritesCount(user) {
|
||||
return user.countArticleIdArticles();
|
||||
}
|
||||
|
||||
static async getUserFollowed(user) {
|
||||
// Sequelize generated method for "users this user follows" is named
|
||||
// `getUserIdUserFollowers()` (see `src/database/relationships/index.js`).
|
||||
return user.getUserIdUserFollowers();
|
||||
}
|
||||
|
||||
static async getFavoritesCount(user) {
|
||||
return user.countArticleIdArticles();
|
||||
static async isUsernameUsed(username) {
|
||||
const user = await User.findOne({ where: { username: username } });
|
||||
return !!user;
|
||||
}
|
||||
|
||||
static async addFavoriteArticle(user, article) {
|
||||
@@ -53,9 +73,25 @@ class UserService {
|
||||
static async removeFavoriteArticle(user, article) {
|
||||
return user.removeArticleIdArticles(article);
|
||||
}
|
||||
|
||||
static slugify(stringToSlug) {
|
||||
return slug(`${stringToSlug}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||
}
|
||||
|
||||
static async updateUser(data) {
|
||||
return User.update(data);
|
||||
static async updateUserById(data, id) {
|
||||
// If `data` is a Sequelize instance, prefer instance.save()
|
||||
try {
|
||||
if (data && typeof data.save === 'function') {
|
||||
const saved = await data.save();
|
||||
return saved;
|
||||
}
|
||||
} catch {
|
||||
// fallthrough to update
|
||||
}
|
||||
|
||||
// Otherwise perform a standard update and return the refreshed instance
|
||||
await User.update(data, { where: { id: id } });
|
||||
return User.findByPk(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user