├── .dockerignore ├── .funcignore ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── Dockerfile ├── README.md ├── coach-code-api.postman_collection.json ├── docker-compose.yml ├── jest.config.js ├── package.json ├── src ├── container.ts ├── extensions.csproj ├── handlers │ ├── HttpTrigger │ │ ├── function.json │ │ ├── index.ts │ │ └── sample.dat │ ├── add-mentee │ │ ├── add-mentee-handler.ts │ │ ├── function.json │ │ └── index.ts │ ├── add-user │ │ ├── add-user-handler.ts │ │ ├── function.json │ │ └── index.ts │ ├── get-mentees │ │ ├── function.json │ │ ├── get-mentees-handler.ts │ │ ├── index.ts │ │ └── sample.dat │ ├── get-mentor │ │ ├── function.json │ │ ├── get-mentor-handler.ts │ │ └── index.ts │ └── get-users │ │ ├── function.json │ │ ├── get-users-handler.ts │ │ └── index.ts ├── host.json ├── local.settings.json ├── modules │ ├── application-module.ts │ ├── boundary-module.ts │ └── index.ts ├── proxies.json └── repositories │ ├── mentee-repository │ ├── index.ts │ ├── mentee-entity.ts │ └── mentee-repository.ts │ ├── mentor-repository │ ├── index.ts │ ├── mentor-entity.ts │ └── mentor-repository.ts │ └── user-repository │ ├── User.ts │ ├── UserRepository.ts │ └── index.ts ├── test ├── integration │ ├── mentee-repository-tests.ts │ ├── models │ │ └── test-mentee-entity.ts │ ├── repositories │ │ └── test-mentee-repository.ts │ ├── setup.ts │ ├── test-container.ts │ └── test-module.ts └── unit │ ├── get-mentees-tests.ts │ ├── get-mentor-tests.ts │ └── setup.ts ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | .env 9 | */bin 10 | */obj 11 | README.md 12 | LICENSE 13 | .vscode 14 | test -------------------------------------------------------------------------------- /.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | # local.settings.json 24 | 25 | node_modules 26 | 27 | *.js 28 | *.map 29 | !jest.config.js 30 | dist 31 | .DS_Store 32 | 33 | *azuite* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | "args": ["--runInBand"], 12 | "cwd": "${workspaceFolder}", 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen", 15 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 16 | }, 17 | { 18 | "name": "Attach to JavaScript Functions", 19 | "type": "node", 20 | "request": "attach", 21 | "port": 5858, 22 | "preLaunchTask": "runFunctionsHost" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.projectRuntime": "~2", 3 | "azureFunctions.projectLanguage": "TypeScript", 4 | "azureFunctions.templateFilter": "Verified", 5 | "azureFunctions.deploySubpath": ".", 6 | "azureFunctions.preDeployTask": "installExtensions", 7 | "files.exclude": { 8 | "obj": true, 9 | "bin": true 10 | }, 11 | "debug.internalConsoleOptions": "neverOpen", 12 | "eslint.validate": [ 13 | "javascript", 14 | "javascriptreact", 15 | { 16 | "language": "typescript", 17 | "autoFix": true 18 | }, 19 | { 20 | "language": "typescriptreact", 21 | "autoFix": true 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "runFunctionsHost", 6 | "type": "shell", 7 | "command": "func host start --prefix dist/handlers", 8 | "isBackground": true, 9 | "problemMatcher": "$func-watch", 10 | "options": { 11 | "env": { 12 | "languageWorkers__node__arguments": "--inspect=5858" 13 | } 14 | } 15 | // "dependsOn": "installExtensions" 16 | }, 17 | { 18 | "label": "installExtensions", 19 | "command": "func extensions install", 20 | "type": "shell" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts 2 | RUN apt-get update 3 | RUN apt-get install dos2unix 4 | RUN yarn global add azurite@2.6.5 5 | RUN dos2unix /usr/local/share/.config/yarn/global/node_modules/azurite/bin/azurite 6 | ENTRYPOINT azurite -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo is deprecated 2 | We are archiving this repo, please consider contributing to https://github.com/Coding-Coach/find-a-mentor-api thanks! 3 | 4 | ### Basics requirements 5 | 6 | - Docker, yarn 7 | 8 | # Serverless Azure Functions Node.js Template 9 | 10 | This starter template allows quickly creating a Node.js-based service to Azure Functions. It relies on the `serverless-azure-functions` plugin, and therefore, before you can deploy it, you simply need to run `npm install` in order to acquire it (this dependency is already saved in the `package.json` file). 11 | 12 | ### Setting up your Azure credentials 13 | 14 | Once the `serverless-azure-functions` plugin is installed, it expects to find your Azure credentials via a set of well-known environment variables. These will be used to actually authenticate with your Azure account, so that the Serverless CLI can generate the necessary Azure resources on your behalf when you request a deployment (see below). 15 | 16 | The following environment variables must be set, with their respective values: 17 | 18 | - *azureSubId* - ID of the Azure subscription you want to create your service within 19 | - *azureServicePrincipalTenantId* - ID of the tenant that your service principal was created within 20 | - *azureServicePrincipalClientId* - ID of the service principal you want to use to authenticate with Azure 21 | - *azureServicePrincipalPassword* - Password of the service principal you want to use to authenticate with Azure 22 | 23 | For details on how to create a service principal and/or acquire your Azure account's subscription/tenant ID, refer to the [Azure credentials](https://serverless.com/framework/docs/providers/azure/guide/credentials/) documentation. 24 | 25 | ### Install container 26 | 27 | `docker-compose up -d` 28 | 29 | ### Install azure function core tools 30 | 31 | `npm install -g azure-functions-core-tools` 32 | 33 | ### Run localy 34 | 35 | `yarn run start` 36 | 37 | ### Basics end-points (Create and List users) 38 | 39 | - Intall Postman: https://www.getpostman.com/ 40 | - Import this collection: `coach-code-api.postman_collection.json` 41 | 42 | ### Deploying the service 43 | 44 | Once your Azure credentials are set, you can immediately deploy your service via the following command: 45 | 46 | ```shell 47 | serverless deploy 48 | ``` 49 | 50 | This will create the necessary Azure resources to support the service and events that are defined in your `serverless.yml` file. 51 | 52 | ### Invoking and inspecting a function 53 | 54 | With the service deployed, you can test it's functions using the following command: 55 | 56 | ```shell 57 | serverless invoke -f hello 58 | ``` 59 | 60 | Additionally, if you'd like to view the logs that a function generates (either via the runtime, or create by your handler by calling `context.log`), you can simply run the following command: 61 | 62 | ```shell 63 | serverless logs -f hello 64 | ``` 65 | 66 | ### Cleaning up 67 | 68 | Once you're finished with your service, you can remove all of the generated Azure resources by simply running the following command: 69 | 70 | ```shell 71 | serverless remove 72 | ``` 73 | 74 | ### Issues / Feedback / Feature Requests? 75 | 76 | If you have any issues, comments or want to see new features, please file an issue in the project repository: 77 | 78 | https://github.com/serverless/serverless-azure-functions 79 | -------------------------------------------------------------------------------- /coach-code-api.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": [], 3 | "info": { 4 | "name": "coach-code-api", 5 | "_postman_id": "56754707-2116-8c18-e175-b555a3ebf666", 6 | "description": "", 7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 8 | }, 9 | "item": [ 10 | { 11 | "name": "Get Users", 12 | "request": { 13 | "url": "http://localhost:7071/api/get-users", 14 | "method": "GET", 15 | "header": [], 16 | "body": {}, 17 | "description": "" 18 | }, 19 | "response": [] 20 | }, 21 | { 22 | "name": "Add User", 23 | "request": { 24 | "url": "http://localhost:7071/api/add-user", 25 | "method": "POST", 26 | "header": [ 27 | { 28 | "key": "Content-Type", 29 | "value": "application/json", 30 | "description": "" 31 | } 32 | ], 33 | "body": { 34 | "mode": "raw", 35 | "raw": "{\n\t\"userId\": \"35024634845\",\n\t\"name\": \"Carlos\",\n\t\"email\": \"analista.carlosh@gmail.com\",\n\t\"avatar\": \"avatar\",\n\t\"title\": \"Developer\",\n\t\"description\": \"Developer\",\n\t\"country\": \"Brazil\"\n}" 36 | }, 37 | "description": "" 38 | }, 39 | "response": [] 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | coding-coach-azurite: 5 | build: . 6 | ports: 7 | - 10000:10000 8 | - 10001:10001 9 | - 10002:10002 10 | expose: 11 | - 10000 12 | - 10001 13 | - 10002 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [""], 3 | transform: { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | testRegex: "\\-tests\\.ts", 7 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 8 | testPathIgnorePatterns: ["\\\\node_modules\\\\"], 9 | moduleNameMapper: { 10 | "^@modules": "/src/modules", 11 | "^@container": "/src/container", 12 | "^@handlers(.*)": "/src/handlers$1", 13 | "^@repositories(.*)": "/src/repositories$1" 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codingcoach-api", 3 | "version": "1.0.0", 4 | "description": "Application to serve Coding Coach FE", 5 | "repository": "https://github.com/Coding-Coach/coding-coach-api", 6 | "license": "MIT", 7 | "private": true, 8 | "keywords": [ 9 | "serverless", 10 | "azure" 11 | ], 12 | "scripts": { 13 | "build": "yarn clean && ttsc && yarn build:configFiles && yarn build:extensions", 14 | "build:production": "yarn build && rimraf $npm_package_config_azureFunctions_outDir/local.settings.json && copyfiles package.json $npm_package_config_azureFunctions_outDir && yarn install --prefix $npm_package_config_azureFunctions_outDir --production && echo \"\nProduction build is ready in '$npm_package_config_azureFunctions_outDir'.\n\"", 15 | "build:configFiles": "copyfiles -u 1 \"$npm_package_config_azureFunctions_rootDir/**/*.json\" \"$npm_package_config_azureFunctions_rootDir/**/*.csproj\" $npm_package_config_azureFunctions_outDir", 16 | "cdout": "cd $npm_package_config_azureFunctions_outDir && pwd", 17 | "build:extensions": "func extensions sync --prefix $npm_package_config_azureFunctions_outDir --output bin", 18 | "install:extensions": "yarn build && func extensions install --prefix $npm_package_config_azureFunctions_outDir && copyfiles -u 1 $npm_package_config_azureFunctions_outDir/extensions.csproj $npm_package_config_azureFunctions_rootDir", 19 | "install:extensions:force": "yarn build && func extensions install --prefix $npm_package_config_azureFunctions_outDir --force && copyfiles -u 1 $npm_package_config_azureFunctions_outDir/extensions.csproj $npm_package_config_azureFunctions_rootDir", 20 | "start": "yarn build && yarn start:host & yarn watch", 21 | "start:host": "func host start --prefix $npm_package_config_azureFunctions_outHandlersDir", 22 | "start:azurite": "docker-compose up -d", 23 | "watch": "tsc -w & onchange \"$npm_package_config_azureFunctions_rootDir/**/*.json\" \"$npm_package_config_azureFunctions_rootDir/**/*.csproj\" -- yarn build:configFiles", 24 | "clean": "rimraf $npm_package_config_azureFunctions_outDir", 25 | "test": "jest", 26 | "lint": "eslint src --ext .ts,.tsx", 27 | "publish:update": "yarn test && yarn build:production && echo \"Publishing '$npm_package_config_azureFunctions_outDir' content to already deployed function app '$npm_package_name'\" && func azure functionapp publish $npm_package_name --prefix $npm_package_config_azureFunctions_outDir" 28 | }, 29 | "dependencies": { 30 | "@azure/functions": "^1.0.1-beta2", 31 | "@graphql-modules/core": "^0.7.0", 32 | "@graphql-modules/di": "^0.7.0", 33 | "@types/node": "^10.12.24", 34 | "azure-storage": "^2.10.2", 35 | "graphql": "^14.2.1", 36 | "guid-typescript": "^1.0.9", 37 | "reflect-metadata": "^0.1.13", 38 | "typescript": "^3.3.3", 39 | "uuid": "^3.3.2" 40 | }, 41 | "devDependencies": { 42 | "@fluffy-spoon/substitute": "^1.70.0", 43 | "@types/async-retry": "^1.4.1", 44 | "@types/jest": "^24.0.0", 45 | "@typescript-eslint/eslint-plugin": "^1.7.0", 46 | "@typescript-eslint/parser": "^1.7.0", 47 | "async-retry": "^1.2.3", 48 | "azurite": "^2.7.0", 49 | "copyfiles": "^2.1.0", 50 | "eslint": "^5.16.0", 51 | "husky": "^2.1.0", 52 | "jest": "23.6.0", 53 | "onchange": "^5.2.0", 54 | "rimraf": "^2.6.3", 55 | "testcontainers": "^1.1.9", 56 | "ts-jest": "^23.10.5", 57 | "ts-transformer-imports": "^0.4.1", 58 | "ttypescript": "^1.5.6" 59 | }, 60 | "config": { 61 | "azureFunctions": { 62 | "outDir": "dist", 63 | "rootDir": "src", 64 | "outHandlersDir": "dist/handlers" 65 | } 66 | }, 67 | "husky": { 68 | "hooks": { 69 | "pre-commit": "yarn lint" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/container.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { GraphQLModule } from '@graphql-modules/core'; 3 | import { ApplicationModule } from '@modules/application-module'; 4 | 5 | const Container = new GraphQLModule({ 6 | imports: [ApplicationModule], 7 | }); 8 | 9 | export { Container }; 10 | -------------------------------------------------------------------------------- /src/extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | ** 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/handlers/HttpTrigger/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["get", "post"] 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/handlers/HttpTrigger/index.ts: -------------------------------------------------------------------------------- 1 | import { AzureFunction, Context, HttpRequest } from '@azure/functions'; 2 | 3 | const httpTrigger: AzureFunction = async function( 4 | context: Context, 5 | req: HttpRequest 6 | ): Promise { 7 | context.log('HTTP trigger function processed a request.'); 8 | const name = req.query.name || (req.body && req.body.name); 9 | 10 | if (name) { 11 | context.res = { 12 | // status: 200, /* Defaults to 200 */ 13 | body: 'Hello ' + (req.query.name || req.body.name), 14 | }; 15 | } else { 16 | context.res = { 17 | status: 400, 18 | body: 'Please pass a name on the query string or in the request body', 19 | }; 20 | } 21 | }; 22 | 23 | export default httpTrigger; 24 | -------------------------------------------------------------------------------- /src/handlers/HttpTrigger/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /src/handlers/add-mentee/add-mentee-handler.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Context, HttpRequest } from '@azure/functions'; 3 | import { 4 | IMentorRepository, 5 | MentorEntity, 6 | } from '@repositories/mentor-repository'; 7 | import { Inject, Injectable } from '@graphql-modules/di'; 8 | 9 | @Injectable() 10 | class AddMentee { 11 | constructor( 12 | @Inject('IMentorRepository') private mentorRepository: IMentorRepository 13 | ) {} 14 | index = async (context: Context, req: HttpRequest): Promise => { 15 | context.log('JavaScript HTTP trigger function processed a request.'); 16 | 17 | const mentorId = req.query.mentorId; 18 | const menteeId = req.query.menteeId; 19 | 20 | const mentor = new MentorEntity(mentorId, menteeId, 'Gurpreet'); 21 | 22 | await this.mentorRepository.addMentee(mentor); 23 | 24 | context.res = { 25 | status: '200', 26 | }; 27 | }; 28 | } 29 | 30 | export { AddMentee }; 31 | -------------------------------------------------------------------------------- /src/handlers/add-mentee/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["get", "post"] 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/handlers/add-mentee/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { AzureFunction } from '@azure/functions'; 3 | import { AddMentee } from './add-mentee-handler'; 4 | import { Container } from '@container'; 5 | 6 | const index: AzureFunction = Container.injector.get(AddMentee).index; 7 | export { index }; 8 | -------------------------------------------------------------------------------- /src/handlers/add-user/add-user-handler.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import uuidv1 from 'uuid/v1'; 3 | import { Context, HttpRequest } from '@azure/functions'; 4 | import { Inject, Injectable } from '@graphql-modules/di'; 5 | import { IUserRepository, User } from '@repositories/user-repository'; 6 | 7 | @Injectable() 8 | class AddUser { 9 | constructor(@Inject('IUserRepository') private userRepository: IUserRepository) { 10 | // Nothing to do here 11 | } 12 | 13 | index = async (context: Context, req: HttpRequest): Promise => { 14 | const user = new User({ 15 | // @TODO: for now using an uuid, but we want to use auth0 Ids to setup this value 16 | userId: uuidv1(), 17 | ...req.body, 18 | }); 19 | 20 | await this.userRepository.save(user); 21 | 22 | context.res = { 23 | headers: { 24 | 'Content-Type': 'application/json', 25 | }, 26 | body: { 27 | success: true, 28 | message: 'Successfully added', 29 | user, 30 | }, 31 | }; 32 | }; 33 | } 34 | 35 | export { AddUser }; 36 | -------------------------------------------------------------------------------- /src/handlers/add-user/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "post" 10 | ] 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/handlers/add-user/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { AzureFunction } from '@azure/functions'; 3 | import { AddUser } from './add-user-handler'; 4 | import { Container } from '@container'; 5 | 6 | const index: AzureFunction = Container.injector.get(AddUser).index; 7 | 8 | export { index }; 9 | -------------------------------------------------------------------------------- /src/handlers/get-mentees/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["get", "post"] 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/handlers/get-mentees/get-mentees-handler.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Inject, Injectable } from '@graphql-modules/di'; 3 | import { Context, HttpRequest } from '@azure/functions'; 4 | import { IMentorRepository } from '@repositories/mentor-repository/mentor-repository'; 5 | 6 | @Injectable() 7 | class GetMentees { 8 | constructor( 9 | @Inject('IMentorRepository') private mentorRepository: IMentorRepository 10 | ) {} 11 | index = async (context: Context, req: HttpRequest): Promise => { 12 | context.log('JavaScript HTTP trigger function processed a request.'); 13 | const mentorId = req.query.mentorId; 14 | const mentees = await this.mentorRepository.getMentees(mentorId); 15 | 16 | context.res = { 17 | status: '200', 18 | body: JSON.stringify(mentees), 19 | }; 20 | }; 21 | } 22 | 23 | export { GetMentees }; 24 | -------------------------------------------------------------------------------- /src/handlers/get-mentees/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { AzureFunction } from '@azure/functions'; 3 | import { GetMentees } from './get-mentees-handler'; 4 | import { Container } from '@container'; 5 | 6 | const index: AzureFunction = Container.injector.get(GetMentees) 7 | .index; 8 | export { index }; 9 | -------------------------------------------------------------------------------- /src/handlers/get-mentees/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /src/handlers/get-mentor/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["get", "post"] 9 | }, 10 | { 11 | "type": "http", 12 | "direction": "out", 13 | "name": "res" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/handlers/get-mentor/get-mentor-handler.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Inject, Injectable } from '@graphql-modules/di'; 3 | import { Context, HttpRequest } from '@azure/functions'; 4 | import { IMenteeRepository } from '@repositories/mentee-repository'; 5 | 6 | @Injectable() 7 | class GetMentor { 8 | constructor( 9 | @Inject('IMenteeRepository') private menteeRepository: IMenteeRepository 10 | ) {} 11 | index = async (context: Context, req: HttpRequest): Promise => { 12 | context.log('JavaScript HTTP trigger function processed a request.'); 13 | const menteeId = req.query.menteeId; 14 | const mentees = await this.menteeRepository.getMentor(menteeId); 15 | 16 | context.res = { 17 | status: '200', 18 | body: JSON.stringify(mentees), 19 | }; 20 | }; 21 | } 22 | 23 | export { GetMentor }; 24 | -------------------------------------------------------------------------------- /src/handlers/get-mentor/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { AzureFunction } from '@azure/functions'; 3 | import { GetMentor } from './get-mentor-handler'; 4 | import { Container } from '@container'; 5 | 6 | const index: AzureFunction = Container.injector.get(GetMentor).index; 7 | export { index }; 8 | -------------------------------------------------------------------------------- /src/handlers/get-users/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "get" 10 | ] 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/handlers/get-users/get-users-handler.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Context } from '@azure/functions'; 3 | import { Inject, Injectable } from '@graphql-modules/di'; 4 | import { IUserRepository } from '@repositories/user-repository'; 5 | 6 | @Injectable() 7 | class GetUsers { 8 | constructor(@Inject('IUserRepository') private userRepository: IUserRepository) { 9 | // Nothing to do here 10 | } 11 | 12 | index = async (context: Context): Promise => { 13 | 14 | // @TODO: Add filters 15 | const users = await this.userRepository.find(); 16 | 17 | context.res = { 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | }, 21 | body: { 22 | success: true, 23 | users, 24 | }, 25 | }; 26 | }; 27 | } 28 | 29 | export { GetUsers }; 30 | -------------------------------------------------------------------------------- /src/handlers/get-users/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { AzureFunction } from '@azure/functions'; 3 | import { GetUsers } from './get-users-handler'; 4 | import { Container } from '@container'; 5 | 6 | const index: AzureFunction = Container.injector.get(GetUsers).index; 7 | 8 | export { index }; 9 | -------------------------------------------------------------------------------- /src/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /src/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "AzureWebJobsDashboard": "UseDevelopmentStorage=true" 6 | }, 7 | "Host": { 8 | "LocalHttpPort": 7071, 9 | "CORS": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/application-module.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { GraphQLModule } from '@graphql-modules/core'; 3 | import { AddMentee } from '@handlers/add-mentee/add-mentee-handler'; 4 | import { AddUser } from '@handlers/add-user/add-user-handler'; 5 | import { GetMentees } from '@handlers/get-mentees/get-mentees-handler'; 6 | import { GetMentor } from '@handlers/get-mentor/get-mentor-handler'; 7 | import { GetUsers } from '@handlers/get-users/get-users-handler'; 8 | import { BoundaryModule } from './boundary-module'; 9 | 10 | const ApplicationModule = new GraphQLModule({ 11 | providers: [AddMentee, AddUser, GetMentees, GetMentor, GetUsers], 12 | imports: [BoundaryModule], 13 | }); 14 | 15 | export { ApplicationModule }; 16 | -------------------------------------------------------------------------------- /src/modules/boundary-module.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { GraphQLModule } from '@graphql-modules/core'; 3 | import { MenteeRepository } from '@repositories/mentee-repository'; 4 | import { MentorRepository } from '@repositories/mentor-repository'; 5 | import { UserRepository } from '@repositories/user-repository'; 6 | import azurestorage from 'azure-storage'; 7 | 8 | const BoundaryModule = new GraphQLModule({ 9 | providers: [ 10 | { 11 | provide: 'IMenteeRepository', 12 | useClass: MenteeRepository, 13 | }, 14 | { 15 | provide: 'IMentorRepository', 16 | useClass: MentorRepository, 17 | }, 18 | { 19 | provide: 'IUserRepository', 20 | useClass: UserRepository, 21 | }, 22 | { 23 | provide: 'TableService', 24 | useValue: new azurestorage.TableService('UseDevelopmentStorage=true'), 25 | }, 26 | ], 27 | }); 28 | 29 | export { BoundaryModule }; 30 | -------------------------------------------------------------------------------- /src/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './application-module'; 2 | export * from './boundary-module'; 3 | -------------------------------------------------------------------------------- /src/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /src/repositories/mentee-repository/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mentee-entity'; 2 | export * from './mentee-repository'; 3 | -------------------------------------------------------------------------------- /src/repositories/mentee-repository/mentee-entity.ts: -------------------------------------------------------------------------------- 1 | class MenteeEntity { 2 | public readonly PartitionKey: string; 3 | public readonly RowKey: string; 4 | public readonly MentorName: string; 5 | constructor(menteeId: string, mentorId: string, mentorName: string) { 6 | this.PartitionKey = menteeId; 7 | this.RowKey = mentorId; 8 | this.MentorName = mentorName; 9 | } 10 | } 11 | 12 | export { MenteeEntity }; 13 | -------------------------------------------------------------------------------- /src/repositories/mentee-repository/mentee-repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@graphql-modules/di'; 2 | import { MenteeEntity } from './mentee-entity'; 3 | import { TableService, TableQuery } from 'azure-storage'; 4 | 5 | export interface IMenteeRepository { 6 | addMentor(mentee: MenteeEntity): Promise; 7 | getMentor(menteeId: string): Promise; 8 | tableName: string; 9 | } 10 | 11 | @Injectable() 12 | class MenteeRepository implements IMenteeRepository { 13 | public tableName: string = 'menteeentity'; 14 | constructor(@Inject('TableService') private tableService: TableService) { 15 | this.tableService.doesTableExist(this.tableName, (error, result) => { 16 | if (!result.exists) { 17 | this.tableService.createTable(this.tableName, (error, result) => { 18 | console.log(error); 19 | console.log(result); 20 | }); 21 | } 22 | }); 23 | } 24 | addMentor(mentee: MenteeEntity): Promise { 25 | return new Promise((resolve, reject) => { 26 | this.tableService.insertEntity( 27 | this.tableName, 28 | mentee, 29 | error => { 30 | if (error) { 31 | reject(error); 32 | } else { 33 | resolve(); 34 | } 35 | } 36 | ); 37 | }); 38 | } 39 | getMentor(menteeId: string): Promise { 40 | return new Promise((resolve, reject) => { 41 | const query = new TableQuery() 42 | .top(1) 43 | .where('PartitionKey eq ?', menteeId); 44 | this.tableService.queryEntities( 45 | this.tableName, 46 | query, 47 | null, 48 | (error, result) => { 49 | if (error) { 50 | reject(error); 51 | } else { 52 | const entity = result.entries[0]; 53 | resolve(entity); 54 | } 55 | } 56 | ); 57 | }); 58 | } 59 | } 60 | 61 | export { MenteeRepository }; 62 | -------------------------------------------------------------------------------- /src/repositories/mentor-repository/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mentor-entity'; 2 | export * from './mentor-repository'; 3 | -------------------------------------------------------------------------------- /src/repositories/mentor-repository/mentor-entity.ts: -------------------------------------------------------------------------------- 1 | class MentorEntity { 2 | public readonly PartitionKey: string; 3 | public readonly RowKey: string; 4 | public readonly MenteeName: string; 5 | constructor(mentorId: string, menteeId: string, public menteeName: string) { 6 | this.PartitionKey = mentorId; 7 | this.RowKey = menteeId; 8 | this.MenteeName = menteeName; 9 | } 10 | } 11 | 12 | export { MentorEntity }; 13 | -------------------------------------------------------------------------------- /src/repositories/mentor-repository/mentor-repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@graphql-modules/di'; 2 | import { MentorEntity } from './mentor-entity'; 3 | import { TableService, TableQuery } from 'azure-storage'; 4 | 5 | export interface IMentorRepository { 6 | getMentees(mentorId: string): Promise; 7 | addMentee(mentor: MentorEntity): Promise; 8 | tableName: string; 9 | } 10 | 11 | @Injectable() 12 | class MentorRepository implements IMentorRepository { 13 | public tableName: string = 'mentorentity'; 14 | 15 | constructor(@Inject('TableService') private tableService: TableService) { 16 | this.tableService.doesTableExist(this.tableName, (error, result) => { 17 | if (!result.exists) { 18 | this.tableService.createTable(this.tableName, (error, result) => { 19 | console.log(error); 20 | console.log(result); 21 | }); 22 | } 23 | }); 24 | } 25 | 26 | public async getMentees(mentorId: string): Promise { 27 | return new Promise((resolve, reject) => { 28 | const query = new TableQuery().where('PartitionKey eq ?', mentorId); 29 | this.tableService.queryEntities( 30 | this.tableName, 31 | query, 32 | null, 33 | (error, result) => { 34 | if (error) { 35 | reject(error); 36 | } else { 37 | const entities = result.entries; 38 | resolve(entities); 39 | } 40 | } 41 | ); 42 | }); 43 | } 44 | 45 | public async addMentee(mentor: MentorEntity): Promise { 46 | return new Promise((resolve, reject) => { 47 | this.tableService.insertEntity( 48 | this.tableName, 49 | mentor, 50 | error => { 51 | if (error) { 52 | reject(error); 53 | } else { 54 | resolve(); 55 | } 56 | } 57 | ); 58 | }); 59 | } 60 | } 61 | 62 | export { MentorRepository }; 63 | -------------------------------------------------------------------------------- /src/repositories/user-repository/User.ts: -------------------------------------------------------------------------------- 1 | 2 | interface UserProps { 3 | userId: string; 4 | name?: string; 5 | email?: string; 6 | avatar?: string; 7 | title?: string; 8 | description?: string; 9 | country?: string; 10 | } 11 | 12 | export class User { 13 | public readonly PartitionKey: string; 14 | public readonly RowKey: string; 15 | public readonly name: string; 16 | public readonly email: string; 17 | public readonly avatar: string; 18 | public readonly title: string; 19 | public readonly description: string; 20 | public readonly country: string; 21 | 22 | constructor(data: UserProps) { 23 | Object.assign(this, data); 24 | this.PartitionKey = data.userId; 25 | this.RowKey = data.email; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/repositories/user-repository/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@graphql-modules/di'; 2 | import { User } from './User'; 3 | import { TableService, TableQuery } from 'azure-storage'; 4 | 5 | export interface IUserRepository { 6 | save(user: User): Promise; 7 | find(): Promise; 8 | tableName: string; 9 | } 10 | 11 | @Injectable() 12 | class UserRepository implements IUserRepository { 13 | public tableName: string = 'userentity'; 14 | 15 | constructor(@Inject('TableService') private tableService: TableService) { 16 | this.tableService.doesTableExist(this.tableName, (error, result) => { 17 | if (!result.exists) { 18 | this.tableService.createTable(this.tableName, (error, result) => { 19 | console.log(error); 20 | console.log(result); 21 | }); 22 | } 23 | }); 24 | } 25 | 26 | public async save(user: User): Promise { 27 | return new Promise((resolve, reject) => { 28 | this.tableService.insertEntity( 29 | this.tableName, 30 | user, 31 | (error) => { 32 | if (error) { 33 | reject(error); 34 | } else { 35 | resolve(); 36 | } 37 | } 38 | ); 39 | }); 40 | } 41 | 42 | // @TODO: Add filters as a parameter, for now we are just returning 43 | // all users, but ideally we should use the filters we have in the alpha site 44 | public async find(/* filters: object */): Promise { 45 | return new Promise((resolve, reject) => { 46 | const query = new TableQuery(); 47 | this.tableService.queryEntities( 48 | this.tableName, 49 | query, 50 | null, 51 | (error, result) => { 52 | if (error) { 53 | reject(error); 54 | } else { 55 | resolve(result.entries); 56 | } 57 | } 58 | ); 59 | }); 60 | } 61 | } 62 | 63 | export { UserRepository }; 64 | -------------------------------------------------------------------------------- /src/repositories/user-repository/index.ts: -------------------------------------------------------------------------------- 1 | export * from './User'; 2 | export * from './UserRepository'; 3 | -------------------------------------------------------------------------------- /test/integration/mentee-repository-tests.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { Guid } from "guid-typescript"; 3 | 4 | import { Setup } from "./setup"; 5 | import { 6 | MenteeRepository, 7 | IMenteeRepository, 8 | MenteeEntity 9 | } from "@repositories/mentee-repository"; 10 | import { TestMenteeRepository } from "./repositories/test-mentee-repository"; 11 | import { TestMenteeEntity } from "./models/test-mentee-entity"; 12 | import { TestContainer } from "./test-container"; 13 | import Retry from "async-retry"; 14 | 15 | describe("mentee repository tests", () => { 16 | const setup = new Setup(); 17 | let menteeRepository!: IMenteeRepository; 18 | let testMenteeRepository!: TestMenteeRepository; 19 | 20 | beforeAll(async () => { 21 | jest.setTimeout(100000); 22 | await setup.initialize(); 23 | 24 | menteeRepository = TestContainer.injector.get( 25 | "IMenteeRepository" 26 | ); 27 | testMenteeRepository = TestContainer.injector.get( 28 | "TestMenteeRepository" 29 | ); 30 | 31 | await setup.createAzureStorageTables(menteeRepository.tableName); 32 | }); 33 | test("When AddMentor is called", async () => { 34 | const menteeId = Guid.create().toString(); 35 | const mentorId = Guid.create().toString(); 36 | const mentorName = "Gurpreet"; 37 | const mentee = new MenteeEntity(menteeId, mentorId, mentorName); 38 | await menteeRepository.addMentor(mentee); 39 | 40 | const expectedMentee = new TestMenteeEntity(menteeId, mentorId, mentorName); 41 | const actualMentee = await testMenteeRepository.getMentor(menteeId); 42 | 43 | expect(actualMentee).toMatchObject(expectedMentee); 44 | }); 45 | 46 | afterAll(async () => { 47 | await setup.dispose(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/integration/models/test-mentee-entity.ts: -------------------------------------------------------------------------------- 1 | class TestMenteeEntity { 2 | public PartitionKey: string; 3 | public RowKey: string; 4 | public MentorName: string; 5 | constructor(menteeId: string, mentorId: string, mentorName: string) { 6 | this.PartitionKey = menteeId; 7 | this.RowKey = mentorId; 8 | this.MentorName = mentorName; 9 | } 10 | } 11 | 12 | export { TestMenteeEntity }; 13 | -------------------------------------------------------------------------------- /test/integration/repositories/test-mentee-repository.ts: -------------------------------------------------------------------------------- 1 | import { TableService, TableQuery } from "azure-storage"; 2 | import { TestMenteeEntity } from "../models/test-mentee-entity"; 3 | import { Injectable, Inject } from "@graphql-modules/di"; 4 | 5 | @Injectable() 6 | class TestMenteeRepository { 7 | public tableName: string = "menteeentity"; 8 | constructor(@Inject("TableService") private tableService: TableService) {} 9 | async getMentor(menteeId: string): Promise { 10 | return new Promise((resolve, reject) => { 11 | const query = new TableQuery() 12 | .top(1) 13 | .where("PartitionKey eq ?", menteeId); 14 | this.tableService.queryEntities( 15 | this.tableName, 16 | query, 17 | null, 18 | (error, result) => { 19 | if (error) { 20 | reject(error); 21 | } else { 22 | const entity = result.entries[0]; 23 | resolve(this.tableRecordToJavacript(entity)); 24 | } 25 | } 26 | ); 27 | }); 28 | } 29 | 30 | tableRecordToJavacript(entity: {}) { 31 | let result: any = {}; 32 | Object.keys(entity).forEach(k => { 33 | // we do not want to decode metadata 34 | if (k !== ".metadata") { 35 | let prop = Object.getOwnPropertyDescriptor(entity, k); 36 | if (prop) { 37 | result[k] = prop.value["_"]; 38 | } 39 | } 40 | }); 41 | return result; 42 | } 43 | } 44 | 45 | export { TestMenteeRepository }; 46 | -------------------------------------------------------------------------------- /test/integration/setup.ts: -------------------------------------------------------------------------------- 1 | // import "reflect-metadata"; 2 | // import { container } from "tsyringe"; 3 | import azurestorage, { TableService } from "azure-storage"; 4 | import { GenericContainer } from "testcontainers"; 5 | import { StartedTestContainer } from "testcontainers/dist/test-container"; 6 | import { TestModule } from "./test-module"; 7 | import { BoundaryModule } from "@modules/boundary-module"; 8 | 9 | class Setup { 10 | private tableService: TableService; 11 | private azureStorageContainer: StartedTestContainer; 12 | 13 | public async initialize(): Promise { 14 | console.log("initialize"); 15 | 16 | console.log("container setup complete"); 17 | 18 | const container = new GenericContainer("zadika/azurite").withExposedPorts( 19 | 10000, 20 | 10001, 21 | 10002 22 | ); 23 | 24 | this.azureStorageContainer = await container.start(); 25 | 26 | const tableStoragePort = this.azureStorageContainer.getMappedPort(10002); 27 | const connectionString = `AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:${tableStoragePort}/devstoreaccount1;`; 28 | console.log(connectionString); 29 | 30 | this.tableService = new azurestorage.TableService(connectionString); 31 | 32 | const testTableService = new azurestorage.TableService(connectionString); 33 | 34 | TestModule.injector.provide({ 35 | provide: "TableService", 36 | useValue: testTableService, 37 | overwrite: true 38 | }); 39 | 40 | BoundaryModule.injector.provide({ 41 | provide: "TableService", 42 | useValue: this.tableService, 43 | overwrite: true 44 | }); 45 | 46 | console.log("container started"); 47 | } 48 | 49 | public async createAzureStorageTables( 50 | ...tableNames: Array 51 | ): Promise { 52 | return await Promise.all( 53 | tableNames.map(async tableName => { 54 | await this.createAzureStorageTable(tableName); 55 | }) 56 | ); 57 | } 58 | 59 | private async createAzureStorageTable( 60 | tableName: string 61 | ): Promise { 62 | return new Promise((resolve, reject) => { 63 | try { 64 | this.tableService.createTable(tableName, (err, result) => { 65 | // if (err) { 66 | // reject(err); 67 | // } 68 | resolve(); 69 | }); 70 | } catch (err) { 71 | reject(err); 72 | } 73 | }); 74 | } 75 | 76 | public async dispose(): Promise { 77 | if (this.azureStorageContainer) { 78 | await this.azureStorageContainer.stop(); 79 | } 80 | } 81 | } 82 | 83 | export { Setup }; 84 | -------------------------------------------------------------------------------- /test/integration/test-container.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { GraphQLModule } from "@graphql-modules/core"; 3 | import { BoundaryModule } from "@modules/boundary-module"; 4 | import { TestModule } from "./test-module"; 5 | 6 | const TestContainer = new GraphQLModule({ 7 | imports: [BoundaryModule, TestModule] 8 | }); 9 | 10 | export { TestContainer }; 11 | -------------------------------------------------------------------------------- /test/integration/test-module.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLModule } from "@graphql-modules/core"; 2 | import { TestMenteeRepository } from "./repositories/test-mentee-repository"; 3 | 4 | const TestModule = new GraphQLModule({ 5 | providers: [ 6 | { 7 | provide: "TestMenteeRepository", 8 | useClass: TestMenteeRepository 9 | } 10 | ] 11 | }); 12 | 13 | export { TestModule }; 14 | -------------------------------------------------------------------------------- /test/unit/get-mentees-tests.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { Substitute } from "@fluffy-spoon/substitute"; 3 | import { 4 | ExecutionContext, 5 | BindingDefinition, 6 | HttpRequest 7 | } from "@azure/functions"; 8 | import { Guid } from "guid-typescript"; 9 | import { setup, mentorRepository } from "./setup"; 10 | import { MentorEntity } from "@repositories/mentor-repository"; 11 | import { GetMentees } from "@handlers/get-mentees/get-mentees-handler"; 12 | import { Container } from "@container"; 13 | 14 | const mentorId = Guid.create().toString(); 15 | const menteeId = Guid.create().toString(); 16 | 17 | beforeEach(async () => { 18 | setup(); 19 | }); 20 | test("Get Mentees", async () => { 21 | const expectedMentees = [new MentorEntity(mentorId, menteeId, "Gurpreet")]; 22 | mentorRepository 23 | .getMentees(mentorId) 24 | .returns(Promise.resolve(expectedMentees)); 25 | const request: HttpRequest = { 26 | query: { 27 | mentorId: mentorId 28 | }, 29 | method: "GET", 30 | url: "", 31 | headers: {}, 32 | params: {} 33 | }; 34 | const context = { 35 | invocationId: "", 36 | executionContext: Substitute.for(), 37 | bindingData: {}, 38 | bindingDefinitions: [Substitute.for()], 39 | done: () => {}, 40 | log: Object.assign(() => {}, { 41 | error: () => {}, 42 | warn: () => {}, 43 | info: () => {}, 44 | verbose: () => {} 45 | }), 46 | bindings: {}, 47 | res: { 48 | status: 200 /* Defaults to 200 */, 49 | body: "" 50 | } 51 | }; 52 | const expectedJson: MentorEntity[] = [ 53 | new MentorEntity(mentorId, menteeId, "Gurpreet") 54 | ]; 55 | const handler = Container.injector.get(GetMentees).index; 56 | await handler(context, request); 57 | mentorRepository.received(1).getMentees(mentorId); 58 | expect(context.res.body).toEqual(JSON.stringify(expectedJson)); 59 | }); 60 | -------------------------------------------------------------------------------- /test/unit/get-mentor-tests.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { Substitute } from "@fluffy-spoon/substitute"; 3 | import { 4 | ExecutionContext, 5 | BindingDefinition, 6 | HttpRequest 7 | } from "@azure/functions"; 8 | import { Guid } from "guid-typescript"; 9 | import { MenteeEntity } from "@repositories/mentee-repository"; 10 | import { GetMentor } from "@handlers/get-mentor/get-mentor-handler"; 11 | import { Container } from "@container"; 12 | import { setup, menteeRepository } from "./setup"; 13 | 14 | const mentorId = Guid.create().toString(); 15 | const menteeId = Guid.create().toString(); 16 | 17 | beforeEach(async () => { 18 | setup(); 19 | }); 20 | test("Get Mentor", async () => { 21 | const expectedMentor = new MenteeEntity(menteeId, mentorId, "Gurpreet"); 22 | menteeRepository.getMentor(menteeId).returns(Promise.resolve(expectedMentor)); 23 | const request: HttpRequest = { 24 | query: { 25 | menteeId: menteeId 26 | }, 27 | method: "GET", 28 | url: "", 29 | headers: {}, 30 | params: {} 31 | }; 32 | const context = { 33 | invocationId: "", 34 | executionContext: Substitute.for(), 35 | bindingData: {}, 36 | bindingDefinitions: [Substitute.for()], 37 | done: () => {}, 38 | log: Object.assign(() => {}, { 39 | error: () => {}, 40 | warn: () => {}, 41 | info: () => {}, 42 | verbose: () => {} 43 | }), 44 | bindings: {}, 45 | res: { 46 | status: 200 /* Defaults to 200 */, 47 | body: "" 48 | } 49 | }; 50 | const expectedJson = new MenteeEntity(menteeId, mentorId, "Gurpreet"); 51 | const handler = Container.injector.get(GetMentor).index; 52 | await handler(context, request); 53 | menteeRepository.received(1).getMentor(menteeId); 54 | expect(context.res.body).toEqual(JSON.stringify(expectedJson)); 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/setup.ts: -------------------------------------------------------------------------------- 1 | import { BoundaryModule } from "@modules/boundary-module"; 2 | import Substitute from "@fluffy-spoon/substitute"; 3 | import { IMentorRepository } from "@repositories/mentor-repository"; 4 | import { IMenteeRepository } from "@repositories/mentee-repository"; 5 | 6 | const mentorRepository = Substitute.for(); 7 | const menteeRepository = Substitute.for(); 8 | 9 | const setup = function() { 10 | BoundaryModule.injector.provide({ 11 | provide: "IMentorRepository", 12 | useValue: mentorRepository, 13 | overwrite: true 14 | }); 15 | 16 | BoundaryModule.injector.provide({ 17 | provide: "IMenteeRepository", 18 | useValue: menteeRepository, 19 | overwrite: true 20 | }); 21 | }; 22 | 23 | export { setup, menteeRepository, mentorRepository }; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "es6", 8 | "dom", 9 | "es2015", 10 | "es5", 11 | "es2015.promise", 12 | "es2015.iterable", 13 | "es2017" 14 | ] /* Specify library files to be included in the compilation. */, 15 | // "allowJs": true, /* Allow javascript files to be compiled. */ 16 | // "checkJs": true, /* Report errors in .js files. */ 17 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 18 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 19 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 20 | "sourceMap": true /* Generates corresponding '.map' file. */, 21 | // "outFile": "./", /* Concatenate and emit output to single file. */ 22 | "outDir": "dist" /* Redirect output structure to the directory. */, 23 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 24 | // "composite": true, /* Enable project compilation */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true /* Do not emit outputs. */, 27 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | 31 | /* Strict Type-Checking Options */ 32 | // "strict": true /* Enable all strict type-checking options. */, 33 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 34 | // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 37 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 38 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 39 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 40 | 41 | /* Additional Checks */ 42 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 43 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 44 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 45 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 46 | 47 | /* Module Resolution Options */ 48 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 49 | "baseUrl": "src" /* Base directory to resolve non-absolute module names. */, 50 | "paths": { 51 | "@modules/*": ["modules/*"], 52 | "@container": ["container"], 53 | "@handlers/*": ["handlers/*"], 54 | "@repositories/*": ["repositories/*"] 55 | }, 56 | // "typeRoots": [], /* List of folders to include type definitions from. */ 57 | "types": [ 58 | "jest", 59 | "reflect-metadata", 60 | "node" 61 | ] /* Type declaration files to be included in compilation. */, 62 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 63 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 64 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 65 | 66 | /* Source Map Options */ 67 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 68 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 69 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 70 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 71 | 72 | /* Experimental Options */ 73 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 74 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 75 | "plugins": [{ "transform": "ts-transformer-imports" }] 76 | }, 77 | "exclude": ["node_modules", "test"] 78 | } 79 | --------------------------------------------------------------------------------