├── .gitignore ├── Documentos ├── Documentos.md └── images │ └── login.png ├── Frontend ├── Procfile ├── src │ ├── containers │ │ ├── Pagination.jsx │ │ ├── NotFound.jsx │ │ ├── PatientInfo.jsx │ │ ├── Exam.jsx │ │ ├── User.jsx │ │ ├── Patient.jsx │ │ ├── Doctor.jsx │ │ ├── AddExamsResults.jsx │ │ ├── Bacteriologist.jsx │ │ ├── Administrator.jsx │ │ ├── AddUser.jsx │ │ └── HomeLogin.jsx │ ├── assets │ │ ├── styles │ │ │ ├── containers │ │ │ │ ├── AddUser.scss │ │ │ │ ├── AddExams.scss │ │ │ │ ├── PatientList.scss │ │ │ │ ├── Login.scss │ │ │ │ ├── Patient.scss │ │ │ │ └── Administrator.scss │ │ │ ├── components │ │ │ │ ├── Useritem.scss │ │ │ │ ├── PatientData.scss │ │ │ │ ├── NavbarDoctor.scss │ │ │ │ ├── User.scss │ │ │ │ ├── ExamItem.scss │ │ │ │ ├── SheduleExams.scss │ │ │ │ ├── RememberInfo.scss │ │ │ │ ├── Modal.scss │ │ │ │ └── Header.scss │ │ │ ├── Media.scss │ │ │ └── App.scss │ │ └── static │ │ │ ├── edit.png │ │ │ ├── logo.png │ │ │ ├── csv-file.png │ │ │ ├── logotipo.png │ │ │ ├── logotype.png │ │ │ ├── delete-user.png │ │ │ ├── edit-user.png │ │ │ ├── iconotipo.png │ │ │ ├── circle-regular.png │ │ │ ├── logo-horizontal.png │ │ │ └── delete-user-black.png │ ├── components │ │ ├── Layout.jsx │ │ ├── Modal.jsx │ │ ├── ExamList.jsx │ │ ├── PatientList.jsx │ │ ├── PersonalInfo.jsx │ │ ├── AddResultsModal.jsx │ │ ├── MedicalHistory.jsx │ │ ├── SheduleExams.jsx │ │ ├── NavbarDoctor.jsx │ │ ├── ExamItem.jsx │ │ ├── RememberInfo.jsx │ │ ├── UserItem.jsx │ │ └── Header.jsx │ ├── utils │ │ └── gravatar.jsx │ ├── conf │ │ ├── config-firebase.json │ │ └── firebase.js │ ├── actions │ │ └── index.js │ ├── hooks │ │ └── useInitialState.js │ ├── reducers │ │ └── index.js │ ├── sw │ │ └── firebase-messaging-sw.js │ ├── routes │ │ └── App.jsx │ ├── mocks │ │ ├── ExamMock.js │ │ └── UsersMock.json │ └── index.js ├── static.json ├── .prettierrc ├── babel.config.js ├── .env.example ├── public │ └── index.html ├── README.md ├── webpack.config.js ├── package.json ├── .gitignore └── .eslintrc ├── Backend ├── Procfile ├── jest.config.js ├── config │ ├── database.js │ └── config.js ├── api │ ├── components │ │ ├── exams │ │ │ ├── index.js │ │ │ ├── controller.js │ │ │ └── network.js │ │ ├── notification │ │ │ ├── index.js │ │ │ ├── network.js │ │ │ └── controller.js │ │ ├── auth │ │ │ ├── index.js │ │ │ ├── network.js │ │ │ └── controller.js │ │ ├── user │ │ │ ├── index.js │ │ │ └── controller.js │ │ ├── email │ │ │ ├── index.js │ │ │ ├── templates │ │ │ │ └── sendNewUser.js │ │ │ └── controller.js │ │ ├── auth_roles │ │ │ ├── index.js │ │ │ └── controller.js │ │ └── welcome │ │ │ └── network.js │ └── index.js ├── .sequelizerc ├── auth │ ├── index.js │ ├── roles.js │ └── strategies │ │ ├── basic.js │ │ ├── cookie.js │ │ └── jwt.js ├── utils │ ├── testServer.js │ └── userUtils.js ├── .eslintrc.js ├── .env_example ├── store │ ├── mocks │ │ ├── mockDB.js │ │ ├── storeMock.js │ │ ├── RolesMock.js │ │ ├── exams │ │ │ └── BoodExam.js │ │ ├── AuthMock.js │ │ └── UsersMock.json │ ├── dummy.js │ └── sequelize.js ├── network │ ├── response.js │ ├── errors.js │ └── __test__ │ │ └── errors.spec.js ├── db │ ├── models │ │ ├── exam.js │ │ ├── auth.js │ │ ├── examstatus.js │ │ ├── rolepermission.js │ │ ├── examtype.js │ │ ├── permission.js │ │ ├── authRole.js │ │ ├── role.js │ │ ├── firebasenotificationtoken.js │ │ ├── user.js │ │ ├── medicalrecord.js │ │ └── index.js │ ├── seeders │ │ └── 20200531065934-roles.js │ └── migrations │ │ ├── 20200526161357-create-auth.js │ │ ├── 20200527002438-create-role.js │ │ ├── 20200527010113-create-auth-role.js │ │ ├── 20200527002503-create-permission.js │ │ ├── 20200528063216-create-examStatus.js │ │ ├── 20200527002633-create-role-permission.js │ │ ├── 20200531013304-create-firebase-notification-token.js │ │ ├── 20200528062121-create-examtype.js │ │ ├── 20200528063918-create-exam.js │ │ ├── 20200527024840-create-user.js │ │ └── 20200528043643-create-medicalrecord.js ├── __test__ │ ├── api.components.welcome.spec.js │ ├── api.components.user.spec.js │ └── api.components.auth.spec.js ├── package.json └── .gitignore ├── LICENSE ├── .travis.yml ├── CODE_OF_CONDUCT.md └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /Documentos/Documentos.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Frontend/Procfile: -------------------------------------------------------------------------------- 1 | web: bin/boot -------------------------------------------------------------------------------- /Backend/Procfile: -------------------------------------------------------------------------------- 1 | web: node api/index.js -------------------------------------------------------------------------------- /Frontend/src/containers/Pagination.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/AddUser.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Backend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true 3 | } 4 | -------------------------------------------------------------------------------- /Documentos/images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Documentos/images/login.png -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/Useritem.scss: -------------------------------------------------------------------------------- 1 | .user-item__details--img { 2 | width: 20px; 3 | } -------------------------------------------------------------------------------- /Frontend/static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "dist/", 3 | "routes": { 4 | "/**": "index.html" 5 | } 6 | } -------------------------------------------------------------------------------- /Frontend/src/assets/static/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/edit.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logo.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/csv-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/csv-file.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/logotipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logotipo.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logotype.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/delete-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/delete-user.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/edit-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/edit-user.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/iconotipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/iconotipo.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/circle-regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/circle-regular.png -------------------------------------------------------------------------------- /Frontend/src/assets/static/logo-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logo-horizontal.png -------------------------------------------------------------------------------- /Frontend/src/assets/styles/Media.scss: -------------------------------------------------------------------------------- 1 | @media screen and(max-width: 736px) { 2 | 3 | } 4 | 5 | @media screen and(max-width: 480px) { 6 | 7 | } -------------------------------------------------------------------------------- /Frontend/src/assets/static/delete-user-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/delete-user-black.png -------------------------------------------------------------------------------- /Frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 85, 3 | "arrowParens": "always", 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true 7 | } -------------------------------------------------------------------------------- /Backend/config/database.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | const database = { 3 | ...config.sequelize 4 | } 5 | 6 | module.exports = database 7 | -------------------------------------------------------------------------------- /Backend/api/components/exams/index.js: -------------------------------------------------------------------------------- 1 | const store = require('../../../store/dummy') 2 | const ctrl = require('./controller') 3 | 4 | module.exports = ctrl(store) 5 | -------------------------------------------------------------------------------- /Backend/api/components/notification/index.js: -------------------------------------------------------------------------------- 1 | const store = require('../../../store/sequelize') 2 | const ctrl = require('./controller') 3 | 4 | module.exports = ctrl(store) 5 | -------------------------------------------------------------------------------- /Frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react'], 3 | plugins: ['@babel/plugin-proposal-class-properties'], 4 | }; 5 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/PatientData.scss: -------------------------------------------------------------------------------- 1 | .Patient__personal_data, .Patient__medical_history { 2 | display: grid; 3 | grid-template-columns: 30% 35%; 4 | align-items: center; 5 | } 6 | -------------------------------------------------------------------------------- /Backend/api/components/auth/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config/config') 2 | const store = require(`../../../store/${config.storeMotor}`) 3 | const ctrl = require('./controller') 4 | 5 | module.exports = ctrl(store) 6 | -------------------------------------------------------------------------------- /Backend/api/components/user/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config/config') 2 | const store = require(`../../../store/${config.storeMotor}`) 3 | const ctrl = require('./controller') 4 | 5 | module.exports = ctrl(store) 6 | -------------------------------------------------------------------------------- /Frontend/src/containers/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NotFound = () => ( 4 | <> 5 |

No encontrado

6 |

Regresa al Home

