From ef14163a16b63f71efc122281df760f45fa56a36 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 13 Jul 2020 09:18:17 -0300 Subject: [PATCH 01/12] adds supply create action under permissions --- src/controllers/supply.controller.ts | 22 ++++++++++++++++++---- src/models/supply.model.ts | 9 +++------ src/routes/private.ts | 2 +- src/utils/rbac_abac.ts | 1 + 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/controllers/supply.controller.ts b/src/controllers/supply.controller.ts index 6a3f336..90bb732 100644 --- a/src/controllers/supply.controller.ts +++ b/src/controllers/supply.controller.ts @@ -12,13 +12,27 @@ class SupplyController implements BaseController{ } public create = async (req: Request, res: Response): Promise => { - const { id, name, unity, sex, image } = req.body; + const { + name, + activePrinciple, + power, + unity, + firstPresentation, + secondPresentation, + description, + observation, + pharmaceutical_form + } = req.body; const newSupply: ISupply = new Supply({ - id, name, + activePrinciple, + power, unity, - sex, - image + firstPresentation, + secondPresentation, + description, + observation, + pharmaceutical_form }); try{ await newSupply.save(); diff --git a/src/models/supply.model.ts b/src/models/supply.model.ts index 09fb439..077a4ac 100644 --- a/src/models/supply.model.ts +++ b/src/models/supply.model.ts @@ -31,12 +31,9 @@ export const supplySchema = new Schema({ }, observation: { type: String, - }, - createdAt: { - type: Date, - default: Date.now - }, - updatedAt: Date, + } +},{ + timestamps: true }); // Model diff --git a/src/routes/private.ts b/src/routes/private.ts index a7e927a..9395c81 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -89,8 +89,8 @@ class PrivateRoutes{ // supply this.router.get(`/supplies/`, hasPermissionIn('readAny','patient'), supplyController.index); + this.router.post(`/supplies/`, hasPermissionIn('createAny','supplies'), supplyController.create); this.router.patch('/supplies/:id', hasPermissionIn('updateAny','supplies'), supplyController.update); - // this.router.post(`/supplies/`, hasPermissionIn('createAny','patient'), supplyController.create); // this.router.get(`/supplies/:id`, hasPermissionIn('readAny','patient'), supplyController.show); // this.router.put(`/supplies/:id`, hasPermissionIn('updateAny','patient'), supplyController.update); // this.router.delete(`/supplies/:id`, hasPermissionIn('deleteAny','patient'), supplyController.delete); diff --git a/src/utils/rbac_abac.ts b/src/utils/rbac_abac.ts index 6ccafa8..fc07276 100644 --- a/src/utils/rbac_abac.ts +++ b/src/utils/rbac_abac.ts @@ -53,6 +53,7 @@ class AccessControlLoader { // supplies { role: 'professional', resource: 'supplies', action: 'read:any', attributes: '*' }, { role: 'pharmacist', resource: 'supplies', action: 'read:any', attributes: '*' }, + { role: 'admin', resource: 'supplies', action: 'create:any', attributes: '*' }, { role: 'admin', resource: 'supplies', action: 'update:any', attributes: '*' } ]; this.accessControl.setGrants(grantList); From ff2ed2f9a7ba093af5f6a519f334303e095dfe0a Mon Sep 17 00:00:00 2001 From: plammel Date: Thu, 11 Feb 2021 15:32:44 -0300 Subject: [PATCH 02/12] feat: incluye recuperar password via mail --- .gitignore | 3 + config.private.example.ts | 27 +++++ package-lock.json | 39 ++++++- package.json | 2 + src/controllers/auth.controller.ts | 137 +++++++++++++++++-------- src/interfaces/user.interface.ts | 1 + src/models/user.model.ts | 5 +- src/routes/auth.ts | 23 ++++- src/utils/roboSender/sendEmail.ts | 73 +++++++++++++ templates/emails/recover-password.html | 12 +++ tsconfig.json | 2 +- 11 files changed, 274 insertions(+), 50 deletions(-) create mode 100644 config.private.example.ts create mode 100644 src/utils/roboSender/sendEmail.ts create mode 100644 templates/emails/recover-password.html diff --git a/.gitignore b/.gitignore index e8d1c81..4a90b82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Archivos de configuración +config.private.ts + # Logs logs *.log diff --git a/config.private.example.ts b/config.private.example.ts new file mode 100644 index 0000000..201c298 --- /dev/null +++ b/config.private.example.ts @@ -0,0 +1,27 @@ +function getEnv(key: any, _default: any, type = 's') { + if (!!process.env[key] === false) { + return _default; + } + const value: any = process.env[key]; + switch (type) { + case 'b': + return value.toLowerCase() === 'true'; + case 'n': + return parseInt(value, 10); + default: + return value; + } +} +​ +// E-mail server settings +export const enviarMail = { + host: getEnv('EMAIL_HOST', 'smtp.gmail.com'), + port: getEnv('EMAIL_PORT', 587, 'n'), + secure: getEnv('EMAIL_SECURE', false, 'b'), + auth: { + user: getEnv('EMAIL_USERNAME', 'xxxxxxxxx@gmail.com'), + pass: getEnv('EMAIL_PASSWORD', 'xxxxxxx') + } +}; + +export const APP_DOMAIN = getEnv('APP_DOMAIN', 'http://localhost:4200'); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b4af5a6..978f25c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -933,6 +933,18 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1369,8 +1381,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "moment": { "version": "2.25.3", @@ -1518,11 +1529,21 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "nocache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, + "nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" + }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -2016,8 +2037,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.16", @@ -2191,6 +2211,12 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "uglify-js": { + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.7.tgz", + "integrity": "sha512-SIZhkoh+U/wjW+BHGhVwE9nt8tWJspncloBcFapkpGRwNPqcH8pzX36BXe3TPBjzHWPMUZotpCigak/udWNr1Q==", + "optional": true + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -2294,6 +2320,11 @@ "string-width": "^2.1.1" } }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index 040a5ee..f9eb25e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "handlebars": "^4.7.6", "helmet": "^3.21.2", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.15", @@ -31,6 +32,7 @@ "mongoose": "^5.6.9", "morgan": "^1.9.1", "needle": "^2.4.1", + "nodemailer": "^6.4.17", "passport": "^0.4.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 2145b06..54c8fc3 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -8,11 +8,13 @@ import IUser from '../interfaces/user.interface'; import User from '../models/user.model'; import IRole from '../interfaces/role.interface'; import Role from '../models/role.model'; +import { renderHTML, MailOptions, sendMail } from '../utils/roboSender/sendEmail'; +import { enviarMail, APP_DOMAIN } from '../../config.private'; -class AuthController{ +class AuthController { public register = async (req: Request, res: Response): Promise => { - try{ + try { const { username, email, enrollment, cuil, businessName, password, roleType } = req.body; const newUser = new User({ username, email, password, enrollment, cuil, businessName }); const role: IRole = await Role.schema.methods.findByRoleOrCreate(roleType); @@ -21,13 +23,13 @@ class AuthController{ await newUser.save(); await role.save(); return res.status(200).json({ - newUser + newUser }); - }catch(e){ + } catch (e) { let errors: { [key: string]: string } = {}; Object.keys(e.errors).forEach(prop => { - errors[ prop ] = e.errors[prop].message; + errors[prop] = e.errors[prop].message; }); return res.status(422).json(errors); } @@ -36,15 +38,29 @@ class AuthController{ public resetPassword = async (req: Request, res: Response): Promise => { const { _id } = req.user as IUser; const { oldPassword, newPassword } = req.body; - try{ + try { const user: IUser | null = await User.findOne({ _id }); - if(!user) return res.status(404).json('No se ha encontrado el usuario'); + if (!user) return res.status(404).json('No se ha encontrado el usuario'); const isMatch: boolean = await User.schema.methods.isValidPassword(user, oldPassword); - if(!isMatch) return res.status(401).json({ message: 'Su contraseña actual no coincide con nuestros registros'}); + if (!isMatch) return res.status(401).json({ message: 'Su contraseña actual no coincide con nuestros registros' }); - await user.update({password: newPassword}); + await user.update({ password: newPassword }); return res.status(200).json('Se ha modificado la contraseña correctamente!'); - }catch(err){ + } catch (err) { + console.log(err); + return res.status(500).json('Server Error'); + } + } + + public recoverPassword = async (req: Request, res: Response): Promise => { + const { authenticationToken, newPassword } = req.body; + try { + const user: IUser | null = await User.findOne({ authenticationToken: authenticationToken }); + if (!user) return res.status(404).json('No se ha encontrado el usuario'); + + await user.update({ password: newPassword }); + return res.status(200).json('Se ha modificado la contraseña correctamente!'); + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -52,19 +68,19 @@ class AuthController{ public login = async (req: Request, res: Response): Promise => { const { _id } = req.user as IUser; - try{ + try { - const user: IUser | null = await User.findOne({_id}).populate({path: 'roles', select: 'role'}); + const user: IUser | null = await User.findOne({ _id }).populate({ path: 'roles', select: 'role' }); - if(user){ + if (user) { const roles: string | string[] = []; - await Promise.all(user.roles.map( async (role) => { + await Promise.all(user.roles.map(async (role) => { roles.push(role.role); })); const token = this.signInToken(user._id, user.username, user.businessName, roles); const refreshToken = uuidv4(); - await User.updateOne({_id: user._id}, {refreshToken: refreshToken}); + await User.updateOne({ _id: user._id }, { refreshToken: refreshToken }); return res.status(200).json({ jwt: token, refreshToken: refreshToken @@ -72,7 +88,7 @@ class AuthController{ } return res.status(httpCodes.EXPECTATION_FAILED).json('Debe iniciar sesión');//in the case that not found user - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -80,10 +96,10 @@ class AuthController{ public logout = async (req: Request, res: Response): Promise => { const { refreshToken } = req.body; - try{ + try { await User.findOneAndUpdate({ refreshToken: refreshToken }, { refreshToken: '' }); return res.status(204).json('Logged out successfully!'); - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json("Server error"); } @@ -91,21 +107,21 @@ class AuthController{ public refresh = async (req: Request, res: Response): Promise => { const refreshToken = req.body.refreshToken; - try{ - const user: IUser | null = await User.findOne({refreshToken: refreshToken }).populate({path: 'roles', select: 'role'}); + try { + const user: IUser | null = await User.findOne({ refreshToken: refreshToken }).populate({ path: 'roles', select: 'role' }); - if(user){ + if (user) { // in next version, should embed roles information const roles: string | string[] = []; - await Promise.all(user.roles.map( async (role) => { - roles.push(role.role); + await Promise.all(user.roles.map(async (role) => { + roles.push(role.role); })); const token = this.signInToken(user._id, user.username, user.businessName, roles); // generate a new refresh_token const refreshToken = uuidv4(); - await User.updateOne({_id: user._id}, {refreshToken: refreshToken}); + await User.updateOne({ _id: user._id }, { refreshToken: refreshToken }); return res.status(200).json({ jwt: token, refreshToken: refreshToken @@ -114,7 +130,7 @@ class AuthController{ return res.status(httpCodes.EXPECTATION_FAILED).json('Debe iniciar sesión');//in the case that not found user - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json('Server error'); } @@ -126,23 +142,23 @@ class AuthController{ // son los campos que permitiremos actualizar. const { id } = req.params; const values: any = {}; - try{ + try { _(req.body).forEach((value: string, key: string) => { - if (!_.isEmpty(value) && _.includes(["email", "password", "username", "enrollment", "cuil", "businessName"], key)){ + if (!_.isEmpty(value) && _.includes(["email", "password", "username", "enrollment", "cuil", "businessName"], key)) { values[key] = value; } }); const opts: any = { runValidators: true, new: true, context: 'query' }; - const user: IUser | null = await User.findOneAndUpdate({_id: id}, values, opts).select("username email cuil enrollment businessName"); + const user: IUser | null = await User.findOneAndUpdate({ _id: id }, values, opts).select("username email cuil enrollment businessName"); return res.status(200).json(user); - }catch(e){ + } catch (e) { // formateamos los errores de validacion - if(e.name !== 'undefined' && e.name === 'ValidationError'){ + if (e.name !== 'undefined' && e.name === 'ValidationError') { let errors: { [key: string]: string } = {}; Object.keys(e.errors).forEach(prop => { - errors[ prop ] = e.errors[prop].message; + errors[prop] = e.errors[prop].message; }); return res.status(422).json(errors); } @@ -154,15 +170,15 @@ class AuthController{ public getUser = async (req: Request, res: Response): Promise => { // obtenemos los datos del usuario, buscando por: "email" / "username" / "cuil" const { email, username, cuil } = req.body; - try{ + try { const users: IUser[] | null = await User.find({ - $or: [{"email": email}, {"username": username}, {"cuil": cuil}] + $or: [{ "email": email }, { "username": username }, { "cuil": cuil }] }).select("username email cuil enrollment, businessName"); - if(!users) return res.status(400).json('Usuario no encontrado'); + if (!users) return res.status(400).json('Usuario no encontrado'); return res.status(200).json(users); - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json("Server Error"); } @@ -171,16 +187,16 @@ class AuthController{ public assignRole = async (req: Request, res: Response): Promise => { const { id } = req.params; const { roleId } = req.body; - try{ - const role: IRole | null = await Role.findOne({ _id : roleId }); - if(role){ - await User.findByIdAndUpdate({ _id: id },{ + try { + const role: IRole | null = await Role.findOne({ _id: roleId }); + if (role) { + await User.findByIdAndUpdate({ _id: id }, { roles: role }); } - const user: IUser | null = await User.findOne({ _id : id }); + const user: IUser | null = await User.findOne({ _id: id }); return res.status(200).json(user); - }catch(err){ + } catch (err) { console.log(err); return res.status(500).json('Server Error'); } @@ -201,6 +217,45 @@ class AuthController{ return token; } + /** + * Envía un link para recuperar la contraseña en caso qeu sea un usuario temporal con email (fuera de onelogin). + * AuthUser + */ + public setValidationTokenAndNotify = async (username: string) => { + try { + let usuario: any = await User.findOne({ username }); + if (usuario) { + usuario.authenticationToken = uuidv4(); + console.log(usuario) + await usuario.save(); + + const extras: any = { + titulo: 'Recuperación de contraseña', + usuario, + url: `${APP_DOMAIN}/auth/recovery-password/${usuario.authenticationToken}`, + }; + console.log('extras', extras) + const htmlToSend = await renderHTML('emails/recover-password.html', extras); + + const options: MailOptions = { + from: enviarMail.auth.user, + to: usuario.email.toString(), + subject: 'Recuperación de contraseña', + text: '', + html: htmlToSend, + attachments: null + }; + + await sendMail(options); + return usuario; + } else { + return null; + } + } catch (error) { + throw error; + } + } + } export default new AuthController(); diff --git a/src/interfaces/user.interface.ts b/src/interfaces/user.interface.ts index 3ad8a89..88e2e43 100644 --- a/src/interfaces/user.interface.ts +++ b/src/interfaces/user.interface.ts @@ -9,6 +9,7 @@ export default interface IUser extends Document{ password: string; roles: IRole[]; refreshToken?: string; + authenticationToken?: string; createdAt?: Date; updatedAt?: Date; isValidPassword(thisUser: IUser, password: string): Promise; diff --git a/src/models/user.model.ts b/src/models/user.model.ts index 05b0b8f..6e33947 100644 --- a/src/models/user.model.ts +++ b/src/models/user.model.ts @@ -60,6 +60,9 @@ export const userSchema = new Schema({ refreshToken: { type: String, }, + authenticationToken: { + type: String, + }, createdAt: { type: Date, default: Date.now @@ -68,7 +71,7 @@ export const userSchema = new Schema({ }); // Model -const User: Model = model('User', userSchema); +const User: Model = model('User', userSchema, 'User'); // Model methods User.schema.method('isValidPassword', async function(thisUser: IUser, password: string): Promise{ diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 649c907..0c86c1a 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,18 +1,35 @@ -import { Router} from 'express'; +import { Router } from 'express'; import { passportMiddlewareLocal, checkAuth } from '../middlewares/passport-config.middleware'; import authController from '../controllers/auth.controller'; class AuthRoutes { - constructor(private router: Router = Router()){} + constructor(private router: Router = Router()) { } - routes(): Router{ + routes(): Router { this.router.post('/login', passportMiddlewareLocal, authController.login); this.router.get('/jwt-login', checkAuth, authController.login); // this.router.post('/register', authController.register); this.router.post('/logout', authController.logout); this.router.post('/refresh', authController.refresh); + this.router.post('/recovery-password', authController.recoverPassword); + this.router.post('/setValidationTokenAndNotify', async (req, res, next) => { + try { + const username = req.body.usuario; + if (username) { + const user = await authController.setValidationTokenAndNotify(username); + if(user) { + res.json( { status: 'ok', msg: 'Se ha enviado un correo a su casilla!'}); + } else { + res.json( { status: 'notfound', msg: 'Usuario no encontrado! Por favor revise sus datos'}); + } + return next(403); + } + } catch (error) { + return next(error); + } + }); return this.router; } diff --git a/src/utils/roboSender/sendEmail.ts b/src/utils/roboSender/sendEmail.ts new file mode 100644 index 0000000..d5cc9f4 --- /dev/null +++ b/src/utils/roboSender/sendEmail.ts @@ -0,0 +1,73 @@ +import { enviarMail } from '../../../config.private'; +import * as fs from 'fs'; +import moment = require('moment'); +const handlebars = require('handlebars'); +const path = require('path'); +const nodemailer = require('nodemailer'); + +handlebars.registerHelper('datetime', (dateTime: any) => { + return moment(dateTime).format('D MMM YYYY [a las] H:mm [hs]'); +}); + +export interface MailOptions { + from: string; + to: string; + subject: string; + text: string; + html: string; + attachments: any; +} + +export function sendMail(options: MailOptions) { + return new Promise((resolve, reject) => { + const transporter = nodemailer.createTransport({ + host: enviarMail.host, + port: enviarMail.port, + secure: enviarMail.secure, + auth: enviarMail.auth, + }); + + const mailOptions = { + from: options.from, + to: options.to, + subject: options.subject, + text: options.text, + html: options.html, + attachments: options.attachments + }; + + transporter.sendMail(mailOptions, (error: any, info: any) => { + if (error) { + return reject(error); + } + return resolve(info); + }); + }); +} + +export function renderHTML(templateName: string, extras: any): Promise { + return new Promise((resolve, reject) => { + // [TODO] Analizar el path relativo o absoluto + const TEMPLATE_PATH = './templates/'; + const url = path.join(TEMPLATE_PATH, templateName); + fs.readFile(url, { encoding: 'utf-8' }, (err, html) => { + if (err) { + return reject(err); + } + try { + const template = handlebars.compile(html); + const htmlToSend = template(extras); + return resolve(htmlToSend); + } catch (exp) { + return reject(exp); + } + + }); + }); +} + +export function registerPartialTemplate(key: string, fileName: string) { + const filePath = path.join(process.cwd(), `templates/${fileName}`); + const file = fs.readFileSync(filePath); + handlebars.registerPartial(key, file.toString()); +} diff --git a/templates/emails/recover-password.html b/templates/emails/recover-password.html new file mode 100644 index 0000000..9af753c --- /dev/null +++ b/templates/emails/recover-password.html @@ -0,0 +1,12 @@ +{{#> layout }} +{{#with usuario}} +
+ Hola {{nombre}} {{apellido}}
+
+

