├── .barrelsby.json ├── .dockerignore ├── .env ├── .eslintignore ├── .eslintrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── config.yml ├── dependabot.yml ├── stale.yml └── workflows │ └── build.yml ├── .gitignore ├── .husky ├── .gitignore ├── post-commit └── pre-commit ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc ├── .yarn └── install-state.gz ├── .yarnrc.yml ├── Dockerfile ├── docker-compose.yml ├── jest.config.js ├── package.json ├── processes.config.js ├── readme.md ├── resources ├── calendars.json ├── events.json └── users.json ├── scripts └── jest │ └── teardown.js ├── src ├── Server.integration.spec.ts ├── Server.ts ├── config │ ├── envs │ │ └── index.ts │ ├── index.ts │ ├── logger │ │ └── index.ts │ └── mongoose │ │ ├── default.config.ts │ │ └── index.ts ├── controllers │ ├── pages │ │ ├── IndexController.ts │ │ ├── IndexCtrl.integration.spec.ts │ │ ├── IndexCtrl.ts │ │ └── index.ts │ └── rest │ │ ├── calendars │ │ ├── CalendarsCtrl.integration.spec.ts │ │ └── CalendarsCtrl.ts │ │ ├── events │ │ └── EventsCtrl.ts │ │ └── index.ts ├── decorators │ ├── calendarId.ts │ └── eventId.ts ├── index.ts ├── middlewares │ └── calendars │ │ └── CheckCalendarId.ts ├── models │ ├── calendars │ │ ├── Calendar.spec.ts │ │ └── Calendar.ts │ └── events │ │ └── CalendarEvent.ts └── services │ └── calendars │ ├── CalendarEventsService.ts │ └── CalendarsService.ts ├── tsconfig.compile.json ├── tsconfig.json ├── tslint.json ├── views ├── index.ejs └── swagger.ejs └── yarn.lock /.barrelsby.json: -------------------------------------------------------------------------------- 1 | { 2 | "directory": ["./src/controllers/rest", "./src/controllers/pages"], 3 | "exclude": ["__mock__", "__mocks__", ".spec.ts"], 4 | "delete": true 5 | } 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Dockerfile 3 | .env.local 4 | .env.development 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsedio/tsed-example-mongoose/e2ba0f3379d85b30b943e6bc8ce758369e3e4dfd/.env -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | jest.config.js 4 | processes.config.js 5 | scripts 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "prettier", 5 | "plugin:@typescript-eslint/recommended" 6 | ], 7 | "plugins": ["@typescript-eslint"], 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module", 11 | "project": "./tsconfig.json" 12 | }, 13 | "env": { 14 | "node": true, 15 | "es6": true 16 | }, 17 | "rules": { 18 | "@typescript-eslint/no-inferrable-types": 0, 19 | "@typescript-eslint/no-unused-vars": 2, 20 | "@typescript-eslint/no-var-requires": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [romakita] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: tsed 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | custom: # Replace with a single custom sponsorship URL 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 Question or help request 4 | url: https://gitter.im/Tsed-io/Community 5 | about: You want to ask a question or discuss with other community members 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | assignees: 8 | - "Romakita" 9 | reviewers: 10 | - "Romakita" 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build & Release 5 | 6 | on: 7 | push: 8 | pull_request: 9 | branches: 10 | - production 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - name: Install dependencies 27 | run: yarn install --frozen-lockfile 28 | - name: Run test 29 | run: yarn test 30 | build: 31 | runs-on: ubuntu-latest 32 | 33 | strategy: 34 | matrix: 35 | node-version: [14.x] 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v1 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | - name: Install dependencies 44 | run: yarn install --frozen-lockfile 45 | - name: Run build 46 | run: yarn build 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node template 2 | .DS_Store 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 30 | node_modules 31 | .npmrc 32 | *.log 33 | 34 | # Typings 35 | typings/ 36 | 37 | # Typescript 38 | src/**/*.js 39 | src/**/*.js.map 40 | test/**/*.js 41 | test/**/*.js.map 42 | 43 | # Test 44 | /.tmp 45 | /.nyc_output 46 | 47 | # IDE 48 | .vscode 49 | .idea 50 | 51 | # Project 52 | /public 53 | /dist 54 | 55 | #env 56 | .env.local 57 | .env.development 58 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | git update-index --again 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged $1 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{ts,js}": ["eslint --fix"], 3 | "**/*.{ts,js,json,md,yml,yaml}": ["prettier --write"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | docs 3 | node_modules 4 | *-lock.json 5 | *.lock 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "singleQuote": false, 4 | "jsxSingleQuote": true, 5 | "semi": true, 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsedio/tsed-example-mongoose/e2ba0f3379d85b30b943e6bc8ce758369e3e4dfd/.yarn/install-state.gz -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ############################################################################### 3 | ## _______ _____ ______ _____ ## 4 | ## |__ __/ ____| ____| __ \ ## 5 | ## | | | (___ | |__ | | | | ## 6 | ## | | \___ \| __| | | | | ## 7 | ## | | ____) | |____| |__| | ## 8 | ## |_| |_____/|______|_____/ ## 9 | ## ## 10 | ## description : Dockerfile for TsED Application ## 11 | ## author : TsED team ## 12 | ## date : 2023-12-11 ## 13 | ## version : 3.0 ## 14 | ## ## 15 | ############################################################################### 16 | ############################################################################### 17 | 18 | ARG NODE_VERSION=20.10.0 19 | 20 | FROM node:${NODE_VERSION}-alpine as build 21 | WORKDIR /opt 22 | 23 | COPY package.json yarn.lock tsconfig.json tsconfig.compile.json .barrelsby.json ./ 24 | 25 | RUN yarn install --pure-lockfile 26 | 27 | COPY ./src ./src 28 | 29 | RUN yarn build 30 | 31 | FROM node:${NODE_VERSION}-alpine as runtime 32 | ENV WORKDIR /opt 33 | WORKDIR $WORKDIR 34 | 35 | RUN apk update && apk add build-base git curl 36 | RUN npm install -g pm2 37 | 38 | COPY --from=build /opt . 39 | 40 | RUN yarn install --pure-lockfile --production 41 | 42 | COPY . . 43 | 44 | EXPOSE 8081 45 | ENV PORT 8081 46 | ENV NODE_ENV production 47 | 48 | CMD ["pm2-runtime", "start", "processes.config.js", "--env", "production"] 49 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | services: 3 | server: 4 | build: 5 | context: . 6 | dockerfile: ./Dockerfile 7 | args: 8 | - http_proxy 9 | - https_proxy 10 | - no_proxy 11 | image: tsed-example-mongoose/server:latest 12 | ports: 13 | - "8081:8081" 14 | mongodb: 15 | image: mongo:5.0.8 16 | ports: 17 | - "27017:27017" 18 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // Automatically clear mock calls and instances between every test 6 | clearMocks: true, 7 | 8 | // Indicates whether the coverage information should be collected while executing the test 9 | collectCoverage: true, 10 | 11 | // An array of glob patterns indicating a set of files for which coverage information should be collected 12 | // collectCoverageFrom: undefined, 13 | 14 | // The directory where Jest should output its coverage files 15 | coverageDirectory: "coverage", 16 | 17 | // An array of regexp pattern strings used to skip coverage collection 18 | coveragePathIgnorePatterns: ["index.ts", "/node_modules/"], 19 | 20 | // An object that configures minimum threshold enforcement for coverage results 21 | coverageThreshold: { 22 | global: { 23 | branches: 70, 24 | functions: 70, 25 | lines: 70, 26 | statements: 70 27 | } 28 | }, 29 | 30 | // An array of file extensions your modules use 31 | moduleFileExtensions: ["js", "json", "jsx", "ts", "tsx", "node"], 32 | 33 | // The test environment that will be used for testing 34 | testEnvironment: "node", 35 | 36 | // The glob patterns Jest uses to detect test files 37 | testMatch: ["**/src/**/__tests__/**/*.[jt]s?(x)", "**/src/**/?(*.)+(spec|test).[tj]s?(x)"], 38 | // A map from regular expressions to paths to transformers 39 | transform: { 40 | "\\.(ts)$": "ts-jest" 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsed-example-mongoose", 3 | "version": "1.0.0", 4 | "description": "Here an example project with Mongoose and Ts.ED framework.", 5 | "scripts": { 6 | "install:db:mock": "cross-env MONGOMS_VERSION=6.0.0 node node_modules/mongodb-memory-server/postinstall.js", 7 | "clean": "rimraf '{src,test}/**/*.{js,js.map}'", 8 | "build": "yarn run barrels && tsc --project tsconfig.compile.json", 9 | "test": "yarn run test:lint && yarn run test:coverage ", 10 | "test:unit": "cross-env NODE_ENV=test jest", 11 | "test:coverage": "yarn run test:unit ", 12 | "travis:deploy-once": "travis-deploy-once", 13 | "travis:coveralls": "nyc report --reporter=text-lcov | coveralls", 14 | "tsc": "tsc --project tsconfig.compile.json", 15 | "tsc:w": "tsc --project tsconfig.json -w", 16 | "start": "yarn run barrels && tsnd --inspect --exit-child --cls --ignore-watch node_modules --respawn --transpile-only src/index.ts", 17 | "start:prod": "cross-env NODE_ENV=production node dist/index.js", 18 | "docker:build": "yarn build && docker-compose build", 19 | "deploy": "exit 0", 20 | "barrels": "barrelsby --config .barrelsby.json", 21 | "test:lint": "eslint '**/*.{ts,js}'", 22 | "test:lint:fix": "eslint '**/*.{ts,js}' --fix", 23 | "prettier": "prettier '**/*.{ts,js,json,md,yml,yaml}' --write", 24 | "prepare": "is-ci || husky install" 25 | }, 26 | "dependencies": { 27 | "@tsed/ajv": "^7.70.0", 28 | "@tsed/common": "^7.70.0", 29 | "@tsed/core": "^7.70.0", 30 | "@tsed/di": "^7.70.0", 31 | "@tsed/engines": "^7.70.0", 32 | "@tsed/exceptions": "^7.70.0", 33 | "@tsed/json-mapper": "^7.70.0", 34 | "@tsed/logger": "^6.7.5", 35 | "@tsed/logger-file": "^6.7.5", 36 | "@tsed/mongoose": "^7.70.0", 37 | "@tsed/openspec": "^7.70.0", 38 | "@tsed/platform-cache": "^7.70.0", 39 | "@tsed/platform-exceptions": "^7.70.0", 40 | "@tsed/platform-express": "^7.70.0", 41 | "@tsed/platform-log-middleware": "^7.70.0", 42 | "@tsed/platform-middlewares": "^7.70.0", 43 | "@tsed/platform-params": "^7.70.0", 44 | "@tsed/platform-response-filter": "^7.70.0", 45 | "@tsed/platform-views": "^7.70.0", 46 | "@tsed/schema": "^7.70.0", 47 | "@tsed/swagger": "^7.70.0", 48 | "@tsed/testing-mongoose": "^7.70.0", 49 | "@types/swagger-schema-official": "2.0.21", 50 | "ajv": "^8.17.1", 51 | "barrelsby": "^2.8.1", 52 | "body-parser": "^1.20.2", 53 | "compression": "^1.7.4", 54 | "cookie-parser": "^1.4.6", 55 | "cors": "^2.8.5", 56 | "cross-env": "^7.0.3", 57 | "dotenv": "^16.4.5", 58 | "dotenv-expand": "^11.0.6", 59 | "dotenv-flow": "^4.1.0", 60 | "express": "^4.19.2", 61 | "express-session": "1.17.1", 62 | "method-override": "^3.0.0", 63 | "mongoose": "^8.6.0", 64 | "serve-static": "^1.13.1" 65 | }, 66 | "devDependencies": { 67 | "@tsed/cli-plugin-eslint": "5.2.7", 68 | "@tsed/cli-plugin-jest": "5.2.7", 69 | "@tsed/cli-plugin-mongoose": "5.2.7", 70 | "@tsed/testing-mongoose": "7.70.0", 71 | "@types/compression": "^1.7.5", 72 | "@types/cookie-parser": "^1.4.7", 73 | "@types/cors": "^2.8.17", 74 | "@types/express": "^4.17.21", 75 | "@types/http-proxy": "^1.17.1", 76 | "@types/jest": "^29.5.12", 77 | "@types/method-override": "^0.0.35", 78 | "@types/multer": "^1.4.12", 79 | "@types/node": "^22.5.1", 80 | "@types/request-promise": "^4.1.42", 81 | "@types/supertest": "^6.0.2", 82 | "@typescript-eslint/eslint-plugin": "^8.3.0", 83 | "@typescript-eslint/parser": "^8.3.0", 84 | "concurrently": "5.3.0", 85 | "eslint": "^8.57.0", 86 | "eslint-config-prettier": "^9.1.0", 87 | "eslint-plugin-prettier": "^5.2.1", 88 | "husky": "^9.1.5", 89 | "is-ci": "^3.0.1", 90 | "jest": "^29.7.0", 91 | "lint-staged": "^15.2.9", 92 | "nodemon": "2.0.6", 93 | "prettier": "^3.3.3", 94 | "rimraf": "^3.0.0", 95 | "supertest": "^7.0.0", 96 | "ts-jest": "^29.2.5", 97 | "ts-node": "^10.9.2", 98 | "ts-node-dev": "^2.0.0", 99 | "tslib": "^2.7.0", 100 | "typescript": "^5.5.4" 101 | }, 102 | "author": "", 103 | "license": "MIT", 104 | "tsed": { 105 | "convention": "conv_default", 106 | "architecture": "arc_default", 107 | "packageManager": "yarn", 108 | "runtime": "node" 109 | }, 110 | "packageManager": "yarn@4.4.1" 111 | } 112 | -------------------------------------------------------------------------------- /processes.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const defaultLogFile = path.join(__dirname, "/logs/project-server.log"); 5 | 6 | module.exports = { 7 | apps: [ 8 | { 9 | name: "api", 10 | 11 | script: `${process.env.WORKDIR}/dist/index.js`, 12 | cwd: process.env.WORKDIR, 13 | exec_mode: "cluster", 14 | instances: process.env.NODE_ENV === "test" ? 1 : process.env.NB_INSTANCES || 2, 15 | autorestart: true, 16 | max_memory_restart: process.env.MAX_MEMORY_RESTART || "750M", 17 | out_file: defaultLogFile, 18 | error_file: defaultLogFile, 19 | merge_logs: true, 20 | kill_timeout: 30000 21 | } 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ts.ED - Mongoose 2 | 3 | Here an example project with Mongoose and Ts.ED framework. 4 | 5 | See [Ts.ED](https://tsed.dev) project for more information. 6 | 7 | ## Features 8 | 9 | - Docker and Docker compose 10 | - Travis CI 11 | - Mongoose 12 | 13 | [](https://docker.com) 14 | [](https://travis-ci.org) 15 | [](https://mongoosejs.com/) 16 | 17 | ## Checkout 18 | 19 | This repository provide getting started project example for each Ts.ED version since `v5.18.1`. 20 | 21 | ```bash 22 | git checkout -b https://github.com/TypedProject/tsed-example-mongoose/tree/v5.18.1 23 | ``` 24 | 25 | To checkout another version just replace `v5.18.1` by the desired version. 26 | 27 | ## Install 28 | 29 | > **Important!** Ts.ED requires Node >= 8, Express >= 4 and TypeScript >= 3. 30 | 31 | ```batch 32 | yarn install 33 | ``` 34 | 35 | ## Run 36 | 37 | ``` 38 | yarn start 39 | ``` 40 | 41 | ## Contributing 42 | 43 | You can make a PR directly on https://github.com/TypedProject/ts-express-decorators repository. 44 | 45 | ## Backers 46 | 47 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/tsed#backer)] 48 | 49 | 50 | 51 | 52 | ## Sponsors 53 | 54 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/tsed#sponsor)] 55 | 56 | ## License 57 | 58 | The MIT License (MIT) 59 | 60 | Copyright (c) 2016 - 2020 Romain Lenzotti 61 | 62 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 67 | 68 | [travis]: https://travis-ci.org/ 69 | -------------------------------------------------------------------------------- /resources/calendars.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5ce4ee471495836c5e2e4cb7", 4 | "name": "Sexton Berg" 5 | }, 6 | { 7 | "_id": "5ce4ee471495836c5e2e4cb8", 8 | "name": "Etta Gonzalez" 9 | }, 10 | { 11 | "_id": "5ce4ee471495836c5e2e4cb9", 12 | "name": "Hall Leon" 13 | }, 14 | { 15 | "_id": "5ce4ee471495836c5e2e4cba", 16 | "name": "Gentry Rowe" 17 | }, 18 | { 19 | "_id": "5ce4ee471495836c5e2e4cbb", 20 | "name": "Janelle Adams" 21 | }, 22 | { 23 | "_id": "5ce4ee471495836c5e2e4cbd", 24 | "name": "Robertson Crane" 25 | }, 26 | { 27 | "_id": "5ce4ee471495836c5e2e4cbc", 28 | "name": "Smith Norris" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /resources/events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "5ce4f06355e6136cf623a2aa", 4 | "calendarId": "5ce4ee471495836c5e2e4cb7", 5 | "name": "QUAILCOM", 6 | "dateStart": "2017-07-01T00:00:00.000Z", 7 | "dateEnd": "2017-07-01T00:00:00.000Z" 8 | }, 9 | { 10 | "_id": "5ce4f06355e6136cf623a2ab", 11 | "calendarId": "5ce4ee471495836c5e2e4cb7", 12 | "name": "MEDIFAX", 13 | "dateStart": "2017-07-01T00:00:00.000Z", 14 | "dateEnd": "2017-07-01T00:00:00.000Z" 15 | }, 16 | { 17 | "_id": "5ce4f06355e6136cf623a2ad", 18 | "calendarId": "5ce4ee471495836c5e2e4cb7", 19 | "name": "ERSUM", 20 | "dateStart": "2017-07-01T00:00:00.000Z", 21 | "dateEnd": "2017-07-01T00:00:00.000Z" 22 | }, 23 | { 24 | "_id": "5ce4f06355e6136cf623a2ac", 25 | "calendarId": "5ce4ee471495836c5e2e4cb7", 26 | "name": "ANARCO", 27 | "dateStart": "2017-07-01T00:00:00.000Z", 28 | "dateEnd": "2017-07-01T00:00:00.000Z" 29 | }, 30 | { 31 | "_id": "5ce4f06355e6136cf623a2ae", 32 | "calendarId": "5ce4ee471495836c5e2e4cb7", 33 | "name": "TALENDULA", 34 | "dateStart": "2017-07-01T00:00:00.000Z", 35 | "dateEnd": "2017-07-01T00:00:00.000Z" 36 | }, 37 | { 38 | "_id": "5ce4f06355e6136cf623a2af", 39 | "calendarId": "5ce4ee471495836c5e2e4cb7", 40 | "name": "MELBACOR", 41 | "dateStart": "2017-07-01T00:00:00.000Z", 42 | "dateEnd": "2017-07-01T00:00:00.000Z" 43 | }, 44 | { 45 | "_id": "5ce4f06355e6136cf623a2b0", 46 | "calendarId": "5ce4ee471495836c5e2e4cb7", 47 | "name": "OPTICON", 48 | "dateStart": "2017-07-01T00:00:00.000Z", 49 | "dateEnd": "2017-07-01T00:00:00.000Z" 50 | }, 51 | { 52 | "_id": "5ce4f06355e6136cf623a2b1", 53 | "calendarId": "5ce4ee471495836c5e2e4cb7", 54 | "name": "KANGLE", 55 | "dateStart": "2017-07-01T00:00:00.000Z", 56 | "dateEnd": "2017-07-01T00:00:00.000Z" 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /resources/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "583538e9605c9e261eb80042", 4 | "firstName": "Claudine", 5 | "lastName": "Noel", 6 | "password": "583538e9f26a4de79fee96bd", 7 | "email": "claudine.noel@undefined.org", 8 | "phone": "+1 (856) 516-3655", 9 | "address": "784 Dunne Court, Bentley, District Of Columbia, 329" 10 | }, 11 | { 12 | "_id": "583538ea448f69d5dfa9dab0", 13 | "firstName": "Wendi", 14 | "lastName": "Small", 15 | "password": "583538ea1083a5ffd88f3e25", 16 | "email": "wendi.small@undefined.net", 17 | "phone": "+1 (830) 402-2797", 18 | "address": "201 Harway Avenue, Gilgo, Wisconsin, 7560" 19 | }, 20 | { 21 | "_id": "583538eabf9ec74ea879c08a", 22 | "firstName": "Fields", 23 | "lastName": "Payne", 24 | "password": "583538ea3b30af778abe5983", 25 | "email": "fields.payne@undefined.us", 26 | "phone": "+1 (899) 573-2046", 27 | "address": "813 Russell Street, Barclay, Marshall Islands, 1740" 28 | }, 29 | { 30 | "_id": "583538ea6aac2a46c5af33a1", 31 | "firstName": "Fernandez", 32 | "lastName": "Crane", 33 | "password": "583538eae784b777a3803761", 34 | "email": "fernandez.crane@undefined.co.uk", 35 | "phone": "+1 (904) 586-2593", 36 | "address": "177 Montauk Court, Olney, New York, 2916" 37 | }, 38 | { 39 | "_id": "583538ea678f0ce762d3ce62", 40 | "firstName": "Amy", 41 | "lastName": "Riley", 42 | "password": "583538ea97489c137ad54db5", 43 | "email": "amy.riley@undefined.io", 44 | "phone": "+1 (841) 438-3631", 45 | "address": "399 Pilling Street, Verdi, North Carolina, 5810" 46 | }, 47 | { 48 | "_id": "583538ea8895e7afb5acf5f4", 49 | "firstName": "Medina", 50 | "lastName": "Lambert", 51 | "password": "583538ea6e73331f764ca84d", 52 | "email": "medina.lambert@undefined.info", 53 | "phone": "+1 (881) 551-3406", 54 | "address": "221 Prescott Place, Roosevelt, Arkansas, 1521" 55 | } 56 | ] -------------------------------------------------------------------------------- /scripts/jest/teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | await global.__MONGOD__ && global.__MONGOD__.stop(); 3 | }; 4 | -------------------------------------------------------------------------------- /src/Server.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformTest } from "@tsed/common"; 2 | import SuperTest from "supertest"; 3 | import { Server } from "./Server"; 4 | 5 | describe("Server", () => { 6 | beforeEach(PlatformTest.bootstrap(Server)); 7 | afterEach(PlatformTest.reset); 8 | 9 | it("should call GET /rest", async () => { 10 | const request = SuperTest(PlatformTest.callback()); 11 | const response = await request.get("/rest").expect(404); 12 | 13 | expect(response.body).toEqual({ 14 | errors: [], 15 | message: 'Resource "/rest" not found', 16 | name: "NOT_FOUND", 17 | status: 404 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/Server.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import { Configuration, Inject } from "@tsed/di"; 3 | import { PlatformApplication } from "@tsed/common"; 4 | import "@tsed/platform-express"; // /!\ keep this import 5 | import "@tsed/ajv"; 6 | import "@tsed/swagger"; 7 | import "@tsed/mongoose"; 8 | import { config } from "./config/index"; 9 | import * as rest from "./controllers/rest/index"; 10 | import * as pages from "./controllers/pages/index"; 11 | 12 | @Configuration({ 13 | ...config, 14 | acceptMimes: ["application/json"], 15 | httpPort: process.env.PORT || 8083, 16 | httpsPort: false, // CHANGE 17 | disableComponentsScan: true, 18 | ajv: { 19 | returnsCoercedValues: true 20 | }, 21 | mount: { 22 | "/rest": [...Object.values(rest)], 23 | "/": [...Object.values(pages)] 24 | }, 25 | swagger: [ 26 | { 27 | path: "/doc", 28 | specVersion: "3.0.1" 29 | } 30 | ], 31 | middlewares: [ 32 | "cors", 33 | "cookie-parser", 34 | "compression", 35 | "method-override", 36 | "json-parser", 37 | { use: "urlencoded-parser", options: { extended: true } } 38 | ], 39 | views: { 40 | root: join(process.cwd(), "../views"), 41 | extensions: { 42 | ejs: "ejs" 43 | } 44 | }, 45 | exclude: ["**/*.spec.ts"] 46 | }) 47 | export class Server { 48 | @Inject() 49 | protected app: PlatformApplication; 50 | 51 | @Configuration() 52 | protected settings: Configuration; 53 | } 54 | -------------------------------------------------------------------------------- /src/config/envs/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv-flow"; 2 | 3 | process.env.NODE_ENV = process.env.NODE_ENV || "development"; 4 | 5 | export const config = dotenv.config(); 6 | export const isProduction = process.env.NODE_ENV === "production"; 7 | export const envs = process.env; 8 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import { envs } from "./envs/index"; 3 | import loggerConfig from "./logger/index"; 4 | import mongooseConfig from "./mongoose/index"; 5 | 6 | const pkg = JSON.parse(readFileSync("./package.json", { encoding: "utf8" })); 7 | 8 | export const config: Partial = { 9 | version: pkg.version, 10 | envs, 11 | logger: loggerConfig, 12 | mongoose: mongooseConfig 13 | // additional shared configuration 14 | }; 15 | -------------------------------------------------------------------------------- /src/config/logger/index.ts: -------------------------------------------------------------------------------- 1 | import { $log, PlatformLoggerSettings } from "@tsed/common"; 2 | import { isProduction } from "../envs/index"; 3 | 4 | if (isProduction) { 5 | $log.appenders.set("stdout", { 6 | type: "stdout", 7 | levels: ["info", "debug"], 8 | layout: { 9 | type: "json" 10 | } 11 | }); 12 | 13 | $log.appenders.set("stderr", { 14 | levels: ["trace", "fatal", "error", "warn"], 15 | type: "stderr", 16 | layout: { 17 | type: "json" 18 | } 19 | }); 20 | } 21 | 22 | export default { 23 | disableRoutesSummary: isProduction 24 | }; 25 | -------------------------------------------------------------------------------- /src/config/mongoose/default.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | id: "default", 3 | url: process.env.DEFAULT_URL || "mongodb://localhost:27017/default", 4 | connectionOptions: {} 5 | }; 6 | -------------------------------------------------------------------------------- /src/config/mongoose/index.ts: -------------------------------------------------------------------------------- 1 | // @tsed/cli do not edit 2 | import defaultConfig from "./default.config"; 3 | 4 | export default [defaultConfig]; 5 | -------------------------------------------------------------------------------- /src/controllers/pages/IndexController.ts: -------------------------------------------------------------------------------- 1 | import { Constant, Controller } from "@tsed/di"; 2 | import { HeaderParams } from "@tsed/platform-params"; 3 | import { View } from "@tsed/platform-views"; 4 | import { SwaggerSettings } from "@tsed/swagger"; 5 | import { Hidden, Get, Returns } from "@tsed/schema"; 6 | 7 | @Hidden() 8 | @Controller("/") 9 | export class IndexController { 10 | @Constant("swagger", []) 11 | private swagger: SwaggerSettings[]; 12 | 13 | @Get("/") 14 | @View("swagger.ejs") 15 | @(Returns(200, String).ContentType("text/html")) 16 | get(@HeaderParams("x-forwarded-proto") protocol: string, @HeaderParams("host") host: string) { 17 | const hostUrl = `${protocol || "http"}://${host}`; 18 | 19 | return { 20 | BASE_URL: hostUrl, 21 | docs: this.swagger.map((conf) => { 22 | return { 23 | url: hostUrl + conf.path, 24 | ...conf 25 | }; 26 | }) 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/controllers/pages/IndexCtrl.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformTest } from "@tsed/common"; 2 | import SuperTest from "supertest"; 3 | import { Server } from "../../Server"; 4 | 5 | describe("IndexCtrl", () => { 6 | beforeAll(PlatformTest.bootstrap(Server)); 7 | afterAll(PlatformTest.reset); 8 | 9 | // then run your test 10 | describe("GET /", () => { 11 | it("should index page", async () => { 12 | const request = SuperTest(PlatformTest.callback()); 13 | const response = await request.get("/").expect(200); 14 | 15 | expect(response.headers["content-type"]).toEqual("text/html; charset=utf-8"); 16 | expect(response.text).toEqual(""); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/controllers/pages/IndexCtrl.ts: -------------------------------------------------------------------------------- 1 | import { Constant, Controller, Get, HeaderParams, View } from "@tsed/common"; 2 | import { Hidden, SwaggerSettings } from "@tsed/swagger"; 3 | 4 | @Hidden() 5 | @Controller("/") 6 | export class IndexCtrl { 7 | @Constant("swagger") 8 | swagger: SwaggerSettings[]; 9 | 10 | @Get("/") 11 | @View("index.ejs") 12 | get(@HeaderParams("x-forwarded-proto") protocol: string, @HeaderParams("host") host: string) { 13 | const hostUrl = `${protocol || "http"}://${host}`; 14 | 15 | return { 16 | BASE_URL: hostUrl, 17 | docs: this.swagger.map((conf) => { 18 | return { 19 | url: hostUrl + conf.path, 20 | ...conf 21 | }; 22 | }) 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/controllers/pages/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from "./IndexController"; 6 | export * from "./IndexCtrl"; 7 | -------------------------------------------------------------------------------- /src/controllers/rest/calendars/CalendarsCtrl.integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformTest } from "@tsed/common"; 2 | import { isArray } from "@tsed/core"; 3 | import { TestMongooseContext } from "@tsed/testing-mongoose"; 4 | import SuperTest from "supertest"; 5 | import { Server } from "../../../Server"; 6 | 7 | async function getCalendarFixture(request: ReturnType): Promise<{ id: string }> { 8 | const response = await request.get("/rest/calendars").expect(200); 9 | 10 | return response.body[0]; 11 | } 12 | 13 | describe("Calendars", () => { 14 | beforeAll( 15 | TestMongooseContext.bootstrap(Server, { 16 | mongod: { 17 | binary: { 18 | version: "6.0.0" 19 | } 20 | } 21 | }) 22 | ); 23 | afterAll(TestMongooseContext.reset); 24 | 25 | // then run your test 26 | describe("GET /rest/calendars", () => { 27 | it("should return all calendars", async () => { 28 | const request = SuperTest(PlatformTest.callback()); 29 | const response = await request.get("/rest/calendars").expect(200); 30 | 31 | expect(isArray(response.body)).toBe(true); 32 | }); 33 | }); 34 | 35 | describe("GET /rest/calendars/:id", () => { 36 | it("should return all calendars", async () => { 37 | const request = SuperTest(PlatformTest.callback()); 38 | // GIVEN 39 | const calendar = await getCalendarFixture(request); 40 | // WHEN 41 | const response = await request.get(`/rest/calendars/${calendar.id}`).expect(200); 42 | 43 | expect(response.body.id).toEqual(calendar.id); 44 | }); 45 | 46 | it("should return a 400 when the id has the wrong format", async () => { 47 | const request = SuperTest(PlatformTest.callback()); 48 | // WHEN 49 | const response = await request.get(`/rest/calendars/1`).expect(400); 50 | 51 | expect(response.body).toEqual({ 52 | name: "AJV_VALIDATION_ERROR", 53 | message: 'Bad request on parameter "request.path.id".\nValue should match pattern "^[0-9a-fA-F]{24}$". Given value: "1"', 54 | status: 400, 55 | errors: [ 56 | { 57 | data: "1", 58 | keyword: "pattern", 59 | dataPath: "", 60 | schemaPath: "#/pattern", 61 | params: { pattern: "^[0-9a-fA-F]{24}$" }, 62 | message: 'should match pattern "^[0-9a-fA-F]{24}$"' 63 | } 64 | ] 65 | }); 66 | }); 67 | 68 | it("should return a 404", async () => { 69 | const request = SuperTest(PlatformTest.callback()); 70 | // WHEN 71 | const response = await request.get(`/rest/calendars/5ce4ee471495836c5e2e4cb0`).expect(404); 72 | 73 | expect(response.body).toEqual({ 74 | name: "NOT_FOUND", 75 | message: "Calendar not found", 76 | status: 404, 77 | errors: [] 78 | }); 79 | }); 80 | }); 81 | 82 | describe("PUT /rest/calendars/:id", () => { 83 | it("should throw a bad request when payload is empty", async () => { 84 | const request = SuperTest(PlatformTest.callback()); 85 | 86 | // GIVEN 87 | const calendar = await getCalendarFixture(request); 88 | 89 | // WHEN 90 | const response = await request.put(`/rest/calendars/${calendar.id}`).expect(400); 91 | 92 | expect(response.body).toEqual({ 93 | name: "AJV_VALIDATION_ERROR", 94 | message: 'Bad request on parameter "request.body".\nCalendar should have required property \'name\'. Given value: "undefined"', 95 | status: 400, 96 | errors: [ 97 | { 98 | keyword: "required", 99 | dataPath: "", 100 | schemaPath: "#/required", 101 | params: { missingProperty: "name" }, 102 | message: "should have required property 'name'", 103 | modelName: "Calendar" 104 | } 105 | ] 106 | }); 107 | }); 108 | 109 | it("should update the calendar", async () => { 110 | const request = SuperTest(PlatformTest.callback()); 111 | 112 | // GIVEN 113 | const calendar = await getCalendarFixture(request); 114 | 115 | // WHEN 116 | const response = await request 117 | .put(`/rest/calendars/${calendar.id}`) 118 | .send({ 119 | ...calendar, 120 | name: "New name" 121 | }) 122 | .expect(200); 123 | 124 | expect(response.body).toEqual({ 125 | ...calendar, 126 | name: "New name" 127 | }); 128 | }); 129 | }); 130 | 131 | describe("POST /rest/calendars", () => { 132 | it("should throw a bad request when payload is empty", async () => { 133 | const request = SuperTest(PlatformTest.callback()); 134 | 135 | // WHEN 136 | const response = await request.post(`/rest/calendars`).expect(400); 137 | 138 | expect(response.body).toEqual({ 139 | name: "AJV_VALIDATION_ERROR", 140 | message: 'Bad request on parameter "request.body".\nCalendar should have required property \'name\'. Given value: "undefined"', 141 | status: 400, 142 | errors: [ 143 | { 144 | keyword: "required", 145 | dataPath: "", 146 | schemaPath: "#/required", 147 | params: { missingProperty: "name" }, 148 | message: "should have required property 'name'", 149 | modelName: "Calendar" 150 | } 151 | ] 152 | }); 153 | }); 154 | 155 | it("should add and delete a calendar", async () => { 156 | const request = SuperTest(PlatformTest.callback()); 157 | 158 | // WHEN 159 | const response = await request 160 | .post(`/rest/calendars`) 161 | .send({ 162 | name: "New name" 163 | }) 164 | .expect(201); 165 | 166 | expect(response.body.name).toEqual("New name"); 167 | 168 | await request.delete(`/rest/calendars/${response.body.id}`).expect(204); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /src/controllers/rest/calendars/CalendarsCtrl.ts: -------------------------------------------------------------------------------- 1 | import {BodyParams, Controller, Delete, Get, PathParams, Post, Put} from "@tsed/common"; 2 | import {NotFound} from "@tsed/exceptions"; 3 | import {Description, Required, Returns, Status, Summary} from "@tsed/schema"; 4 | import {CalendarId} from "../../../decorators/calendarId"; 5 | import {Calendar} from "../../../models/calendars/Calendar"; 6 | import {CalendarsService} from "../../../services/calendars/CalendarsService"; 7 | import {EventsCtrl} from "../events/EventsCtrl"; 8 | 9 | /** 10 | * Add @Controller annotation to declare your class as Router controller. 11 | * The first param is the global path for your controller. 12 | * The others params is the controller dependencies. 13 | * 14 | * In this case, EventsCtrl is a dependency of CalendarsCtrl. 15 | * All routes of EventsCtrl will be mounted on the `/calendars` path. 16 | */ 17 | @Controller({ 18 | path: "/calendars", 19 | children: [EventsCtrl] 20 | }) 21 | export class CalendarsCtrl { 22 | constructor(private calendarsService: CalendarsService) {} 23 | 24 | @Get("/:id") 25 | @Summary("Return a calendar from his ID") 26 | @(Status(200, Calendar).Description("Success")) 27 | async get(@PathParams("id") @CalendarId() id: string): Promise { 28 | const calendar = await this.calendarsService.find(id); 29 | 30 | if (calendar) { 31 | return calendar; 32 | } 33 | 34 | throw new NotFound("Calendar not found"); 35 | } 36 | 37 | @Post("/") 38 | @Summary("Create a new Calendar") 39 | @(Returns(201, Calendar).Description("Created")) 40 | save( 41 | @Description("Calendar model") 42 | @BodyParams() 43 | @Required() 44 | calendar: Calendar 45 | ) { 46 | return this.calendarsService.save(calendar); 47 | } 48 | 49 | @Put("/:id") 50 | @Summary("Update calendar information") 51 | @(Returns(200, Calendar).Description("Success")) 52 | async update(@PathParams("id") @CalendarId() id: string, @BodyParams() @Required() calendar: Calendar): Promise { 53 | calendar._id = id; 54 | 55 | return this.calendarsService.save(calendar); 56 | } 57 | 58 | @Delete("/:id") 59 | @Summary("Remove a calendar.") 60 | @(Returns(204).Description("No content")) 61 | async remove(@PathParams("id") @CalendarId() id: string): Promise { 62 | await this.calendarsService.remove(id); 63 | } 64 | 65 | @Get("/") 66 | @Summary("Return all calendars") 67 | @(Returns(200, Array).Of(Calendar)) 68 | async getAllCalendars(): Promise { 69 | return this.calendarsService.query(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/controllers/rest/events/EventsCtrl.ts: -------------------------------------------------------------------------------- 1 | import { BodyParams, Controller, Delete, Get, Inject, PathParams, Post, Put } from "@tsed/common"; 2 | import { NotFound } from "@tsed/exceptions"; 3 | import { Description, Required, Returns, Status, Summary } from "@tsed/schema"; 4 | import { InCalendarId } from "../../../decorators/calendarId"; 5 | import { EventId } from "../../../decorators/eventId"; 6 | import { CalendarEvent } from "../../../models/events/CalendarEvent"; 7 | import { CalendarEventsService } from "../../../services/calendars/CalendarEventsService"; 8 | 9 | @Controller("/:calendarId/events") 10 | @InCalendarId() 11 | export class EventsCtrl { 12 | @Inject() 13 | private calendarEventsService: CalendarEventsService; 14 | 15 | @Get("/:id") 16 | @Summary("Get an event from his ID") 17 | @(Returns(200).Description("Success")) 18 | async get(@EventId() @PathParams("id") id: string): Promise { 19 | return this.calendarEventsService.find(id).catch((err) => { 20 | throw new NotFound("Event not found", err); 21 | }); 22 | } 23 | 24 | @Post("/") 25 | @Summary("Create an event") 26 | @(Status(201).Description("Created")) 27 | async save(@BodyParams() calendarEvent: CalendarEvent): Promise { 28 | return this.calendarEventsService.save(calendarEvent); 29 | } 30 | 31 | @Put("/:id") 32 | @Summary("Update event information") 33 | @(Status(200).Description("Success")) 34 | async update( 35 | @EventId() 36 | @PathParams("id") 37 | id: string, 38 | @BodyParams() calendarEvent: CalendarEvent 39 | ): Promise { 40 | return this.calendarEventsService 41 | .find(id) 42 | .then(() => this.calendarEventsService.save(calendarEvent)) 43 | .catch(() => { 44 | throw new NotFound("Calendar id not found"); 45 | }); 46 | } 47 | 48 | @Delete("/:id") 49 | @Summary("Remove an event") 50 | @(Status(204).Description("No content")) 51 | async remove( 52 | @Description("The calendar id of the event") 53 | @Required() 54 | @PathParams("calendarId") 55 | calendarId: string, 56 | @Description("The event id") 57 | @PathParams("id") 58 | id: string 59 | ) { 60 | return this.calendarEventsService.remove(id); 61 | } 62 | 63 | @Get("/") 64 | @Summary("Get all events for a calendar") 65 | @(Status(200).Description("Success")) 66 | async getEvents( 67 | @Description("The calendar id of the event") 68 | @Required() 69 | @PathParams("calendarId") 70 | calendarId: string 71 | ): Promise { 72 | return this.calendarEventsService.query(calendarId); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/controllers/rest/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from "./calendars/CalendarsCtrl"; 6 | export * from "./events/EventsCtrl"; 7 | -------------------------------------------------------------------------------- /src/decorators/calendarId.ts: -------------------------------------------------------------------------------- 1 | import {UseBefore} from "@tsed/common"; 2 | import {decorateMethodsOf, useDecorators} from "@tsed/core"; 3 | import {ObjectID} from "@tsed/mongoose"; 4 | import {Description, In, JsonParameterTypes} from "@tsed/schema"; 5 | import {CheckCalendarIdMiddleware} from "../middlewares/calendars/CheckCalendarId"; 6 | 7 | export function CalendarId() { 8 | return useDecorators(ObjectID(), Description("The calendar id")); 9 | } 10 | 11 | export function InCalendarId(): ClassDecorator { 12 | return (target) => { 13 | decorateMethodsOf( 14 | target, 15 | useDecorators( 16 | In(JsonParameterTypes.PATH) 17 | .Type(String) 18 | .Name("calendarId") 19 | .Description("The calendar id") 20 | .Pattern(/^[0-9a-fA-F]{24}$/), 21 | UseBefore(CheckCalendarIdMiddleware) 22 | ) 23 | ); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/decorators/eventId.ts: -------------------------------------------------------------------------------- 1 | import {useDecorators} from "@tsed/core"; 2 | import {ObjectID} from "@tsed/mongoose"; 3 | import {Description} from "@tsed/schema"; 4 | 5 | export function EventId() { 6 | return useDecorators(ObjectID(), Description("The event id")); 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { $log } from "@tsed/common"; 2 | import { PlatformExpress } from "@tsed/platform-express"; 3 | import { Server } from "./Server"; 4 | 5 | async function bootstrap() { 6 | try { 7 | const platform = await PlatformExpress.bootstrap(Server); 8 | await platform.listen(); 9 | 10 | process.on("SIGINT", () => { 11 | platform.stop(); 12 | }); 13 | } catch (error) { 14 | $log.error({ event: "SERVER_BOOTSTRAP_ERROR", message: error.message, stack: error.stack }); 15 | } 16 | } 17 | 18 | bootstrap(); 19 | -------------------------------------------------------------------------------- /src/middlewares/calendars/CheckCalendarId.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, PathParams } from "@tsed/common"; 2 | import { NotFound } from "@tsed/exceptions"; 3 | import { CalendarsService } from "../../services/calendars/CalendarsService"; 4 | import { Required } from "@tsed/schema"; 5 | 6 | @Middleware() 7 | export class CheckCalendarIdMiddleware { 8 | constructor(private calendarService: CalendarsService) {} 9 | 10 | async use(@Required() @PathParams("calendarId") calendarId: string) { 11 | try { 12 | await this.calendarService.find(calendarId); 13 | } catch { 14 | throw new NotFound("CalendarDTO not found"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/models/calendars/Calendar.spec.ts: -------------------------------------------------------------------------------- 1 | import {MongooseModel} from "@tsed/mongoose"; 2 | import {TestMongooseContext} from "@tsed/testing-mongoose"; 3 | import {Calendar} from "./Calendar"; 4 | 5 | describe("UserModel", () => { 6 | beforeAll(TestMongooseContext.create); 7 | afterAll(TestMongooseContext.reset); 8 | 9 | it("should run pre and post hook", async () => { 10 | const calendarModel = TestMongooseContext.get>(Calendar); 11 | // GIVEN 12 | const calendar = new calendarModel({ 13 | name: "name" 14 | }); 15 | 16 | // WHEN 17 | await calendar.save(); 18 | 19 | // THEN 20 | expect(calendar.name).toEqual("name"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/models/calendars/Calendar.ts: -------------------------------------------------------------------------------- 1 | import { Model, ObjectID } from "@tsed/mongoose"; 2 | import { Required, Property } from "@tsed/schema"; 3 | 4 | @Model() 5 | export class Calendar { 6 | @ObjectID("id") 7 | _id: string; 8 | 9 | @Required() 10 | name: string; 11 | 12 | @Property() 13 | owner: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/models/events/CalendarEvent.ts: -------------------------------------------------------------------------------- 1 | import { Description, Property, Name, Required } from "@tsed/schema"; 2 | import { Model, Ref } from "@tsed/mongoose"; 3 | import { Calendar } from "../calendars/Calendar"; 4 | 5 | @Model() 6 | export class CalendarEvent { 7 | @Name("id") 8 | _id: string; 9 | 10 | @Ref(Calendar) 11 | @Description("Calendar ID") 12 | calendarId: Ref; 13 | 14 | @Name("name") 15 | @Description("The name of the event") 16 | name: string; 17 | 18 | @Property() 19 | @Description("Creation's date") 20 | dateCreate: Date = new Date(); 21 | 22 | @Property() 23 | @Description("Last modification date") 24 | dateUpdate: Date = new Date(); 25 | 26 | @Property() 27 | @Description("Beginning date of the event") 28 | dateStart: Date = new Date(); 29 | 30 | @Property() 31 | @Required() 32 | @Description("Ending date of the event") 33 | dateEnd: Date = new Date(); 34 | 35 | @Property() 36 | @Description("Description the event") 37 | description: string; 38 | } 39 | -------------------------------------------------------------------------------- /src/services/calendars/CalendarEventsService.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Service } from "@tsed/common"; 2 | import { MongooseModel } from "@tsed/mongoose"; 3 | import { BadRequest } from "@tsed/exceptions"; 4 | import { $log } from "@tsed/logger"; 5 | import { CalendarEvent } from "../../models/events/CalendarEvent"; 6 | import eventsMock from "../../../resources/events.json"; 7 | 8 | @Service() 9 | export class CalendarEventsService { 10 | @Inject(CalendarEvent) 11 | private Event: MongooseModel; 12 | 13 | private static checkPrecondition(event: CalendarEvent) { 14 | if (event.dateStart.getTime() > event.dateEnd.getTime()) { 15 | new BadRequest("dateStart to be under dateEnd."); 16 | } 17 | } 18 | 19 | $onInit() { 20 | this.reload(); 21 | } 22 | 23 | async reload() { 24 | const events = await this.Event.find({}); 25 | 26 | if (events.length === 0) { 27 | this.Event.create(...eventsMock); 28 | } 29 | } 30 | 31 | /** 32 | * Find a CalendarEvent by his ID. 33 | * @param id 34 | * @returns {undefined|EventModel} 35 | */ 36 | async find(id: string): Promise { 37 | return await this.Event.findById(id).exec(); 38 | } 39 | 40 | async save(event: CalendarEvent) { 41 | CalendarEventsService.checkPrecondition(event); 42 | $log.debug({ message: "Validate event", event }); 43 | const eventModel = new this.Event(event); 44 | 45 | await eventModel.validate(); 46 | $log.debug({ message: "Save event", eventModel }); 47 | await eventModel.updateOne(event, { upsert: true }); 48 | 49 | $log.debug({ message: "Event saved", event }); 50 | 51 | return eventModel; 52 | } 53 | 54 | /** 55 | * Return all CalendarEvent for a calendarID. 56 | * @returns {CalendarEvent[]} 57 | */ 58 | async query(calendarId: string): Promise { 59 | const events = await this.Event.find({ calendarId: calendarId }).exec(); 60 | 61 | return events; 62 | } 63 | 64 | async remove(id: string) { 65 | return await this.Event.findByIdAndDelete(id).exec(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/services/calendars/CalendarsService.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Service } from "@tsed/common"; 2 | import { $log } from "@tsed/logger"; 3 | import { MongooseModel } from "@tsed/mongoose"; 4 | import { Calendar } from "../../models/calendars/Calendar"; 5 | 6 | @Service() 7 | export class CalendarsService { 8 | @Inject(Calendar) 9 | private Calendar: MongooseModel; 10 | 11 | $onInit() { 12 | this.reload(); 13 | } 14 | 15 | async reload() { 16 | const calendars = await this.Calendar.find({}); 17 | 18 | if (calendars.length === 0) { 19 | // eslint-disable-next-line @typescript-eslint/no-require-imports 20 | const promises = require("../../../resources/calendars.json").map((calendar: never) => this.save(calendar)); 21 | await Promise.all(promises); 22 | } 23 | } 24 | 25 | /** 26 | * Find a calendar by his ID. 27 | * @param id 28 | * @returns {undefined|Calendar} 29 | */ 30 | async find(id: string): Promise { 31 | $log.debug("Search a calendar from ID", id); 32 | const calendar = await this.Calendar.findById(id).exec(); 33 | 34 | $log.debug("Found", calendar); 35 | 36 | return calendar; 37 | } 38 | 39 | /** 40 | * 41 | * @param calendar 42 | * @returns {Promise} 43 | */ 44 | async save(calendar: Calendar): Promise { 45 | $log.debug({ message: "Validate calendar", calendar }); 46 | 47 | // const m = new CModel(calendar); 48 | // console.log(m); 49 | // await m.update(calendar, {upsert: true}); 50 | 51 | const model = new this.Calendar(calendar); 52 | $log.debug({ message: "Save calendar", calendar }); 53 | await model.updateOne(calendar, { upsert: true }); 54 | 55 | $log.debug({ message: "Calendar saved", model }); 56 | 57 | return model; 58 | } 59 | 60 | /** 61 | * 62 | * @returns {Calendar[]} 63 | */ 64 | async query(options = {}): Promise { 65 | return this.Calendar.find(options); 66 | } 67 | 68 | /** 69 | * 70 | * @param id 71 | * @returns {Promise} 72 | */ 73 | async remove(id: string) { 74 | await this.Calendar.deleteOne({ 75 | _id: id 76 | }).exec(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tsconfig.compile.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "noResolve": false, 9 | "preserveConstEnums": true, 10 | "sourceMap": true, 11 | "noEmit": false, 12 | "emitDeclarationOnly": false, 13 | "inlineSources": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "sourceRoot": "src", 5 | "module": "commonjs", 6 | "target": "esnext", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "moduleResolution": "node", 12 | "isolatedModules": false, 13 | "suppressImplicitAnyIndexErrors": false, 14 | "noImplicitAny": true, 15 | "strictNullChecks": true, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": false, 18 | "allowSyntheticDefaultImports": true, 19 | "importHelpers": true, 20 | "newLine": "LF", 21 | "noEmit": true, 22 | "esModuleInterop": true, 23 | "resolveJsonModule": true, 24 | "lib": ["es7", "dom", "ESNext.AsyncIterable"], 25 | "typeRoots": ["./node_modules/@types"] 26 | }, 27 | "include": ["src"], 28 | "linterOptions": { 29 | "exclude": [] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-trailing-whitespace": true, 16 | "no-var-keyword": true, 17 | "one-line": [ 18 | true, 19 | "check-open-brace", 20 | "check-whitespace" 21 | ], 22 | "quotemark": [ 23 | true, 24 | "double" 25 | ], 26 | "semicolon": [ 27 | true, 28 | "always", 29 | "ignore-bound-class-methods" 30 | ], 31 | "triple-equals": [ 32 | true, 33 | "allow-null-check" 34 | ], 35 | "typedef-whitespace": [ 36 | true, 37 | { 38 | "call-signature": "nospace", 39 | "index-signature": "nospace", 40 | "parameter": "nospace", 41 | "property-declaration": "nospace", 42 | "variable-declaration": "nospace" 43 | } 44 | ], 45 | "variable-name": [ 46 | true, 47 | "ban-keywords" 48 | ], 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | client 9 | 10 | 81 | 82 | 83 |
84 |
85 | 88 | 89 | 96 |
97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /views/swagger.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | client 9 | 10 | 81 | 82 | 83 |
84 |
85 | 88 | 89 | 96 |
97 |
98 | 99 | 100 | 101 | --------------------------------------------------------------------------------