├── src ├── v1 │ ├── utils │ │ ├── schema │ │ │ ├── auth.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── constants │ │ │ └── index.ts │ │ └── functions │ │ │ └── index.ts │ ├── services │ │ ├── prisma.ts │ │ └── yup.ts │ ├── routes │ │ └── index.ts │ └── middleware │ │ ├── validator.ts │ │ └── index.ts ├── index.ts └── server.ts ├── test └── setup │ ├── config.ts │ ├── setupFile.ts │ ├── globalTeardown.ts │ └── globalSetup.ts ├── tsconfig.prod.json ├── .prettierrc.js ├── prisma ├── schema.prisma └── seed.js ├── .env.example ├── .eslintrc.js ├── tsconfig.json ├── jest.config.ts ├── README.md ├── package.json └── .gitignore /src/v1/utils/schema/auth.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v1/utils/schema/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v1/utils/types/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v1/utils/constants/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v1/services/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | export const dbApp = new PrismaClient(); 4 | -------------------------------------------------------------------------------- /test/setup/config.ts: -------------------------------------------------------------------------------- 1 | export = { 2 | Memory: true, 3 | IP: '127.0.0.1', 4 | Port: '27017', 5 | DataBase: 'database', 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false 5 | }, 6 | "exclude": ["test"] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2, 7 | endOfLine: 'auto' 8 | }; -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("DATABASE_APP_URL") 8 | } 9 | -------------------------------------------------------------------------------- /src/v1/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | const router = Router(); 4 | 5 | router.get('/', (req, res) => { 6 | res.json({ 7 | message: 'Extrico api', 8 | }); 9 | }); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import app from './server'; 2 | 3 | // Start the server 4 | const PORT = process.env.PORT || 3001; 5 | 6 | app.listen(PORT, () => { 7 | console.info('Express server started on port: ' + PORT); 8 | }); 9 | 10 | export default app; 11 | -------------------------------------------------------------------------------- /test/setup/setupFile.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import Logger from '@btcsinc/logger'; 3 | import config from './config'; 4 | 5 | beforeAll(async () => { 6 | await mongoose.connect(`${process.env.MONGO_URI}/${config.DataBase}`, {}); 7 | Logger.createLogger('', mongoose.connection.db); 8 | }); 9 | 10 | afterAll(async () => { 11 | await mongoose.disconnect(); 12 | }); 13 | -------------------------------------------------------------------------------- /test/setup/globalTeardown.ts: -------------------------------------------------------------------------------- 1 | import { MongoMemoryServer } from 'mongodb-memory-server'; 2 | import config from './config'; 3 | 4 | export = async function globalTeardown() { 5 | if (config.Memory) { 6 | // Config to decided if an mongodb-memory-server instance should be used 7 | const instance: MongoMemoryServer = (global as any).__MONGOINSTANCE; 8 | await instance.stop(); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /prisma/seed.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { PrismaClient } = require('@prisma/client'); 3 | 4 | const prisma = new PrismaClient(); 5 | 6 | async function main() { 7 | return null; 8 | } 9 | 10 | main() 11 | .catch((e) => { 12 | console.error(e); 13 | process.exit(1); 14 | }) 15 | .finally(async () => { 16 | await prisma.$disconnect(); 17 | }); 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # PORT 2 | PORT=3000 3 | 4 | # ENVIRONMENT 5 | NODE_ENV= 6 | 7 | #DATABASE 8 | DATABASE_APP_URL="postgresql://user:pass@localhost:5432/eatcheap?schema=public" 9 | 10 | # TOKEN 11 | ACCESS_SECRET_KEY=access_secret_key 12 | ACCESS_TOKEN_EXPIRE_TIME=10m 13 | REFRESH_SECRET_KEY=refresh_secret_key 14 | REFRESH_TOKEN_EXPIRE_TIME=1d 15 | REFRESH_TOKEN_REMEMBER_EXPIRE_TIME=7d 16 | 17 | # LOG 18 | LOG_FORMAT=dev 19 | LOG_DIR=../logs 20 | 21 | # CORS 22 | ORIGIN=* 23 | CREDENTIALS=true 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended'], 4 | parserOptions: { 5 | ecmaVersion: 2020, 6 | sourceType: 'module', 7 | }, 8 | env: { 9 | es6: true, 10 | node: true, 11 | }, 12 | rules: { 13 | '@typescript-eslint/no-explicit-any': 'off', 14 | '@typescript-eslint/ban-ts-comment': 'off', 15 | 'prettier/prettier': 'error', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/v1/utils/functions/index.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import jwt from 'jsonwebtoken'; 3 | 4 | export const hashToken = (token: string) => { 5 | return crypto.createHash('sha512').update(token).digest('hex'); 6 | }; 7 | 8 | export const generateToken = (data: { [key: string]: any }, secretKey: string, expiresIn?: string) => { 9 | if (secretKey) { 10 | return expiresIn 11 | ? jwt.sign(data, secretKey, { 12 | expiresIn, 13 | }) 14 | : jwt.sign(data, secretKey); 15 | } else { 16 | return undefined; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "strict": true, 7 | "baseUrl": "./", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "useUnknownInCatchVariables": false, 13 | "paths": { 14 | "@server": [ 15 | "src/server" 16 | ] 17 | } 18 | }, 19 | "include": [ 20 | "src/**/*.ts", 21 | "spec/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "test" 25 | ] 26 | } -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from 'ts-jest'; 2 | 3 | const config: JestConfigWithTsJest = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | roots: ['/test'], 7 | globalSetup: '/test/setup/globalSetup.ts', 8 | globalTeardown: '/test/setup/globalTeardown.ts', 9 | setupFilesAfterEnv: ['/test/setup/setupFile.ts'], 10 | testMatch: ['**/*.test.ts'], 11 | transform: { 12 | '^.+\\.ts?$': 'ts-jest', 13 | '^.+\\.js?$': 'babel-jest', 14 | }, 15 | setupFiles: ['dotenv/config'], 16 | testTimeout: 180000, 17 | }; 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /src/v1/middleware/validator.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { AnyObjectSchema } from 'yup'; 3 | 4 | const validator = 5 | (schema: AnyObjectSchema) => 6 | async (req: Request, res: Response, next: NextFunction): Promise => { 7 | try { 8 | await schema.validate({ 9 | body: req.body, 10 | query: req.query, 11 | }); 12 | return next(); 13 | } catch (error) { 14 | return res.status(400).json({ 15 | error: error?.errors ?? error?.message ?? error, 16 | }); 17 | } 18 | }; 19 | 20 | export default validator; 21 | -------------------------------------------------------------------------------- /src/v1/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | 3 | export function notFound(req: Request, res: Response, next: NextFunction) { 4 | res.status(404); 5 | const error = new Error(`🔍 - Not Found - ${req.originalUrl}`); 6 | next(error); 7 | } 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) { 11 | const statusCode = res.statusCode !== 200 ? res.statusCode : 500; 12 | res.status(statusCode); 13 | res.json({ 14 | message: err.message, 15 | stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import 'express-async-errors'; 2 | import * as dotenv from 'dotenv'; 3 | import cookieParser from 'cookie-parser'; 4 | import cors from 'cors'; 5 | import express from 'express'; 6 | import helmet from 'helmet'; 7 | import morgan from 'morgan'; 8 | 9 | import V1Router from './v1/routes'; 10 | import { errorHandler, notFound } from './v1/middleware'; 11 | 12 | dotenv.config(); 13 | 14 | const app = express(); 15 | 16 | app.use(express.json()); 17 | app.use(cors()); 18 | app.use(cookieParser()); 19 | 20 | if (process.env.NODE_ENV === 'development') { 21 | app.use(morgan('dev')); 22 | } 23 | 24 | if (process.env.NODE_ENV === 'production') { 25 | app.use(morgan('combined')); 26 | app.use(helmet()); 27 | } 28 | 29 | app.use('/api/v1', V1Router); 30 | app.use(errorHandler); 31 | app.use(notFound); 32 | 33 | export default app; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README # 2 | 3 | This README would normally document whatever steps are necessary to get your application up and running. 4 | 5 | ### What is this repository for? ### 6 | 7 | * Quick summary 8 | * Version 9 | * [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) 10 | 11 | ### How do I get set up? ### 12 | 13 | * Summary of set up 14 | * Configuration 15 | * Dependencies 16 | * Database configuration 17 | * How to run tests 18 | * Deployment instructions 19 | 20 | ### Contribution guidelines ### 21 | 22 | * Writing tests 23 | * Code review 24 | * Other guidelines 25 | 26 | ### Who do I talk to? ### 27 | 28 | * Repo owner or admin 29 | * Other community or team contact 30 | 31 | ### Prisma 32 | ``` 33 | generate:user: npx prisma generate --schema prisma/schema.prisma 34 | npx prisma migrate dev --name update_app --schema prisma/schema.prisma 35 | npx prisma db seed 36 | ``` -------------------------------------------------------------------------------- /test/setup/globalSetup.ts: -------------------------------------------------------------------------------- 1 | import { MongoMemoryServer } from 'mongodb-memory-server'; 2 | import mongoose from 'mongoose'; 3 | import config from './config'; 4 | 5 | export = async function globalSetup() { 6 | if (config.Memory) { 7 | // Config to decided if a mongodb-memory-server instance should be used 8 | // it's needed in global space, because we don't want to create a new instance every test-suite 9 | const instance = await MongoMemoryServer.create(); 10 | const uri = instance.getUri(); 11 | (global as any).__MONGOINSTANCE = instance; 12 | process.env.MONGO_URI = uri.slice(0, uri.lastIndexOf('/')); 13 | } else { 14 | process.env.MONGO_URI = `mongodb://${config.IP}:${config.Port}`; 15 | } 16 | 17 | // The following is to make sure the database is clean before a test starts 18 | await mongoose.connect(`${process.env.MONGO_URI}/${config.DataBase}`, {}); 19 | await mongoose.connection.db.dropDatabase(); 20 | await mongoose.disconnect(); 21 | }; 22 | -------------------------------------------------------------------------------- /src/v1/services/yup.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | import { ObjectShape } from 'yup/lib/object'; 3 | 4 | export const phoneRegExp = 5 | /^((\+[1-9]{1,4}[ \-]*)|(\([0-9]{2,3}\)[ \-]*)|([0-9]{2,4})[ \-]*)*?[0-9]{3,4}?[ \-]*[0-9]{3,4}?$/; 6 | 7 | export const yupObject = (object?: ObjectShape) => yup.object(object); 8 | 9 | export const yupString = yup.string().nullable(); 10 | export const yupBoolean = yup.boolean().nullable(); 11 | export const yupNumber = yup.number().nullable(); 12 | export const yupMixed = yup.mixed().nullable(); 13 | export const yupDate = yup.date().nullable(); 14 | export const yupArray = (type: any) => yup.array().of(type).nullable(); 15 | 16 | export const requiredString = yupString.required(); 17 | export const requiredBoolean = yupBoolean.required(); 18 | export const requiredNumber = yupNumber.required(); 19 | export const requiredMixed = yupMixed.required(); 20 | export const requiredDate = yupDate.required(); 21 | export const requiredObject = (object?: ObjectShape) => 22 | yupObject(object) 23 | .required() 24 | .transform((value) => (JSON.stringify(value) === '{}' ? undefined : value)); 25 | export const requiredArray = (type: any) => yupArray(type).min(1); 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extrico_health_api", 3 | "version": "1.0.0", 4 | "description": "API for Extrico Health Applications", 5 | "private": true, 6 | "dependencies": { 7 | "@prisma/client": "^4.10.0", 8 | "axios": "^1.3.4", 9 | "bcrypt": "^5.1.0", 10 | "cookie-parser": "^1.4.6", 11 | "cors": "^2.8.5", 12 | "crypto": "^1.0.1", 13 | "dotenv": "^16.0.3", 14 | "express": "^4.18.2", 15 | "express-async-errors": "^3.1.1", 16 | "fs-extra": "^11.1.0", 17 | "helmet": "^6.0.0", 18 | "jet-logger": "^1.2.6", 19 | "jsonwebtoken": "^8.5.1", 20 | "moment": "^2.29.4", 21 | "morgan": "^1.10.0", 22 | "multer": "^1.4.5-lts.1", 23 | "uuid": "^9.0.0", 24 | "yup": "^0.32.11" 25 | }, 26 | "scripts": { 27 | "dev": "nodemon src/index.js", 28 | "lint": "eslint --fix --ext .ts --no-error-on-unmatched-pattern \"src/**/*.ts\" \"test/**/*.ts\"", 29 | "compile": "tsc", 30 | "build": "npx ts-node build.ts", 31 | "start": "./node_modules/.bin/ts-node -r tsconfig-paths/register dist/index.js", 32 | "start:dev": "NODE_ENV=development nodemon" 33 | }, 34 | "prisma": { 35 | "seed": "node prisma/seed.js" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.0.0", 39 | "@tsconfig/node14": "^1.0.3", 40 | "@types/bcrypt": "^5.0.0", 41 | "@types/cookie-parser": "^1.4.3", 42 | "@types/cors": "^2.8.12", 43 | "@types/fs-extra": "^9.0.13", 44 | "@types/jest": "^29.1.0", 45 | "@types/jsonwebtoken": "^8.5.9", 46 | "@types/morgan": "^1.9.3", 47 | "@types/multer": "^1.4.7", 48 | "@types/node": "^17.0.13", 49 | "@types/uuid": "^8.3.4", 50 | "@typescript-eslint/eslint-plugin": "^5.44.0", 51 | "@typescript-eslint/parser": "^5.44.0", 52 | "babel-jest": "^29.1.0", 53 | "esbuild": "^0.14.14", 54 | "eslint": "^8.28.0", 55 | "eslint-config-prettier": "^8.5.0", 56 | "eslint-plugin-prettier": "^4.2.1", 57 | "jest": "^29.1.1", 58 | "lint-staged": "^13.0.3", 59 | "nodemon": "^2.0.20", 60 | "prettier": "^2.5.1", 61 | "prisma": "^4.10.0", 62 | "ts-jest": "^29.0.3", 63 | "ts-node": "^10.4.0", 64 | "tsconfig-paths": "^4.1.0", 65 | "tscpaths": "^0.0.9", 66 | "typescript": "^4.5.5" 67 | }, 68 | "husky": { 69 | "hooks": { 70 | "pre-commit": "lint-staged" 71 | } 72 | }, 73 | "lint-staged": { 74 | "*.{js,ts,tsx}": [ 75 | "eslint --fix --quiet" 76 | ] 77 | }, 78 | "nodemonConfig": { 79 | "watch": [ 80 | "src" 81 | ], 82 | "ext": "ts, html", 83 | "exec": "./node_modules/.bin/ts-node -r tsconfig-paths/register ./src" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 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 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | env.json 99 | 100 | # parcel-bundler cache (https://parceljs.org/) 101 | .cache 102 | .parcel-cache 103 | 104 | # Storybook build outputs 105 | .out 106 | .storybook-out 107 | storybook-static 108 | 109 | # rollup.js default build output 110 | dist/ 111 | 112 | # Gatsby files 113 | .cache/ 114 | # Comment in the public line in if your project uses Gatsby and not Next.js 115 | # https://nextjs.org/blog/next-9-1#public-directory-support 116 | # public 117 | 118 | # Serverless directories 119 | .serverless/ 120 | 121 | # FuseBox cache 122 | .fusebox/ 123 | 124 | # DynamoDB Local files 125 | .dynamodb/ 126 | 127 | # TernJS port file 128 | .tern-port 129 | 130 | # Stores VSCode versions used for testing VSCode extensions 131 | .vscode-test 132 | 133 | # Temporary folders 134 | tmp/ 135 | temp/ 136 | 137 | ### OSX ### 138 | # General 139 | .DS_Store 140 | .AppleDouble 141 | .LSOverride 142 | 143 | # Icon must end with two \r 144 | Icon 145 | 146 | 147 | # Thumbnails 148 | ._* 149 | 150 | # Files that might appear in the root of a volume 151 | .DocumentRevisions-V100 152 | .fseventsd 153 | .Spotlight-V100 154 | .TemporaryItems 155 | .Trashes 156 | .VolumeIcon.icns 157 | .com.apple.timemachine.donotpresent 158 | 159 | # Directories potentially created on remote AFP share 160 | .AppleDB 161 | .AppleDesktop 162 | Network Trash Folder 163 | Temporary Items 164 | .apdisk 165 | 166 | ### SAM ### 167 | # Ignore build directories for the AWS Serverless Application Model (SAM) 168 | # Info: https://aws.amazon.com/serverless/sam/ 169 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 170 | 171 | **/.aws-sam 172 | 173 | ### Windows ### 174 | # Windows thumbnail cache files 175 | Thumbs.db 176 | Thumbs.db:encryptable 177 | ehthumbs.db 178 | ehthumbs_vista.db 179 | 180 | # Dump file 181 | *.stackdump 182 | 183 | # Folder config file 184 | [Dd]esktop.ini 185 | 186 | # Recycle Bin used on file shares 187 | $RECYCLE.BIN/ 188 | 189 | # Windows Installer files 190 | *.cab 191 | *.msi 192 | *.msix 193 | *.msm 194 | *.msp 195 | 196 | # Windows shortcuts 197 | *.lnk 198 | 199 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 200 | 201 | #IDE 202 | .idea 203 | .scannerwork 204 | src/scratchpad*.ts 205 | env.*.json 206 | package-lock.json 207 | prisma/migrations 208 | prisma/generated --------------------------------------------------------------------------------