+ Recibiste este mensaje porque solicitaste cambiar tu contraseña. +

+{{/with}} +
+Para obtener una nueva contraseña, dirigite al formulario de Regeneración de contraseña +{{/layout}} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e687315..7f8d846 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -64,7 +64,7 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": [ - "./src/**/*" + "./src/**/*", "config.private.ts" ], "exclude": [ "node_modules" From 5cff75013402edc34dcc49bbad512ebbcdd6cf39 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 17 Feb 2021 12:48:20 -0300 Subject: [PATCH 03/12] fix user model --- src/models/user.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/user.model.ts b/src/models/user.model.ts index 6e33947..0cb8492 100644 --- a/src/models/user.model.ts +++ b/src/models/user.model.ts @@ -71,7 +71,7 @@ export const userSchema = new Schema({ }); // Model -const User: Model = model('User', userSchema, 'User'); +const User: Model = model('User', userSchema); // Model methods User.schema.method('isValidPassword', async function(thisUser: IUser, password: string): Promise{ From 6306d804dae8cae210d035d381564098feef17d8 Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 18 Feb 2021 08:54:48 -0300 Subject: [PATCH 04/12] fix username on the template --- src/controllers/auth.controller.ts | 3 --- src/utils/roboSender/sendEmail.ts | 11 ++++++----- templates/emails/recover-password.html | 5 ++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 54c8fc3..58ed901 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -226,7 +226,6 @@ class AuthController { let usuario: any = await User.findOne({ username }); if (usuario) { usuario.authenticationToken = uuidv4(); - console.log(usuario) await usuario.save(); const extras: any = { @@ -234,9 +233,7 @@ class AuthController { usuario, url: `${APP_DOMAIN}/auth/recovery-password/${usuario.authenticationToken}`, }; - console.log('extras', extras) const htmlToSend = await renderHTML('emails/recover-password.html', extras); - const options: MailOptions = { from: enviarMail.auth.user, to: usuario.email.toString(), diff --git a/src/utils/roboSender/sendEmail.ts b/src/utils/roboSender/sendEmail.ts index d5cc9f4..5829659 100644 --- a/src/utils/roboSender/sendEmail.ts +++ b/src/utils/roboSender/sendEmail.ts @@ -1,11 +1,12 @@ import { enviarMail } from '../../../config.private'; import * as fs from 'fs'; import moment = require('moment'); -const handlebars = require('handlebars'); +import Handlebars from 'handlebars'; +// const handlebars = require('handlebars'); const path = require('path'); const nodemailer = require('nodemailer'); -handlebars.registerHelper('datetime', (dateTime: any) => { +Handlebars.registerHelper('datetime', (dateTime: any) => { return moment(dateTime).format('D MMM YYYY [a las] H:mm [hs]'); }); @@ -55,8 +56,8 @@ export function renderHTML(templateName: string, extras: any): Promise { return reject(err); } try { - const template = handlebars.compile(html); - const htmlToSend = template(extras); + const template = Handlebars.compile(html); + const htmlToSend = template({nombre: extras.usuario.businessName, url: extras.url}); return resolve(htmlToSend); } catch (exp) { return reject(exp); @@ -69,5 +70,5 @@ export function renderHTML(templateName: string, extras: any): Promise { export function registerPartialTemplate(key: string, fileName: string) { const filePath = path.join(process.cwd(), `templates/${fileName}`); const file = fs.readFileSync(filePath); - handlebars.registerPartial(key, file.toString()); + Handlebars.registerPartial(key, file.toString()); } diff --git a/templates/emails/recover-password.html b/templates/emails/recover-password.html index 9af753c..dc21e66 100644 --- a/templates/emails/recover-password.html +++ b/templates/emails/recover-password.html @@ -1,12 +1,11 @@ {{#> layout }} -{{#with usuario}}
- Hola {{nombre}} {{apellido}}
+ Hola {{ nombre }}!

