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
|
SERVER_PORT=3201
|
||||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
SESSION_LIFETIME=3600
|
SESSION_LIFETIME=3600
|
||||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||||
SENDGRID_API_KEY="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"
|
SENDGRID_API_KEY="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"
|
||||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
|||||||
SERVER_PORT=3201
|
SERVER_PORT=3201
|
||||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
SESSION_LIFETIME=86400
|
SESSION_LIFETIME=86400
|
||||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||||
SENDGRID_API_KEY="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"
|
SENDGRID_API_KEY="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"
|
||||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
|||||||
SERVER_PORT=3201
|
SERVER_PORT=3201
|
||||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
SESSION_LIFETIME=3600
|
SESSION_LIFETIME=3600
|
||||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||||
SENDGRID_API_KEY="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"
|
SENDGRID_API_KEY="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"
|
||||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ DEBUG=false
|
|||||||
SERVER_PORT=3201
|
SERVER_PORT=3201
|
||||||
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
SECRET="$2b$10$2.JkQCRUa3fXESy/WyKVWO"
|
||||||
SESSION_LIFETIME=3600
|
SESSION_LIFETIME=3600
|
||||||
MONGODB_URI="mongodb://localhost:27017/headupdb"
|
MONGODB_URI="mongodb://localhost:27017/adastradb"
|
||||||
SENDGRID_API_KEY="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"
|
SENDGRID_API_KEY="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"
|
||||||
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
SENDGRID_FROM_MAIL="contact@rampeur.fr"
|
||||||
SENDGRID_TO_MAIL="rampeur@gmail.com"
|
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
|
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
|
# Exécuter tous les seeders
|
||||||
npx sequelize-cli db:seed:all
|
npx sequelize-cli db:seed:all
|
||||||
|
|
||||||
@@ -14,4 +49,19 @@ npx sequelize-cli db:seed:undo:all
|
|||||||
|
|
||||||
# Annuler un seeder spécifique
|
# Annuler un seeder spécifique
|
||||||
npx sequelize-cli db:seed:undo --seed 20251127-add-articles.js
|
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,
|
"SERVER_PORT": 3201,
|
||||||
"SECRET": "$2b$10$aEsAUG7Dy8dcTORi1vaQle",
|
"SECRET": "$2b$10$aEsAUG7Dy8dcTORi1vaQle",
|
||||||
"SESSION_LIFETIME": 3600,
|
"SESSION_LIFETIME": 3600,
|
||||||
"MONGODB_URI": "mongodb://localhost:27017/headupdb",
|
"MONGODB_URI": "mongodb://localhost:27017/adastradb",
|
||||||
"SENDGRID_API_KEY": "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",
|
"SENDGRID_API_KEY": "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",
|
||||||
"SENDGRID_FROM_MAIL": "contact@rampeur.fr",
|
"SENDGRID_FROM_MAIL": "contact@rampeur.fr",
|
||||||
"SENDGRID_TO_MAIL": "rampeur@gmail.com",
|
"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",
|
"test": "newman run ./tests/adastra-api-tests.postman_collection.json -e ./tests/adastra-api-tests.postman_environment.json",
|
||||||
"stop": "lsof -ti :3200 | xargs kill",
|
"stop": "lsof -ti :3200 | xargs kill",
|
||||||
"mongo:start": "docker run --name hu-mongo -p 27017:27017 mongo & sleep 5",
|
"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": {
|
"engines": {
|
||||||
"node": "^16.20.2 || ^18.19.1 || ^20.11.1"
|
"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 { UserService } = require('../services');
|
||||||
const asyncHandler = require("../middlewares/asyncHandler");
|
const asyncHandler = require("../middlewares/asyncHandler");
|
||||||
const ErrorResponse = require("../utils/errorResponse");
|
const ErrorResponse = require("../utils/errorResponse");
|
||||||
|
var crypto = require('crypto');
|
||||||
const passport = require('passport');
|
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) => {
|
module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
passport.authenticate('headerapikey', { session: false }, function (err, application, info) {
|
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) => {
|
module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||||
try {
|
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({
|
const user = await UserService.createUser({
|
||||||
|
id: uuid,
|
||||||
username,
|
username,
|
||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
email
|
phone,
|
||||||
|
email,
|
||||||
|
password
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
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) => {
|
module.exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const users = await UserService.getAllUsers();
|
const users = await UserService.getAllUsers();
|
||||||
@@ -69,6 +108,7 @@ module.exports.getUser = asyncHandler(async (req, res, next) => {
|
|||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
user: user.toAuthJSON(),
|
user: user.toAuthJSON(),
|
||||||
|
uuid: crypto.randomUUID()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
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) {
|
if (!user) {
|
||||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
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') {
|
if (typeof req.body.user.firstname !== 'undefined') {
|
||||||
user.firstname = req.body.user.firstname;
|
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') {
|
if (typeof req.body.user.bg_image !== 'undefined') {
|
||||||
user.bg_image = req.body.user.bg_image;
|
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') {
|
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({
|
res.status(200).json({
|
||||||
user: user.toAuthJSON(),
|
user: updatedUser.toAuthJSON()
|
||||||
});*/
|
});
|
||||||
return user.save().then(function () {
|
/*return user.save().then(function () {
|
||||||
return res.json({
|
return res.json({
|
||||||
message: 'User updated successfully.',
|
message: 'User updated successfully.',
|
||||||
user: user.toAuthJSON()
|
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) {
|
} catch (err) {
|
||||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
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 {
|
try {
|
||||||
return res.status(200).json({
|
let user = await UserService.getUserById(req.payload.id);
|
||||||
message: 'The user is now being followed.',
|
if (!user) {
|
||||||
user: true
|
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) {
|
} 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 {
|
try {
|
||||||
return res.status(200).json({
|
let user = await UserService.getUserById(req.payload.id);
|
||||||
message: 'The user is no longer being followed.',
|
if (!user) {
|
||||||
user: true
|
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) {
|
} 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
|
// Create User table
|
||||||
await queryInterface.createTable('user', {
|
await queryInterface.createTable('user', {
|
||||||
id: {
|
id: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
},
|
},
|
||||||
@@ -202,7 +202,7 @@ module.exports = {
|
|||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
authorId: {
|
authorId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
references: {
|
references: {
|
||||||
model: 'user',
|
model: 'user',
|
||||||
@@ -236,7 +236,7 @@ module.exports = {
|
|||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
authorId: {
|
authorId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
references: {
|
references: {
|
||||||
model: 'user',
|
model: 'user',
|
||||||
@@ -306,7 +306,7 @@ module.exports = {
|
|||||||
// Create Favorite table (junction table for User-Article)
|
// Create Favorite table (junction table for User-Article)
|
||||||
await queryInterface.createTable('favorite', {
|
await queryInterface.createTable('favorite', {
|
||||||
userId: {
|
userId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
references: {
|
references: {
|
||||||
@@ -342,7 +342,7 @@ module.exports = {
|
|||||||
// Create Follower table (junction table for User-User)
|
// Create Follower table (junction table for User-User)
|
||||||
await queryInterface.createTable('follower', {
|
await queryInterface.createTable('follower', {
|
||||||
userId: {
|
userId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
references: {
|
references: {
|
||||||
@@ -353,7 +353,7 @@ module.exports = {
|
|||||||
onUpdate: 'CASCADE'
|
onUpdate: 'CASCADE'
|
||||||
},
|
},
|
||||||
followerId: {
|
followerId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
references: {
|
references: {
|
||||||
@@ -437,7 +437,7 @@ module.exports = {
|
|||||||
onUpdate: 'CASCADE'
|
onUpdate: 'CASCADE'
|
||||||
},
|
},
|
||||||
ownerId: {
|
ownerId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
references: {
|
references: {
|
||||||
model: 'user',
|
model: 'user',
|
||||||
@@ -471,7 +471,7 @@ module.exports = {
|
|||||||
// Create UserRoleXref table (junction table for User-Role)
|
// Create UserRoleXref table (junction table for User-Role)
|
||||||
await queryInterface.createTable('userRoleXref', {
|
await queryInterface.createTable('userRoleXref', {
|
||||||
userId: {
|
userId: {
|
||||||
type: Sequelize.STRING(24),
|
type: Sequelize.STRING(36),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
references: {
|
references: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const UserModel = () => {
|
|||||||
User.init(
|
User.init(
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
type: DataTypes.STRING(24),
|
type: DataTypes.UUID(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
},
|
},
|
||||||
@@ -87,16 +87,16 @@ const UserModel = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
User.beforeCreate((user) => {
|
/*User.beforeCreate((user) => {
|
||||||
if (typeof user.username === 'undefined') {
|
if (typeof user.username === 'undefined') {
|
||||||
user.username = `${user.firstname}_${user.lastname}`;
|
user.username = `${user.firstname}_${user.lastname}`;
|
||||||
}
|
}
|
||||||
user.setPassword(user.password);
|
user.setPassword(user.password);
|
||||||
user.role = 'User';
|
user.role = 'User';
|
||||||
});
|
});*/
|
||||||
|
|
||||||
User.prototype.validPassword = function (password) {
|
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;
|
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 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 = [
|
const ARTICLES = [
|
||||||
{
|
{
|
||||||
@@ -46,6 +46,40 @@ module.exports = {
|
|||||||
// Get transaction for consistency
|
// Get transaction for consistency
|
||||||
const transaction = await queryInterface.sequelize.transaction();
|
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 {
|
try {
|
||||||
// Create articles
|
// Create articles
|
||||||
for (const articleData of ARTICLES) {
|
for (const articleData of ARTICLES) {
|
||||||
@@ -127,7 +161,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async down(queryInterface, Sequelize) {
|
async down(queryInterface) {
|
||||||
try {
|
try {
|
||||||
// Delete in correct order due to foreign keys
|
// Delete in correct order due to foreign keys
|
||||||
await queryInterface.sequelize.query('DELETE FROM tagList WHERE 1=1');
|
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 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 auth = require('../../../middlewares/auth');
|
||||||
|
|
||||||
const userRouter = express.Router();
|
const userRouter = express.Router();
|
||||||
@@ -11,6 +14,10 @@ userRouter.get('/authenticate', auth.optional, authenticate);
|
|||||||
userRouter.post('/login', auth.optional, loginAsUser);
|
userRouter.post('/login', auth.optional, loginAsUser);
|
||||||
//userRouter.post('/register', auth.optional, createUser);
|
//userRouter.post('/register', auth.optional, createUser);
|
||||||
//userRouter.get('/users', auth.required, getAllUsers);
|
//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.post('/:username/follow', auth.required, addFollowUser);
|
||||||
userRouter.delete('/:username/follow', auth.required, deleteFollowUser);
|
userRouter.delete('/:username/follow', auth.required, deleteFollowUser);
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
|
const slug = require("slug");
|
||||||
const DB = require('../../database/mysql');
|
const DB = require('../../database/mysql');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
const { User } = DB;
|
const { User } = DB;
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
static async createUser(data) {
|
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);
|
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() {
|
static async getAllUsers() {
|
||||||
return User.findAll({
|
return User.findAll({
|
||||||
order: [['createdAt', 'DESC']],
|
order: [['createdAt', 'DESC']],
|
||||||
@@ -36,14 +51,19 @@ class UserService {
|
|||||||
return user.getArticleIdArticles(searchOptions);
|
return user.getArticleIdArticles(searchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getFavoritesCount(user) {
|
||||||
|
return user.countArticleIdArticles();
|
||||||
|
}
|
||||||
|
|
||||||
static async getUserFollowed(user) {
|
static async getUserFollowed(user) {
|
||||||
// Sequelize generated method for "users this user follows" is named
|
// Sequelize generated method for "users this user follows" is named
|
||||||
// `getUserIdUserFollowers()` (see `src/database/relationships/index.js`).
|
// `getUserIdUserFollowers()` (see `src/database/relationships/index.js`).
|
||||||
return user.getUserIdUserFollowers();
|
return user.getUserIdUserFollowers();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getFavoritesCount(user) {
|
static async isUsernameUsed(username) {
|
||||||
return user.countArticleIdArticles();
|
const user = await User.findOne({ where: { username: username } });
|
||||||
|
return !!user;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addFavoriteArticle(user, article) {
|
static async addFavoriteArticle(user, article) {
|
||||||
@@ -54,8 +74,24 @@ class UserService {
|
|||||||
return user.removeArticleIdArticles(article);
|
return user.removeArticleIdArticles(article);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateUser(data) {
|
static slugify(stringToSlug) {
|
||||||
return User.update(data);
|
return slug(`${stringToSlug}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
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