├── .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 |
86 |

87 |
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 |
86 |

87 |
88 |
89 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------