Recibiste este mensaje porque solicitaste cambiar tu contraseña.

-{{/with}}
+ Para obtener una nueva contraseña, dirigite al formulario de Regeneración de contraseña {{/layout}} \ No newline at end of file From 8985de972e7217dfe16a85075dc534e2c6e1bfe1 Mon Sep 17 00:00:00 2001 From: paul Date: Thu, 18 Feb 2021 12:25:54 -0300 Subject: [PATCH 05/12] fix env var --- config.private.example.ts | 27 --------------------------- src/controllers/auth.controller.ts | 5 ++--- src/utils/roboSender/sendEmail.ts | 12 +++++++----- 3 files changed, 9 insertions(+), 35 deletions(-) delete mode 100644 config.private.example.ts diff --git a/config.private.example.ts b/config.private.example.ts deleted file mode 100644 index 201c298..0000000 --- a/config.private.example.ts +++ /dev/null @@ -1,27 +0,0 @@ -function getEnv(key: any, _default: any, type = 's') { - if (!!process.env[key] === false) { - return _default; - } - const value: any = process.env[key]; - switch (type) { - case 'b': - return value.toLowerCase() === 'true'; - case 'n': - return parseInt(value, 10); - default: - return value; - } -} -​ -// E-mail server settings -export const enviarMail = { - host: getEnv('EMAIL_HOST', 'smtp.gmail.com'), - port: getEnv('EMAIL_PORT', 587, 'n'), - secure: getEnv('EMAIL_SECURE', false, 'b'), - auth: { - user: getEnv('EMAIL_USERNAME', 'xxxxxxxxx@gmail.com'), - pass: getEnv('EMAIL_PASSWORD', 'xxxxxxx') - } -}; - -export const APP_DOMAIN = getEnv('APP_DOMAIN', 'http://localhost:4200'); \ No newline at end of file diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 58ed901..65de6fc 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -9,7 +9,6 @@ import User from '../models/user.model'; import IRole from '../interfaces/role.interface'; import Role from '../models/role.model'; import { renderHTML, MailOptions, sendMail } from '../utils/roboSender/sendEmail'; -import { enviarMail, APP_DOMAIN } from '../../config.private'; class AuthController { @@ -231,11 +230,11 @@ class AuthController { const extras: any = { titulo: 'Recuperación de contraseña', usuario, - url: `${APP_DOMAIN}/auth/recovery-password/${usuario.authenticationToken}`, + url: `${process.env.APP_DOMAIN}/auth/recovery-password/${usuario.authenticationToken}`, }; const htmlToSend = await renderHTML('emails/recover-password.html', extras); const options: MailOptions = { - from: enviarMail.auth.user, + from: `${process.env.EMAIL_USERNAME}`, to: usuario.email.toString(), subject: 'Recuperación de contraseña', text: '', diff --git a/src/utils/roboSender/sendEmail.ts b/src/utils/roboSender/sendEmail.ts index 5829659..04f761d 100644 --- a/src/utils/roboSender/sendEmail.ts +++ b/src/utils/roboSender/sendEmail.ts @@ -1,4 +1,3 @@ -import { enviarMail } from '../../../config.private'; import * as fs from 'fs'; import moment = require('moment'); import Handlebars from 'handlebars'; @@ -22,10 +21,13 @@ export interface MailOptions { export function sendMail(options: MailOptions) { return new Promise((resolve, reject) => { const transporter = nodemailer.createTransport({ - host: enviarMail.host, - port: enviarMail.port, - secure: enviarMail.secure, - auth: enviarMail.auth, + host: `${process.env.EMAIL_HOST}`, + port: parseInt(`${process.env.EMAIL_PORT}`, 10), + secure: (`${process.env.EMAIL_SECURE}` === 'true'), + auth: { + user: `${process.env.EMAIL_USERNAME}`, + pass: `${process.env.EMAIL_PASSWORD}` + }, }); const mailOptions = { From 301bd42dfc4ad5c9c5d9ca9aba46611db09fd4a8 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 1 Mar 2021 11:27:20 -0300 Subject: [PATCH 06/12] set register path as public --- src/routes/private.ts | 2 +- src/routes/public.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/private.ts b/src/routes/private.ts index 9395c81..27a8265 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -32,7 +32,7 @@ class PrivateRoutes{ // this.router.post('/roles/:id/assign-user', roleController.assignUser); this.router.get('/auth/user/find', hasPermissionIn('readAny','user'), authController.getUser); - this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); + // this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); this.router.post('/auth/reset-password', authController.resetPassword); this.router.patch('/auth/user/:id', hasPermissionIn('updateAny','user'), authController.updateUser); diff --git a/src/routes/public.ts b/src/routes/public.ts index 7f9c34c..df2d840 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -1,4 +1,6 @@ import { Router } from 'express'; +import authController from '../controllers/auth.controller'; + class PublicRoutes{ constructor(private router: Router = Router()){} @@ -6,6 +8,7 @@ class PublicRoutes{ // deefine your public routes inside of routes function public routes(): Router{ // this.router.get('home', (req: Request, res: Response): Response => { return res.send('Welcome home') } ) // example + this.router.post('/auth/register', authController.register); return this.router; } } From 44b4f3262e572a66c0104cc7a83f3ce770ead62b Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 1 Mar 2021 11:44:23 -0300 Subject: [PATCH 07/12] rollback register path to private --- src/routes/private.ts | 2 +- src/routes/public.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/private.ts b/src/routes/private.ts index 27a8265..9395c81 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -32,7 +32,7 @@ class PrivateRoutes{ // this.router.post('/roles/:id/assign-user', roleController.assignUser); this.router.get('/auth/user/find', hasPermissionIn('readAny','user'), authController.getUser); - // this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); + this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); this.router.post('/auth/reset-password', authController.resetPassword); this.router.patch('/auth/user/:id', hasPermissionIn('updateAny','user'), authController.updateUser); diff --git a/src/routes/public.ts b/src/routes/public.ts index df2d840..4a0165d 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -1,5 +1,4 @@ import { Router } from 'express'; -import authController from '../controllers/auth.controller'; class PublicRoutes{ @@ -8,7 +7,6 @@ class PublicRoutes{ // deefine your public routes inside of routes function public routes(): Router{ // this.router.get('home', (req: Request, res: Response): Response => { return res.send('Welcome home') } ) // example - this.router.post('/auth/register', authController.register); return this.router; } } From 2f6bd794bd94564741c536025b4b0cd8af44cfe2 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 1 Mar 2021 12:32:40 -0300 Subject: [PATCH 08/12] adds andes end point --- .../andesPrescription.controller.ts | 20 +++++++++++++++++++ src/routes/private.ts | 3 +++ src/utils/rbac_abac.ts | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/controllers/andesPrescription.controller.ts diff --git a/src/controllers/andesPrescription.controller.ts b/src/controllers/andesPrescription.controller.ts new file mode 100644 index 0000000..ec4168b --- /dev/null +++ b/src/controllers/andesPrescription.controller.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express'; + +class AndesPrescriptionController { + + public create = async (req: Request, res: Response): Promise => { + try{ + const body = req.body; + const params = req.params; + console.log( body, params); + + return res.status(200).json( { msg: "Success", body: req.body} ); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + +} + +export default new AndesPrescriptionController(); diff --git a/src/routes/private.ts b/src/routes/private.ts index 9395c81..e89a62f 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -13,6 +13,7 @@ import patientController from '../controllers/patient.controller'; // import pharmacyController from '../controllers/pharmacy.controller'; import supplyController from '../controllers/supply.controller'; import authController from '../controllers/auth.controller'; +import andesController from '../controllers/andesPrescription.controller'; class PrivateRoutes{ constructor(private router: Router = Router()){} @@ -32,6 +33,7 @@ class PrivateRoutes{ // this.router.post('/roles/:id/assign-user', roleController.assignUser); this.router.get('/auth/user/find', hasPermissionIn('readAny','user'), authController.getUser); + this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); this.router.post('/auth/reset-password', authController.resetPassword); this.router.patch('/auth/user/:id', hasPermissionIn('updateAny','user'), authController.updateUser); @@ -95,6 +97,7 @@ class PrivateRoutes{ // this.router.put(`/supplies/:id`, hasPermissionIn('updateAny','patient'), supplyController.update); // this.router.delete(`/supplies/:id`, hasPermissionIn('deleteAny','patient'), supplyController.delete); + this.router.post('/andes-prescriptions', hasPermissionIn('createAny','user'), andesController.create); return this.router; } } diff --git a/src/utils/rbac_abac.ts b/src/utils/rbac_abac.ts index fc07276..b96c235 100644 --- a/src/utils/rbac_abac.ts +++ b/src/utils/rbac_abac.ts @@ -54,7 +54,8 @@ class AccessControlLoader { { role: 'professional', resource: 'supplies', action: 'read:any', attributes: '*' }, { role: 'pharmacist', resource: 'supplies', action: 'read:any', attributes: '*' }, { role: 'admin', resource: 'supplies', action: 'create:any', attributes: '*' }, - { role: 'admin', resource: 'supplies', action: 'update:any', attributes: '*' } + { role: 'admin', resource: 'supplies', action: 'update:any', attributes: '*' }, + { role: 'andes', resource: 'andesPrescription', action: 'create:any', attributes: '*' } ]; this.accessControl.setGrants(grantList); console.log('grants initialized'); From 2ed629589183eb3640d1b3159b19c8791b604fa5 Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 1 Mar 2021 12:42:27 -0300 Subject: [PATCH 09/12] fix andes permision route --- src/routes/private.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/private.ts b/src/routes/private.ts index e89a62f..6db208a 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -97,7 +97,7 @@ class PrivateRoutes{ // this.router.put(`/supplies/:id`, hasPermissionIn('updateAny','patient'), supplyController.update); // this.router.delete(`/supplies/:id`, hasPermissionIn('deleteAny','patient'), supplyController.delete); - this.router.post('/andes-prescriptions', hasPermissionIn('createAny','user'), andesController.create); + this.router.post('/andes-prescriptions', hasPermissionIn('createAny','andesPrescription'), andesController.create); return this.router; } } From 632cdee1c1d719780d007ab4dcbbca78521380d4 Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 2 Mar 2021 10:12:06 -0300 Subject: [PATCH 10/12] adds bearer jwt token without expire date --- src/controllers/auth.controller.ts | 32 ++++++++++ .../passport-config-andes.middleware.ts | 60 +++++++++++++++++++ src/routes/andes.ts | 20 +++++++ src/routes/private.ts | 4 +- src/routes/routes.ts | 5 +- 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/middlewares/passport-config-andes.middleware.ts create mode 100644 src/routes/andes.ts diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 65de6fc..46fef2d 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -201,6 +201,38 @@ class AuthController { } } + /* modificar toke */ + public getToken = async (req: Request, res: Response): Promise => { + const { username } = req.body; + + try { + const user: IUser | null = await User.findOne({ username: username }).populate({ path: 'roles', select: 'role' }); + if (!user) { + return res.status(422).json({message: "Usuario no encontrado."}); + } + // in next version, should embed roles information + const roles: string | string[] = []; + await Promise.all(user.roles.map(async (role) => { + roles.push(role.role); + })); + + const token = JWT.sign({ + iss: "recetar.andes", + sub: user._id, + usrn: user.username, + bsname: user.businessName, + rl: roles, + iat: new Date().getTime() + }, (process.env.JWT_SECRET || env.JWT_SECRET), { + algorithm: 'HS256' + }); + return res.status(200).json({jwt: token}); + } catch (err) { + console.log(err); + return res.status(500).json('Server Error'); + } + } + private signInToken = (userId: string, username: string, businessName: string, role: string | string[]): any => { const token = JWT.sign({ iss: "recetar.andes", diff --git a/src/middlewares/passport-config-andes.middleware.ts b/src/middlewares/passport-config-andes.middleware.ts new file mode 100644 index 0000000..465331a --- /dev/null +++ b/src/middlewares/passport-config-andes.middleware.ts @@ -0,0 +1,60 @@ +import {Request, Response, NextFunction} from 'express'; +import passport from 'passport'; +import passportJwt from 'passport-jwt'; +import { env, httpCodes } from '../config/config'; +import User from '../models/user.model'; +import IUser from '../interfaces/user.interface'; + +const JwtStrategy = passportJwt.Strategy; +const ExtractJwt = passportJwt.ExtractJwt; + +// Config passport JWT strategy +// We will use Bearer token to authenticate +// This configuration checks: +// -token expiration +// -user exists +passport.use(new JwtStrategy({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: (process.env.JWT_SECRET || env.JWT_SECRET) +}, async (payload, done: (err?: any, user?: IUser | boolean, info?: {code: number, message: string}) => any | Response) => { + try{ + // find the user specified in token + const user = await User.findOne({ _id: payload.sub }).select('_id'); + + // if user doesn't exists, handle it + if(!user){ + return done(null, false, {code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión'}); + } + + // otherwise, return the user + done(null, user); + }catch(err){ + console.log('in error'); + done(err, false); + } +})); + + + +const authenticationMiddleware = (req: Request, res: Response, next: NextFunction, authenticationType: string) => { + passport.authenticate(authenticationType, {session: false}, (err, user: IUser | boolean, info?: {code: number, message: string}): any | Response => { + try{ + + if (err) return next(err) + + if(typeof(info) !== 'undefined') return res.status(info.code).json({message: info.message}); + + req.user = user; + next(); + }catch(error){ + if(error.code == 'ERR_HTTP_INVALID_STATUS_CODE') return res.status(httpCodes.EXPECTATION_FAILED).json({message: 'Debe iniciar sesión'}); + + return res.status(500).json('Server Error') + } + })(req, res, next); + +}; + +export const checkAuthAndes = (req: Request, res: Response, next: NextFunction) => { + authenticationMiddleware(req, res, next, 'jwt'); +} diff --git a/src/routes/andes.ts b/src/routes/andes.ts new file mode 100644 index 0000000..5798498 --- /dev/null +++ b/src/routes/andes.ts @@ -0,0 +1,20 @@ +import { Router } from 'express'; +import andesController from '../controllers/andesPrescription.controller'; +import { checkAuthAndes } from '../middlewares/passport-config-andes.middleware'; + + +class AndesRoutes{ + + constructor(private router: Router = Router()){} + + // deefine your public routes inside of routes function + public routes(): Router{ + + this.router.post('/prescriptions', checkAuthAndes, andesController.create); + + return this.router; + } +} + +const andesRoutes: AndesRoutes = new AndesRoutes(); +export default andesRoutes.routes(); diff --git a/src/routes/private.ts b/src/routes/private.ts index 6db208a..db538c6 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -13,7 +13,7 @@ import patientController from '../controllers/patient.controller'; // import pharmacyController from '../controllers/pharmacy.controller'; import supplyController from '../controllers/supply.controller'; import authController from '../controllers/auth.controller'; -import andesController from '../controllers/andesPrescription.controller'; + class PrivateRoutes{ constructor(private router: Router = Router()){} @@ -32,6 +32,7 @@ class PrivateRoutes{ // this.router.post('/roles/:id/assign-user', roleController.assignUser); + this.router.get('/user/get-token', hasPermissionIn('readAny','user'), authController.getToken); this.router.get('/auth/user/find', hasPermissionIn('readAny','user'), authController.getUser); this.router.post('/auth/register', hasPermissionIn('updateAny','user'), authController.register); @@ -97,7 +98,6 @@ class PrivateRoutes{ // this.router.put(`/supplies/:id`, hasPermissionIn('updateAny','patient'), supplyController.update); // this.router.delete(`/supplies/:id`, hasPermissionIn('deleteAny','patient'), supplyController.delete); - this.router.post('/andes-prescriptions', hasPermissionIn('createAny','andesPrescription'), andesController.create); return this.router; } } diff --git a/src/routes/routes.ts b/src/routes/routes.ts index b84bfc3..65aa4e2 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -2,10 +2,12 @@ import { Router } from 'express'; import { checkAuth } from '../middlewares/passport-config.middleware'; + // routes import authRoutes from './auth'; import pbulicRoutes from './public'; import privateRoutes from './private'; +import andesRoutes from './andes'; class Routes { @@ -16,7 +18,8 @@ class Routes { // auth this.router.use('/auth', authRoutes); this.router.use('', pbulicRoutes); - + // andes specific routes + this.router.use('/andes', andesRoutes); // private: requires authentication this.router.all('*', checkAuth, privateRoutes); From ff88e41e3694885a6def06bb8ec24c102a0eebd9 Mon Sep 17 00:00:00 2001 From: plammel Date: Fri, 12 Mar 2021 15:30:19 -0300 Subject: [PATCH 11/12] get de productos --- src/controllers/prescription.controller.ts | 10 ++++++++ src/controllers/supply.controller.ts | 27 ++++++++++++++++++++++ src/interfaces/supply.interface.ts | 10 ++++---- src/routes/private.ts | 4 +++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/controllers/prescription.controller.ts b/src/controllers/prescription.controller.ts index a52b4c1..3084ce5 100644 --- a/src/controllers/prescription.controller.ts +++ b/src/controllers/prescription.controller.ts @@ -100,6 +100,16 @@ class PrescriptionController implements BaseController{ } } + public get = async (req: Request, res: Response): Promise => { + try{ + const prescriptions: IPrescription[] | null = await Prescription.find({}).sort({ field: 'desc', date: -1}); + return res.status(200).json(prescriptions); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + public getByUserId = async (req: Request, res: Response): Promise => { try{ const { userId } = req.params; diff --git a/src/controllers/supply.controller.ts b/src/controllers/supply.controller.ts index 90bb732..18b4425 100644 --- a/src/controllers/supply.controller.ts +++ b/src/controllers/supply.controller.ts @@ -123,6 +123,33 @@ class SupplyController implements BaseController{ } } + public get = async (req: Request, res: Response): Promise => { + try{ + const { supplyName } = req.query; + let target: string = decodeURIComponent(supplyName); + target = target.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + let search: string = ""; + let supplies: ISupply[]; + // first word should apper in all coincidence + const words = target.split(" "); + if(words.length > 1){ + // find by multiple words + for(let i = 0; i < words.length; i++){ + search += '\"' + words[i].trim() + '\"' + " "; + } + supplies = await Supply.find({$text: {$search: search}}).limit(20); + }else{ + // find by regex with the first word + supplies = await Supply.find({name: { $regex: new RegExp( target, "ig")} }).limit(20); + } + + return res.status(200).json(supplies); + }catch(err){ + console.log(err); + return res.status(500).json('Server Error'); + } + } + } export default new SupplyController(); diff --git a/src/interfaces/supply.interface.ts b/src/interfaces/supply.interface.ts index c829af1..dcf3ba1 100644 --- a/src/interfaces/supply.interface.ts +++ b/src/interfaces/supply.interface.ts @@ -3,10 +3,12 @@ import { Document } from 'mongoose'; export default interface ISupply extends Document { id: string; name: string; + activePrinciple: string; + power: string; + unity: string; + firstPresentation: string; + secondPresentation: string; description: string; observation: string; - unity: string; - supply_area: string; - createdAt?: Date; - updatedAt?: Date; + pharmaceutical_form: string; } \ No newline at end of file diff --git a/src/routes/private.ts b/src/routes/private.ts index 9395c81..d1a7e55 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -39,6 +39,8 @@ class PrivateRoutes{ this.router.get('/patients/get-by-dni/:dni', patientController.getByDni); this.router.get('/prescriptions/find/:patient_id&:date?', prescriptionController.getPrescriptionsByDateOrPatientId); this.router.get('/prescriptions/get-by-user-id/:userId', prescriptionController.getByUserId); + this.router.get('/prescriptions', prescriptionController.get); + this.router.get('/supplies', supplyController.get); this.router.get('/supplies/get-by-name', supplyController.getByName); // roles @@ -88,7 +90,7 @@ class PrivateRoutes{ // this.router.delete(`/professionals/:id`, hasPermissionIn('deleteAny','patient'), professionalController.delete); // supply - this.router.get(`/supplies/`, hasPermissionIn('readAny','patient'), supplyController.index); + this.router.get(`/supplies/`, hasPermissionIn('readAny','supplies'), supplyController.index); this.router.post(`/supplies/`, hasPermissionIn('createAny','supplies'), supplyController.create); this.router.patch('/supplies/:id', hasPermissionIn('updateAny','supplies'), supplyController.update); // this.router.get(`/supplies/:id`, hasPermissionIn('readAny','patient'), supplyController.show); From 4343262e992b461503fd8787b1bc7fd5334b854c Mon Sep 17 00:00:00 2001 From: paul Date: Mon, 19 Apr 2021 11:23:28 -0300 Subject: [PATCH 12/12] permit user with admin role to cancel a dispensed prescription --- src/controllers/prescription.controller.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/controllers/prescription.controller.ts b/src/controllers/prescription.controller.ts index 3084ce5..06c5b1b 100644 --- a/src/controllers/prescription.controller.ts +++ b/src/controllers/prescription.controller.ts @@ -7,8 +7,10 @@ import Supply from '../models/supply.model'; import IPatient from '../interfaces/patient.interface'; import Patient from '../models/patient.model'; import User from '../models/user.model'; +import Role from '../models/role.model'; import IUser from '../interfaces/user.interface'; import moment = require('moment'); +import IRole from '../interfaces/role.interface'; class PrescriptionController implements BaseController{ @@ -160,15 +162,19 @@ class PrescriptionController implements BaseController{ const { pharmacistId } = req.body; const dispensedBy: IUser | null = await User.findOne({_id: pharmacistId}); + if(!dispensedBy) return res.status(4000).json("Farmacia no encontrada"); + const userRole: IRole | null = await Role.findOne({role: "admin", _id: { $in: dispensedBy.roles } }) // checkeamos el rol del usuario no sea admin + const controlPrescription: IPrescription | null = await Prescription.findOne({_id: id, status: 'Dispensada'}); if(!controlPrescription) return res.status(404).json('La receta no se encontró.'); const limitTime = moment(controlPrescription.dispensedAt).add(2, 'hours'); // plus 2 hours to dispensedBy const timeNow = moment(); - - if(timeNow.isAfter(limitTime)) return res.status(422).json('Ya no se puede anular la dispensa de la receta.'); + + /* Si ya pasó el tiempo valido para cancelar y no tiene rol admin, entonces cancelamos la accion */ + if(timeNow.isAfter(limitTime) && userRole?.role !== 'admin') return res.status(422).json('Ya no se puede anular la dispensa de la receta.'); const opts: any = {new: true}; const prescription: IPrescription | null = await Prescription.findOneAndUpdate({_id: id, status: 'Dispensada'}, {