7 | 8 | ); 9 | export default NotFound; 10 | -------------------------------------------------------------------------------- /Backend/api/components/email/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config/config') 2 | const store = require(`../../../store/${config.storeMotor}`) 3 | const ctrl = require('./controller') 4 | 5 | module.exports = ctrl(store) 6 | -------------------------------------------------------------------------------- /Backend/api/components/auth_roles/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config/config') 2 | const store = require(`../../../store/${config.storeMotor}`) 3 | const ctrl = require('./controller') 4 | 5 | module.exports = ctrl(store) 6 | -------------------------------------------------------------------------------- /Backend/.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('config', 'database.js'), 5 | 'models-path': path.resolve('db', 'models'), 6 | 'seeders-path': path.resolve('db', 'seeders'), 7 | 'migrations-path': path.resolve('db', 'migrations') 8 | }; -------------------------------------------------------------------------------- /Backend/auth/index.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const config = require('../config/config') 3 | const secret = config.jwt.secret 4 | 5 | async function sign (data) { 6 | return jwt.sign(data, secret, { 7 | expiresIn: '90m' 8 | }) 9 | } 10 | 11 | module.exports = { 12 | sign 13 | } 14 | -------------------------------------------------------------------------------- /Frontend/.env.example: -------------------------------------------------------------------------------- 1 | FIREBASE_CONFIG_API_KEY= 2 | 3 | FIREBASE_CONFIG_AUTH_DOMAIN= 4 | 5 | FIREBASE_CONFIG_DATABASE_URL= 6 | 7 | FIREBASE_CONFIG_PROJECT_ID= 8 | 9 | FIREBASE_CONFIG_STORAGE_BUCKET= 10 | 11 | FIREBASE_CONFIG_MESSAGING_SENDER_ID= 12 | 13 | FIREBASE_CONFIG_WEB_APP_ID= 14 | 15 | FIREBASE_CONFIG_MEASUREMENT_ID= -------------------------------------------------------------------------------- /Frontend/src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | // import Footer from './Footer'; 4 | 5 | const Layout = ({ children }) => ( 6 |
7 |
8 | {children} 9 | {/*
11 | ); 12 | 13 | export default Layout; 14 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/NavbarDoctor.scss: -------------------------------------------------------------------------------- 1 | .Patient__menu { 2 | padding: 0; 3 | display: flex; 4 | justify-content: space-evenly; 5 | align-items: center; 6 | border-bottom: 2px solid gray; 7 | > li { 8 | list-style: none; 9 | } 10 | a { 11 | text-decoration: none; 12 | color: #19243b; 13 | } 14 | } -------------------------------------------------------------------------------- /Backend/utils/testServer.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const supertest = require('supertest') 3 | const errors = require('../network/errors') 4 | 5 | function testServer (route) { 6 | const app = express() 7 | app.use('/', route) 8 | app.use(errors) 9 | return supertest(app) 10 | } 11 | 12 | module.exports = testServer 13 | -------------------------------------------------------------------------------- /Frontend/src/utils/gravatar.jsx: -------------------------------------------------------------------------------- 1 | import md5 from 'md5'; 2 | 3 | const gravatar = (email) => { 4 | const base = 'https://gravatar.com/avatar/'; 5 | const formattedEmail = (email).trim().toLowerCase(); 6 | const hash = md5(formattedEmail, { encoding: 'binary' }); 7 | 8 | return `${base}${hash}`; 9 | }; 10 | 11 | export default gravatar; 12 | -------------------------------------------------------------------------------- /Frontend/src/containers/PatientInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NavbarDoctor from '../components/NavbarDoctor'; 3 | 4 | const PatientInfo = () => { 5 | return ( 6 | <> 7 |
8 | 9 |
10 | 11 | ); 12 | }; 13 | 14 | export default PatientInfo; 15 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/User.scss: -------------------------------------------------------------------------------- 1 | $color__board--content: rgb(250, 221, 209); 2 | $color__board--board: rgb(219, 155, 36); 3 | 4 | .margen { 5 | background-color: $color__board--content; 6 | margin: 1em; 7 | } 8 | 9 | .tabla thead { 10 | background-color: $color__board--board; 11 | } 12 | 13 | .tabla th { 14 | height: 2em; 15 | } -------------------------------------------------------------------------------- /Backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | commonjs: true, 4 | es6: true, 5 | node: true, 6 | jest: true 7 | }, 8 | extends: [ 9 | 'standard' 10 | ], 11 | globals: { 12 | Atomics: 'readonly', 13 | SharedArrayBuffer: 'readonly' 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018 17 | }, 18 | rules: { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Backend/.env_example: -------------------------------------------------------------------------------- 1 | PORT= 2 | JWT_SECRET= 3 | KNEX_HOST= 4 | KNEX_USER= 5 | KNEX_PASSWORD= 6 | KNEX_DATABASE= 7 | KNEX_PORT= 8 | KNEX_CLIENT= 9 | DB_MOTOR= 10 | FB_APP_NAME= 11 | FB_CERT= 12 | DB_USERNAME= 13 | DB_PASSWORD= 14 | DB_DATABASE= 15 | DB_HOST= 16 | DB_PORT= 17 | DB_DIALECT= 18 | STORE_MOTOR= 19 | 20 | // Email 21 | EMAIL_HOST= 22 | EMAIL_PORT= 23 | EMAIL_USER= 24 | EMAIL_PASSWORD= 25 | -------------------------------------------------------------------------------- /Backend/api/components/exams/controller.js: -------------------------------------------------------------------------------- 1 | 2 | const bloodExamn = require('../../../store/mocks/exams/BoodExam') 3 | // const TABLE = 'exam_types' 4 | 5 | module.exports = (store) => { 6 | const get = async () => { 7 | // const q = 'get all types of exams' 8 | // const types = await store.query(TABLE, q) 9 | const types = bloodExamn 10 | return types 11 | } 12 | return { get } 13 | } 14 | -------------------------------------------------------------------------------- /Backend/api/components/auth_roles/controller.js: -------------------------------------------------------------------------------- 1 | const boom = require('@hapi/boom') 2 | const TABLE = 'auth_roles' 3 | module.exports = (store) => { 4 | async function insert (authId, roleId) { 5 | try { 6 | await store.insert(TABLE, { authId, roleId }) 7 | return true 8 | } catch (error) { 9 | throw boom.internal() 10 | } 11 | } 12 | 13 | return { 14 | insert 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Backend/api/components/welcome/network.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const response = require('../../../network/response') 4 | 5 | router.get('/', welcome) 6 | 7 | function welcome (req, res) { 8 | const message = '🔬 Welcome to Exam Management System for Clinical Laboratories API' 9 | response.success(req, res, message) 10 | } 11 | 12 | module.exports = router 13 | -------------------------------------------------------------------------------- /Backend/store/mocks/mockDB.js: -------------------------------------------------------------------------------- 1 | const users = require('./UsersMock') 2 | const auths = require('./AuthMock') 3 | 4 | const db = { 5 | users, 6 | auths, 7 | roles: [], 8 | permissions: [], 9 | medicalrecords: [], 10 | exams: [], 11 | examStatuses: [], 12 | examTypes: [], 13 | role_permissions: [], 14 | auth_roles: [], 15 | firebase_notification_tokens: [] 16 | } 17 | 18 | module.exports = db 19 | -------------------------------------------------------------------------------- /Backend/network/response.js: -------------------------------------------------------------------------------- 1 | exports.success = (req, res, message, status) => { 2 | const statusCode = status || 200 3 | res.status(statusCode).json({ 4 | error: false, 5 | status: statusCode, 6 | body: message 7 | }) 8 | } 9 | 10 | exports.error = (req, res, error, status = 500) => { 11 | res.status(status).json({ 12 | error: true, 13 | status: status, 14 | body: error 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/ExamItem.scss: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | margin: 0; 3 | font-weight: bold; 4 | text-align: center; 5 | } 6 | 7 | .exam__status { 8 | color: rgb(201, 201, 201); 9 | background-color: rgb(86, 46, 139); 10 | font-weight: bold; 11 | font-size: 15px; 12 | text-align: center 13 | } 14 | 15 | .fas.fa-check-square { 16 | color: #0C6B21; 17 | } 18 | 19 | .fas.fa-eye { 20 | color: #162188; 21 | } -------------------------------------------------------------------------------- /Backend/api/components/user/controller.js: -------------------------------------------------------------------------------- 1 | const boom = require('@hapi/boom') 2 | 3 | const TABLE = 'users' 4 | 5 | module.exports = (store) => { 6 | async function insert (user) { 7 | if (!user) throw boom.badData() 8 | const userSaved = await store.insert(TABLE, user) 9 | return userSaved 10 | } 11 | 12 | async function get (id) { 13 | return store.get(TABLE, id) 14 | } 15 | 16 | return { 17 | insert, 18 | get 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Frontend/src/conf/config-firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "AIzaSyB2xfqqIkJ_mFuG0YpSjnqjWcJtXAfyAFI", 3 | "authDomain": "nextep-notifications.firebaseapp.com", 4 | "databaseURL": "https://nextep-notifications.firebaseio.com", 5 | "projectId": "nextep-notifications", 6 | "storageBucket": "nextep-notifications.appspot.com", 7 | "messagingSenderId": "53070194869", 8 | "appId": "1:53070194869:web:ebeedbca3e48efb5b3f6fc", 9 | "measurementId": "G-HHLTD6RZK6" 10 | } -------------------------------------------------------------------------------- /Frontend/src/conf/firebase.js: -------------------------------------------------------------------------------- 1 | import * as firebase from "firebase/app"; 2 | import "firebase/messaging"; 3 | import firebaseConfig from "./config-firebase.json" 4 | 5 | // eslint-disable-next-line import/no-mutable-exports 6 | let messaging 7 | try { 8 | const initializedFirebaseApp = firebase.initializeApp(firebaseConfig); 9 | messaging = initializedFirebaseApp.messaging(); 10 | } catch (err) { 11 | console.warn(err.message) 12 | messaging = null 13 | } 14 | 15 | export default messaging ; 16 | -------------------------------------------------------------------------------- /Backend/db/models/exam.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const exam = sequelize.define('exam', { 4 | content: DataTypes.JSON, 5 | reservationDate: DataTypes.DATE, 6 | statusId: DataTypes.UUID, 7 | medicId: DataTypes.UUID, 8 | bacteriologistId: DataTypes.UUID, 9 | typeId: DataTypes.UUID, 10 | isDeleted: DataTypes.BOOLEAN 11 | }, {}) 12 | exam.associate = function (models) { 13 | // associations can be defined here 14 | } 15 | return exam 16 | } 17 | -------------------------------------------------------------------------------- /Backend/utils/userUtils.js: -------------------------------------------------------------------------------- 1 | const normalizeText = (text) => text 2 | .trim() 3 | .normalize('NFD') 4 | .replace(/[\u0300-\u036f]/g, '') 5 | .toLocaleLowerCase() 6 | .split(' ')[0] 7 | 8 | const generateUsername = (idNumber, firstName, lastName) => { 9 | const name = normalizeText(firstName) 10 | const surname = normalizeText(lastName) 11 | const identifier = idNumber.toString().slice(-4) 12 | 13 | return `${name}.${surname}.${identifier}` 14 | } 15 | 16 | module.exports = { 17 | generateUsername 18 | } 19 | -------------------------------------------------------------------------------- /Frontend/src/actions/index.js: -------------------------------------------------------------------------------- 1 | export const editUser = (payload) => ({ 2 | type: "EDIT_USER", 3 | payload, 4 | }); 5 | 6 | export const addUser = (payload) => ({ 7 | type: "ADD_USER", 8 | payload, 9 | }); 10 | 11 | export const deleteUser = payload => ({ 12 | type: "DELETE_USER", 13 | payload, 14 | }); 15 | 16 | export const loginRequest = (payload) => ({ 17 | type: "LOGIN_REQUEST", 18 | payload, 19 | }); 20 | 21 | export const logoutRequest = (payload) => ({ 22 | type: "LOGOUT_REQUEST", 23 | payload, 24 | }); 25 | -------------------------------------------------------------------------------- /Frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Nextep 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /Frontend/src/hooks/useInitialState.js: -------------------------------------------------------------------------------- 1 | // This is a Custom Hooks, separating the logic of the components to this function 2 | import { useState, useEffect } from "react"; 3 | 4 | const useInitialState = (API) => { 5 | const [dataInfo, setDataInfo] = useState({ 6 | users: [], 7 | exams: [], 8 | }); 9 | 10 | useEffect(() => { 11 | fetch(API) 12 | .then((response) => response.json()) 13 | .then((data) => setDataInfo(data)); 14 | }, []); 15 | console.log(dataInfo); 16 | return dataInfo; 17 | }; 18 | 19 | export default useInitialState; 20 | -------------------------------------------------------------------------------- /Backend/network/errors.js: -------------------------------------------------------------------------------- 1 | const response = require('./response') 2 | 3 | function errors (err, req, res, next) { 4 | let message = 'Internal error' 5 | let statusCode = 500 6 | 7 | if (err.output && err.output.payload) { 8 | message = err.output.payload.message 9 | statusCode = err.output.payload.statusCode 10 | } else if (err.message || err.statusCode) { 11 | message = err.message || message 12 | statusCode = err.statusCode || statusCode 13 | } 14 | 15 | return response.error(req, res, message, statusCode) 16 | } 17 | 18 | module.exports = errors 19 | -------------------------------------------------------------------------------- /Backend/api/components/exams/network.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const Controller = require('./index') 4 | const response = require('../../../network/response') 5 | 6 | function typeOfExman (req, res) { 7 | return Controller.get() 8 | .then(data => { 9 | response.success(req, res, data, 200) 10 | }) 11 | .catch(() => 12 | response.error(req, res, 'Invalid information', 401) 13 | ) 14 | } 15 | 16 | router.get('/', typeOfExman) 17 | const network = module.exports = router 18 | network.typeOfExman = typeOfExman 19 | -------------------------------------------------------------------------------- /Backend/db/models/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const auth = sequelize.define('auth', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | username: DataTypes.STRING, 10 | password: DataTypes.STRING, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | auth.associate = function (models) { 17 | auth.belongsToMany(models.role, { through: models.auth_role }) 18 | } 19 | return auth 20 | } 21 | -------------------------------------------------------------------------------- /Backend/db/models/examstatus.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const examStatus = sequelize.define('examStatus', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | name: DataTypes.STRING, 10 | description: DataTypes.TEXT, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | examStatus.associate = function (models) { 17 | // associations can be defined here 18 | } 19 | return examStatus 20 | } 21 | -------------------------------------------------------------------------------- /Frontend/src/containers/Exam.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Exam = ({ children }) => ( 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {children} 18 |
Paciente-userIdFechaNombre del exámenStatusDisponibilidadDescargarVisualizar
19 |
20 | ); 21 | 22 | export default Exam; 23 | -------------------------------------------------------------------------------- /Backend/api/components/email/templates/sendNewUser.js: -------------------------------------------------------------------------------- 1 | const sendNewUser = (data) => ` 2 | 8 |

Hola ${data.firstName} ${data.lastName}

9 |

Le damos la bienvenida a Nextep Labs, use estas credenciales en nuestra plataforma para acceder a todas las características creadas para usted:

10 | 11 | 15 | ` 16 | 17 | module.exports = sendNewUser 18 | -------------------------------------------------------------------------------- /Backend/db/models/rolepermission.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const rolePermission = sequelize.define('role_permission', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | roleId: DataTypes.UUID, 10 | permissionId: DataTypes.UUID, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | rolePermission.associate = function (models) { 17 | // associations can be defined here 18 | } 19 | return rolePermission 20 | } 21 | -------------------------------------------------------------------------------- /Backend/db/models/examtype.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const examType = sequelize.define('examType', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | name: DataTypes.STRING, 10 | description: DataTypes.TEXT, 11 | templateContent: DataTypes.JSON, 12 | isDeleted: { 13 | type: DataTypes.BOOLEAN, 14 | defaultValue: false 15 | } 16 | }, {}) 17 | examType.associate = function (models) { 18 | // associations can be defined here 19 | } 20 | return examType 21 | } 22 | -------------------------------------------------------------------------------- /Backend/db/models/permission.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const permission = sequelize.define('permission', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | name: DataTypes.STRING, 10 | description: DataTypes.TEXT, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | permission.associate = function (models) { 17 | permission.belongsToMany(models.role, { through: models.role_permission }) 18 | } 19 | return permission 20 | } 21 | -------------------------------------------------------------------------------- /Frontend/src/components/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import '../assets/styles/components/Modal.scss'; 4 | 5 | const Modal = (props) => { 6 | if (!props.isOpen) { 7 | return null; 8 | } 9 | 10 | return ReactDOM.createPortal( 11 |
12 |
13 | 16 | {props.children} 17 |
18 |
, 19 | document.getElementById('modal') 20 | ); 21 | }; 22 | 23 | export default Modal; 24 | -------------------------------------------------------------------------------- /Backend/auth/roles.js: -------------------------------------------------------------------------------- 1 | const boom = require('@hapi/boom') 2 | 3 | function roleValidationHandler (allowedRoles) { 4 | return function (req, res, next) { 5 | if (!req.user || (!req.user && !req.user.roles)) { 6 | next(boom.unauthorized()) 7 | } 8 | 9 | const userRoles = req.user.roles.map(role => role.name) 10 | 11 | const hasAccess = allowedRoles 12 | .map(allowedRole => userRoles.includes(allowedRole)) 13 | .find(allowed => Boolean(allowed)) 14 | 15 | if (hasAccess) { 16 | next() 17 | } else { 18 | next(boom.unauthorized()) 19 | } 20 | } 21 | } 22 | 23 | module.exports = roleValidationHandler 24 | -------------------------------------------------------------------------------- /Backend/db/models/authRole.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const authRole = sequelize.define('auth_role', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | authId: DataTypes.UUID, 10 | roleId: DataTypes.UUID, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | authRole.associate = function (models) { 17 | authRole.belongsTo(models.auth, { foreignKey: 'authId' }) 18 | authRole.belongsTo(models.role, { foreignKey: 'roleId' }) 19 | } 20 | return authRole 21 | } 22 | -------------------------------------------------------------------------------- /Frontend/src/containers/User.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../assets/styles/components/User.scss'; 3 | 4 | const User = ({ children }) => ( 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {children} 20 |
No. IdentificaciónNombre y ApellidosUsernameEmailTeléfonoDomicilioEditarEliminar
21 |
22 | ); 23 | 24 | export default User; 25 | -------------------------------------------------------------------------------- /Backend/db/seeders/20200531065934-roles.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const { Op } = require('sequelize') 3 | const { roles, rolesNameEnum } = require('../../store/mocks/RolesMock.js') 4 | 5 | module.exports = { 6 | up: (queryInterface, Sequelize) => { 7 | return queryInterface.bulkInsert('roles', [...roles], {}) 8 | }, 9 | 10 | down: (queryInterface, Sequelize) => { 11 | return queryInterface.bulkDelete('roles', { 12 | name: { 13 | [Op.in]: [ 14 | rolesNameEnum.ADMINISTRATOR, 15 | rolesNameEnum.MEDIC, 16 | rolesNameEnum.BACTERIOLOGIST, 17 | rolesNameEnum.PACIENT 18 | ] 19 | } 20 | }, {}) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Backend/db/models/role.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const role = sequelize.define('role', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | name: DataTypes.STRING, 10 | description: DataTypes.TEXT, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | role.associate = function (models) { 17 | role.belongsToMany(models.auth, { through: models.auth_role }) 18 | role.belongsToMany(models.permission, { through: models.role_permission, as: 'permissions' }) 19 | } 20 | return role 21 | } 22 | -------------------------------------------------------------------------------- /Backend/db/models/firebasenotificationtoken.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const firebaseNotificationToken = sequelize.define('firebaseNotificationToken', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | userId: DataTypes.UUID, 10 | token: DataTypes.JSON, 11 | isDeleted: { 12 | type: DataTypes.BOOLEAN, 13 | defaultValue: false 14 | } 15 | }, {}) 16 | firebaseNotificationToken.associate = function (models) { 17 | firebaseNotificationToken.belongsTo(models.user, { foreignKey: 'userId' }) 18 | } 19 | return firebaseNotificationToken 20 | } 21 | -------------------------------------------------------------------------------- /Backend/store/mocks/storeMock.js: -------------------------------------------------------------------------------- 1 | const db = require('./mockDB') 2 | const dummy = require('../dummy') 3 | 4 | const get = jest.fn((table, id) => Promise.resolve(db[table][0])) 5 | const list = jest.fn((table) => Promise.resolve(db[table])) 6 | const insert = jest.fn((table, data, trx = null) => Promise.resolve(db[table][0])) 7 | const update = jest.fn((table, id, data) => Promise.resolve(db[table][1])) 8 | const remove = jest.fn((table, id) => Promise.resolve(true)) 9 | const query = jest.fn((table, query, join = null) => { 10 | return dummy.query(table, query) 11 | }) 12 | const model = jest.fn((table) => table) 13 | 14 | module.exports = { 15 | get, 16 | list, 17 | insert, 18 | update, 19 | remove, 20 | query, 21 | model 22 | } 23 | -------------------------------------------------------------------------------- /Frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | const reducer = (state, action) => { 2 | 3 | switch (action.type) { 4 | 5 | case "ADD_USER": 6 | return { 7 | ...state, 8 | users: [...state.users, action.payload], 9 | }; 10 | 11 | case "DELETE_USER": 12 | return { 13 | ...state, 14 | users: state.users.filter((items) => items.id !== action.payload), 15 | }; 16 | 17 | case "LOGIN_REQUEST": 18 | return { 19 | ...state, 20 | user: action.payload, 21 | }; 22 | 23 | case "LOGOUT_REQUEST": 24 | return { 25 | ...state, 26 | user: action.payload, 27 | }; 28 | 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | export default reducer; 35 | -------------------------------------------------------------------------------- /Backend/auth/strategies/basic.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport') 2 | const { BasicStrategy } = require('passport-http') 3 | const boom = require('@hapi/boom') 4 | const bcrypt = require('bcrypt') 5 | 6 | const authService = require('../../api/components/auth') 7 | 8 | passport.use(new BasicStrategy(async (username, password, cb) => { 9 | try { 10 | const user = await authService.get({ username }) 11 | 12 | if (!user || user.isDeleted) { 13 | return cb(boom.unauthorized(), false) 14 | } 15 | 16 | const isValidLogin = await bcrypt.compare(password, user.password) 17 | 18 | if (!isValidLogin) { 19 | return cb(boom.unauthorized(), false) 20 | } 21 | return cb(null, user) 22 | } catch (error) { 23 | return cb(error) 24 | } 25 | })) 26 | -------------------------------------------------------------------------------- /Backend/db/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const user = sequelize.define('user', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | idNumber: DataTypes.INTEGER, 10 | username: DataTypes.STRING, 11 | firstName: DataTypes.STRING, 12 | lastName: DataTypes.STRING, 13 | email: DataTypes.STRING, 14 | contactNumber: DataTypes.STRING, 15 | address: DataTypes.STRING, 16 | medicalRecordId: DataTypes.UUID, 17 | isDeleted: { 18 | type: DataTypes.BOOLEAN, 19 | defaultValue: false 20 | } 21 | }, {}) 22 | user.associate = function (models) { 23 | // associations can be defined here 24 | } 25 | return user 26 | } 27 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/AddExams.scss: -------------------------------------------------------------------------------- 1 | @import '../App.scss'; 2 | 3 | .Add__exam { 4 | h1 { 5 | color: #FF5C00; 6 | text-align: center; 7 | } 8 | } 9 | 10 | .Add__exam--options { 11 | display: grid; 12 | grid-template-columns: 25% 50% 25%; 13 | justify-items: center; 14 | border-bottom: 2px solid #A1A1A1; 15 | } 16 | 17 | .Add__exam--list--cards { 18 | margin: 0; 19 | padding: 1.5em 0 0 0; 20 | } 21 | 22 | .Exam__card { 23 | @include cardList; 24 | .Exam__watch--icon { 25 | @include cardIcon; 26 | background: 0; 27 | border: none; 28 | } 29 | } 30 | 31 | .Add__exam--name--patient { 32 | width: 30%; 33 | margin-left: auto; 34 | background: #E96216; 35 | border-radius: 15px; 36 | h3 { 37 | color: #fff; 38 | text-align: center; 39 | } 40 | } -------------------------------------------------------------------------------- /Backend/auth/strategies/cookie.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport') 2 | const boom = require('@hapi/boom') 3 | const CookieStrategy = require('passport-cookie') 4 | const config = require('../../config/config') 5 | const authService = require('../../api/components/auth') 6 | const { verify } = require('jsonwebtoken') 7 | 8 | passport.use(new CookieStrategy( 9 | async function (token, cb) { 10 | try { 11 | const userData = await verify(token, config.jwt.secret) 12 | 13 | const user = await authService.get({ id: userData.id }) 14 | 15 | if (!user || user.isBloqued) { 16 | return (boom.unauthorized(), null) 17 | } 18 | delete user.password 19 | 20 | return cb(null, user) 21 | } catch (error) { 22 | return cb(boom.unauthorized(), null) 23 | } 24 | } 25 | )) 26 | -------------------------------------------------------------------------------- /Backend/__test__/api.components.welcome.spec.js: -------------------------------------------------------------------------------- 1 | const testServer = require('../utils/testServer') 2 | 3 | describe('API | Components | Welcome', () => { 4 | const route = require('../api/components/welcome/network') 5 | const request = testServer(route) 6 | const message = '🔬 Welcome to Exam Management System for Clinical Laboratories API' 7 | describe('Network', () => { 8 | it('should respond with status 201', (done) => { 9 | request.get('/').expect(200, done) 10 | }) 11 | 12 | it('should respond with a response.success structure', (done) => { 13 | const expected = { 14 | status: 200, 15 | error: false, 16 | body: message 17 | } 18 | request.get('/').end((_, res) => { 19 | expect(res.body).toEqual(expected) 20 | done() 21 | }) 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /Backend/auth/strategies/jwt.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport') 2 | const { ExtractJwt, Strategy } = require('passport-jwt') 3 | const boom = require('@hapi/boom') 4 | 5 | const config = require('../../config/config') 6 | const authService = require('../../api/components/auth') 7 | 8 | passport.use( 9 | new Strategy({ 10 | secretOrKey: config.jwt.secret, 11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken() 12 | }, 13 | async (tokenPayload, cb) => { 14 | try { 15 | const user = await authService.get({ id: tokenPayload.id }) 16 | 17 | if (!user || user.isDeleted) { 18 | return cb(boom.unauthorized(), false) 19 | } 20 | 21 | delete user.password 22 | 23 | return cb(null, user) 24 | } catch (error) { 25 | return cb(boom.unauthorized(), false) 26 | } 27 | }) 28 | ) 29 | -------------------------------------------------------------------------------- /Backend/db/models/medicalrecord.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (sequelize, DataTypes) => { 3 | const medicalRecord = sequelize.define('medicalRecord', { 4 | id: { 5 | type: DataTypes.UUID, 6 | defaultValue: DataTypes.UUIDV4, 7 | primaryKey: true 8 | }, 9 | birthdate: DataTypes.DATE, 10 | blodType: DataTypes.STRING, 11 | allergies: DataTypes.TEXT, 12 | weight: DataTypes.STRING, 13 | heigth: DataTypes.STRING, 14 | medicationNotes: DataTypes.TEXT, 15 | diabetes: DataTypes.BOOLEAN, 16 | smoking: DataTypes.BOOLEAN, 17 | alcoholism: DataTypes.BOOLEAN, 18 | isDeleted: { 19 | type: DataTypes.BOOLEAN, 20 | defaultValue: false 21 | } 22 | }, {}) 23 | medicalRecord.associate = function (models) { 24 | // associations can be defined here 25 | } 26 | return medicalRecord 27 | } 28 | -------------------------------------------------------------------------------- /Frontend/src/components/ExamList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const ExamList = (props) => { 6 | const { id, examName, examDescription } = props; 7 | 8 | return ( 9 | <> 10 |
  • 11 |

    Exámen

    12 |

    {examName}

    13 |

    Descripción

    14 |

    {examDescription}

    15 | 19 |
  • 20 | 21 | ); 22 | }; 23 | 24 | ExamList.propTypes = { 25 | id: PropTypes.number, 26 | examName: PropTypes.string, 27 | examDescription: PropTypes.string, 28 | }; 29 | 30 | export default connect(null, null)(ExamList); 31 | -------------------------------------------------------------------------------- /Backend/api/components/email/controller.js: -------------------------------------------------------------------------------- 1 | const boom = require('@hapi/boom') 2 | const nodemailer = require('nodemailer') 3 | const config = require('../../../config/config') 4 | const sendNewUserTemplate = require('./templates/sendNewUser') 5 | 6 | const transport = nodemailer.createTransport({ 7 | host: config.email.emailHost, 8 | port: config.email.emailPort, 9 | auth: { 10 | user: config.email.emailUser, 11 | pass: config.email.emailPassword 12 | } 13 | }) 14 | module.exports = () => { 15 | async function sendNewUser (data) { 16 | try { 17 | const mailOptions = { 18 | from: 'not-reply@nextsteplabs.com', 19 | to: data.email, 20 | subject: 'Bienvenido a Next Step Labs', 21 | html: sendNewUserTemplate(data) 22 | } 23 | 24 | await transport.sendMail(mailOptions) 25 | 26 | return true 27 | } catch (error) { 28 | throw boom.internal() 29 | } 30 | } 31 | 32 | return { 33 | sendNewUser 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Backend/api/components/notification/network.js: -------------------------------------------------------------------------------- 1 | const Controller = require('./index') 2 | 3 | const express = require('express') 4 | const response = require('../../../network/response') 5 | const passport = require('passport') 6 | const router = express.Router() 7 | 8 | router.get('/', testNotification) 9 | router.post('/setToken', passport.authenticate('basic', { session: true }), addTokentoUser) 10 | 11 | function testNotification (req, res) { 12 | return Controller.sendMessageMulticast(req.body) 13 | .then(data => { 14 | response.success(req, res, data, 200) 15 | }) 16 | .catch((err) => { 17 | response.error(req, res, err, 401) 18 | }) 19 | } 20 | 21 | function addTokentoUser (req, res) { 22 | return Controller.saveTokenToUser((req && req.user) || null, req.body) 23 | .then(data => { 24 | response.success(req, res, data, 200) 25 | }) 26 | .catch(err => { 27 | response.error(req, res, err, 401) 28 | }) 29 | } 30 | 31 | module.exports = router 32 | -------------------------------------------------------------------------------- /Backend/network/__test__/errors.spec.js: -------------------------------------------------------------------------------- 1 | const errors = require('../errors') 2 | jest.mock('../response.js', () => ({ 3 | error: (req, res, message, statusCode) => ({ error: true, status: statusCode, body: message }) 4 | })) 5 | const boom = require('@hapi/boom') 6 | describe('Error Middleware', () => { 7 | describe('Boom error', () => { 8 | it('should manage boom error ', () => { 9 | const expectedResult = { 10 | error: true, 11 | status: 401, 12 | body: 'Unauthorized' 13 | } 14 | expect(errors(boom.unauthorized())).toEqual(expectedResult) 15 | }) 16 | }) 17 | 18 | describe('Regular error', () => { 19 | it('should manage regular error', () => { 20 | const expectedResult = { 21 | error: true, 22 | status: 500, 23 | body: 'Regular error' 24 | } 25 | 26 | const regularError = new Error(expectedResult.body) 27 | 28 | expect(errors(regularError)).toEqual(expectedResult) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200526161357-create-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('auths', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | username: { 11 | type: Sequelize.STRING 12 | }, 13 | password: { 14 | type: Sequelize.STRING 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('auths') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200527002438-create-role.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('roles', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | description: { 14 | type: Sequelize.TEXT 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('roles') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200527010113-create-auth-role.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('auth_roles', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | authId: { 11 | type: Sequelize.UUID 12 | }, 13 | roleId: { 14 | type: Sequelize.UUID 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('auth_roles') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/SheduleExams.scss: -------------------------------------------------------------------------------- 1 | .Shedule__exam--list { 2 | margin-top: 2.5em; 3 | .Shedule__exam--cards{ 4 | padding: 0; 5 | margin: 0; 6 | display: grid; 7 | grid-template-columns: repeat(3, 30%); 8 | justify-content: space-between; 9 | grid-gap: 20px 0; 10 | } 11 | .Shedule__exam--item { 12 | padding: 1em; 13 | list-style: none; 14 | background: #fff; 15 | border: 1px solid #e0e0e0; 16 | border-radius: 15px; 17 | h3 { 18 | margin-top: 0; 19 | } 20 | } 21 | .Shedule__add--button { 22 | width: 60%; 23 | margin: auto; 24 | color: #fff; 25 | background: #19243B; 26 | padding: 0.8em; 27 | border: none; 28 | border-radius: 5px; 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | transition: all 0.3s; 33 | &:hover { 34 | transform: scale(0.9); 35 | } 36 | > i { 37 | margin-left: 10px; 38 | font-size: 20px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Frontend/src/components/PatientList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | 6 | const PatientList = (props) => { 7 | const { id, idNumber, firstName, lastName } = props; 8 | 9 | return ( 10 | <> 11 |
  • 12 |

    {idNumber}

    13 |

    14 | {firstName} {lastName} 15 |

    16 | 21 | 22 | 23 | 24 | 25 |
  • 26 | 27 | ); 28 | }; 29 | 30 | PatientList.propTypes = { 31 | // id: PropTypes.number, 32 | idNumber: PropTypes.number, 33 | firstName: PropTypes.string, 34 | lastName: PropTypes.string, 35 | }; 36 | 37 | export default connect(null, null)(PatientList); 38 | -------------------------------------------------------------------------------- /Frontend/src/components/PersonalInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import UsersMock from '../mocks/UsersMock.json' 3 | import '../assets/styles/components/PatientData.scss' 4 | 5 | const PersonalInfo = () => { 6 | return ( 7 |
    8 | {UsersMock.map((item) => { 9 | return ( 10 |
    11 |

    Nombres:

    12 |

    {item.first_name}

    13 |

    Apellidos:

    14 |

    {item.last_name}

    15 |

    Número de Identificación:

    16 |

    {item.id_number}

    17 |

    Usuario:

    18 |

    {item.username}

    19 |

    Correo electrónico:

    20 |

    {item.email}

    21 |

    Dirección:

    22 |

    {item.address}

    23 |

    Teléfono

    24 |

    {item.contact_number}

    25 |
    26 | ); 27 | })} 28 |
    29 | ); 30 | } 31 | 32 | export default PersonalInfo -------------------------------------------------------------------------------- /Backend/db/migrations/20200527002503-create-permission.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('permissions', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | description: { 14 | type: Sequelize.TEXT 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('permissions') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200528063216-create-examStatus.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('examStatuses', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | description: { 14 | type: Sequelize.TEXT 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('examStatuses') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Frontend/src/components/AddResultsModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Modal from './Modal'; 3 | 4 | const AddResultsModal = (props) => { 5 | return ( 6 | 7 |
    8 |

    Glucosa

    9 |
    10 | 14 | 18 | 22 | 26 | 29 |
    30 |
    31 |
    32 | ); 33 | }; 34 | 35 | export default AddResultsModal; -------------------------------------------------------------------------------- /Backend/db/migrations/20200527002633-create-role-permission.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('role_permissions', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | roleId: { 11 | type: Sequelize.UUID 12 | }, 13 | permissionId: { 14 | type: Sequelize.UUID 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('role_permissions') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Frontend/src/components/MedicalHistory.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../assets/styles/components/PatientData.scss'; 3 | 4 | const MedicalHistory = (props) => { 5 | 6 | return ( 7 |
    8 |
    9 |

    Fecha de Nacimiento:

    10 |

    {}

    11 |

    Género:

    12 |

    13 |

    Tipo de sangre:

    14 |

    15 |

    Peso:

    16 |

    17 |

    Altura:

    18 |

    19 |

    Enfermedades crónicas propias:

    20 |

    21 |

    Enfermedades crónicas heredadas:

    22 |

    23 |

    Consume medicamentos:

    24 |

    25 |

    Alergías:

    26 |

    27 |

    Diabetes:

    28 |

    29 |

    Tabaquismo:

    30 |

    31 |

    Alcoholismo:

    32 |

    33 |
    34 |
    35 | ); 36 | }; 37 | 38 | export default MedicalHistory; 39 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200531013304-create-firebase-notification-token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('firebaseNotificationTokens', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | userId: { 11 | type: Sequelize.UUID 12 | }, 13 | token: { 14 | type: Sequelize.JSON 15 | }, 16 | isDeleted: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE, 23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 29 | } 30 | }) 31 | }, 32 | down: (queryInterface, Sequelize) => { 33 | return queryInterface.dropTable('firebaseNotificationTokens') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Frontend/README.md: -------------------------------------------------------------------------------- 1 | # FRONTEND - MegaCat 2 | 3 | Final Project PlatziMaster 4 | 5 | ## Converting to React Components, Frontend 6 | 7 | WIP 8 | Working adding React JS 9 | 10 | --- 11 | 12 | ### Clone this repo 13 | 14 | You can use and change 'my_folder' on this instruction to create a new folder 15 | 16 | ```bash 17 | git clone git@github.com:FernandoTorresL/Curso_ReactJS.git my_folder 18 | ``` 19 | 20 | ### Change to working directory and install 21 | 22 | ```bash 23 | cd my_folder 24 | npm install 25 | ``` 26 | 27 | ### Start fake API 28 | Start the json server to simulate an API for this project. 29 | ``` 30 | json-server initialState.json 31 | ``` 32 | You can see the data [here](http://localhost:3000/initialState) 33 | 34 | ### Execute 35 | 36 | Now, open a new terminal, in the same folder, and execute with 37 | 38 | ```bash 39 | npm run start 40 | ``` 41 | 42 | --- 43 | 44 | ### Follow me 45 | 46 | [fertorresmx.dev](http://fertorresmx.dev/) 47 | [fertorresmx.com](http://fertorresmx.com/) 48 | 49 | #### :globe_with_meridians: Twitter, Instagram: @fertorresmx 50 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200528062121-create-examtype.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('examTypes', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | description: { 14 | type: Sequelize.TEXT 15 | }, 16 | templateContent: { 17 | type: Sequelize.JSON 18 | }, 19 | isDeleted: { 20 | type: Sequelize.BOOLEAN, 21 | defaultValue: false 22 | }, 23 | createdAt: { 24 | allowNull: false, 25 | type: Sequelize.DATE, 26 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 27 | }, 28 | updatedAt: { 29 | allowNull: false, 30 | type: Sequelize.DATE, 31 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 32 | } 33 | }) 34 | }, 35 | down: (queryInterface, Sequelize) => { 36 | return queryInterface.dropTable('examTypes') 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/RememberInfo.scss: -------------------------------------------------------------------------------- 1 | @import '../App.scss'; 2 | 3 | .RememberInfo { 4 | @include home-themes; 5 | height: 100vh;; 6 | .Logo { 7 | margin: 0; 8 | padding-top: 5%; 9 | } 10 | &__container { 11 | width: 50%; 12 | margin: auto; 13 | &--form--options { 14 | input { 15 | width: 45%; 16 | margin: 1em auto; 17 | line-height: 1.2; 18 | color: #333333; 19 | display: block; 20 | background: #fff; 21 | border-radius: 2em; 22 | padding: 1em; 23 | border: none; 24 | text-align: center; 25 | } 26 | span { 27 | font-size: 15px; 28 | color: #999999; 29 | display: flex; 30 | align-items: center; 31 | position: absolute; 32 | bottom: 0; 33 | left: 36%; 34 | width: inherit; 35 | height: 100%; 36 | padding-left: 40px; 37 | transition: all 0.4s; 38 | } 39 | } 40 | &--form { 41 | button { 42 | @include home-button-send; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Backend/db/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const Sequelize = require('sequelize') 6 | const basename = path.basename(__filename) 7 | const env = process.env.NODE_ENV || 'development' 8 | const config = require(path.join(__dirname, '/../../config/database'))[env] 9 | const db = {} 10 | 11 | let sequelize 12 | if (config.use_env_variable) { 13 | sequelize = new Sequelize(process.env[config.use_env_variable], config) 14 | } else { 15 | sequelize = new Sequelize(config.database, config.username, config.password, config) 16 | } 17 | 18 | fs 19 | .readdirSync(__dirname) 20 | .filter(file => { 21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') 22 | }) 23 | .forEach(file => { 24 | const model = sequelize.import(path.join(__dirname, file)) 25 | db[model.name] = model 26 | }) 27 | 28 | Object.keys(db).forEach(modelName => { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db) 31 | } 32 | }) 33 | 34 | db.sequelize = sequelize 35 | db.Sequelize = Sequelize 36 | 37 | module.exports = db 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeffer Barragán 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Backend/__test__/api.components.user.spec.js: -------------------------------------------------------------------------------- 1 | const Controller = require('../api/components/user/controller.js') 2 | const storeMock = require('../store/mocks/storeMock.js') 3 | const userMock = require('../store/mocks/UsersMock.json') 4 | 5 | describe('API | Components | User', () => { 6 | const controller = Controller(storeMock) 7 | it('should call insert user', async (done) => { 8 | const userData = { 9 | idNumber: userMock[0].idNumber, 10 | firstName: userMock[0].firstName, 11 | lastName: userMock[0].lastName, 12 | email: userMock[0].email, 13 | contactNumber: userMock[0].contactNumber, 14 | address: userMock[0].address 15 | } 16 | const expectedCall = ['users', userData] 17 | await controller.insert(userData) 18 | expect(storeMock.insert.mock.calls.length).toBe(1) 19 | expect(storeMock.insert.mock.calls[0]).toEqual(expectedCall) 20 | await storeMock.insert.mockReset() 21 | done() 22 | }) 23 | 24 | it('should return an error if not pass data', async (done) => { 25 | const response = controller.insert() 26 | expect(response).rejects.toThrow() 27 | done() 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /Frontend/src/components/SheduleExams.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ExamList from '../components/ExamList'; 4 | // import { addExam } from '../actions' 5 | import '../assets/styles/components/SheduleExams.scss'; 6 | const SheduleExams = ({ exams }) => { 7 | // onHandleSubmit = (e) => { 8 | // e.preventDefault(); 9 | // }; 10 | return ( 11 |
    12 | {/*
    13 |

    /

    14 |
    */} 15 |
    16 | 27 |
    28 |
    29 | ); 30 | }; 31 | 32 | const mapStateToProps = (state) => { 33 | return { 34 | exams: state.exams, 35 | }; 36 | }; 37 | 38 | export default connect(mapStateToProps, null)(SheduleExams); 39 | -------------------------------------------------------------------------------- /Frontend/src/components/NavbarDoctor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import '../assets/styles/components/NavbarDoctor.scss'; 4 | 5 | const NavbarDoctor = () => { 6 | return ( 7 | 41 | ); 42 | }; 43 | 44 | export default NavbarDoctor; 45 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/PatientList.scss: -------------------------------------------------------------------------------- 1 | @import '../App.scss'; 2 | 3 | .Patient--search { 4 | position: absolute; 5 | top: 18%; 6 | width: 30%; 7 | > input { 8 | padding: 0 30px 0 53px; 9 | border: 1px solid #A1A1A1; 10 | width: 100%; 11 | font-size: 15px; 12 | line-height: 1.2; 13 | color: #333333; 14 | display: block; 15 | height: 35px; 16 | border-radius: 25px; 17 | box-sizing: border-box; 18 | } 19 | > span { 20 | font-size: 15px; 21 | color: #999999; 22 | display: flex; 23 | align-items: center; 24 | position: absolute; 25 | border-radius: 25px; 26 | bottom: 0; 27 | left: 0; 28 | height: 100%; 29 | padding-left: 20px; 30 | } 31 | } 32 | 33 | .Patient__list--options { 34 | display: grid; 35 | grid-template-columns: 25% 50% 25%; 36 | justify-items: center; 37 | border-bottom: 2px solid #A1A1A1; 38 | text-align: center; 39 | h3 { 40 | color: #19243B; 41 | } 42 | } 43 | 44 | .Patient__list--cards { 45 | margin: 0; 46 | padding: 1.5em 0 0 0; 47 | } 48 | 49 | .Patient__card { 50 | @include cardList; 51 | .Patient__watch--icon { 52 | @include cardIcon; 53 | } 54 | } -------------------------------------------------------------------------------- /Frontend/src/components/ExamItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import '../assets/styles/components/ExamItem.scss' 5 | 6 | const ExamItem = (props) => { 7 | 8 | const { 9 | id, 10 | userId, 11 | examDate, 12 | examName, 13 | examStatus, 14 | } = props; 15 | 16 | return ( 17 | 18 | {userId} 19 | {examDate} 20 | {examName} 21 | {examStatus} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | ExamItem.propTypes = { 36 | id: PropTypes.number, 37 | userId: PropTypes.string, 38 | examDate: PropTypes.string, 39 | examName: PropTypes.string, 40 | examStatus: PropTypes.string 41 | }; 42 | 43 | export default connect(null, null)(ExamItem); 44 | -------------------------------------------------------------------------------- /Frontend/src/sw/firebase-messaging-sw.js: -------------------------------------------------------------------------------- 1 | importScripts("https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"); 2 | importScripts("https://www.gstatic.com/firebasejs/6.2.0/firebase-messaging.js"); 3 | 4 | const firebaseConfig = { 5 | "apiKey": "AIzaSyB2xfqqIkJ_mFuG0YpSjnqjWcJtXAfyAFI", 6 | "authDomain": "nextep-notifications.firebaseapp.com", 7 | "databaseURL": "https://nextep-notifications.firebaseio.com", 8 | "projectId": "nextep-notifications", 9 | "storageBucket": "nextep-notifications.appspot.com", 10 | "messagingSenderId": "53070194869", 11 | "appId": "1:53070194869:web:ebeedbca3e48efb5b3f6fc", 12 | "measurementId": "G-HHLTD6RZK6" 13 | }; 14 | 15 | console.log(process.env.FIREBASE_CONFIG_MEASUREMENT_ID); 16 | 17 | // Initialize Firebase 18 | firebase.initializeApp(firebaseConfig); 19 | 20 | const messaging = firebase.messaging(); 21 | 22 | messaging.setBackgroundMessageHandler((payload) => { 23 | console.log( 24 | "[firebase-messaging-sw.js] Received background message ", 25 | payload 26 | ); 27 | const { data } = payload; 28 | const options = { 29 | body: data.body, 30 | }; 31 | // eslint-disable-next-line no-restricted-globals 32 | return self.registration.showNotification(data.title, options); 33 | }); 34 | -------------------------------------------------------------------------------- /Frontend/src/components/RememberInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import logo from '../assets/static/logo.png'; 4 | import '../assets/styles/components/RememberInfo.scss'; 5 | 6 | const RememberInfo = () => { 7 | return ( 8 |
    9 | 10 |
    11 | Logo de Nextep 12 |
    13 | 14 |
    15 |

    16 | Digite su usuario, se enviará un link a su correo asociado para 17 | reestablecer la contraseña 18 |

    19 |
    20 |
    21 | 27 | 28 | 29 | 30 |
    31 | 32 | 33 |
    34 |
    35 | ); 36 | }; 37 | 38 | export default RememberInfo; 39 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/App.scss: -------------------------------------------------------------------------------- 1 | @import './Media.scss'; 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: 'Quicksand', sans-serif; 8 | } 9 | 10 | a, 11 | button { 12 | cursor: pointer; 13 | } 14 | 15 | input:focus, 16 | button:focus { 17 | outline: none !important; 18 | box-shadow: none; 19 | } 20 | 21 | @mixin home-themes{ 22 | background-color: #19243b; 23 | color: #fff; 24 | text-align: center; 25 | } 26 | 27 | @mixin home-button-send{ 28 | width: 20%; 29 | margin: 2em auto; 30 | padding: .7em; 31 | display: block; 32 | background: rgba(255, 255, 255, 0.5); 33 | border: none; 34 | border-radius: 2em; 35 | transition: all 0.5s; 36 | text-decoration: none; 37 | &:hover { 38 | transform: scale(0.9); 39 | } 40 | } 41 | @mixin cardList { 42 | margin: .5em 0; 43 | display: grid; 44 | grid-template-columns: 25% 50% 25%; 45 | justify-items: center; 46 | align-items: center; 47 | border: 1px solid #efefef; 48 | border-radius: 15px; 49 | transition: all .3s; 50 | &:hover { 51 | background: #f5f5f5; 52 | } 53 | } 54 | 55 | @mixin cardIcon { 56 | color: #19243B; 57 | cursor: pointer; 58 | } 59 | 60 | .Container { 61 | max-width: 85%; 62 | margin: auto; 63 | } -------------------------------------------------------------------------------- /Backend/db/migrations/20200528063918-create-exam.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('exams', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | content: { 11 | type: Sequelize.JSON 12 | }, 13 | reservationDate: { 14 | type: Sequelize.DATE 15 | }, 16 | statusId: { 17 | type: Sequelize.UUID 18 | }, 19 | medicId: { 20 | type: Sequelize.UUID 21 | }, 22 | bacteriologistId: { 23 | type: Sequelize.UUID 24 | }, 25 | typeId: { 26 | type: Sequelize.UUID 27 | }, 28 | isDeleted: { 29 | type: Sequelize.BOOLEAN, 30 | defaultValue: false 31 | }, 32 | createdAt: { 33 | allowNull: false, 34 | type: Sequelize.DATE, 35 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 36 | }, 37 | updatedAt: { 38 | allowNull: false, 39 | type: Sequelize.DATE, 40 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 41 | } 42 | }) 43 | }, 44 | down: (queryInterface, Sequelize) => { 45 | return queryInterface.dropTable('exams') 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Backend/api/components/auth/network.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const response = require('../../../network/response') 3 | const Controller = require('./index') 4 | const passport = require('passport') 5 | const roleValidationHandler = require('../../../auth/roles') 6 | const { rolesNameEnum } = require('../../../store/mocks/RolesMock') 7 | const router = express.Router() 8 | 9 | // Passport Strategies 10 | require('../../../auth/strategies/basic') 11 | require('../../../auth/strategies/cookie') 12 | require('../../../auth/strategies/jwt') 13 | 14 | router.post('/login', passport.authenticate('basic', { session: false }), login) 15 | router.post( 16 | '/add_user', 17 | passport.authenticate(['jwt', 'cookie'], { session: false }), 18 | roleValidationHandler([rolesNameEnum.ADMINISTRATOR]), 19 | addUser 20 | ) 21 | 22 | function login (req, res, next) { 23 | return Controller.login((req && req.user) || null) 24 | .then(data => { 25 | res.cookie('token', data.token, { maxAge: 5400000, httpOnly: true }) 26 | response.success(req, res, data, 200) 27 | }) 28 | .catch(next) 29 | } 30 | 31 | function addUser (req, res, next) { 32 | return Controller.addUser(req.body) 33 | .then(user => response.success(req, res, user, 201)) 34 | .catch(next) 35 | } 36 | 37 | const network = module.exports = router 38 | network.login = login 39 | -------------------------------------------------------------------------------- /Backend/api/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const express = require('express') 3 | const bodyParser = require('body-parser') 4 | const passport = require('passport') 5 | const cookieParser = require('cookie-parser') 6 | const cors = require('cors') 7 | 8 | const config = require('../config/config') 9 | const errors = require('../network/errors') 10 | const welcome = require('./components/welcome/network') 11 | const auth = require('./components/auth/network') 12 | const examTypes = require('./components/exams/network') 13 | const notification = require('./components/notification/network') 14 | 15 | const app = express() 16 | 17 | const whitelist = ['http://localhost:8080', 'https://nextep-lab.herokuapp.com'] 18 | app.use(cors({ 19 | credentials: true, 20 | origin: function (origin, callback) { 21 | if (whitelist.indexOf(origin) !== -1) { 22 | callback(null, true) 23 | } else { 24 | callback(new Error('Not allowed by CORS')) 25 | } 26 | } 27 | })) 28 | app.use(bodyParser.json()) 29 | app.use(cookieParser()) 30 | app.use(passport.initialize()) 31 | 32 | app.use('/', welcome) 33 | app.use('/api/auth', auth) 34 | app.use('/api/types', examTypes) 35 | app.use('/api/notification', notification) 36 | 37 | app.use(errors) 38 | 39 | app.listen(config.api.port || 5000, () => { 40 | console.log(`🔬API listen on port: http://localhost:${config.api.port}`) 41 | }) 42 | -------------------------------------------------------------------------------- /Backend/config/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | const dbConfig = { 4 | username: process.env.DB_USERNAME || null, 5 | password: process.env.DB_PASSWORD || null, 6 | database: process.env.DB_DATABASE || 'DBDemo', 7 | host: process.env.DB_HOST || 'localhost', 8 | port: process.env.DB_PORT || '5432', 9 | dialect: process.env.DB_DIALECT || 'sqlite' 10 | } 11 | 12 | module.exports = { 13 | api: { 14 | port: process.env.PORT || 5000 15 | }, 16 | jwt: { 17 | secret: process.env.JWT_SECRET || 'secret' 18 | }, 19 | email: { 20 | emailHost: process.env.EMAIL_HOST || '', 21 | emailPort: process.env.EMAIL_PORT || '', 22 | emailUser: process.env.EMAIL_USER || '', 23 | emailPassword: process.env.EMAIL_PASSWORD || '' 24 | }, 25 | knex: { 26 | host: process.env.KNEX_HOST || '', 27 | user: process.env.KNEX_USER || '', 28 | password: process.env.KNEX_PASSWORD || '', 29 | database: process.env.KNEX_DATABASE || '', 30 | port: process.env.KNEX_PORT || '', 31 | client: process.env.KNEX_CLIENT || '' 32 | }, 33 | fbase: { 34 | name: process.env.FB_APP_NAME || '', 35 | cert: process.env.FB_CERT || {} 36 | }, 37 | storeMotor: process.env.STORE_MOTOR || 'dummy', 38 | sequelize: { 39 | development: { 40 | ...dbConfig 41 | }, 42 | test: { 43 | ...dbConfig 44 | }, 45 | production: { ...dbConfig, logging: false } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200527024840-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('users', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | idNumber: { 11 | type: Sequelize.INTEGER 12 | }, 13 | username: { 14 | type: Sequelize.STRING 15 | }, 16 | firstName: { 17 | type: Sequelize.STRING 18 | }, 19 | lastName: { 20 | type: Sequelize.STRING 21 | }, 22 | email: { 23 | type: Sequelize.STRING 24 | }, 25 | contactNumber: { 26 | type: Sequelize.STRING 27 | }, 28 | address: { 29 | type: Sequelize.STRING 30 | }, 31 | medicalRecordId: { 32 | type: Sequelize.UUID 33 | }, 34 | isDeleted: { 35 | type: Sequelize.BOOLEAN, 36 | defaultValue: false 37 | }, 38 | createdAt: { 39 | allowNull: false, 40 | type: Sequelize.DATE, 41 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 42 | }, 43 | updatedAt: { 44 | allowNull: false, 45 | type: Sequelize.DATE, 46 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 47 | } 48 | }) 49 | }, 50 | down: (queryInterface, Sequelize) => { 51 | return queryInterface.dropTable('users') 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/Login.scss: -------------------------------------------------------------------------------- 1 | @import '../App.scss'; 2 | 3 | .Login { 4 | @include home-themes; 5 | height: 100vh; 6 | .Logo { 7 | margin: 0; 8 | padding-top: 5%; 9 | } 10 | &__container--form { 11 | .button--send { 12 | @include home-button-send; 13 | } 14 | > a { 15 | color: #fff; 16 | } 17 | } 18 | &__container--form--options { 19 | position: relative; 20 | input { 21 | width: 25%; 22 | margin: 1em auto; 23 | line-height: 1.2; 24 | color: #333333; 25 | display: block; 26 | background: #fff; 27 | border-radius: 2em; 28 | padding: 1em; 29 | border: none; 30 | text-align: center; 31 | } 32 | > span { 33 | font-size: 15px; 34 | color: #999999; 35 | display: flex; 36 | align-items: center; 37 | position: absolute; 38 | bottom: 0; 39 | left: 35%; 40 | width: inherit; 41 | height: 100%; 42 | padding-left: 35px; 43 | transition: all 0.4s; 44 | } 45 | } 46 | &__container--info { 47 | padding: 1em 0; 48 | display: flex; 49 | justify-content: space-evenly; 50 | align-items: center; 51 | a { 52 | color: #fff; 53 | } 54 | span { 55 | background: #3083c9; 56 | padding: 1em; 57 | border-radius: 50%; 58 | > i { 59 | font-size: 25px; 60 | vertical-align: middle; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Backend/db/migrations/20200528043643-create-medicalrecord.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('medicalrecords', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | unique: true 9 | }, 10 | birthdate: { 11 | type: Sequelize.DATE 12 | }, 13 | blodType: { 14 | type: Sequelize.STRING 15 | }, 16 | allergies: { 17 | type: Sequelize.TEXT 18 | }, 19 | weight: { 20 | type: Sequelize.STRING 21 | }, 22 | heigth: { 23 | type: Sequelize.STRING 24 | }, 25 | medicationNotes: { 26 | type: Sequelize.TEXT 27 | }, 28 | diabetes: { 29 | type: Sequelize.BOOLEAN 30 | }, 31 | smoking: { 32 | type: Sequelize.BOOLEAN 33 | }, 34 | alcoholism: { 35 | type: Sequelize.BOOLEAN 36 | }, 37 | isDeleted: { 38 | type: Sequelize.BOOLEAN, 39 | defaultValue: false 40 | }, 41 | createdAt: { 42 | allowNull: false, 43 | type: Sequelize.DATE, 44 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 45 | }, 46 | updatedAt: { 47 | allowNull: false, 48 | type: Sequelize.DATE, 49 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') 50 | } 51 | }) 52 | }, 53 | down: (queryInterface, Sequelize) => { 54 | return queryInterface.dropTable('medicalrecords') 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Backend/store/mocks/RolesMock.js: -------------------------------------------------------------------------------- 1 | const rolesEnum = { 2 | ADMINISTRATOR: { 3 | id: 'd436e99b-afea-44cf-a31c-ff35b7740c67', 4 | name: 'administrator', 5 | description: 'An administrator Role', 6 | isDeleted: false, 7 | createdAt: '2020-05-15 00:00:00', 8 | updatedAt: '2020-05-15 00:00:00' 9 | }, 10 | MEDIC: { 11 | id: 'a81bd60a-b09e-4936-b74a-22b823e39464', 12 | name: 'medic', 13 | description: 'A medic Role', 14 | isDeleted: false, 15 | createdAt: '2020-05-15 00:00:00', 16 | updatedAt: '2020-05-15 00:00:00' 17 | }, 18 | BACTERIOLOGIST: { 19 | id: 'bf90b55d-0e69-4f31-8211-514049a42625', 20 | name: 'bacteriologist', 21 | description: 'A bacteriologist Role', 22 | isDeleted: false, 23 | createdAt: '2020-05-15 00:00:00', 24 | updatedAt: '2020-05-15 00:00:00' 25 | }, 26 | PACIENT: { 27 | id: '9511dfc8-e17e-4c8e-927e-cb933f9b77f6', 28 | name: 'pacient', 29 | description: 'A pacient Role', 30 | isDeleted: false, 31 | createdAt: '2020-05-15 00:00:00', 32 | updatedAt: '2020-05-15 00:00:00' 33 | } 34 | } 35 | 36 | const rolesNameEnum = { 37 | ADMINISTRATOR: rolesEnum.ADMINISTRATOR.name, 38 | MEDIC: rolesEnum.MEDIC.name, 39 | BACTERIOLOGIST: rolesEnum.BACTERIOLOGIST.name, 40 | PACIENT: rolesEnum.PACIENT.name 41 | } 42 | 43 | const roles = [ 44 | rolesEnum.ADMINISTRATOR, 45 | rolesEnum.MEDIC, 46 | rolesEnum.BACTERIOLOGIST, 47 | rolesEnum.PACIENT 48 | ] 49 | 50 | module.exports = { 51 | rolesEnum, 52 | rolesNameEnum, 53 | roles 54 | } 55 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/Modal.scss: -------------------------------------------------------------------------------- 1 | .Modal { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | background-color: rgba(0, 0, 0, 0.5); 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | } 12 | 13 | .Modal__container { 14 | position: relative; 15 | top: 10rem; 16 | background-color: #ffffff; 17 | padding: 1rem; 18 | width: 50%; 19 | border-radius: 0.5em; 20 | text-align: center; 21 | h3 { 22 | text-align: center; 23 | } 24 | } 25 | 26 | .Modal__close-button { 27 | position: absolute; 28 | top: -25px; 29 | right: -20px; 30 | border: 0; 31 | background-color: #fff; 32 | padding: 10px; 33 | border-radius: 50%; 34 | width: 40px; 35 | font-weight: 700; 36 | color: #cac4c4; 37 | font-size: 18px; 38 | &:hover { 39 | cursor: pointer; 40 | color: #000; 41 | } 42 | } 43 | 44 | .Modal__content { 45 | display: grid; 46 | grid-template-columns: 50% 50%; 47 | grid-gap: 10px; 48 | align-items: center; 49 | justify-content: center; 50 | input { 51 | border-radius: 0.5em; 52 | padding: 10px; 53 | border: 1px solid #cac4c4; 54 | } 55 | } 56 | 57 | .Modal__button--save { 58 | width: 20%; 59 | padding: 10px; 60 | margin: auto; 61 | margin-top: 1em; 62 | background: #19243B; 63 | color: #ffffff; 64 | text-align: center; 65 | font-size: 18px; 66 | border: none; 67 | border-radius: 5px; 68 | transition: all 0.3s; 69 | grid-column: 1/3; 70 | &:hover { 71 | transform: scale(0.9); 72 | cursor: pointer; 73 | } 74 | } -------------------------------------------------------------------------------- /Frontend/src/containers/Patient.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import '../assets/styles/containers/Patient.scss'; 5 | import Exam from './Exam'; 6 | import ExamItem from '../components/ExamItem'; 7 | 8 | const Patient = ({ exams }) => { 9 | return ( 10 |
    11 |
    12 | 13 |

    Mis Resultados de Laboratorio

    14 |
    15 | 16 | Seleccionar registro 17 | 18 |

    19 | Ordenar por 20 | 24 |

    25 | 26 | 27 |

    1,2,3,4,5

    28 |
    29 | 30 | 34 | 35 |
    36 | 37 | {exams.map((item) => ( 38 | 39 | ))} 40 | 41 |
    42 |
    43 |
    44 | ); 45 | }; 46 | 47 | const mapStateToProps = (state) => { 48 | return { 49 | exams: state.exams, 50 | }; 51 | }; 52 | 53 | export default connect(mapStateToProps, null)(Patient); 54 | -------------------------------------------------------------------------------- /Backend/store/dummy.js: -------------------------------------------------------------------------------- 1 | const { nanoid } = require('nanoid') 2 | const db = require('./mocks/mockDB') 3 | 4 | async function list (table) { 5 | return db[table] 6 | } 7 | async function get (table, id) { 8 | const collection = await list(table) 9 | const item = new Promise((resolve, reject) => { 10 | const itemRecord = collection.find(item => item.id === id) || null 11 | if (itemRecord) { 12 | return resolve(itemRecord) 13 | } else { 14 | return reject(new Error(`${table} not found`)) 15 | } 16 | }) 17 | 18 | return { ...item } 19 | } 20 | async function insert (table, data) { 21 | if (!db[table]) { 22 | db[table] = [] 23 | } 24 | const id = data.id || nanoid() 25 | 26 | db[table].push({ ...data, id }) 27 | return { ...db[table].find(item => item.id === id) } 28 | } 29 | async function update (table, id, data) { 30 | if (!db[table]) { 31 | db[table] = [] 32 | } 33 | 34 | const itemIndex = db[table].findIndex(item => item.id === id) 35 | 36 | if (itemIndex >= 0) { 37 | db[table][itemIndex] = { ...db[table][itemIndex], ...data } 38 | } 39 | 40 | return { ...db[table][itemIndex] } 41 | } 42 | 43 | async function remove (table, id) { 44 | return true 45 | } 46 | 47 | async function query (table, q) { 48 | const collection = await list(table) 49 | const keys = Object.keys(q) 50 | const key = keys[0] 51 | const results = collection.filter(item => item[key] === q[key]) || null 52 | return results && results.length > 0 ? [...results] : [] 53 | } 54 | 55 | module.exports = { 56 | list, 57 | get, 58 | insert, 59 | update, 60 | remove, 61 | query 62 | } 63 | -------------------------------------------------------------------------------- /Backend/api/components/notification/controller.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config/config') 2 | const admin = require('firebase-admin') 3 | const boom = require('@hapi/boom') 4 | 5 | const FB_APP_NAME = config.fbase.name 6 | const FB_CERT = config.fbase.cert 7 | 8 | const TABLE = 'firebaseNotificationTokens' 9 | 10 | const appFirebase = admin.initializeApp({ 11 | credential: admin.credential.cert(JSON.parse(FB_CERT)), 12 | databaseURL: FB_APP_NAME 13 | }) 14 | 15 | module.exports = (store) => { 16 | function sendMessageMulticast (information) { 17 | return new Promise((resolve, reject) => { 18 | const { token, message, title } = information 19 | const notification = { 20 | token: token, 21 | data: { 22 | title: title, 23 | body: message 24 | } 25 | } 26 | appFirebase 27 | .messaging() 28 | .send(notification) 29 | .then((e) => resolve(e)) 30 | .catch((err) => reject(err)) 31 | }) 32 | } 33 | function saveTokenToUser (profile, { token = null }) { 34 | return new Promise((resolve, reject) => { 35 | if (profile === null || token === null) { reject(boom.internal) } 36 | store.insert(TABLE, { userId: profile.id, token: token }) 37 | .then(response => { 38 | if (response.id !== '') { 39 | resolve(true) 40 | } else { 41 | reject(new Error(false)) 42 | } 43 | }).catch(e => { 44 | console.log(e, 'ERROR') 45 | reject(new Error(false)) 46 | }) 47 | }) 48 | } 49 | 50 | return { 51 | sendMessageMulticast, 52 | saveTokenToUser 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/Patient.scss: -------------------------------------------------------------------------------- 1 | $fuente-principal: 'Quicksand', sans-serif; 2 | $fuente-secundaria: Helvetica; 3 | $color-font:#FFB015; 4 | 5 | .mainContainer { 6 | display: grid; 7 | grid-template-columns: repeat(4, 1fr); 8 | grid-template-rows: 10% 15% 1fr; 9 | } 10 | 11 | .tittle__tipeuser { 12 | display: grid; 13 | // justify-self: center; 14 | grid-column: 2/4; 15 | margin: -60px auto auto auto; 16 | width: 300px; 17 | height: 75px; 18 | h2 { 19 | width: 350px; 20 | color: $color-font; 21 | font-family: $fuente-principal; 22 | font-weight: 600; 23 | } 24 | } 25 | 26 | .selectTypeUser { 27 | grid-column: 1; 28 | } 29 | 30 | .selectrOderList { 31 | grid-column: 2; 32 | justify-content: center; 33 | text-align: center; 34 | display: block; 35 | } 36 | 37 | .selectrOderList select { 38 | margin-left: 15px; 39 | } 40 | 41 | .searchIcon { 42 | color: #999999; 43 | } 44 | 45 | .check-item { 46 | grid-column: 1; 47 | grid-row: 2; 48 | margin: 15px 49 | } 50 | 51 | .pagination { 52 | grid-column: 3; 53 | margin: auto; 54 | } 55 | 56 | #button-download { 57 | width: 280px; 58 | height: 35px; 59 | border-radius: 15px; 60 | grid-column: 4; 61 | margin: auto; 62 | font-family: Helvetica; 63 | font-weight: bold; 64 | } 65 | 66 | .fas.fa-file-pdf { 67 | color: #c41212; 68 | font-size: medium; 69 | margin-right: 10px; 70 | } 71 | 72 | .exam-container { 73 | display: grid; 74 | grid-column: span 3; 75 | } 76 | 77 | .exam-container Exam { 78 | text-align: center; 79 | justify-content: center; 80 | } 81 | 82 | @media screen and(max-width: 736px) { 83 | Exam { 84 | visibility: collapse 85 | } 86 | } -------------------------------------------------------------------------------- /Frontend/src/components/UserItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import deleteIcon from '../assets/static/delete-user.png'; 5 | import editIcon from '../assets/static/edit-user.png'; 6 | import { editUser, deleteUser } from '../actions/index'; 7 | 8 | const UserItem = (props) => { 9 | 10 | const { 11 | id, 12 | idNumber, 13 | firstName, 14 | lastName, 15 | username, 16 | email, 17 | contactNumber, 18 | address 19 | } = props; 20 | 21 | const handleDeleteUser = (itemId) => { 22 | props.deleteUser(itemId); 23 | }; 24 | 25 | return ( 26 | 27 | {idNumber} 28 | 29 | {firstName} 30 | {lastName} 31 | 32 | {username} 33 | {email} 34 | {contactNumber} 35 | {address} 36 | 37 | Edit user 42 | 43 | 44 | Delete user handleDeleteUser(id)} 49 | /> 50 | 51 | 52 | ); 53 | }; 54 | 55 | UserItem.propTypes = { 56 | id: PropTypes.string, 57 | idNumber: PropTypes.number, 58 | firstName: PropTypes.string, 59 | lastName: PropTypes.string, 60 | username: PropTypes.string, 61 | email: PropTypes.string, 62 | contactNumber: PropTypes.string, 63 | address: PropTypes.string, 64 | }; 65 | 66 | const mapDispatchToProps = { 67 | editUser, 68 | deleteUser, 69 | }; 70 | 71 | export default connect(null, mapDispatchToProps)(UserItem); 72 | -------------------------------------------------------------------------------- /Backend/store/mocks/exams/BoodExam.js: -------------------------------------------------------------------------------- 1 | const templateBloodExam = [ 2 | { 3 | name: 'Glucosa', 4 | max: 106, 5 | min: 17, 6 | value: null, 7 | unit: 'mg/dL' 8 | }, 9 | { 10 | name: 'Urea', 11 | max: 49, 12 | min: 17, 13 | value: null, 14 | unit: 'mg/dL' 15 | }, 16 | { 17 | name: 'Ácido úrico', 18 | max: 8, 19 | min: 4.2, 20 | value: null, 21 | unit: 'mg/dl' 22 | }, 23 | { 24 | name: 'Creatinina', 25 | max: 1.3, 26 | min: 0.7, 27 | value: null, 28 | unit: 'mg/dl' 29 | }, 30 | { 31 | name: 'Colesterol', 32 | max: 130, 33 | min: 0, 34 | value: null, 35 | unit: 'mg/dL' 36 | }, 37 | { 38 | name: 'Triglicéridos', 39 | max: 150, 40 | min: 0, 41 | value: null, 42 | unit: 'mg/dl' 43 | }, 44 | { 45 | name: 'Transaminasas', 46 | max: 55, 47 | min: 0, 48 | value: null, 49 | unit: 'unidades/litro' 50 | }, 51 | { 52 | name: 'Hierro', 53 | max: 170, 54 | min: 65, 55 | value: null, 56 | unit: 'mg/dL' 57 | }, 58 | { 59 | name: 'Calcio', 60 | max: 10.2, 61 | min: 8.6, 62 | value: null, 63 | unit: 'mg/dL' 64 | }, 65 | { 66 | name: 'Potasio', 67 | max: 3.5, 68 | min: 5.1, 69 | value: null, 70 | unit: 'mEq/litro' 71 | }, 72 | { 73 | name: 'Sodio', 74 | max: 146, 75 | min: 136, 76 | value: null, 77 | unit: 'mEq/litro' 78 | }, 79 | { 80 | name: 'Bilirrubina', 81 | max: 1.2, 82 | min: 0.8, 83 | value: null, 84 | unit: 'mg/dL' 85 | } 86 | ] 87 | const BloodExam = { 88 | types: [ 89 | { 90 | id: 121, 91 | name: 'Examen de general saguineo', 92 | desc: 'Examen para el analisis general de la sangre', 93 | template_cont: templateBloodExam 94 | } 95 | ] 96 | } 97 | 98 | module.exports = BloodExam 99 | -------------------------------------------------------------------------------- /Frontend/src/containers/Doctor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PatientList from '../components/PatientList'; 4 | import '../assets/styles/containers/PatientList.scss'; 5 | 6 | const Doctor = ({ users }) => { 7 | // state = { 8 | // search: '', 9 | // }; 10 | 11 | // onChange = (e) => { 12 | // this.setState({ search: e.target.value }); 13 | // }; 14 | // const { search } = this.state; 15 | 16 | // const filteredUsers = usersMock.filter((item) => { 17 | // return ( 18 | // `${item.id_number} ${item.first_name} ${item.last_name}` 19 | // .toLowerCase() 20 | // .indexOf(search.toLowerCase()) !== -1 21 | // ); 22 | // }); 23 | 24 | return ( 25 | <> 26 |
    27 |
    28 |

    Listado de Pacientes

    29 |
    30 |
    31 | 37 | 38 | 39 | 40 |
    41 |
    42 |
    43 |

    No. Identificación

    44 |

    Nombres y Apellidos

    45 |

    Ver registro de paciente

    46 |
    47 |
      48 | {users.map((item) => { 49 | return ; 50 | })} 51 |
    52 |
    53 |
    54 | 55 | ); 56 | }; 57 | 58 | const mapStateToProps = (state) => { 59 | return { 60 | users: state.users, 61 | }; 62 | }; 63 | 64 | export default connect(mapStateToProps, null)(Doctor); 65 | -------------------------------------------------------------------------------- /Frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const HtmlWebpackPlugin = require("html-webpack-plugin") 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin") 4 | const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin") 5 | const Dotenv = require("dotenv-webpack") 6 | 7 | module.exports = { 8 | entry: "./src/index.js", 9 | output: { 10 | path: path.resolve(__dirname, "dist"), 11 | filename: "bundle.js", 12 | }, 13 | resolve: { 14 | extensions: [".js", ".jsx"], 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.(js|jsx)$/, 20 | exclude: /node_modules/, 21 | use: { 22 | loader: "babel-loader", 23 | }, 24 | }, 25 | { 26 | test: /\.html$/, 27 | use: [ 28 | { 29 | loader: "html-loader", 30 | }, 31 | ], 32 | }, 33 | { 34 | test: /\.(s*)css$/, 35 | use: [ 36 | { 37 | loader: MiniCssExtractPlugin.loader, 38 | }, 39 | "css-loader", 40 | "sass-loader", 41 | ], 42 | }, 43 | { 44 | test: /\.(png|gif|jpg|jpeg)$/, 45 | use: [ 46 | { 47 | loader: "file-loader", 48 | options: { 49 | name: "assets/[hash].[ext]", 50 | }, 51 | }, 52 | ], 53 | }, 54 | ], 55 | }, 56 | devServer: { 57 | historyApiFallback: true, 58 | open: "Google Chrome" 59 | }, 60 | plugins: [ 61 | new Dotenv(), 62 | new HtmlWebpackPlugin({ 63 | template: "./public/index.html", 64 | filename: "./index.html", 65 | }), 66 | new MiniCssExtractPlugin({ 67 | filename: "assets/[name].css" 68 | }), 69 | new ServiceWorkerWebpackPlugin({ 70 | entry: path.join(__dirname, "./src/sw/firebase-messaging-sw.js"), 71 | filename: "firebase-messaging-sw.js" 72 | }) 73 | ], 74 | } 75 | -------------------------------------------------------------------------------- /Frontend/src/containers/AddExamsResults.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { examTypes } from '../mocks/ExamMock' 3 | import AddResultsModal from '../components/AddResultsModal' 4 | import '../assets/styles/containers/AddExams.scss'; 5 | 6 | class AddExamsResults extends React.Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | modalIsOpen: false, 11 | }; 12 | } 13 | 14 | //State Modal 15 | handleOpenModal = () => { 16 | this.setState({ modalIsOpen: true }); 17 | }; 18 | 19 | handleCloseModal = () => { 20 | this.setState({ modalIsOpen: false }); 21 | }; 22 | 23 | handleSubmit = (e) => { 24 | e.preventDefault(); 25 | } 26 | 27 | render() { 28 | return ( 29 |
    30 |
    31 |

    Agregar resultados a exámenes

    32 |
    33 |

    Exámen

    34 |

    Descripción

    35 |

    Añadir resultado

    36 |
    37 |
      38 | {examTypes.map(({ id, name, description }) => { 39 | return ( 40 |
    • 41 |

      {name}

      42 |

      {description}

      43 | 50 |
    • 51 | ); 52 | })} 53 |
    54 | 60 |
    61 |
    62 | ); 63 | } 64 | } 65 | 66 | export default AddExamsResults; 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | directories: 5 | - node_modules 6 | - ~/.npm 7 | 8 | node_js: 9 | - '12' 10 | 11 | git: 12 | depth: 3 13 | 14 | branches: 15 | only: 16 | - master 17 | - develop 18 | 19 | jobs: 20 | include: 21 | - stage: backend deploy 22 | before_script: cd Backend 23 | script: yarn install && yarn test 24 | deploy: 25 | provider: heroku 26 | skip_cleanup: true 27 | strategy: git 28 | api_key: 29 | secure: "w/l6OsYnLxjoMSX+VSOuIQz7TIdblvPfAI1s+Ags/wLgsRZJiv2v5TNg/2DdrItnQzv45FfstOKA8iiz4aMpNmdTyXDmHABuY+CA8PeA9YEuhFMDDMXTkZVoj1TtdnhSScPGQUBlvUawUZjp/T1kEHH0kO8QHJD5J1UdkdjNXMh+cojm5kFQqBl/ZthHjobPDUk9mVkRE6lfgSfe8GoqkT05e1c5YIlipk7rffVswS7GGcfr3OB5tNiHaD4r0qto68JqhtPEosoRjahlhfdt41byFgDuuDE+KIqM1Kro3sLU6jLjyoRhgGpmqeVSM0PaP5A09z6umuHNyYmQnUmClYCsEdsztH+hSQJjd6Vhzq7XtzXEfpelatcho2Enk/r6s3xQuUAVnzpm8iqksypNLQftV0zRuXvLMXom4q5+Ua05zw45SiEmkI5ZoajpNuAyjGzjRwEhIZ0LHFQM3TFd8s1qEZq7psZBHGFav45I2sgH1CSVF+/px6eSe2d2h8IZHH8k3Bmf1hoWc+EQ2i6c/O2jTKjAGjoR4NUW2TkObWcm6itpltgvde9FWYFP6p2wlD40zE5/LpIwUM7qPhLRoSdEVww4xvE8kGu8nM5ZeUb2ofYl0Swj/KzMZSi8DbkGo6NhR+TwjB4HXYbZjocN9PkTYwcPhyiDTzptzyd0ivE=" 30 | app: megacat-backend 31 | on: master 32 | run: 33 | - "npm run migration:run" 34 | - stage : frontend deploy 35 | before_script: cd Frontend 36 | script: yarn install 37 | deploy: 38 | provider: heroku 39 | skip_cleanup: true 40 | strategy: git 41 | api_key: 42 | secure: "w/l6OsYnLxjoMSX+VSOuIQz7TIdblvPfAI1s+Ags/wLgsRZJiv2v5TNg/2DdrItnQzv45FfstOKA8iiz4aMpNmdTyXDmHABuY+CA8PeA9YEuhFMDDMXTkZVoj1TtdnhSScPGQUBlvUawUZjp/T1kEHH0kO8QHJD5J1UdkdjNXMh+cojm5kFQqBl/ZthHjobPDUk9mVkRE6lfgSfe8GoqkT05e1c5YIlipk7rffVswS7GGcfr3OB5tNiHaD4r0qto68JqhtPEosoRjahlhfdt41byFgDuuDE+KIqM1Kro3sLU6jLjyoRhgGpmqeVSM0PaP5A09z6umuHNyYmQnUmClYCsEdsztH+hSQJjd6Vhzq7XtzXEfpelatcho2Enk/r6s3xQuUAVnzpm8iqksypNLQftV0zRuXvLMXom4q5+Ua05zw45SiEmkI5ZoajpNuAyjGzjRwEhIZ0LHFQM3TFd8s1qEZq7psZBHGFav45I2sgH1CSVF+/px6eSe2d2h8IZHH8k3Bmf1hoWc+EQ2i6c/O2jTKjAGjoR4NUW2TkObWcm6itpltgvde9FWYFP6p2wlD40zE5/LpIwUM7qPhLRoSdEVww4xvE8kGu8nM5ZeUb2ofYl0Swj/KzMZSi8DbkGo6NhR+TwjB4HXYbZjocN9PkTYwcPhyiDTzptzyd0ivE=" 43 | app: nextep-lab 44 | on: master 45 | -------------------------------------------------------------------------------- /Frontend/src/containers/Bacteriologist.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import usersMock from '../mocks/UsersMock.json'; 4 | import '../assets/styles/containers/PatientList.scss'; 5 | 6 | class Bacteriologist extends React.Component { 7 | state = { 8 | search: '', 9 | }; 10 | 11 | onChange = (e) => { 12 | this.setState({ search: e.target.value }); 13 | }; 14 | 15 | render() { 16 | const { search } = this.state; 17 | 18 | const filteredUsersMock = usersMock.filter(item => { 19 | return ( 20 | `${item.id_number} ${item.first_name} ${item.last_name}` 21 | .toLowerCase() 22 | .indexOf(search.toLowerCase()) !== -1 23 | ); 24 | }); 25 | 26 | return ( 27 | <> 28 |
    29 |
    30 | 34 | 35 | 36 | 37 |
    38 |
    39 |
    40 |

    No. Identificación

    41 |

    Nombres y Apellidos

    42 |

    Ver Registro de Exámenes

    43 |
    44 |
      45 | {filteredUsersMock.map((item) => { 46 | return ( 47 |
    • 48 |

      {item.id_number}

      49 |

      50 | {item.first_name} {item.last_name} 51 |

      52 | 57 | 58 | 59 | 60 | 61 |
    • 62 | ); 63 | })} 64 |
    65 |
    66 |
    67 | 68 | ); 69 | } 70 | } 71 | 72 | export default Bacteriologist; 73 | -------------------------------------------------------------------------------- /Backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laboratory_backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "standard:fix": "standard --fix", 8 | "standard:validation": "standard", 9 | "start:api": "nodemon api/index.js", 10 | "test": "jest", 11 | "test:watch": "jest --watch ", 12 | "coverage": "jest --coverage", 13 | "start": "node api/index.js", 14 | "migration:run": "sequelize db:migrate", 15 | "migration:undo": "sequelize db:migrate:undo", 16 | "migration:undo:all": "sequelize db:migrate:undo:all" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/yoshuadiaz/megaCat.git" 21 | }, 22 | "keywords": [], 23 | "author": "MegaCat Team", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/yoshuadiaz/megaCat/issues" 27 | }, 28 | "homepage": "https://github.com/yoshuadiaz/megaCat#readme", 29 | "devDependencies": { 30 | "eslint": "^7.0.0", 31 | "eslint-config-standard": "^14.1.1", 32 | "eslint-plugin-import": "^2.20.2", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-standard": "^4.0.1", 36 | "husky": "^4.2.5", 37 | "jest": "^26.0.1", 38 | "nodemon": "^2.0.4", 39 | "standard": "^14.3.4" 40 | }, 41 | "dependencies": { 42 | "@hapi/boom": "^9.1.0", 43 | "bcrypt": "^4.0.1", 44 | "body-parser": "^1.19.0", 45 | "cookie-parser": "^1.4.5", 46 | "cors": "^2.8.5", 47 | "crypto-random-string": "^3.2.0", 48 | "dotenv": "^8.2.0", 49 | "express": "^4.17.1", 50 | "firebase-admin": "^8.12.1", 51 | "jsonwebtoken": "^8.5.1", 52 | "mysql2": "^2.1.0", 53 | "nanoid": "^3.1.9", 54 | "nodemailer": "^6.4.8", 55 | "passport": "^0.4.1", 56 | "passport-cookie": "^1.0.6", 57 | "passport-http": "^0.3.0", 58 | "passport-jwt": "^4.0.0", 59 | "pluralize": "^8.0.0", 60 | "proxyquire": "^2.1.3", 61 | "sequelize": "^5.21.11", 62 | "sequelize-cli": "^5.5.1", 63 | "supertest": "^4.0.2" 64 | }, 65 | "engines": { 66 | "node": "^12.16.2" 67 | }, 68 | "standard": { 69 | "env": "jest" 70 | }, 71 | "husky": { 72 | "hooks": { 73 | "pre-commit": "npm test && npm run standard:validation", 74 | "pre-push": "npm run standard:validation" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "megacat", 3 | "version": "1.0.0", 4 | "description": "Sistema Gestor de Exámenes para Laboratorios Clínicos", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --mode production", 8 | "start": "webpack-dev-server --open --mode development", 9 | "format": "prettier --write '{*.js,src/**/*.{js,jsx}}'", 10 | "lint": "eslint src --fix", 11 | "test": "jest" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/FernandoTorresL/megaCat.git" 16 | }, 17 | "author": "Fernando Torres ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/FernandoTorresL/megaCat/issues" 21 | }, 22 | "homepage": "https://github.com/FernandoTorresL/megaCat#readme", 23 | "dependencies": { 24 | "axios": "^0.19.2", 25 | "babel-eslint": "^10.1.0", 26 | "eslint-config-airbnb": "^18.1.0", 27 | "eslint-plugin-import": "^2.20.2", 28 | "eslint-plugin-jsx-a11y": "^6.2.3", 29 | "eslint-plugin-react": "^7.20.0", 30 | "firebase": "^7.14.6", 31 | "md5": "^2.2.1", 32 | "prop-types": "^15.7.2", 33 | "react": "^16.13.1", 34 | "react-dom": "^16.13.1", 35 | "react-redux": "^7.2.0", 36 | "react-router-dom": "^5.2.0", 37 | "redux": "^4.0.5", 38 | "serviceworker-webpack-plugin": "^1.0.1" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.10.1", 42 | "@babel/preset-env": "^7.10.1", 43 | "@babel/preset-react": "^7.10.1", 44 | "babel-loader": "^8.1.0", 45 | "css-loader": "^3.5.3", 46 | "dotenv-webpack": "^1.8.0", 47 | "eslint": "^7.1.0", 48 | "eslint-config-prettier": "^6.11.0", 49 | "eslint-loader": "^4.0.2", 50 | "eslint-plugin-prettier": "^3.1.3", 51 | "file-loader": "^6.0.0", 52 | "html-loader": "^1.1.0", 53 | "html-webpack-plugin": "^4.3.0", 54 | "husky": "^4.2.5", 55 | "lint-staged": "^10.2.6", 56 | "mini-css-extract-plugin": "^0.9.0", 57 | "node-sass": "^4.14.1", 58 | "prettier": "^2.0.5", 59 | "sass-loader": "^8.0.2", 60 | "webpack": "^4.43.0", 61 | "webpack-cli": "^3.3.11", 62 | "webpack-dev-server": "^3.11.0" 63 | }, 64 | "husky": { 65 | "hooks": { 66 | "pre-commit": "lint-staged" 67 | } 68 | }, 69 | "lint-staged": { 70 | "*.{js,jsx}": [ 71 | "npm run lint" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/components/Header.scss: -------------------------------------------------------------------------------- 1 | $fuente-principal: "Quicksand", sans-serif; 2 | $background-color: linear-gradient(to left bottom, #1b2470 50%, #293585 50%); 3 | $color-font: #ffb015; 4 | $color-box: #ff5c00; 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | } 11 | 12 | #header-container { 13 | margin-block-start: 0em; 14 | display: grid; 15 | grid-template-columns: 350px 700px 1fr; 16 | grid-template-rows: 100px 90px; 17 | } 18 | 19 | #header-container { 20 | width: 100vw; 21 | height: 130px; 22 | padding-top: 10px; 23 | background: $background-color; 24 | } 25 | 26 | .imagotipo { 27 | margin: 0; 28 | width: 260px; 29 | height: 75px; 30 | &__iconotipo { 31 | width: 100px; 32 | margin-left: 20px; 33 | } 34 | &__logotipo { 35 | width: 140px; 36 | margin-bottom: 5px; 37 | } 38 | } 39 | 40 | .user-container { 41 | display: grid; 42 | justify-self: flex-end; 43 | align-items: flex-end; 44 | margin-right: 40px; 45 | } 46 | 47 | .userIcon { 48 | display: grid; 49 | align-items: end; 50 | padding-right: 50px; 51 | width: 40px; 52 | } 53 | 54 | .tittle-indication p { 55 | font-family: "Quicksand", sans-serif; 56 | font-weight: 600; 57 | font-size: medium; 58 | color: $color-font; 59 | } 60 | 61 | .tittle-indication { 62 | margin-left: 25px; 63 | margin-top: -30px; 64 | grid-column: 1; 65 | grid-row: 2; 66 | justify-content: center; 67 | } 68 | 69 | .namebar { 70 | background: $color-box; 71 | display: grid; 72 | grid-column: 3; 73 | width: 350px; 74 | height: 30px; 75 | margin-top: 25px; 76 | border: 4px solid red; 77 | box-sizing: border-box; 78 | border-radius: 30px; 79 | p { 80 | display: grid; 81 | grid-column: 2; 82 | grid-row: 1; 83 | width: 350px; 84 | height: 30px; 85 | margin: auto; 86 | box-sizing: border-box; 87 | border-radius: 30px; 88 | p { 89 | display: grid; 90 | justify-items: center; 91 | align-items: center; 92 | margin: 0; 93 | color: white; 94 | font-weight: 600; 95 | } 96 | .header__menu { 97 | grid-column: end; 98 | } 99 | .header__menu p { 100 | margin: 0; 101 | } 102 | .header__menu li a { 103 | color: white; 104 | text-decoration: none; 105 | } 106 | .header__menu li a:hover { 107 | text-decoration: underline; 108 | // display: block; 109 | } 110 | .header__menu--profile { 111 | align-items: center; 112 | display: grid; 113 | cursor: pointer; 114 | grid-column: 3; 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Frontend/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import iconotipo from '../assets/static/iconotipo.png' 5 | import logotipo from '../assets/static/logotype.png' 6 | import userIcon from '../assets/static/circle-regular.png' 7 | import '../assets/styles/components/Header.scss' 8 | import gravatar from '../utils/gravatar'; 9 | import { logoutRequest } from '../actions'; 10 | 11 | const Header = (props) => { 12 | 13 | const { user, setRoleType } = props; 14 | const hasUser = Object.keys(user).length > 0; 15 | 16 | const handleLogout = () => { 17 | props.logoutRequest({}); 18 | }; 19 | 20 | return ( 21 |
    22 |
    23 | 24 | Iconotipo de Netxtep 29 | logotipo 34 | 35 |
    36 | 37 | 38 |

    39 | Panel del 40 | {' '} 41 | {setRoleType} 42 |

    43 |
    44 | 45 |
    46 |
    47 | {hasUser ? 48 | ( 49 | {user.email} 54 | ) : 55 | ( 56 | User Profile 61 | )} 62 |

    Perfil

    63 |
    64 | 65 |
      66 | {hasUser ? 67 |
    • {user.name}
    • : 68 | null } 69 | 70 | {hasUser ? 71 | ( 72 |
    • 73 | 74 | Cerrar Sesión 75 | 76 |
    • 77 | ) : 78 | ( 79 |
    • 80 | 81 | Iniciar Sesión 82 | 83 |
    • 84 | ) } 85 |
    86 |
    87 | 88 |
    89 |

    Name user/ ID Code

    90 |
    91 |
    92 | ); 93 | }; 94 | 95 | const mapStateToProps = (state) => { 96 | return { 97 | user: state.user, 98 | }; 99 | }; 100 | 101 | const mapDispatchToProps = { 102 | logoutRequest, 103 | }; 104 | 105 | export default connect(mapStateToProps, mapDispatchToProps)(Header); 106 | -------------------------------------------------------------------------------- /Frontend/src/assets/styles/containers/Administrator.scss: -------------------------------------------------------------------------------- 1 | $fuente-principal: 'Quicksand', sans-serif; 2 | $fuente-secundaria: Helvetica; 3 | 4 | .Container-Admin { 5 | display: grid; 6 | grid-template-columns: 25% 25% 25% 25%; 7 | grid-template-rows: 10% 10% 10% 1fr; 8 | background-color: #EFF1FF; 9 | } 10 | 11 | .searchBox{ 12 | display: flex; 13 | grid-column: 1 / 4; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | .searchbar{ 19 | background-color: #FFFFFF; 20 | border-radius: 25px; 21 | padding-left: 20px; 22 | width: 290px; 23 | border: 1px solid #A1A1A1 ; 24 | } 25 | 26 | .searchBusqueda { 27 | text-align: center; 28 | width: 80%; 29 | padding-left: 5%; 30 | border: none; 31 | } 32 | 33 | .searchBusqueda { 34 | font-size: 15px; 35 | line-height: 1.2; 36 | height: 35px; 37 | } 38 | 39 | .selectTypeUser { 40 | grid-column: 1; 41 | justify-content: center; 42 | text-align: center; 43 | display: block; 44 | } 45 | 46 | .selectrOderList p { 47 | grid-column: 2; 48 | text-align: start; 49 | display: block; 50 | } 51 | 52 | .selectrOderList select { 53 | margin-left: 15px; 54 | } 55 | 56 | .searchIcon { 57 | color: #999999; 58 | } 59 | 60 | .check-card p { 61 | grid-column: 1; 62 | text-align: start; 63 | margin-left: 15px; 64 | } 65 | 66 | #checkbox1 { 67 | margin-left: 25px; 68 | } 69 | 70 | .cvsIcon__container{ 71 | display: grid; 72 | grid-column: 4; 73 | grid-row: 2; 74 | margin: auto; 75 | } 76 | 77 | .addUser { 78 | display: grid; 79 | grid-column: 3; 80 | display: inline; 81 | text-align: start; 82 | } 83 | 84 | .addUser p { 85 | color: #00c2ff; 86 | font-size: 15px; 87 | font-weight: bold; 88 | text-align: start; 89 | display: inline-block; 90 | } 91 | 92 | .fas.fa-plus-circle { 93 | font-size: 40px; 94 | color: #00c2ff; 95 | margin-left: 10px; 96 | } 97 | 98 | .pagination p { 99 | text-align: start; 100 | } 101 | 102 | .tittle-SideBar { 103 | margin-top: 15px; 104 | text-align: center; 105 | font-family: $fuente-secundaria; 106 | font-weight: bold; 107 | } 108 | 109 | .csvIcon { 110 | width: 70px; 111 | margin: 0; 112 | } 113 | 114 | #button-csv { 115 | width: 155px; 116 | height: 35px; 117 | border-radius: 15px; 118 | grid-column: 4; 119 | margin: 0 auto auto auto; 120 | } 121 | 122 | .user__section { 123 | grid-row: 4; 124 | grid-column: span 4; 125 | } 126 | 127 | @media screen and(max-width: 736px) { 128 | .user__section { 129 | grid-column: span 2; 130 | } 131 | #button-csv { 132 | visibility: hidden; 133 | } 134 | .pagination { 135 | display: block; 136 | } 137 | } 138 | 139 | @media screen and(max-width: 480px) { 140 | .user__section { 141 | visibility: collapse; 142 | }} -------------------------------------------------------------------------------- /Frontend/src/routes/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Switch, Route } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import Layout from "../components/Layout"; 5 | import Login from "../containers/HomeLogin"; 6 | import RememberInfo from "../components/RememberInfo"; 7 | import Bacteriologist from "../containers/Bacteriologist"; 8 | import AddExamsResults from "../containers/AddExamsResults"; 9 | import Doctor from "../containers/Doctor"; 10 | import PatientInfo from "../containers/PatientInfo"; 11 | import PersonalInfo from "../components/PersonalInfo"; 12 | import MedicalHistory from "../components/MedicalHistory"; 13 | import SheduleExams from "../components/SheduleExams"; 14 | import Administrator from "../containers/Administrator"; 15 | import Patient from "../containers/Patient"; 16 | import NotFound from "../containers/NotFound"; 17 | import "../assets/styles/App.scss"; 18 | import AddUser from "../containers/AddUser"; 19 | 20 | const App = ({ user }) => { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | {user && 28 | user.roles && 29 | user.roles.find((role) => role.name === "bacteriologist") && ( 30 | <> 31 | 32 | 33 | 34 | )} 35 | {user && 36 | user.roles && 37 | user.roles.find((role) => role.name === "doctor") && ( 38 | <> 39 | 40 | 44 | 45 | 46 | 47 | 48 | )} 49 | {user && 50 | user.roles && 51 | user.roles.find((role) => role.name === "administrator") && ( 52 | <> 53 | 54 | 55 | 56 | )} 57 | {user && 58 | user.roles && 59 | user.roles.find((role) => role.name === "patient") && ( 60 | <> 61 | 62 | 63 | )} 64 | 65 | 66 | 67 | 68 | ); 69 | }; 70 | 71 | const mapStateToProps = state => ({ 72 | user: state.user 73 | }) 74 | 75 | export default connect(mapStateToProps)(App); 76 | -------------------------------------------------------------------------------- /Backend/api/components/auth/controller.js: -------------------------------------------------------------------------------- 1 | const boom = require('@hapi/boom') 2 | const cryptoRandomString = require('crypto-random-string') 3 | const bcrypt = require('bcrypt') 4 | const auth = require('../../../auth/') 5 | const { generateUsername } = require('../../../utils/userUtils') 6 | const userCtrl = require('../user/index') 7 | const emailCtrl = require('../email/index') 8 | const authRolesCtrl = require('../auth_roles/index') 9 | 10 | const TABLE = 'auths' 11 | const saltRounds = 10 12 | 13 | module.exports = (store) => { 14 | async function get (q) { 15 | if (!q) throw boom.badData() 16 | const user = await store.query(TABLE, q, null, [ 17 | { 18 | as: 'roles', 19 | model: store.model('role'), 20 | attributes: ['id', 'name'], 21 | through: { 22 | attributes: [] 23 | } 24 | } 25 | ]) 26 | 27 | return user[0] 28 | } 29 | 30 | async function addUser (userData) { 31 | try { 32 | if (!userData) throw new Error('no data') 33 | 34 | const { idNumber, firstName, lastName, email, contactNumber, address, roleId } = userData 35 | 36 | let username = generateUsername(idNumber, firstName, lastName) 37 | const password = cryptoRandomString({ length: 16, type: 'base64' }) 38 | 39 | const hasUsername = await store.query(TABLE, { username }) 40 | 41 | if (hasUsername.length > 0) { 42 | const numberRange = Array.from({ length: 100 }, (_, idx) => idx < 10 ? `0${idx}` : `${idx}`) 43 | const numberRangeFiltered = numberRange.filter(nr => nr !== username.slice(-2)) 44 | const index = Math.ceil(Math.random() * numberRangeFiltered.length) 45 | const lastTwoRandomNumbers = numberRangeFiltered[index] 46 | username = `${username.slice(0, -2)}${lastTwoRandomNumbers}` 47 | } 48 | 49 | const authData = { 50 | username, 51 | password: await bcrypt.hash(password, saltRounds) 52 | } 53 | 54 | const user = await userCtrl.insert({ 55 | idNumber, 56 | firstName, 57 | lastName, 58 | email, 59 | contactNumber, 60 | address, 61 | username 62 | }) 63 | 64 | await authRolesCtrl.insert(user.id, roleId) 65 | 66 | await store.insert(TABLE, { ...authData, id: user.id }) 67 | 68 | const emailInfo = { 69 | ...JSON.parse(JSON.stringify(user)), 70 | password: password 71 | } 72 | await emailCtrl.sendNewUser(emailInfo) 73 | 74 | return true 75 | } catch (error) { 76 | throw boom.badRequest() 77 | } 78 | } 79 | async function login (User) { 80 | try { 81 | if (!User) { 82 | throw boom.unauthorized() 83 | } 84 | 85 | const user = { 86 | id: User.id, 87 | idNumber: User.idNumber, 88 | username: User.username, 89 | roles: User.roles 90 | } 91 | 92 | const token = await auth.sign({ ...user }) 93 | 94 | const userData = await userCtrl.get(User.id) 95 | return { 96 | token, 97 | user: { ...user, ...userData } 98 | } 99 | } catch (error) { 100 | throw boom.unauthorized() 101 | } 102 | } 103 | 104 | return { 105 | login, 106 | get, 107 | addUser 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Frontend/src/containers/Administrator.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import { connect } from 'react-redux'; 5 | import '../assets/styles/containers/Administrator.scss'; 6 | import User from './User'; 7 | import UserItem from '../components/UserItem'; 8 | import csvIcon from '../assets/static/csv-file.png'; 9 | 10 | const Administrator = ({ users }) => { 11 | return ( 12 |
    13 |
    14 |
    15 | 16 | 17 | 18 | 19 | 24 | 25 |
    26 | 27 | 28 |

    Cargar información de grupo

    29 |
    30 | 31 | 32 |

    33 | Seleccionar tipo de usuario 34 | 41 |

    42 |
    43 | 44 | 45 |

    46 | Ordenar por: 47 | 51 |

    52 |
    53 | 54 | 55 |

    Paginación

    56 |
    57 | 58 | 59 |

    Seleccionar registro

    60 | 61 |
    62 | 63 | 64 |

    Usted esta viendo la lista de

    65 |
    66 | 67 |
    68 | 69 |

    Agregar nuevo usuario

    70 | 71 | 72 |
    73 | 74 |
    75 | CSVIcon 76 |
    77 | 78 | 81 | 82 |
    83 | 84 | {users.map((item) => ( 85 | 86 | ))} 87 | 88 |
    89 |
    90 |
    91 | ); 92 | }; 93 | 94 | const mapStateToProps = (state) => { 95 | return { 96 | users: state.users, 97 | }; 98 | }; 99 | 100 | export default connect(mapStateToProps, null)(Administrator); 101 | -------------------------------------------------------------------------------- /Backend/store/sequelize.js: -------------------------------------------------------------------------------- 1 | const pluralize = require('pluralize') 2 | const store = require('../db/models') 3 | 4 | const DEFAULTS = { 5 | ITEMS_PER_PAGE: 5, 6 | CURRENT_PAGE: 1 7 | } 8 | const toJSON = (data) => JSON 9 | .parse(JSON.stringify(data)) 10 | 11 | const pagination = (count, rows, itemsPerPage, currentPage) => ({ 12 | total: count, 13 | itemsPerPage, 14 | lastPage: Math.ceil(count / itemsPerPage), 15 | page: currentPage, 16 | rows: [...rows] 17 | }) 18 | async function list (table, paginationConfig) { 19 | const model = pluralize.singular(table) 20 | const defaultWhere = { isDeleted: false } 21 | const config = { where: { ...defaultWhere } } 22 | 23 | if (paginationConfig) { 24 | const itemsPerPage = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE 25 | const currentPage = paginationConfig.currentPage || DEFAULTS.CURRENT_PAGE 26 | config.limit = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE 27 | config.offset = currentPage <= 1 ? 0 : (currentPage - 1) * config.limit 28 | 29 | const results = await store[model].findAndCountAll({ 30 | ...config, 31 | distinct: true, 32 | raw: true 33 | }).then(({ count, rows }) => pagination(count, rows, itemsPerPage, currentPage)) 34 | 35 | return toJSON(results) 36 | } 37 | 38 | return store[model].findAll({ 39 | ...config, 40 | where: { isDeleted: false } 41 | }) 42 | } 43 | async function get (table, id) { 44 | const model = pluralize.singular(table) 45 | return store[model].findByPk(id, { raw: true }) 46 | } 47 | async function insert (table, data) { 48 | const model = pluralize.singular(table) 49 | return store[model].create(data, { raw: true }) 50 | } 51 | async function update (table, id, data) { 52 | const model = pluralize.singular(table) 53 | return store[model].update({ ...data }, { where: { id }, raw: true }) 54 | } 55 | async function remove (table, id) { 56 | const model = pluralize.singular(table) 57 | await store[model].update({ isDeleted: true }, { where: { id } }) 58 | return true 59 | } 60 | async function query (table, where, paginationConfig = null, include = null) { 61 | const model = pluralize.singular(table) 62 | const defaultWhere = { isDeleted: false } 63 | const config = { where: { ...defaultWhere } } 64 | if (where) { 65 | config.where = { ...config.where, ...where } 66 | } 67 | 68 | if (include) { 69 | config.include = include 70 | } 71 | 72 | if (paginationConfig) { 73 | const itemsPerPage = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE 74 | const currentPage = paginationConfig.currentPage || DEFAULTS.CURRENT_PAGE 75 | config.limit = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE 76 | config.offset = currentPage <= 1 ? 0 : (currentPage - 1) * config.limit 77 | 78 | const results = await store[model].findAndCountAll({ 79 | ...config, 80 | distinct: true, 81 | raw: true 82 | }).then(({ count, rows }) => pagination(count, rows, itemsPerPage, currentPage)) 83 | 84 | return toJSON(results) 85 | } 86 | 87 | const results = await store[model].findAll({ 88 | ...config 89 | }) 90 | return toJSON(results) 91 | } 92 | 93 | function model (model) { 94 | return store[model] 95 | } 96 | 97 | module.exports = { 98 | list, 99 | get, 100 | insert, 101 | update, 102 | remove, 103 | query, 104 | model 105 | } 106 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at leshz@me.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Backend/store/mocks/AuthMock.js: -------------------------------------------------------------------------------- 1 | const { rolesEnum } = require('./RolesMock') 2 | const auths = [ 3 | { 4 | id: '018a5472-bf8e-4889-a3a2-ebdd4722d6b2', 5 | idNumber: 1767, 6 | username: 'leodora.crouch.1767', 7 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 8 | roleId: rolesEnum.ADMINISTRATOR.id, 9 | isDeleted: false, 10 | createdAt: '2020-01-22 14:00:18', 11 | updatedAt: '2020-02-27 20:11:39' 12 | }, 13 | { 14 | id: '832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c', 15 | idNumber: 3469, 16 | username: 'hayden.haseman.3469', 17 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 18 | roleId: rolesEnum.MEDIC.id, 19 | isDeleted: false, 20 | createdAt: '2020-02-03 08:45:34', 21 | updatedAt: '2019-12-31 18:44:32' 22 | }, 23 | { 24 | id: '795b5699-7ca9-4344-8fe5-4a4f501678a9', 25 | idNumber: 5582, 26 | username: 'basile.matteini.5582', 27 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 28 | roleId: rolesEnum.BACTERIOLOGIST.id, 29 | isDeleted: true, 30 | createdAt: '2019-10-10 02:05:12', 31 | updatedAt: '2020-02-03 00:16:37' 32 | }, 33 | { 34 | id: '1cffa615-e1d8-4113-b585-60603f0854fb', 35 | idNumber: 3123, 36 | username: 'hersh.oloman.3123', 37 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 38 | roleId: rolesEnum.PACIENT.id, 39 | isDeleted: false, 40 | createdAt: '2019-10-18 22:22:15', 41 | updatedAt: '2019-10-27 06:28:39' 42 | }, 43 | { 44 | id: '0a96b420-ba2b-4741-b290-c916d92abdc0', 45 | idNumber: 1011, 46 | username: 'ursula.cowen.1011', 47 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 48 | roleId: rolesEnum.ADMINISTRATOR.id, 49 | isDeleted: true, 50 | createdAt: '2019-05-23 01:10:13', 51 | updatedAt: '2019-11-11 16:03:54' 52 | }, 53 | { 54 | id: 'b9df98a3-f23d-4711-b710-59efe5387921', 55 | idNumber: 7897, 56 | username: 'norry.vockins.7897', 57 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 58 | roleId: rolesEnum.MEDIC.id, 59 | isDeleted: false, 60 | createdAt: '2020-02-02 06:11:16', 61 | updatedAt: '2019-08-20 00:41:38' 62 | }, 63 | { 64 | id: 'ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc', 65 | idNumber: 5839, 66 | username: 'hort.mantram.5839', 67 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 68 | roleId: rolesEnum.BACTERIOLOGIST.id, 69 | isDeleted: true, 70 | createdAt: '2019-10-12 12:44:17', 71 | updatedAt: '2020-04-14 12:24:05' 72 | }, 73 | { 74 | id: '9d7f71ff-49fe-4819-859c-727ce7f44be3', 75 | idNumber: 6214, 76 | username: 'christie.baelde.6214', 77 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 78 | roleId: rolesEnum.PACIENT.id, 79 | isDeleted: false, 80 | createdAt: '2019-12-08 08:22:59', 81 | updatedAt: '2020-05-12 06:13:23' 82 | }, 83 | { 84 | id: 'c0f557a2-da71-4974-930f-17e52935bc7a', 85 | idNumber: 1325, 86 | username: 'byram.doblin.1325', 87 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 88 | roleId: rolesEnum.PACIENT.id, 89 | isDeleted: true, 90 | createdAt: '2020-01-02 01:26:33', 91 | updatedAt: '2020-02-27 09:26:02' 92 | }, 93 | { 94 | id: '5a12fd8d-24f5-4327-a40a-3b1ff02c8d55', 95 | idNumber: 1382, 96 | username: 'dniren.pidwell.1382', 97 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G', 98 | roleId: rolesEnum.PACIENT.id, 99 | isDeleted: false, 100 | createdAt: '2019-09-16 09:43:44', 101 | updatedAt: '2019-06-21 00:51:37' 102 | } 103 | ] 104 | 105 | module.exports = auths 106 | -------------------------------------------------------------------------------- /Backend/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows 3 | # Edit at https://www.gitignore.io/?templates=node,linux,macos,windows 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### macOS ### 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### Node ### 49 | # Logs 50 | logs 51 | *.log 52 | npm-debug.log* 53 | yarn-debug.log* 54 | yarn-error.log* 55 | lerna-debug.log* 56 | 57 | # Diagnostic reports (https://nodejs.org/api/report.html) 58 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 59 | 60 | # Runtime data 61 | pids 62 | *.pid 63 | *.seed 64 | *.pid.lock 65 | 66 | # Directory for instrumented libs generated by jscoverage/JSCover 67 | lib-cov 68 | 69 | # Coverage directory used by tools like istanbul 70 | coverage 71 | *.lcov 72 | 73 | # nyc test coverage 74 | .nyc_output 75 | 76 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 77 | .grunt 78 | 79 | # Bower dependency directory (https://bower.io/) 80 | bower_components 81 | 82 | # node-waf configuration 83 | .lock-wscript 84 | 85 | # Compiled binary addons (https://nodejs.org/api/addons.html) 86 | build/Release 87 | 88 | # Dependency directories 89 | node_modules/ 90 | jspm_packages/ 91 | 92 | # TypeScript v1 declaration files 93 | typings/ 94 | 95 | # TypeScript cache 96 | *.tsbuildinfo 97 | 98 | # Optional npm cache directory 99 | .npm 100 | 101 | # Optional eslint cache 102 | .eslintcache 103 | 104 | # Optional REPL history 105 | .node_repl_history 106 | 107 | # Output of 'npm pack' 108 | *.tgz 109 | 110 | # Yarn Integrity file 111 | .yarn-integrity 112 | 113 | # dotenv environment variables file 114 | .env 115 | .env.test 116 | 117 | # parcel-bundler cache (https://parceljs.org/) 118 | .cache 119 | 120 | # next.js build output 121 | .next 122 | 123 | # nuxt.js build output 124 | .nuxt 125 | 126 | # rollup.js default build output 127 | dist/ 128 | 129 | # Uncomment the public line if your project uses Gatsby 130 | # https://nextjs.org/blog/next-9-1#public-directory-support 131 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 132 | # public 133 | 134 | # Storybook build outputs 135 | .out 136 | .storybook-out 137 | 138 | # vuepress build output 139 | .vuepress/dist 140 | 141 | # Serverless directories 142 | .serverless/ 143 | 144 | # FuseBox cache 145 | .fusebox/ 146 | 147 | # DynamoDB Local files 148 | .dynamodb/ 149 | 150 | # Temporary folders 151 | tmp/ 152 | temp/ 153 | 154 | ### Windows ### 155 | # Windows thumbnail cache files 156 | Thumbs.db 157 | Thumbs.db:encryptable 158 | ehthumbs.db 159 | ehthumbs_vista.db 160 | 161 | # Dump file 162 | *.stackdump 163 | 164 | # Folder config file 165 | [Dd]esktop.ini 166 | 167 | # Recycle Bin used on file shares 168 | $RECYCLE.BIN/ 169 | 170 | # Windows Installer files 171 | *.cab 172 | *.msi 173 | *.msix 174 | *.msm 175 | *.msp 176 | 177 | # Windows shortcuts 178 | *.lnk 179 | 180 | # End of https://www.gitignore.io/api/node,linux,macos,windows 181 | .env 182 | coverage -------------------------------------------------------------------------------- /Frontend/src/mocks/ExamMock.js: -------------------------------------------------------------------------------- 1 | // Exams 2 | const Exams = [ 3 | { 4 | id: "a0108392-00af-4557-865d-fa378789f4cc", 5 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }], 6 | reservation_date: "", 7 | status_id: "72a88d3f-933a-4ab5-96ed-70e5bcb0dbbd", 8 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e", 9 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec", 10 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f", 11 | type_id: "10b9be0a-db18-448b-98aa-002d53339805", 12 | is_deleted: false, 13 | created_at: "2020-05-25 03:48:19.437-05", 14 | updated_at: "2020-05-25 03:48:19.437-05" 15 | }, 16 | { 17 | id: "a0108392-00af-4557-865d-fa378789f4ca", 18 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }], 19 | reservation_date: "2020-05-25 03:48:19.437-05", 20 | status_id: "c47400b7-9b11-43c4-93ff-c72652e42903", 21 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e", 22 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec", 23 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f", 24 | type_id: "10b9be0a-db18-448b-98aa-002d53339805", 25 | is_deleted: false, 26 | created_at: "2020-05-25 03:48:19.437-05", 27 | updated_at: "2020-05-25 03:48:19.437-05" 28 | }, 29 | { 30 | id: "a0108392-00af-4557-865d-fa378789f4cd", 31 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }], 32 | reservation_date: "2020-05-25 03:48:19.437-05", 33 | status_id: "ed7efc9d-1be8-4fc9-b761-4debfb4d547f", 34 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e", 35 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec", 36 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f", 37 | type_id: "10b9be0a-db18-448b-98aa-002d53339805", 38 | is_deleted: false, 39 | created_at: "2020-05-25 03:48:19.437-05", 40 | updated_at: "2020-05-25 03:48:19.437-05" 41 | }, 42 | ] 43 | 44 | // exam_types 45 | 46 | const examTypes = [ 47 | { 48 | id: "", 49 | name: "Biometría hemática", 50 | description: "lorem ipsum dolor sit amet", 51 | template_content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: null }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: null }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: null }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: null }], 52 | is_deleted: false, 53 | created_at: "2020-05-25 03:48:19.437-05", 54 | updated_at: "2020-05-25 03:48:19.437-05" 55 | } 56 | ] 57 | 58 | // exam_statuses 59 | const examStatus = [ 60 | { 61 | id: "72a88d3f-933a-4ab5-96ed-70e5bcb0dbbd", 62 | name: "Pendiente", 63 | description: "Lorem ipsum", 64 | is_deleted: false, 65 | created_at: "2020-05-25 03:48:19.437-05", 66 | updated_at: "2020-05-25 03:48:19.437-05" 67 | }, 68 | { 69 | id: "c47400b7-9b11-43c4-93ff-c72652e42903", 70 | name: "Asignado", 71 | description: "Lorem ipsum", 72 | is_deleted: false, 73 | created_at: "2020-05-25 03:48:19.437-05", 74 | updated_at: "2020-05-25 03:48:19.437-05" 75 | }, 76 | { 77 | id: "ed7efc9d-1be8-4fc9-b761-4debfb4d547f", 78 | name: "Completado", 79 | description: "Lorem ipsum", 80 | is_deleted: false, 81 | created_at: "2020-05-25 03:48:19.437-05", 82 | updated_at: "2020-05-25 03:48:19.437-05" 83 | } 84 | ] 85 | 86 | module.exports = { 87 | Exams, 88 | examTypes, 89 | examStatus 90 | } 91 | -------------------------------------------------------------------------------- /Frontend/src/containers/AddUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import { addUser } from '../actions/index'; 5 | 6 | import Header from '../components/Header'; 7 | import '../assets/styles/containers/AddUser.scss'; 8 | 9 | const AddUser = (props) => { 10 | 11 | const [form, setForm] = useState({ 12 | rolName: '', 13 | idNumber: '', 14 | firstName: '', 15 | lastName: '', 16 | contactNumber: '', 17 | address: '', 18 | email: '', 19 | }); 20 | 21 | const handleInput = (event) => { 22 | setForm({ 23 | ...form, 24 | [event.target.name]: event.target.value, 25 | }); 26 | }; 27 | 28 | const handleSubmit = (event) => { 29 | event.preventDefault(); 30 | console.log(form); 31 | props.addUser(form); 32 | props.history.push('/administrator'); 33 | }; 34 | 35 | const handleChange = (event) => { 36 | setForm({ rolName: event.target.value }); 37 | } 38 | 39 | return ( 40 |
    41 |
    42 |
    43 |
    44 |
    45 | 46 |

    47 | Seleccionar rol del usuario 48 | {' '} 49 | {/* Traer estos valores de la BD */} 50 | 56 |

    57 |
    58 | 59 | 66 | 67 | 74 | 81 | 88 | 95 | 102 | 103 | 106 | 107 | 110 | 111 |
    112 |
    113 |
    114 |
    115 | ); 116 | }; 117 | const mapDispatchToProps = { 118 | addUser, 119 | }; 120 | 121 | export default connect(null, mapDispatchToProps)(AddUser); 122 | -------------------------------------------------------------------------------- /Frontend/src/containers/HomeLogin.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/button-has-type */ 2 | import React, { useState } from "react"; 3 | import { Link } from "react-router-dom"; 4 | import { connect } from "react-redux"; 5 | import axios from "axios" 6 | import { loginRequest } from "../actions"; 7 | import messaging from "../conf/firebase"; 8 | 9 | import logo from "../assets/static/logo.png"; 10 | import "../assets/styles/containers/Login.scss"; 11 | 12 | const Login = (props) => { 13 | 14 | const [form, setValues] = useState({ 15 | username: "", 16 | password: "" 17 | }); 18 | 19 | const handleInput = (event) => { 20 | setValues({ 21 | ...form, 22 | [event.target.name]: event.target.value, 23 | }); 24 | }; 25 | 26 | const handleSubmit = (event) => { 27 | event.preventDefault(); 28 | 29 | axios.post("https://megacat-backend.herokuapp.com/api/auth/login", {}, { 30 | 31 | auth: { 32 | username: form.username, 33 | password: form.password 34 | } 35 | }).then(response => { 36 | const roles = response.data.body.user.roles || [{ name: "None" }] 37 | props.loginRequest({ ...response.data.body.user, token: response.data.body.token }); 38 | if (roles[0].name !== "None") { 39 | props.history.push(`/${roles[0].name}`) 40 | } 41 | }) 42 | }; 43 | messaging.requestPermission() 44 | .then(() =>{ 45 | return messaging.getToken(); 46 | }) 47 | .then(e => { 48 | console.log(e); 49 | }) 50 | .catch(function (err) { 51 | console.log("Unable to get permission to notify.", err); 52 | }); 53 | navigator.serviceWorker.addEventListener("message", (message) => console.log(message)); 54 | 55 | return ( 56 |
    57 | 58 |
    59 | Logo de Nextep 60 |
    61 | 62 |
    63 |

    Bienvenidos a Nextep

    64 | 65 |
    66 |
    67 | 75 | 76 | 77 | 78 |
    79 |
    80 | 88 | 89 | 90 | 91 |
    92 | 93 | {/* 94 | Ingresar como admin 95 | 96 | 97 | Ingresar como paciente 98 | */} 99 | ¿Has olvidado tu Usuario/Contraseña? 100 | 101 |
    102 | 103 | 104 | 105 | 106 | 107 | 108 | Aviso de privacidad 109 | 110 |
    111 |
    112 |
    113 | ); 114 | }; 115 | 116 | const mapDispatchToProps = { 117 | loginRequest, 118 | }; 119 | 120 | export default connect(null, mapDispatchToProps)(Login); 121 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Welcome to Nextep Lab 👋 2 | 3 | ![Version](https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000) 4 | [![Documentation](https://img.shields.io/badge/documentation-yes-brightgreen.svg)](https://github.com/leshz/megaCat#readme) 5 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/leshz/megaCat/graphs/commit-activity) 6 | 7 | > Nextep born to make easier and faster manage your clinical laboratory with a friendly way for your patients. 8 | 9 | ![Version](Documentos/images/login.png) 10 | 11 | # Take A Look 12 | 13 | - [Login](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=4%3A0&scaling=scale-down) 14 | - [Patient](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=25%3A2&scaling=scale-down) 15 | - [Admin](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=255%3A1&scaling=scale-down) 16 | - [Bacteriologist](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=388%3A37&scaling=scale-down) 17 | - [Doctor](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=37%3A99&scaling=scale-down) 18 | 19 | We build two wonderful projects: **Backend** and **Frontend** 20 | 21 | ## **Backend APP** 22 | 23 | You can read the [API REST Docs here](https://documenter.getpostman.com/view/1023966/Szt8c97m?version=latest) 24 | 25 | ### ✨ [API DEMO](https://megacat-backend.herokuapp.com) 26 | 27 | ### Install 28 | 29 | ```sh 30 | npm install 31 | ``` 32 | 33 | ### Usage 34 | 35 | ```sh 36 | npm run start:api 37 | ``` 38 | 39 | ### Run Tests 40 | 41 | ```sh 42 | npm run test 43 | ``` 44 | 45 | ### Build Model DB 46 | 47 | ```sh 48 | npm run migration:run 49 | ``` 50 | 51 | ### Sequelize Usefull Commands 52 | 53 | If you want create a Sequelize model with a migration you need to run: 54 | 55 | ```sh 56 | npx sequelize model:generate --name --attributes :,: 57 | 58 | Example: 59 | 60 | npx sequelize model:generate user --attributes username:string,password:string 61 | ``` 62 | 63 | For more information [read this docs](https://sequelize.org/master/manual/migrations.html) 64 | 65 | ### Stack 66 | 67 | - NodeJS 68 | - Express 69 | - Sequelize 70 | - MySQL 71 | - Jest 72 | - JWT 73 | - Passport 74 | - Standard 75 | - Hapi/Boom 76 | - Firebase 77 | 78 | --- 79 | 80 | ## Frontend APP 81 | 82 | ### ✨ [Demo](https://nextep-lab.herokuapp.com) 83 | 84 | ## Install 85 | 86 | ```sh 87 | npm install 88 | ``` 89 | 90 | ## Usage 91 | 92 | ```sh 93 | npm run start 94 | ``` 95 | 96 | ## Run tests 97 | 98 | ```sh 99 | npm run test 100 | ``` 101 | 102 | ### Stack 103 | 104 | - ReactJs 105 | - Redux 106 | - Firebase 107 | - SCSS 108 | - Standard 109 | 110 | ## Authors 111 | 112 | 👤 **Guiselle Mejía ** 113 | 114 | - Github: [@guissmejia](github.com/guissmejia) 115 | - LinkedIn: [in/guiselle-mejía-561a61191](https://www.linkedin.com/in/guiselle-mejía-561a61191/) 116 | 117 | 👤 **Yoshua Díaz** 118 | 119 | - Website: [yoshuadiaz.com](https://yoshuadiaz.com) 120 | - Github: [@yoshuadiaz](https://github.com/yoshuadiaz) 121 | - LinkedIn: [in/yoshuadiaz](https://www.linkedin.com/in/yoshua-diaz) 122 | 123 | 👤 **Omar Betanzos ** 124 | 125 | - Github: [@dashdancing](https://github.com/dashdancing) 126 | - LinkedIn: [in/omar.gbet](https://www.linkedin.com/in/omar.gbet) 127 | 128 | 👤 **Jeffer Barragan ** 129 | 130 | - Website: [gitlab.com/leshz](https://gitlab.com/leshz) 131 | - Github: [@leshz](https://gitlab.com/leshz) 132 | - LinkedIn: [in/jeffbarragan/](https://www.linkedin.com/in/jeffbarragan/) 133 | 134 | 👤**Fernando Torres ** 135 | 136 | - Website: [fertorresmx.com](https://www.fertorresmx.com) 137 | - Github: [@FernandoTorresL](https://github.com/FernandoTorresL) 138 | - LinkedIn: [@fertorresmx](https://www.linkedin.com/in/fertorresmx/) 139 | 140 | ## 🤝 Contributing :) 141 | 142 | Contributions, issues and feature requests are welcome! 143 | 144 | Feel free to check [issues page](https://github.com/leshz/megaCat/issues). You can also take a look at the [contributing guide](https://github.com/leshz/megaCat/blob/master/CONTRIBUTING.md). 145 | 146 | ## Documentation 147 | 148 | You can find the Nextep App documentation [On Notion](https://www.notion.so/MegaCat-Documentaci-n-26f9cb69824d4fd5bbb43898ca7e9ea6) 149 | 150 | 151 | ## Show your support 152 | 153 | Give a ⭐️ if this project helped you! 154 | 155 | ## 📝 License 156 | 157 | Copyright © 2020 [Guiselle Mejía ](https://github.com/leshz). 158 | 159 | This project is [MIT](https://github.com/leshz/megaCat/blob/master/LICENSE) licensed. 160 | -------------------------------------------------------------------------------- /Backend/__test__/api.components.auth.spec.js: -------------------------------------------------------------------------------- 1 | const auths = require('../store/mocks/AuthMock') 2 | const users = require('../store/mocks/UsersMock') 3 | const storeMock = require('../store/mocks/storeMock') 4 | const userCtrl = require('../api/components/user/index') 5 | const emailCtrl = require('../api/components/email/index') 6 | const authRolesCtrl = require('../api/components/auth_roles/index') 7 | jest.mock('../api/components/user/index.js', () => ({ 8 | insert: jest.fn((user) => ({ ...user, id: '1234567890' })), 9 | get: jest.fn((id) => ({ id })) 10 | })) 11 | 12 | jest.mock('../api/components/email/index.js', () => ({ 13 | sendNewUser: jest.fn((data) => true) 14 | })) 15 | 16 | jest.mock('../api/components/auth_roles/index', () => ({ 17 | insert: jest.fn(() => true) 18 | })) 19 | 20 | describe('API | Components | Auth', () => { 21 | const { rolesEnum } = require('../store/mocks/RolesMock') 22 | 23 | describe('Controllers', () => { 24 | const ctrl = require('../api/components/auth/controller') 25 | 26 | const controller = ctrl(storeMock) 27 | 28 | describe('login', () => { 29 | it('should receive a user', async () => { 30 | const result = await controller.login(auths[0]) 31 | const expected = { 32 | id: users[0].id, 33 | idNumber: users[0].idNumber, 34 | username: users[0].username, 35 | roles: undefined 36 | } 37 | 38 | expect(result.user).toEqual(expected) 39 | }) 40 | 41 | it('should respond with a user and token data', () => { 42 | expect().toBeFalsy() 43 | }) 44 | 45 | it('should respond with an error if not pass a user', async () => { 46 | const response = controller.login() 47 | expect(response).rejects.toThrow() 48 | }) 49 | }) 50 | 51 | describe('get', () => { 52 | it('should call store.query', async (done) => { 53 | const user = users[0] 54 | const userAuth = auths.find(u => u.id === user.id) 55 | const response = await controller.get({ id: user.id }) 56 | expect(response).toEqual(userAuth) 57 | done() 58 | }) 59 | 60 | it('should fail without query data', () => { 61 | const response = controller.get() 62 | expect(response).rejects.toThrow() 63 | }) 64 | 65 | it('should return an user object', () => { 66 | }) 67 | }) 68 | 69 | describe('addUser', () => { 70 | const { generateUsername } = require('../utils/userUtils') 71 | const userDataMock = { 72 | idNumber: users[1].idNumber, 73 | firstName: users[1].firstName, 74 | lastName: users[1].lastName, 75 | email: users[1].email, 76 | contactNumber: users[1].contactNumber, 77 | address: users[1].address, 78 | roleId: rolesEnum.ADMINISTRATOR.id 79 | } 80 | 81 | beforeEach(() => { 82 | userCtrl.insert.mockClear() 83 | emailCtrl.sendNewUser.mockClear() 84 | }) 85 | it('should insert a user', async (done) => { 86 | const userData = { 87 | ...userDataMock, 88 | idNumber: 1010101010101010 89 | } 90 | const userExpected = { 91 | id: '1234567890', 92 | idNumber: userData.idNumber, 93 | firstName: userData.firstName, 94 | lastName: userData.lastName, 95 | email: userData.email, 96 | contactNumber: userData.contactNumber, 97 | address: userData.address, 98 | username: generateUsername(userData.idNumber, userData.firstName, userData.lastName) 99 | } 100 | 101 | const response = await controller.addUser(userData) 102 | const userInserted = userCtrl.insert.mock.results[0].value 103 | expect(response).toBeTruthy() 104 | expect(userInserted).toEqual(userExpected) 105 | done() 106 | }) 107 | 108 | it('should generate a random two numbers different if hasUsername', async () => { 109 | const userData = { 110 | ...userDataMock 111 | } 112 | const response = await controller.addUser(userData) 113 | const username = generateUsername(userData.idNumber, userData.firstName, userData.lastName) 114 | const userInserted = userCtrl.insert.mock.results[0].value 115 | const authRoleInserted = authRolesCtrl.insert.mock.calls[0] 116 | expect(response).toBeTruthy() 117 | expect(username.slice(0, -2)) 118 | .toBe(userInserted.username.slice(0, -2)) 119 | expect(authRoleInserted).toEqual([userInserted.id, userData.roleId]) 120 | expect(username) 121 | .not 122 | .toBe(userInserted.username) 123 | }) 124 | 125 | it('should return an error if the process fails', () => { 126 | const response = controller.addUser() 127 | expect(response).rejects.toThrow() 128 | }) 129 | }) 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /Backend/store/mocks/UsersMock.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 4 | "idNumber": 1767, 5 | "firstName": "Leodora", 6 | "lastName": "Crouch", 7 | "username": "leodora.crouch.1767", 8 | "email": "lcrouch0@wix.com", 9 | "contactNumber": "715-982-0519", 10 | "address": "26 Quincy Pass", 11 | "medicalRecord": "03254d70-723b-4bed-8adb-477dffe8f092", 12 | "isDeleted": false, 13 | "createdAt": "2020-01-22 14:00:18", 14 | "updatedAt": "2020-02-27 20:11:39" 15 | }, 16 | { 17 | "id": "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c", 18 | "idNumber": 3469, 19 | "firstName": "Hayden", 20 | "lastName": "Haseman", 21 | "username": "hayden.haseman.3469", 22 | "email": "hhaseman1@apple.com", 23 | "contactNumber": "552-913-5574", 24 | "address": "5761 Melvin Drive", 25 | "medicalRecord": "088b8665-d176-4021-9416-ac787339c568", 26 | "isDeleted": false, 27 | "createdAt": "2020-02-03 08:45:34", 28 | "updatedAt": "2019-12-31 18:44:32" 29 | }, 30 | { 31 | "id": "795b5699-7ca9-4344-8fe5-4a4f501678a9", 32 | "idNumber": 5582, 33 | "firstName": "Basile", 34 | "lastName": "Matteini", 35 | "username": "basile.matteini.5582", 36 | "email": "bmatteini2@squarespace.com", 37 | "contactNumber": "978-756-5028", 38 | "address": "939 7th Place", 39 | "medicalRecord": "56021793-eed0-44c1-b2ab-72b245f1bb17", 40 | "isDeleted": true, 41 | "createdAt": "2019-10-10 02:05:12", 42 | "updatedAt": "2020-02-03 00:16:37" 43 | }, 44 | { 45 | "id": "1cffa615-e1d8-4113-b585-60603f0854fb", 46 | "idNumber": 3123, 47 | "firstName": "Hersh", 48 | "lastName": "Oloman", 49 | "username": "hersh.oloman.3123", 50 | "email": "holoman3@themeforest.net", 51 | "contactNumber": "746-918-2444", 52 | "address": "05898 Southridge Alley", 53 | "medicalRecord": "5823cf51-b27c-4cec-a0ac-c42b249ddc7c", 54 | "isDeleted": false, 55 | "createdAt": "2019-10-18 22:22:15", 56 | "updatedAt": "2019-10-27 06:28:39" 57 | }, 58 | { 59 | "id": "0a96b420-ba2b-4741-b290-c916d92abdc0", 60 | "idNumber": 1011, 61 | "firstName": "Ursula", 62 | "lastName": "Cowen", 63 | "username": "ursula.cowen.1011", 64 | "email": "ucowen4@oakley.com", 65 | "contactNumber": "376-721-4543", 66 | "address": "1 Amoth Place", 67 | "medicalRecord": "bb881424-e27e-4c27-be49-843b7febd7e7", 68 | "isDeleted": true, 69 | "createdAt": "2019-05-23 01:10:13", 70 | "updatedAt": "2019-11-11 16:03:54" 71 | }, 72 | { 73 | "id": "b9df98a3-f23d-4711-b710-59efe5387921", 74 | "idNumber": 7897, 75 | "firstName": "Norry", 76 | "lastName": "Vockins", 77 | "username": "norry.vockins.7897", 78 | "email": "nvockins5@github.io", 79 | "contactNumber": "148-183-3666", 80 | "address": "084 Hoard Terrace", 81 | "medicalRecord": "635fb5b6-1eae-4c51-9b84-971b238d75fe", 82 | "isDeleted": false, 83 | "createdAt": "2020-02-02 06:11:16", 84 | "updatedAt": "2019-08-20 00:41:38" 85 | }, 86 | { 87 | "id": "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc", 88 | "idNumber": 5839, 89 | "firstName": "Hort", 90 | "lastName": "Mantram", 91 | "username": "hort.mantram.5839", 92 | "email": "hmantram6@state.gov", 93 | "contactNumber": "273-256-7690", 94 | "address": "8791 Twin Pines Avenue", 95 | "medicalRecord": "a7dd94eb-f2e6-411f-baa7-60761c03ccb3", 96 | "isDeleted": true, 97 | "createdAt": "2019-10-12 12:44:17", 98 | "updatedAt": "2020-04-14 12:24:05" 99 | }, 100 | { 101 | "id": "9d7f71ff-49fe-4819-859c-727ce7f44be3", 102 | "idNumber": 6214, 103 | "firstName": "Christie", 104 | "lastName": "Baelde", 105 | "username": "christie.baelde.6214", 106 | "email": "cbaelde7@icio.us", 107 | "contactNumber": "715-754-7698", 108 | "address": "246 Delaware Avenue", 109 | "medicalRecord": "672f2993-4366-4b16-9aec-f12830494d33", 110 | "isDeleted": false, 111 | "createdAt": "2019-12-08 08:22:59", 112 | "updatedAt": "2020-05-12 06:13:23" 113 | }, 114 | { 115 | "id": "c0f557a2-da71-4974-930f-17e52935bc7a", 116 | "idNumber": 1325, 117 | "firstName": "Byram", 118 | "lastName": "Doblin", 119 | "username": "byram.doblin.1325", 120 | "email": "bdoblin8@storify.com", 121 | "contactNumber": "148-633-0195", 122 | "address": "44 Kinsman Alley", 123 | "medicalRecord": "527d5cc2-0eb0-484d-9756-90a99feb650d", 124 | "isDeleted": true, 125 | "createdAt": "2020-01-02 01:26:33", 126 | "updatedAt": "2020-02-27 09:26:02" 127 | }, 128 | { 129 | "id": "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55", 130 | "idNumber": 1382, 131 | "firstName": "Dniren", 132 | "lastName": "Pidwell", 133 | "username": "dniren.pidwell.1382", 134 | "email": "dpidwell9@hexun.com", 135 | "contactNumber": "311-548-0007", 136 | "address": "39 Meadow Ridge Alley", 137 | "medicalRecord": "da16d247-a252-4abc-9692-82167f84c9e2", 138 | "isDeleted": false, 139 | "createdAt": "2019-09-16 09:43:44", 140 | "updatedAt": "2019-06-21 00:51:37" 141 | } 142 | ] 143 | -------------------------------------------------------------------------------- /Frontend/src/mocks/UsersMock.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 4 | "id_number": 1767, 5 | "first_name": "Leodora", 6 | "last_name": "Crouch", 7 | "username": "leodora.crouch.1767", 8 | "email": "lcrouch0@wix.com", 9 | "contact_number": "715-982-0519", 10 | "address": "26 Quincy Pass", 11 | "medical_record": "03254d70-723b-4bed-8adb-477dffe8f092", 12 | "is_deleted": false, 13 | "created_at": "2020-01-22 14:00:18", 14 | "updated_at": "2020-02-27 20:11:39" 15 | }, 16 | { 17 | "id": "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c", 18 | "id_number": 3469, 19 | "first_name": "Hayden", 20 | "last_name": "Haseman", 21 | "username": "hayden.haseman.3469", 22 | "email": "hhaseman1@apple.com", 23 | "contact_number": "552-913-5574", 24 | "address": "5761 Melvin Drive", 25 | "medical_record": "088b8665-d176-4021-9416-ac787339c568", 26 | "is_deleted": false, 27 | "created_at": "2020-02-03 08:45:34", 28 | "updated_at": "2019-12-31 18:44:32" 29 | }, 30 | { 31 | "id": "795b5699-7ca9-4344-8fe5-4a4f501678a9", 32 | "id_number": 5582, 33 | "first_name": "Basile", 34 | "last_name": "Matteini", 35 | "username": "basile.matteini.5582", 36 | "email": "bmatteini2@squarespace.com", 37 | "contact_number": "978-756-5028", 38 | "address": "939 7th Place", 39 | "medical_record": "56021793-eed0-44c1-b2ab-72b245f1bb17", 40 | "is_deleted": true, 41 | "created_at": "2019-10-10 02:05:12", 42 | "updated_at": "2020-02-03 00:16:37" 43 | }, 44 | { 45 | "id": "1cffa615-e1d8-4113-b585-60603f0854fb", 46 | "id_number": 3123, 47 | "first_name": "Hersh", 48 | "last_name": "Oloman", 49 | "username": "hersh.oloman.3123", 50 | "email": "holoman3@themeforest.net", 51 | "contact_number": "746-918-2444", 52 | "address": "05898 Southridge Alley", 53 | "medical_record": "5823cf51-b27c-4cec-a0ac-c42b249ddc7c", 54 | "is_deleted": false, 55 | "created_at": "2019-10-18 22:22:15", 56 | "updated_at": "2019-10-27 06:28:39" 57 | }, 58 | { 59 | "id": "0a96b420-ba2b-4741-b290-c916d92abdc0", 60 | "id_number": 1011, 61 | "first_name": "Ursula", 62 | "last_name": "Cowen", 63 | "username": "ursula.cowen.1011", 64 | "email": "ucowen4@oakley.com", 65 | "contact_number": "376-721-4543", 66 | "address": "1 Amoth Place", 67 | "medical_record": "bb881424-e27e-4c27-be49-843b7febd7e7", 68 | "is_deleted": true, 69 | "created_at": "2019-05-23 01:10:13", 70 | "updated_at": "2019-11-11 16:03:54" 71 | }, 72 | { 73 | "id": "b9df98a3-f23d-4711-b710-59efe5387921", 74 | "id_number": 7897, 75 | "first_name": "Norry", 76 | "last_name": "Vockins", 77 | "username": "norry.vockins.7897", 78 | "email": "nvockins5@github.io", 79 | "contact_number": "148-183-3666", 80 | "address": "084 Hoard Terrace", 81 | "medical_record": "635fb5b6-1eae-4c51-9b84-971b238d75fe", 82 | "is_deleted": false, 83 | "created_at": "2020-02-02 06:11:16", 84 | "updated_at": "2019-08-20 00:41:38" 85 | }, 86 | { 87 | "id": "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc", 88 | "id_number": 5839, 89 | "first_name": "Hort", 90 | "last_name": "Mantram", 91 | "username": "hort.mantram.5839", 92 | "email": "hmantram6@state.gov", 93 | "contact_number": "273-256-7690", 94 | "address": "8791 Twin Pines Avenue", 95 | "medical_record": "a7dd94eb-f2e6-411f-baa7-60761c03ccb3", 96 | "is_deleted": true, 97 | "created_at": "2019-10-12 12:44:17", 98 | "updated_at": "2020-04-14 12:24:05" 99 | }, 100 | { 101 | "id": "9d7f71ff-49fe-4819-859c-727ce7f44be3", 102 | "id_number": 6214, 103 | "first_name": "Christie", 104 | "last_name": "Baelde", 105 | "username": "christie.baelde.6214", 106 | "email": "cbaelde7@icio.us", 107 | "contact_number": "715-754-7698", 108 | "address": "246 Delaware Avenue", 109 | "medical_record": "672f2993-4366-4b16-9aec-f12830494d33", 110 | "is_deleted": false, 111 | "created_at": "2019-12-08 08:22:59", 112 | "updated_at": "2020-05-12 06:13:23" 113 | }, 114 | { 115 | "id": "c0f557a2-da71-4974-930f-17e52935bc7a", 116 | "id_number": 1325, 117 | "first_name": "Byram", 118 | "last_name": "Doblin", 119 | "username": "byram.doblin.1325", 120 | "email": "bdoblin8@storify.com", 121 | "contact_number": "148-633-0195", 122 | "address": "44 Kinsman Alley", 123 | "medical_record": "527d5cc2-0eb0-484d-9756-90a99feb650d", 124 | "is_deleted": true, 125 | "created_at": "2020-01-02 01:26:33", 126 | "updated_at": "2020-02-27 09:26:02" 127 | }, 128 | { 129 | "id": "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55", 130 | "id_number": 1382, 131 | "first_name": "Dniren", 132 | "last_name": "Pidwell", 133 | "username": "dniren.pidwell.1382", 134 | "email": "dpidwell9@hexun.com", 135 | "contact_number": "311-548-0007", 136 | "address": "39 Meadow Ridge Alley", 137 | "medical_record": "da16d247-a252-4abc-9692-82167f84c9e2", 138 | "is_deleted": false, 139 | "created_at": "2019-09-16 09:43:44", 140 | "updated_at": "2019-06-21 00:51:37" 141 | } 142 | ] 143 | -------------------------------------------------------------------------------- /Frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/test,node,linux,macos,windows,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=test,node,linux,macos,windows,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | # General 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | # Thumbnails 29 | ._* 30 | 31 | # Files that might appear in the root of a volume 32 | .DocumentRevisions-V100 33 | .fseventsd 34 | .Spotlight-V100 35 | .TemporaryItems 36 | .Trashes 37 | .VolumeIcon.icns 38 | .com.apple.timemachine.donotpresent 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | 47 | ### Node ### 48 | # Logs 49 | logs 50 | *.log 51 | npm-debug.log* 52 | yarn-debug.log* 53 | yarn-error.log* 54 | lerna-debug.log* 55 | 56 | # Diagnostic reports (https://nodejs.org/api/report.html) 57 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 58 | 59 | # Runtime data 60 | pids 61 | *.pid 62 | *.seed 63 | *.pid.lock 64 | 65 | # Directory for instrumented libs generated by jscoverage/JSCover 66 | lib-cov 67 | 68 | # Coverage directory used by tools like istanbul 69 | coverage 70 | *.lcov 71 | 72 | # nyc test coverage 73 | .nyc_output 74 | 75 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 76 | .grunt 77 | 78 | # Bower dependency directory (https://bower.io/) 79 | bower_components 80 | 81 | # node-waf configuration 82 | .lock-wscript 83 | 84 | # Compiled binary addons (https://nodejs.org/api/addons.html) 85 | build/Release 86 | 87 | # Dependency directories 88 | node_modules/ 89 | jspm_packages/ 90 | 91 | # TypeScript v1 declaration files 92 | typings/ 93 | 94 | # TypeScript cache 95 | *.tsbuildinfo 96 | 97 | # Optional npm cache directory 98 | .npm 99 | 100 | # Optional eslint cache 101 | .eslintcache 102 | 103 | # Optional REPL history 104 | .node_repl_history 105 | 106 | # Output of 'npm pack' 107 | *.tgz 108 | 109 | # Yarn Integrity file 110 | .yarn-integrity 111 | 112 | # dotenv environment variables file 113 | .env 114 | .env.test 115 | 116 | # parcel-bundler cache (https://parceljs.org/) 117 | .cache 118 | 119 | # next.js build output 120 | .next 121 | 122 | # nuxt.js build output 123 | .nuxt 124 | 125 | # rollup.js default build output 126 | dist/ 127 | 128 | # Uncomment the public line if your project uses Gatsby 129 | # https://nextjs.org/blog/next-9-1#public-directory-support 130 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 131 | # public 132 | 133 | # Storybook build outputs 134 | .out 135 | .storybook-out 136 | 137 | # vuepress build output 138 | .vuepress/dist 139 | 140 | # Serverless directories 141 | .serverless/ 142 | 143 | # FuseBox cache 144 | .fusebox/ 145 | 146 | # DynamoDB Local files 147 | .dynamodb/ 148 | 149 | # Temporary folders 150 | tmp/ 151 | temp/ 152 | 153 | ### Test ### 154 | ### Ignore all files that could be used to test your code and 155 | ### you wouldn't want to push 156 | 157 | # Reference https://en.wikipedia.org/wiki/Metasyntactic_variable 158 | 159 | # Most common 160 | *foo 161 | *bar 162 | *fubar 163 | *foobar 164 | *baz 165 | 166 | # Less common 167 | *qux 168 | *quux 169 | *bongo 170 | *bazola 171 | *ztesch 172 | 173 | # UK, Australia 174 | *wibble 175 | *wobble 176 | *wubble 177 | *flob 178 | *blep 179 | *blah 180 | *boop 181 | *beep 182 | 183 | # Japanese 184 | *hoge 185 | *piyo 186 | *fuga 187 | *hogera 188 | *hogehoge 189 | 190 | # Portugal, Spain 191 | *fulano 192 | *sicrano 193 | *beltrano 194 | *mengano 195 | *perengano 196 | *zutano 197 | 198 | # France, Italy, the Netherlands 199 | *toto 200 | *titi 201 | *tata 202 | *tutu 203 | *pipppo 204 | *pluto 205 | *paperino 206 | *aap 207 | *noot 208 | *mies 209 | 210 | # Other names that would make sense 211 | *tests 212 | *testsdir 213 | *testsfile 214 | *testsfiles 215 | *test 216 | *testdir 217 | *testfile 218 | *testfiles 219 | *testing 220 | *testingdir 221 | *testingfile 222 | *testingfiles 223 | *temp 224 | *tempdir 225 | *tempfile 226 | *tempfiles 227 | *tmp 228 | *tmpdir 229 | *tmpfile 230 | *tmpfiles 231 | *lol 232 | 233 | ### VisualStudioCode ### 234 | .vscode/* 235 | !.vscode/settings.json 236 | !.vscode/tasks.json 237 | !.vscode/launch.json 238 | !.vscode/extensions.json 239 | 240 | ### VisualStudioCode Patch ### 241 | # Ignore all local history of files 242 | .history 243 | 244 | ### Windows ### 245 | # Windows thumbnail cache files 246 | Thumbs.db 247 | Thumbs.db:encryptable 248 | ehthumbs.db 249 | ehthumbs_vista.db 250 | 251 | # Dump file 252 | *.stackdump 253 | 254 | # Folder config file 255 | [Dd]esktop.ini 256 | 257 | # Recycle Bin used on file shares 258 | $RECYCLE.BIN/ 259 | 260 | # Windows Installer files 261 | *.cab 262 | *.msi 263 | *.msix 264 | *.msm 265 | *.msp 266 | 267 | # Windows shortcuts 268 | *.lnk 269 | 270 | # End of https://www.gitignore.io/api/test,node,linux,macos,windows,visualstudiocode 271 | 272 | -------------------------------------------------------------------------------- /Frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "airbnb", 9 | "prettier" 10 | ], 11 | "globals": { 12 | "document": false, 13 | "escape": false, 14 | "navigator": false, 15 | "unescape": false, 16 | "window": false, 17 | "describe": true, 18 | "before": true, 19 | "it": true, 20 | "expect": true, 21 | "sinon": true 22 | }, 23 | "parser": "babel-eslint", 24 | "plugins": [ 25 | "prettier", 26 | "react", 27 | "jsx-a11y", 28 | "import" 29 | ], 30 | "rules": { 31 | "react/jsx-filename-extension": 0, 32 | "array-bracket-spacing": 2, 33 | "arrow-body-style": 0, 34 | "block-scoped-var": 2, 35 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 36 | "comma-spacing": [2, { "before": false, "after": true }], 37 | "comma-style": [2, "last"], 38 | "complexity": 0, 39 | "consistent-return": 1, 40 | "consistent-this": 0, 41 | "curly": [2, "multi-line"], 42 | "default-case": 0, 43 | "dot-location": [2, "property"], 44 | "dot-notation": 0, 45 | "eol-last": 2, 46 | "eqeqeq": [2, "allow-null"], 47 | "func-names": 0, 48 | "func-style": 0, 49 | "generator-star-spacing": [2, "both"], 50 | "guard-for-in": 0, 51 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], 52 | "indent": [2, 2, { "SwitchCase": 1 }], 53 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 54 | "linebreak-style": 0, 55 | "max-depth": 0, 56 | "max-len": [2, 1550, 4], 57 | "max-nested-callbacks": 0, 58 | "max-params": 0, 59 | "max-statements": 0, 60 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 61 | "newline-after-var": [0, "never"], 62 | "new-parens": 2, 63 | "no-alert": 0, 64 | "no-array-constructor": 2, 65 | "no-bitwise": 0, 66 | "no-caller": 2, 67 | "no-catch-shadow": 0, 68 | "no-cond-assign": 2, 69 | "no-console": 0, 70 | "no-constant-condition": 0, 71 | "no-continue": 0, 72 | "no-control-regex": 2, 73 | "no-debugger": 0, 74 | "no-delete-var": 2, 75 | "no-div-regex": 0, 76 | "no-dupe-args": 2, 77 | "no-dupe-keys": 2, 78 | "no-duplicate-case": 2, 79 | "no-else-return": 2, 80 | "no-empty": 0, 81 | "no-empty-character-class": 2, 82 | "no-labels": 2, 83 | "no-eq-null": 0, 84 | "no-eval": 2, 85 | "no-ex-assign": 2, 86 | "no-extend-native": 2, 87 | "no-extra-bind": 2, 88 | "no-extra-boolean-cast": 2, 89 | "no-extra-parens": 0, 90 | "no-extra-semi": 0, 91 | "no-extra-strict": 0, 92 | "no-fallthrough": 2, 93 | "no-floating-decimal": 2, 94 | "no-func-assign": 2, 95 | "no-implied-eval": 2, 96 | "no-inline-comments": 0, 97 | "no-inner-declarations": [2, "functions"], 98 | "no-invalid-regexp": 2, 99 | "no-irregular-whitespace": 2, 100 | "no-iterator": 2, 101 | "no-label-var": 2, 102 | "no-lone-blocks": 0, 103 | "no-lonely-if": 0, 104 | "no-loop-func": 0, 105 | "no-mixed-requires": 0, 106 | "no-mixed-spaces-and-tabs": [2, false], 107 | "no-multi-spaces": 2, 108 | "no-multi-str": 0, 109 | "no-multiple-empty-lines": [2, { "max": 1 }], 110 | "no-native-reassign": 2, 111 | "no-negated-in-lhs": 2, 112 | "no-nested-ternary": 0, 113 | "no-new": 2, 114 | "no-new-func": 2, 115 | "no-new-object": 2, 116 | "no-new-require": 2, 117 | "no-new-wrappers": 2, 118 | "no-obj-calls": 2, 119 | "no-octal": 2, 120 | "no-octal-escape": 2, 121 | "no-path-concat": 0, 122 | "no-plusplus": 0, 123 | "no-process-env": 0, 124 | "no-process-exit": 0, 125 | "no-proto": 2, 126 | "no-redeclare": 2, 127 | "no-regex-spaces": 2, 128 | "no-reserved-keys": 0, 129 | "no-restricted-modules": 0, 130 | "no-script-url": 0, 131 | "no-self-compare": 2, 132 | "no-sequences": 2, 133 | "no-shadow": 0, 134 | "no-shadow-restricted-names": 2, 135 | "no-spaced-func": 2, 136 | "no-sparse-arrays": 2, 137 | "no-sync": 0, 138 | "no-ternary": 0, 139 | "no-throw-literal": 2, 140 | "no-trailing-spaces": 2, 141 | "no-undef": 0, 142 | "no-undef-init": 2, 143 | "no-undefined": 0, 144 | "no-underscore-dangle": 0, 145 | "no-unneeded-ternary": 2, 146 | "no-unreachable": 2, 147 | "no-unused-expressions": 0, 148 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 149 | "no-var": 2, 150 | "no-void": 0, 151 | "no-warning-comments": 0, 152 | "no-with": 2, 153 | "object-curly-newline": 0, 154 | "object-curly-spacing": [2, "always"], 155 | "one-var": 0, 156 | "operator-assignment": 0, 157 | "operator-linebreak": [2, "after"], 158 | "padded-blocks": 0, 159 | "prefer-const": 2, 160 | "quote-props": 0, 161 | "quotes": [2, "double", "avoid-escape"], 162 | "radix": 2, 163 | "jsx-quotes": [2, "prefer-double"], 164 | "jsx-a11y/click-events-have-key-events": 0, 165 | "jsx-a11y/no-noninteractive-element-interactions": 0, 166 | "jsx-a11y/media-has-caption": 0, 167 | "react/display-name": 0, 168 | "react/jsx-boolean-value": 0, 169 | "react/jsx-closing-bracket-location": 2, 170 | "react/jsx-curly-spacing": [2, "never"], 171 | "react/jsx-equals-spacing": [2, "never"], 172 | "react/jsx-indent-props": [2, 2], 173 | "react/jsx-no-bind": [2, { "allowArrowFunctions": true }], 174 | "react/jsx-no-undef": 2, 175 | "react/jsx-pascal-case": 2, 176 | "react/jsx-sort-prop-types": 0, 177 | "react/jsx-sort-props": 0, 178 | "react/jsx-uses-react": 2, 179 | "react/jsx-uses-vars": 2, 180 | "react/no-did-mount-set-state": 2, 181 | "react/no-did-update-set-state": 2, 182 | "react/no-multi-comp": 0, 183 | "react/no-unknown-property": 2, 184 | "react/prop-types": 0, 185 | "react/forbid-prop-types": 0, 186 | "react/prefer-stateless-function": 0, 187 | "react/require-default-props": 0, 188 | "react/react-in-jsx-scope": 2, 189 | "react/self-closing-comp": 2, 190 | "react/sort-comp": 0, 191 | "react/jsx-wrap-multilines": 2, 192 | "semi-spacing": 0, 193 | "sort-vars": 0, 194 | "space-before-blocks": [2, "always"], 195 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 196 | "space-in-parens": [2, "never"], 197 | "space-infix-ops": 2, 198 | "keyword-spacing": 2, 199 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 200 | "spaced-comment": [0, "always"], 201 | "strict": 0, 202 | "use-isnan": 2, 203 | "valid-jsdoc": 0, 204 | "valid-typeof": 2, 205 | "vars-on-top": 2, 206 | "wrap-iife": [2, "any"], 207 | "wrap-regex": 0, 208 | "yoda": [2, "never"] 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { createStore, compose } from "redux"; 5 | import runtime from "serviceworker-webpack-plugin/lib/runtime"; 6 | import App from "./routes/App"; 7 | import reducer from "./reducers"; 8 | 9 | if ("serviceWorker" in navigator) { 10 | runtime.register(); 11 | } 12 | 13 | const initialState = { 14 | user: {}, 15 | titles: [ 16 | { 17 | name: "Panel del Administrador", 18 | titlePatient: "Panel del Paciente", 19 | }, 20 | { 21 | name: "Panel del Administrador", 22 | titlePatient: "Panel del Paciente", 23 | }, 24 | ], 25 | users: [ 26 | { 27 | id: "97588236-ft73-1234-c4n5-ebdd2689h4j7", 28 | idNumber: 5252, 29 | firstName: "Fernando", 30 | lastName: "Torres", 31 | username: "fernando.torres.5252", 32 | email: "fertorresmx@gmail.com", 33 | contactNumber: "55 1234 5678", 34 | address: "Toledo 21, Juárez, CDMX", 35 | medicalRecord: "03254d70-723b-4bed-8adb-477dffe8f092", 36 | isDeleted: false, 37 | roleId: "d436e99b-afea-44cf-a31c-ff35b7740c67", 38 | createdAt: "2020-01-16 13:05:18", 39 | updatedAt: "2020-02-17 19:16:39", 40 | }, 41 | { 42 | id: "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c", 43 | idNumber: 3469, 44 | firstName: "Hayden", 45 | lastName: "Haseman", 46 | username: "hayden.haseman.3469", 47 | email: "hhaseman1@apple.com", 48 | contactNumber: "552-913-5574", 49 | address: "5761 Melvin Drive", 50 | medicalRecord: "088b8665-d176-4021-9416-ac787339c568", 51 | isDeleted: false, 52 | createdAt: "2020-02-03 08:45:34", 53 | updatedAt: "2019-12-31 18:44:32", 54 | }, 55 | { 56 | id: "795b5699-7ca9-4344-8fe5-4a4f501678a9", 57 | idNumber: 5582, 58 | firstName: "Basile", 59 | lastName: "Matteini", 60 | username: "basile.matteini.5582", 61 | email: "bmatteini2@squarespace.com", 62 | contactNumber: "978-756-5028", 63 | address: "939 7th Place", 64 | medicalRecord: "56021793-eed0-44c1-b2ab-72b245f1bb17", 65 | isDeleted: true, 66 | createdAt: "2019-10-10 02:05:12", 67 | updatedAt: "2020-02-03 00:16:37", 68 | }, 69 | { 70 | id: "1cffa615-e1d8-4113-b585-60603f0854fb", 71 | idNumber: 3123, 72 | firstName: "Hersh", 73 | lastName: "Oloman", 74 | username: "hersh.oloman.3123", 75 | email: "holoman3@themeforest.net", 76 | contactNumber: "746-918-2444", 77 | address: "05898 Southridge Alley", 78 | medicalRecord: "5823cf51-b27c-4cec-a0ac-c42b249ddc7c", 79 | isDeleted: false, 80 | createdAt: "2019-10-18 22:22:15", 81 | updatedAt: "2019-10-27 06:28:39", 82 | }, 83 | { 84 | id: "0a96b420-ba2b-4741-b290-c916d92abdc0", 85 | idNumber: 1011, 86 | firstName: "Ursula", 87 | lastName: "Cowen", 88 | username: "ursula.cowen.1011", 89 | email: "ucowen4@oakley.com", 90 | contactNumber: "376-721-4543", 91 | address: "1 Amoth Place", 92 | medicalRecord: "bb881424-e27e-4c27-be49-843b7febd7e7", 93 | isDeleted: true, 94 | createdAt: "2019-05-23 01:10:13", 95 | updatedAt: "2019-11-11 16:03:54", 96 | }, 97 | { 98 | id: "b9df98a3-f23d-4711-b710-59efe5387921", 99 | idNumber: 7897, 100 | firstName: "Norry", 101 | lastName: "Vockins", 102 | username: "norry.vockins.7897", 103 | email: "nvockins5@github.io", 104 | contactNumber: "148-183-3666", 105 | address: "084 Hoard Terrace", 106 | medicalRecord: "635fb5b6-1eae-4c51-9b84-971b238d75fe", 107 | isDeleted: false, 108 | createdAt: "2020-02-02 06:11:16", 109 | updatedAt: "2019-08-20 00:41:38", 110 | }, 111 | { 112 | id: "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc", 113 | idNumber: 5839, 114 | firstName: "Hort", 115 | lastName: "Mantram", 116 | username: "hort.mantram.5839", 117 | email: "hmantram6@state.gov", 118 | contactNumber: "273-256-7690", 119 | address: "8791 Twin Pines Avenue", 120 | medicalRecord: "a7dd94eb-f2e6-411f-baa7-60761c03ccb3", 121 | isDeleted: true, 122 | createdAt: "2019-10-12 12:44:17", 123 | updatedAt: "2020-04-14 12:24:05", 124 | }, 125 | { 126 | id: "9d7f71ff-49fe-4819-859c-727ce7f44be3", 127 | idNumber: 6214, 128 | firstName: "Christie", 129 | lastName: "Baelde", 130 | username: "christie.baelde.6214", 131 | email: "cbaelde7@icio.us", 132 | contactNumber: "715-754-7698", 133 | address: "246 Delaware Avenue", 134 | medicalRecord: "672f2993-4366-4b16-9aec-f12830494d33", 135 | isDeleted: false, 136 | createdAt: "2019-12-08 08:22:59", 137 | updatedAt: "2020-05-12 06:13:23", 138 | }, 139 | { 140 | id: "c0f557a2-da71-4974-930f-17e52935bc7a", 141 | idNumber: 1325, 142 | firstName: "Byram", 143 | lastName: "Doblin", 144 | username: "byram.doblin.1325", 145 | email: "bdoblin8@storify.com", 146 | contactNumber: "148-633-0195", 147 | address: "44 Kinsman Alley", 148 | medicalRecord: "527d5cc2-0eb0-484d-9756-90a99feb650d", 149 | isDeleted: true, 150 | createdAt: "2020-01-02 01:26:33", 151 | updatedAt: "2020-02-27 09:26:02", 152 | }, 153 | { 154 | id: "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55", 155 | idNumber: 1382, 156 | firstName: "Dniren", 157 | lastName: "Pidwell", 158 | username: "dniren.pidwell.1382", 159 | email: "dpidwell9@hexun.com", 160 | contactNumber: "311-548-0007", 161 | address: "39 Meadow Ridge Alley", 162 | medicalRecord: "da16d247-a252-4abc-9692-82167f84c9e2", 163 | isDeleted: false, 164 | createdAt: "2019-09-16 09:43:44", 165 | updatedAt: "2019-06-21 00:51:37", 166 | }, 167 | ], 168 | users_auth: [ 169 | { 170 | id: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 171 | idNumber: 1767, 172 | username: "leodora.crouch.1767", 173 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 174 | role_id: "rolesEnum.ADMINISTRATOR.id", 175 | isDeleted: false, 176 | createdAt: "2020-01-22 14:00:18", 177 | updatedAt: "2020-02-27 20:11:39", 178 | }, 179 | { 180 | id: "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c", 181 | idNumber: 3469, 182 | username: "hayden.haseman.3469", 183 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 184 | role_id: "rolesEnum.MEDIC.id", 185 | isDeleted: false, 186 | createdAt: "2020-02-03 08:45:34", 187 | updatedAt: "2019-12-31 18:44:32", 188 | }, 189 | { 190 | id: "795b5699-7ca9-4344-8fe5-4a4f501678a9", 191 | idNumber: 5582, 192 | username: "basile.matteini.5582", 193 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 194 | role_id: "rolesEnum.BACTERIOLOGIST.id", 195 | isDeleted: true, 196 | createdAt: "2019-10-10 02:05:12", 197 | updatedAt: "2020-02-03 00:16:37", 198 | }, 199 | { 200 | id: "1cffa615-e1d8-4113-b585-60603f0854fb", 201 | idNumber: 3123, 202 | username: "hersh.oloman.3123", 203 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 204 | role_id: "rolesEnum.PACIENT.id", 205 | isDeleted: false, 206 | createdAt: "2019-10-18 22:22:15", 207 | updatedAt: "2019-10-27 06:28:39", 208 | }, 209 | { 210 | id: "0a96b420-ba2b-4741-b290-c916d92abdc0", 211 | idNumber: 1011, 212 | username: "ursula.cowen.1011", 213 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 214 | role_id: "rolesEnum.ADMINISTRATOR.id", 215 | isDeleted: true, 216 | createdAt: "2019-05-23 01:10:13", 217 | updatedAt: "2019-11-11 16:03:54", 218 | }, 219 | { 220 | id: "b9df98a3-f23d-4711-b710-59efe5387921", 221 | idNumber: 7897, 222 | username: "norry.vockins.7897", 223 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 224 | role_id: "rolesEnum.MEDIC.id", 225 | isDeleted: false, 226 | createdAt: "2020-02-02 06:11:16", 227 | updatedAt: "2019-08-20 00:41:38", 228 | }, 229 | { 230 | id: "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc", 231 | idNumber: 5839, 232 | username: "hort.mantram.5839", 233 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 234 | role_id: "rolesEnum.BACTERIOLOGIST.id", 235 | isDeleted: true, 236 | createdAt: "2019-10-12 12:44:17", 237 | updatedAt: "2020-04-14 12:24:05", 238 | }, 239 | { 240 | id: "9d7f71ff-49fe-4819-859c-727ce7f44be3", 241 | idNumber: 6214, 242 | username: "christie.baelde.6214", 243 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 244 | role_id: "rolesEnum.PACIENT.id", 245 | isDeleted: false, 246 | createdAt: "2019-12-08 08:22:59", 247 | updatedAt: "2020-05-12 06:13:23", 248 | }, 249 | { 250 | id: "c0f557a2-da71-4974-930f-17e52935bc7a", 251 | idNumber: 1325, 252 | username: "byram.doblin.1325", 253 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 254 | role_id: "rolesEnum.PACIENT.id", 255 | isDeleted: true, 256 | createdAt: "2020-01-02 01:26:33", 257 | updatedAt: "2020-02-27 09:26:02", 258 | }, 259 | { 260 | id: "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55", 261 | idNumber: 1382, 262 | username: "dniren.pidwell.1382", 263 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G", 264 | role_id: "rolesEnum.PACIENT.id", 265 | isDeleted: false, 266 | createdAt: "2019-09-16 09:43:44", 267 | updatedAt: "2019-06-21 00:51:37", 268 | }, 269 | ], 270 | roles: [ 271 | { 272 | ADMINISTRATOR: { 273 | id: "d436e99b-afea-44cf-a31c-ff35b7740c67", 274 | name: "administrator", 275 | description: "An administrator Role", 276 | isDeleted: false, 277 | createdAt: "2020-05-15 00:00:00", 278 | updatedAt: "2020-05-15 00:00:00", 279 | }, 280 | MEDIC: { 281 | id: "a81bd60a-b09e-4936-b74a-22b823e39464", 282 | name: "medic", 283 | description: "A medic Role", 284 | isDeleted: false, 285 | createdAt: "2020-05-15 00:00:00", 286 | updatedAt: "2020-05-15 00:00:00", 287 | }, 288 | BACTERIOLOGIST: { 289 | id: "bf90b55d-0e69-4f31-8211-514049a42625", 290 | name: "bacteriologist", 291 | description: "A bacteriologist Role", 292 | isDeleted: false, 293 | createdAt: "2020-05-15 00:00:00", 294 | updatedAt: "2020-05-15 00:00:00", 295 | }, 296 | PACIENT: { 297 | id: "9511dfc8-e17e-4c8e-927e-cb933f9b77f6", 298 | name: "pacient", 299 | description: "A pacient Role", 300 | isDeleted: false, 301 | createdAt: "2020-05-15 00:00:00", 302 | updatedAt: "2020-05-15 00:00:00", 303 | }, 304 | }, 305 | ], 306 | exams: [ 307 | { 308 | id: 1, 309 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 310 | examDate: "2020-02-14", 311 | examName: "Exámen de Orina", 312 | examDescription: "Lorem Ipsum", 313 | examStatus: "Referenciado", 314 | }, 315 | { 316 | id: 2, 317 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 318 | examDate: "2020-02-14", 319 | examName: "Exámen de Sangre", 320 | examDescription: "Lorem Ipsum", 321 | examStatus: "Pendiente", 322 | }, 323 | { 324 | id: 3, 325 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 326 | examDate: "2020-02-10", 327 | examName: "Exámen de Glucosa", 328 | examDescription: "Lorem Ipsum", 329 | examStatus: "Con Resultados", 330 | }, 331 | { 332 | id: 4, 333 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 334 | examDate: "2020-01-01", 335 | examName: "Exámen de Colesterol", 336 | examDescription: "Lorem Ipsum", 337 | examStatus: "Referenciado", 338 | }, 339 | { 340 | id: 5, 341 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 342 | examDate: "2020-02-21", 343 | examName: "Exámen Glóbulos blancos", 344 | examDescription: "Lorem Ipsum", 345 | examStatus: "Pendiente", 346 | }, 347 | { 348 | id: 6, 349 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 350 | examDate: "2020-02-14", 351 | examName: "Exámen de Sangre", 352 | examDescription: "Lorem Ipsum", 353 | examStatus: "Referenciado", 354 | }, 355 | { 356 | id: 7, 357 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2", 358 | examDate: "2020-01-18", 359 | examName: "Exámen de Plaquetas", 360 | examDescription: "Lorem Ipsum", 361 | examStatus: "Con Resultados", 362 | }, 363 | ], 364 | }; 365 | 366 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 367 | const store = createStore(reducer, initialState, composeEnhancers()); 368 | 369 | ReactDOM.render( 370 | 371 | 372 | , 373 | document.getElementById("app") 374 | ); 375 | 376 | ReactDOM.render(, container); 377 | --------------------------------------------------------------------------------