From 7b053b2bfc2956b619f7a91bec0a9ef3a5d907b2 Mon Sep 17 00:00:00 2001 From: firdaous elhalafi Date: Sat, 4 Nov 2023 23:50:06 +0100 Subject: [PATCH] ajout des token JWT --- api/openapi.yaml | 270 +++++++++++++++++++++++++++++++++++++---- controllers/Ad.js | 62 ++++++---- controllers/User.js | 13 +- package-lock.json | 99 ++++++++++++++- package.json | 4 +- service/AdService.js | 232 ++++++++++++++++++++++++++--------- service/UserService.js | 29 ++++- 7 files changed, 594 insertions(+), 115 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index e6aa22c..b7c6323 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -40,6 +40,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Ad' + security: + - bearerAuth: [] responses: "200": description: Successful operation @@ -49,10 +51,182 @@ paths: $ref: '#/components/schemas/Ad' "405": description: Invalid input + x-swagger-router-controller: Ad + /update/{id}: + put: + tags: + - ad + summary: Mettre à jour une annonce + description: Met à jour une annonce. + operationId: updateAd + parameters: + - name: id + in: path + description: ID de l'annonce à mettre à jour + required: true + schema: + type: string + requestBody: + description: Annonce à mettre à jour + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateAd' + security: + - bearerAuth: [] + responses: + '200': + description: Succès. Renvoie les détails de l'annonce mise à jour. + '403': + description: Accès interdit. L'utilisateur n'est pas autorisé à effectuer cette action. + '404': + description: Non trouvé. L'annonce avec cet ID n'a pas été trouvée. + x-swagger-router-controller: Ad + /announcements/delete/{id}: + post: + tags: + - ad + summary: Supprimer une annonce + description: Supprime une annonce + operationId: deleteAd + parameters: + - name: id + in: path + description: ID de l'annonce à supprimer + required: true + schema: + type: string + security: + - bearerAuth: [] + responses: + '200': + description: Succès. L'annonce a été supprimée avec succès. + '404': + description: Annonce non trouvée. + '403': + description: Utilisateur non autorisé. + '401': + description: + Authentification requise. + '500': + description: Erreur serveur interne. Veuillez réessayer plus tard. + x-swagger-router-controller: Ad + /announcements: + get: + tags: + - ad + summary: Obtenir la liste des annonces + description: Récupère la liste des annonces + operationId: getAds + security: + - bearerAuth: [] + responses: + "200": + description: Succès. Liste des annonces récupérée avec succès. + content: + application/json: + schema: + $ref: '#/components/schemas/AnnouncementList' + "500": + description: Erreur serveur interne. Veuillez réessayer plus tard. + x-swagger-router-controller: Ad + /announcements/{id}: + get: + tags: + - ad + summary: Obtenir les détails d'une annonce + description: Récupère les détails d'une annonce + operationId: getAdById + parameters: + - name: id + in: path + description: ID de l'annonce à récupérer + required: true + schema: + type: string + security: + - bearerAuth: [] + responses: + "200": + description: Succès. Détails de l'annonce récupérés avec succès. + content: + application/json: + schema: + $ref: '#/components/schemas/Ad' + "403": + description: Accès interdit. L'utilisateur n'est pas autorisé à accéder à cette annonce. + "404": + description: Non trouvé. L'annonce avec l'ID spécifié n'a pas été trouvée ou n'est pas disponible. + "500": + description: Erreur serveur interne. Veuillez réessayer plus tard. + x-swagger-router-controller: Ad + /announcements/{id}/ask: + put: + tags: + - ad + summary: Poser une question sur une annonce + description: Pose une question sur une annonce + operationId: askQuestion + parameters: + - name: id + in: path + description: ID de l'annonce sur laquelle poser la question + required: true + schema: + type: string + requestBody: + description: Question à poser sur l'annonce + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/askQuestion' + security: + - bearerAuth: [] + responses: + '200': + description: Succès. La question a été posée avec succès. + '500': + description: Erreur serveur interne. Veuillez réessayer plus tard. + x-swagger-router-controller: Ad + /announcements/{id}/question/{questionId}/answer: + put: + tags: + - ad + summary: Répondre à une question sur une annonce + description: Répond à une question sur une annonce + operationId: answerQuestion + parameters: + - name: id + in: path + description: ID de l'annonce sur laquelle répondre à la question + required: true + schema: + type: string + - name: questionId + in: path + description: ID de la question à laquelle répondre + required: true + schema: + type: string + requestBody: + description: Réponse à la question + required: true + content: + application/json: + schema: + type: object + properties: + answer: + type: string security: - - real-estate-ads_auth: - - write:ads - - read:ads + - bearerAuth: [] + responses: + '200': + description: Succès. La réponse a été enregistrée avec succès. + '500': + description: Erreur serveur interne. Veuillez réessayer plus tard. x-swagger-router-controller: Ad /user: post: @@ -73,6 +247,10 @@ paths: application/x-www-form-urlencoded: schema: $ref: '#/components/schemas/User' + security: + - oAuthSample: + - write_pets + - read_pets responses: default: description: successful operation @@ -141,11 +319,14 @@ paths: description: "" operationId: logoutUser parameters: [] + security: + - bearerAuth: [] responses: default: description: successful operation x-swagger-router-controller: User components: + schemas: Ad: type: object @@ -176,31 +357,50 @@ components: availabilityDate: type: string format: date-time + example: "2023-11-03T14:30:00Z" photos: type: array items: type: string - userName: + format: binary + description: Sélectionnez un ou plusieurs fichiers image. + required: + - title + - propertyType + - publicationStatus + - propertyStatus + - description + - price + - availabilityDate + UpdateAd: + type: object + properties: + title: type: string - questions: - type: array - items: - type: object - properties: - user: - type: string - question: - type: string - answers: - type: array - items: - type: object - properties: - answer: - type: string - date: - type: string - format: date-time + propertyType: + type: string + enum: + - À la vente + - À la location + publicationStatus: + type: string + enum: + - Publiée + - Non publiée + propertyStatus: + type: string + enum: + - Disponible + - Loué + - Vendu + description: + type: string + price: + type: number + minimum: 0 + availabilityDate: + type: string + format: date-time required: - title - propertyType @@ -209,7 +409,17 @@ components: - description - price - availabilityDate - - userName + AnnouncementList: + type: array + items: + $ref: '#/components/schemas/Ad' + askQuestion: + type: object + properties: + question: + type: string + required: + - question User: type: object properties: @@ -271,6 +481,18 @@ components: items: $ref: '#/components/schemas/User' securitySchemes: + bearerAuth: + type: http + scheme: bearer + oAuthSample: + type: oauth2 + description: This API uses OAuth 2 with the implicit grant flow. + flows: + implicit: + authorizationUrl: https://api.example.com/oauth2/authorize + scopes: + read_pets: read your pets + write_pets: modify pets in your account real-estate-ads_auth: type: oauth2 flows: diff --git a/controllers/Ad.js b/controllers/Ad.js index 28ea513..8ca5102 100644 --- a/controllers/Ad.js +++ b/controllers/Ad.js @@ -2,9 +2,11 @@ var utils = require('../utils/writer.js'); var Ad = require('../service/AdService'); // Assurez-vous d'importer le service Announcement +var User = require('../service/UserService'); module.exports.addAd = function addAd (req, res, next, body) { - Ad.addAd(body) + const token = req.headers.authorization.split(' ')[1]; + Ad.addAd(body, token) .then(function (response) { utils.writeJson(res, response); }) @@ -13,8 +15,9 @@ module.exports.addAd = function addAd (req, res, next, body) { }); }; -/*module.exports.deleteAnnouncement = function deleteAnnouncement (req, res, next, announcementId) { - Announcement.deleteAnnouncement(announcementId) +module.exports.updateAd = function updateAd (req, res, next) { + const token = req.headers.authorization.split(' ')[1]; + Ad.updateAd(req.openapi.pathParams.id, req.body, token) .then(function (response) { utils.writeJson(res, response); }) @@ -23,8 +26,9 @@ module.exports.addAd = function addAd (req, res, next, body) { }); }; -module.exports.getAllAnnouncements = function getAllAnnouncements (req, res, next) { - Announcement.getAllAnnouncements() +module.exports.deleteAd = function deleteAd(req, res, next, announcementId) { + const token = req.headers.authorization.split(' ')[1]; + Ad.deleteAd(announcementId, token) .then(function (response) { utils.writeJson(res, response); }) @@ -33,42 +37,56 @@ module.exports.getAllAnnouncements = function getAllAnnouncements (req, res, nex }); }; -module.exports.getAnnouncementById = function getAnnouncementById (req, res, next, announcementId) { - Announcement.getAnnouncementById(announcementId) +module.exports.getAds = function getAnnouncements(req, res) { + const token = req.headers.authorization.split(' ')[1]; + + Ad.getAds(token) .then(function (response) { utils.writeJson(res, response); }) - .catch(function (response) { - utils.writeJson(res, response); + .catch(function (error) { + res.status(500).json({ message: error.message }); }); }; -module.exports.updateAnnouncement = function updateAnnouncement (req, res, next, announcementId, body) { - Announcement.updateAnnouncement(announcementId, body) +module.exports.getAdById = function getAnnouncementById(req, res) { + const announcementId = req.openapi.pathParams.id; + const token = req.headers.authorization.split(' ')[1]; + + Ad.getAdById(announcementId, token) .then(function (response) { utils.writeJson(res, response); }) - .catch(function (response) { - utils.writeJson(res, response); + .catch(function (error) { + res.status(500).json({ message: error.message }); }); }; -module.exports.findAnnouncementsByStatus = function findAnnouncementsByStatus (req, res, next, status) { - Announcement.findAnnouncementsByStatus(status) +exports.askQuestion = async function(req, res) { + const announcementId = req.openapi.pathParams.id; + const { question } = req.body; + const token = req.headers.authorization.split(' ')[1]; + + Ad.askQuestion(announcementId, question, token) .then(function (response) { utils.writeJson(res, response); }) - .catch(function (response) { - utils.writeJson(res, response); + .catch(function (error) { + res.status(500).json({ message: error.message }); }); }; -module.exports.findAnnouncementsByPropertyType = function findAnnouncementsByPropertyType (req, res, next, propertyType) { - Announcement.findAnnouncementsByPropertyType(propertyType) +exports.answerQuestion = async function(req, res) { + const announcementId = req.openapi.pathParams.id; + const questionId = req.openapi.pathParams.questionId; + const { answer } = req.body; + const token = req.headers.authorization.split(' ')[1]; + + Ad.answerQuestion(announcementId, questionId, answer, token) .then(function (response) { utils.writeJson(res, response); }) - .catch(function (response) { - utils.writeJson(res, response); + .catch(function (error) { + res.status(500).json({ message: error.message }); }); -};*/ +}; \ No newline at end of file diff --git a/controllers/User.js b/controllers/User.js index c1350e1..6218f48 100644 --- a/controllers/User.js +++ b/controllers/User.js @@ -5,8 +5,11 @@ var User = require('../service/UserService'); var passport = require('../passeport-config'); var UserModel = require('../models/User'); -// passport.use(UserModel.createStrategy()); +const jwt = require('jsonwebtoken'); +const { extractUserFromToken } = require( '../service/UserService'); +const secretKey = 'secretKey'; +// passport.use(UserModel.createStrategy()); module.exports.createUser = async function(req, res) { try { const { username, password, isAgent } = req.body; @@ -26,6 +29,7 @@ module.exports.loginUser = async function loginUser(req, res, next) { if (!user) { return res.status(401).send('Invalid username or password.'); // Non autorisé } + const token = jwt.sign({ username: user.username }, 'secretKey', { expiresIn: '1h' }); // User.loginUser() // .then(function (response) { // utils.writeJson(res, response); @@ -33,7 +37,7 @@ module.exports.loginUser = async function loginUser(req, res, next) { // .catch(function (response) { // utils.writeJson(res, response); // }); - return res.status(200).send('Authentication successful.'); // Succès + return res.status(200).json({ token }); })(req, res, next); } catch (error) { console.error("Erreur lors de la tentative de connexion :", error); @@ -41,7 +45,7 @@ module.exports.loginUser = async function loginUser(req, res, next) { } } -module.exports.logoutUser = function(req, res, next) { +module.exports.logoutUser = async function(req, res, next) { try { /* req.logout(function(err) { @@ -56,4 +60,5 @@ module.exports.logoutUser = function(req, res, next) { console.error("Erreur lors de la déconnexion de l'utilisateur :", error); return res.status(500).send(error); } -}; \ No newline at end of file +}; + diff --git a/package-lock.json b/package-lock.json index b9fa18e..0fdce2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "dotenv": "^16.3.1", "express-session": "^1.17.3", "js-yaml": "^3.3.0", + "jsonwebtoken": "^9.0.2", "mongoose": "^7.6.3", "oas3-tools": "^2.2.3", "passport": "^0.6.0", @@ -338,6 +339,11 @@ "node": ">=14.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -584,6 +590,14 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1204,6 +1218,51 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", @@ -1227,6 +1286,41 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -1241,7 +1335,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -1869,7 +1962,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -2253,8 +2345,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index c977aa0..a2d30d0 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ }, "nodemonConfig": { "watch": [ - "api/openapi.yaml" + "api/openapi.yaml", + "index.js" ], "ext": "js,yaml", "exec": "node index.js" @@ -24,6 +25,7 @@ "dotenv": "^16.3.1", "express-session": "^1.17.3", "js-yaml": "^3.3.0", + "jsonwebtoken": "^9.0.2", "mongoose": "^7.6.3", "oas3-tools": "^2.2.3", "passport": "^0.6.0", diff --git a/service/AdService.js b/service/AdService.js index 1563c03..bc4b09d 100644 --- a/service/AdService.js +++ b/service/AdService.js @@ -1,102 +1,218 @@ const Ad = require('../models/Ad'); +const User = require('../models/User'); -// Méthode pour ajouter une nouvelle annonce -exports.addAd = function (body) { - return new Promise(async (resolve, reject) => { - try { - const newAd = new Ad(body); - const savedAd = await newAd.save(); - resolve(savedAd); - } catch (error) { - reject(error); - } - }); +const {extractUserFromToken} = require("./UserService"); + +const secretKey = 'secretKey'; + +exports.addAd = function (body, token) { + return new Promise(async (resolve, reject) => { + + try { + const { title, propertyType, publicationStatus, propertyStatus, description, price, availabilityDate} = body; + const photos = []; + /* + req.files.forEach(file => { + photos.push(file.filename); + }); + */ + const decoded = await extractUserFromToken(token, secretKey); + const user = await User.findOne({ username: decoded.username }); + + if (user.isAgent === false) { + return resolve({ status: 403, message: 'User non autorisée.' }); + } + const newAnnouncement = new Ad({ + title, + propertyType, + publicationStatus, + propertyStatus, + description, + price, + availabilityDate, + photos, + userName: user.username + }); + + await newAnnouncement.save(); + //res.status(200).json(newAnnouncement); + resolve({ status: 200, message: 'Annonce Crée avec succes' }); + } catch (error) { + reject(error); + } + }); } -// Méthode pour supprimer une annonce par son ID -exports.deleteAd = function (AdId) { +// Méthode pour mettre à jour une annonce +exports.updateAd = function (announcementId, reqBody, token) { return new Promise(async (resolve, reject) => { try { - const deletedAd = await Ad.findByIdAndRemove(AdId); - if (!deletedAd) { - reject('Annonce non trouvée'); - } else { - resolve('Annonce supprimée avec succès'); + const user = await extractUserFromToken(token, secretKey); + const { title, propertyType, publicationStatus, propertyStatus, description, price, availabilityDate } = reqBody; + /* + let photos = []; + const oldPhotos = reqBody.oldPhotos || []; + + reqFiles.forEach(file => { + photos.push(file.filename); + }); + + photos = photos.concat(oldPhotos); + */ + const updatedAnnouncement = await Ad.findByIdAndUpdate( + announcementId, + { + title, + propertyType, + publicationStatus, + propertyStatus, + description, + price, + availabilityDate, + //photos, + } + ); + if (!updatedAnnouncement) { + return resolve({status: 404, message: 'annonce non trouvée'}); } + if (user.username !== updatedAnnouncement.userName) { + return resolve({status: 401, message: 'Utilisateur non autorisé'}); + } + + resolve({ status: 200, message: 'Annonce Modifié avec succès' }); } catch (error) { reject(error); } }); } -// Méthode pour récupérer toutes les annonces -exports.getAllAds = function () { +exports.deleteAd = function (announcementId, token) { return new Promise(async (resolve, reject) => { try { - const Ads = await Ad.find(); - resolve(Ads); + const user = await extractUserFromToken(token, secretKey); + const Announcement = await Ad.findById(announcementId); + if (user.username !== Announcement.userName) { + return resolve({ status: 403, message: 'User non autorisé' }); + } + + const deletedAnnouncement = await Ad.findByIdAndRemove(announcementId); + + if (!deletedAnnouncement) { + return resolve({ status: 404, message: 'annonce non trouvée' }); + } + + /* + deletedAnnouncement.photos.forEach((photo) => { + const filePath = path.join('public', 'uploads', photo); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + }); + */ + resolve({ status: 200, message: 'Annonce supprimée avec succès' }); } catch (error) { reject(error); } }); -} +}; -// Méthode pour récupérer une annonce par son ID -exports.getAdById = function (AdId) { +exports.getAds = function (token) { return new Promise(async (resolve, reject) => { try { - const Ad = await Ad.findById(AdId); - if (!Ad) { - reject('Annonce non trouvée'); - } else { - resolve(Ad); - } + const decoded = await extractUserFromToken(token, secretKey); + let announcements; + const user = await User.findOne({ username: decoded.username }); + if (user && user.isAgent) { + announcements = await Ad.find({ userName: user.username }); + } else { + announcements = await Ad.find({ publicationStatus: 'Publiée' }); + } + resolve(announcements); } catch (error) { reject(error); } }); -} +}; -// Méthode pour mettre à jour une annonce par son ID -exports.updateAd = function (AdId, body) { +exports.getAdById = function (announcementId, token) { return new Promise(async (resolve, reject) => { try { - const updatedAd = await Ad.findByIdAndUpdate( - AdId, - body, - { new: true } - ); - if (!updatedAd) { - reject('Annonce non trouvée'); - } else { - resolve(updatedAd); - } + const announcementDetails = await Ad.findById(announcementId); + const decoded = await extractUserFromToken(token, secretKey); + const user = await User.findOne({ username: decoded.username }); + + + if ( + (user && user.username !== announcementDetails.userName || !user) && + announcementDetails.publicationStatus !== 'Publiée' + ) { + return resolve({ status: 404, message: 'Cette annonce est introuvable ou non disponible.' }); + } + if (user && user.username !== announcementDetails.userName && user.isAgent) { + return resolve({ status: 403, message: 'User non autorisée.' }); + } + + if( !announcementDetails ) { + return resolve({ status: 404, message: 'Cette annonce est introuvable ou non disponible.' }); + } + resolve(announcementDetails); } catch (error) { reject(error); } }); -} +}; -// Méthode pour rechercher des annonces par statut -exports.findAdsByStatus = function (status) { +// Service pour poser une question sur une annonce +exports.askQuestion = async function(announcementId, question, token) { return new Promise(async (resolve, reject) => { - try { - const Ads = await Ad.find({ publicationStatus: status }); - resolve(Ads); + try { + const announcement = await Ad.findById(announcementId); + const decoded = await extractUserFromToken(token, secretKey); + const user = await User.findOne({ username: decoded.username }); + + if (!announcement) { + return resolve({ status: 404, message: 'Annonce non trouvée.' }); + } + if (announcement.propertyStatus !== 'Disponible') { + return resolve({ status: 404, message: 'Vous ne pouvez poser une question que sur les annonces non disponibles.' }); + } + announcement.questions.push({ + user: user.username, + question + }); + await announcement.save(); + resolve({ status: 200, message: 'Question posée avec succès.' }); } catch (error) { - reject(error); + throw error; } }); -} +}; -// Méthode pour rechercher des annonces par type de propriété -exports.findAdsByPropertyType = function (propertyType) { +// Service pour répondre à une question sur une annonce +exports.answerQuestion = async function(announcementId, questionId, answer, token) { return new Promise(async (resolve, reject) => { - try { - const Ads = await Ad.find({ propertyType }); - resolve(Ads); + try { + const announcement = await Ad.findById(announcementId); + const decoded = await extractUserFromToken(token, secretKey); + const user = await User.findOne({ username: decoded.username }); + + if (!announcement) { + return resolve({ status: 404, message: 'Annonce non trouvée.' }); + } + + if (user.username !== announcement.userName) { + return resolve({ status: 403, message: 'User non autorisée.' }); + } + + const question = announcement.questions.id(questionId); + if (!question) { + return resolve({ status: 404, message: 'Question non trouvée.' }); + } + question.answers.push({ user: user.username, answer }); + await announcement.save(); + resolve({ status: 200, message: 'Réponse ajoutée avec succès.' }); } catch (error) { - reject(error); + throw error; } }); } diff --git a/service/UserService.js b/service/UserService.js index 94127af..15690fd 100644 --- a/service/UserService.js +++ b/service/UserService.js @@ -3,6 +3,8 @@ const passport = require('../passeport-config'); const User = require('../models/User'); +const jwt = require('jsonwebtoken'); + passport.use(User.createStrategy()); /** @@ -31,8 +33,31 @@ exports.createUser = async function(username, password, isAgent) { **/ exports.isAuthenticated = function (req, res, next) { if (req.isAuthenticated()) { - return true; + return next(); } - return false; + res.status(401).json({ message: 'Authentification requise' }); } +exports.extractUserFromToken = function(token, secretKey) { + return new Promise(async (resolve, reject) => { + try { + const decoded = await jwt.verify(token, secretKey); + // Vérifier la date d'expiration + const currentTimestamp = Math.floor(Date.now() / 1000); + if (decoded.exp && decoded.exp < currentTimestamp) { + reject(new Error('Token expiré.')); + return; + } + + const user = await User.findOne({ username: decoded.username }); + + if (!user) { + reject(new Error('Utilisateur non trouvé.')); + } else { + resolve(decoded); + } + } catch (err) { + reject(err); + } + }); +}; -- GitLab