├── .eslintignore
├── .prettierrc
├── globals.js
├── docs
├── assets
│ ├── swagger.png
│ └── appy-api-screenshot.png
├── testing.md
├── questions.md
├── model-generation.md
├── swagger-documentation.md
├── soft-delete.md
├── quick-start.md
├── misc.md
├── support.md
├── authentication.md
├── introduction.md
├── metadata.md
├── route-customization.md
├── middleware.md
├── audit-logs.md
└── querying.md
├── website
├── static
│ └── img
│ │ ├── joi.png
│ │ ├── appy.png
│ │ ├── favicon.png
│ │ ├── oss_logo.png
│ │ ├── querying.png
│ │ ├── flexible_icon.png
│ │ ├── powerful_icon.png
│ │ ├── efficient_icon.png
│ │ ├── favicon
│ │ └── favicon.ico
│ │ ├── rest-hapi-logo.png
│ │ ├── rest-hapi-logo-alt.png
│ │ └── appy-api-screenshot.png
├── publish.sh
├── versions.json
├── versioned_docs
│ ├── version-1.6.x
│ │ ├── testing.md
│ │ ├── questions.md
│ │ ├── model-generation.md
│ │ ├── swagger-documentation.md
│ │ ├── soft-delete.md
│ │ ├── quick-start.md
│ │ ├── misc.md
│ │ ├── support.md
│ │ ├── authentication.md
│ │ ├── introduction.md
│ │ ├── metadata.md
│ │ ├── route-customization.md
│ │ ├── middleware.md
│ │ ├── audit-logs.md
│ │ ├── querying.md
│ │ └── validation.md
│ ├── version-2.0.x
│ │ ├── quick-start.md
│ │ ├── introduction.md
│ │ └── querying.md
│ ├── version-3.0.x
│ │ └── quick-start.md
│ ├── version-2.2.x
│ │ └── route-customization.md
│ └── version-3.2.x
│ │ └── middleware.md
├── data
│ └── users.js
├── package.json
├── pages
│ └── en
│ │ ├── demo.js
│ │ ├── users.js
│ │ ├── help.js
│ │ └── versions.js
├── sidebars.json
├── versioned_sidebars
│ ├── version-3.0.x-sidebars.json
│ └── version-1.6.x-sidebars.json
├── siteConfig.js
├── core
│ └── Footer.js
└── blog
│ └── 2016-11-19-The-Problem-With-APIs.md
├── .npmignore
├── .gitignore
├── seed
├── linking-models
│ ├── role_permission.model.js
│ ├── user_permission.model.js
│ └── group_permission.model.js
├── group.model.js
├── role.model.js
├── permission.model.js
└── user.model.js
├── tests
└── e2e
│ ├── test-scenarios
│ ├── scenario-5
│ │ └── models
│ │ │ ├── linking-models
│ │ │ └── segment_tag.model.js
│ │ │ ├── video.model.js
│ │ │ ├── tag.model.js
│ │ │ └── segment.model.js
│ ├── scenario-3
│ │ └── models
│ │ │ ├── linking-models
│ │ │ └── user_permission.model.js
│ │ │ ├── hashtag.model.js
│ │ │ ├── business.model.js
│ │ │ ├── user-profile.model.js
│ │ │ ├── permission.model.js
│ │ │ ├── role.model.js
│ │ │ └── user.model.js
│ ├── scenario-4
│ │ └── models
│ │ │ ├── facility.model.js
│ │ │ └── building.model.js
│ ├── scenario-2
│ │ └── models
│ │ │ └── role.model.js
│ └── scenario-1
│ │ └── models
│ │ └── role.model.js
│ ├── end-to-end.tests.js
│ └── advance-assoc.tests.js
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── test-on-pull.yml
│ └── manual.yml
├── utilities
├── policy-generator.js
├── validation-helper.js
├── api-generator.js
├── log-util.js
├── model-generator.js
├── auth-helper.js
└── test-helper.js
├── .eslintrc.js
├── LICENSE.txt
├── rest-hapi-cli.js
├── policies
├── add-document-scope.js
├── populate-duplicate-fields.js
├── track-duplicated-fields.js
├── add-by-meta-data.js
└── authorize-document-creator.js
├── CONTRIBUTING.md
├── scripts
└── seed.js
├── models
└── audit-log.model.js
├── CODE_OF_CONDUCT.md
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | website
3 | docs
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/globals.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | mongoose: {}
5 | }
6 |
--------------------------------------------------------------------------------
/docs/assets/swagger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/docs/assets/swagger.png
--------------------------------------------------------------------------------
/website/static/img/joi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/joi.png
--------------------------------------------------------------------------------
/website/static/img/appy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/appy.png
--------------------------------------------------------------------------------
/website/static/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/favicon.png
--------------------------------------------------------------------------------
/website/static/img/oss_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/oss_logo.png
--------------------------------------------------------------------------------
/website/static/img/querying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/querying.png
--------------------------------------------------------------------------------
/website/publish.sh:
--------------------------------------------------------------------------------
1 | GIT_USER=JKHeadley \
2 | CURRENT_BRANCH=master \
3 | USE_SSH=true \
4 | yarn run publish-gh-pages
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .travis.yml
2 | _config.yml
3 | .idea
4 | tests
5 | .nyc_output
6 | coverage
7 | coverage.lcov
8 | docs
9 | website
--------------------------------------------------------------------------------
/docs/assets/appy-api-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/docs/assets/appy-api-screenshot.png
--------------------------------------------------------------------------------
/website/static/img/flexible_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/flexible_icon.png
--------------------------------------------------------------------------------
/website/static/img/powerful_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/powerful_icon.png
--------------------------------------------------------------------------------
/website/static/img/efficient_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/efficient_icon.png
--------------------------------------------------------------------------------
/website/static/img/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/favicon/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/rest-hapi-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/rest-hapi-logo.png
--------------------------------------------------------------------------------
/website/static/img/rest-hapi-logo-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/rest-hapi-logo-alt.png
--------------------------------------------------------------------------------
/website/static/img/appy-api-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JKHeadley/rest-hapi/HEAD/website/static/img/appy-api-screenshot.png
--------------------------------------------------------------------------------
/website/versions.json:
--------------------------------------------------------------------------------
1 | [
2 | "3.2.x",
3 | "3.1.x",
4 | "3.0.x",
5 | "2.3.x",
6 | "2.2.x",
7 | "2.0.x",
8 | "1.9.x",
9 | "1.8.x",
10 | "1.7.x",
11 | "1.6.x"
12 | ]
13 |
--------------------------------------------------------------------------------
/docs/testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: testing
3 | title: Testing
4 | sidebar_label: Testing
5 | ---
6 |
7 | If you have downloaded the source you can run the tests with:
8 | ```
9 | $ npm test
10 | ```
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #api/config.js
2 | api/node_modules
3 | *npm-debug.log
4 | .idea/
5 | build
6 | node_modules
7 | .nyc_output
8 | coverage
9 | coverage.lcov
10 |
11 | # local uploads
12 | uploads/*
13 |
14 | # OSX files
15 | *.DS_Store
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-testing
3 | title: Testing
4 | sidebar_label: Testing
5 | original_id: testing
6 | ---
7 |
8 | If you have downloaded the source you can run the tests with:
9 | ```
10 | $ npm test
11 | ```
12 |
--------------------------------------------------------------------------------
/website/data/users.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // Please add your logo in alphabetical order of caption.
3 | {
4 | caption: 'appy',
5 | image: '/img/appy.png',
6 | infoLink: 'https://www.appyapp.io',
7 | pinned: true
8 | }
9 | // Please add your logo in alphabetical order of caption.
10 | ]
11 |
--------------------------------------------------------------------------------
/docs/questions.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: questions
3 | title: Questions
4 | sidebar_label: Questions
5 | ---
6 |
7 | If you have any questions/issues/feature requests, please feel free to open an [issue](https://github.com/JKHeadley/rest-hapi/issues/new). We'd love to hear from you!
8 |
9 | For more options please see the [help page](https://resthapi.com/help).
--------------------------------------------------------------------------------
/seed/linking-models/role_permission.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | module.exports = function() {
4 | const Types = mongoose.Schema.Types
5 |
6 | const Model = {
7 | Schema: {
8 | enabled: {
9 | type: Types.Boolean,
10 | default: true
11 | }
12 | },
13 | modelName: 'role_permission'
14 | }
15 |
16 | return Model
17 | }
18 |
--------------------------------------------------------------------------------
/seed/linking-models/user_permission.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | module.exports = function() {
4 | const Types = mongoose.Schema.Types
5 |
6 | const Model = {
7 | Schema: {
8 | enabled: {
9 | type: Types.Boolean,
10 | default: true
11 | }
12 | },
13 | modelName: 'user_permission'
14 | }
15 |
16 | return Model
17 | }
18 |
--------------------------------------------------------------------------------
/seed/linking-models/group_permission.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | module.exports = function() {
4 | const Types = mongoose.Schema.Types
5 |
6 | const Model = {
7 | Schema: {
8 | enabled: {
9 | type: Types.Boolean,
10 | default: true
11 | }
12 | },
13 | modelName: 'group_permission'
14 | }
15 |
16 | return Model
17 | }
18 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-5/models/linking-models/segment_tag.model.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose')
2 |
3 | module.exports = function() {
4 | var Types = mongoose.Schema.Types
5 |
6 | var Model = {
7 | Schema: {
8 | rank: {
9 | type: Types.Number,
10 | required: true
11 | }
12 | },
13 | modelName: 'segment_tag'
14 | }
15 |
16 | return Model
17 | }
18 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/questions.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-questions
3 | title: Questions
4 | sidebar_label: Questions
5 | original_id: questions
6 | ---
7 |
8 | If you have any questions/issues/feature requests, please feel free to open an [issue](https://github.com/JKHeadley/rest-hapi/issues/new). We'd love to hear from you!
9 |
10 | For more options please see the [help page](https://resthapi.com/help).
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/linking-models/user_permission.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const mongoose = require('mongoose')
4 |
5 | module.exports = function() {
6 | const Types = mongoose.Schema.Types
7 |
8 | const Model = {
9 | Schema: {
10 | enabled: {
11 | type: Types.Boolean,
12 | default: true
13 | }
14 | },
15 | modelName: 'user_permission'
16 | }
17 |
18 | return Model
19 | }
20 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "examples": "docusaurus-examples",
4 | "start": "docusaurus-start",
5 | "build": "docusaurus-build",
6 | "publish-gh-pages": "docusaurus-publish",
7 | "write-translations": "docusaurus-write-translations",
8 | "version": "docusaurus-version",
9 | "rename-version": "docusaurus-rename-version"
10 | },
11 | "devDependencies": {
12 | "docusaurus": "^1.14.7"
13 | },
14 | "version": "3.0.0"
15 | }
16 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-4/models/facility.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'facility'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String
10 | }
11 | },
12 | { collection: modelName }
13 | )
14 |
15 | Schema.statics = {
16 | collectionName: modelName,
17 | routeOptions: {
18 | associations: {}
19 | }
20 | }
21 |
22 | return Schema
23 | }
24 |
--------------------------------------------------------------------------------
/docs/model-generation.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: model-generation
3 | title: Model Generation
4 | sidebar_label: Model Generation
5 | ---
6 |
7 | In some situations models may be required before or without endpoint generation. For example some hapi plugins may require models to exist before the routes are registered. In these cases rest-hapi provides a ``generateModels`` function that can be called independently.
8 |
9 | > **NOTE:** See [scripts/seed.js](https://github.com/JKHeadley/rest-hapi/blob/master/scripts/seed.js) for an example usage of ``generateModels``.
10 |
11 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/hashtag.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'hashtag'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | text: {
9 | type: Types.String,
10 | required: true
11 | }
12 | },
13 | { collection: modelName }
14 | )
15 |
16 | Schema.statics = {
17 | collectionName: modelName,
18 | routeOptions: {
19 | associations: {}
20 | }
21 | }
22 |
23 | return Schema
24 | }
25 |
--------------------------------------------------------------------------------
/website/pages/en/demo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | const React = require('react')
9 |
10 | class Demo extends React.Component {
11 | render() {
12 | return (
13 |
20 | )
21 | }
22 | }
23 |
24 | module.exports = Demo
25 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/model-generation.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-model-generation
3 | title: Model Generation
4 | sidebar_label: Model Generation
5 | original_id: model-generation
6 | ---
7 |
8 | In some situations models may be required before or without endpoint generation. For example some hapi plugins may require models to exist before the routes are registered. In these cases rest-hapi provides a ``generateModels`` function that can be called independently.
9 |
10 | > **NOTE:** See [scripts/seed.js](https://github.com/JKHeadley/rest-hapi/blob/master/scripts/seed.js) for an example usage of ``generateModels``.
11 |
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-4/models/building.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'building'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String
10 | },
11 | facilitiesPerFloor: [
12 | {
13 | type: Types.ObjectId,
14 | ref: 'facility'
15 | }
16 | ]
17 | },
18 | { collection: modelName }
19 | )
20 |
21 | Schema.statics = {
22 | collectionName: modelName,
23 | routeOptions: {
24 | associations: {}
25 | }
26 | }
27 |
28 | return Schema
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/test-on-pull.yml:
--------------------------------------------------------------------------------
1 | name: test-on-pull
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - '**.md'
9 | push:
10 | branches:
11 | - master
12 | paths-ignore:
13 | - '**.md'
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | strategy:
21 | matrix:
22 | node-version: [14.x, 15.x, 16.x]
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Use Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v3
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 | - run: npm ci
31 | - run: npm test
--------------------------------------------------------------------------------
/utilities/policy-generator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const internals = {}
6 |
7 | internals.policyObjects = require('require-all')(
8 | path.join(__dirname, '/../policies')
9 | )
10 |
11 | internals.policies = {}
12 |
13 | for (const policyName in internals.policyObjects) {
14 | if (internals.policyObjects[policyName].applyPoint) {
15 | internals.policies[policyName] = internals.policyObjects[policyName]
16 | } else {
17 | const policyObject = internals.policyObjects[policyName]
18 | for (const policyName in policyObject) {
19 | internals.policies[policyName] = policyObject[policyName]
20 | }
21 | }
22 | }
23 |
24 | module.exports = internals.policies
25 |
--------------------------------------------------------------------------------
/website/sidebars.json:
--------------------------------------------------------------------------------
1 | {
2 | "docs": {
3 | "Getting Started": [
4 | "quick-start",
5 | "introduction",
6 | "swagger-documentation"
7 | ],
8 | "Usage": [
9 | "configuration",
10 | "creating-endpoints",
11 | "associations",
12 | "route-customization",
13 | "querying",
14 | "duplicate-fields",
15 | "validation",
16 | "middleware",
17 | "authentication",
18 | "authorization",
19 | "audit-logs",
20 | "policies",
21 | "mongoose-wrapper-methods",
22 | "soft-delete",
23 | "metadata",
24 | "model-generation"
25 | ],
26 | "Other": [
27 | "testing",
28 | "questions",
29 | "support"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-5/models/video.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | var modelName = 'video'
5 | var Types = mongoose.Schema.Types
6 | var Schema = new mongoose.Schema(
7 | {
8 | title: {
9 | type: Types.String,
10 | description: 'Video title from YouTube'
11 | }
12 | },
13 | { collection: modelName }
14 | )
15 |
16 | Schema.statics = {
17 | collectionName: modelName,
18 | routeOptions: {
19 | associations: {
20 | segments: {
21 | type: 'ONE_MANY',
22 | alias: 'segment',
23 | foreignField: 'video',
24 | model: 'segment'
25 | }
26 | }
27 | }
28 | }
29 |
30 | return Schema
31 | }
32 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module',
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
13 | extends: ['prettier-standard'],
14 | plugins: ['prettier'],
15 | // add your custom rules here
16 | rules: {
17 | // allow paren-less arrow functions
18 | 'arrow-parens': 0,
19 | // allow async-await
20 | 'generator-star-spacing': 0,
21 | // allow debugger during development
22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
23 | 'prettier/prettier': 'error',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/docs/swagger-documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: swagger-documentation
3 | title: Swagger Documentation
4 | sidebar_label: Swagger Documentation
5 | ---
6 |
7 | Swagger documentation (via [hapi-swagger](https://github.com/glennjones/hapi-swagger)) is automatically generated for all endpoints and can be viewed by pointing a browser at the server URL. If you use the [intro script](introduction.md#using-the-plugin) this will be [http://localhost:8080/](http://localhost:8080/). The swagger docs provide quick access to testing your endpoints along with model schema descriptions and query options.
8 |
9 | Below is an example from [demo.resthapi.com](https://demo.resthapi.com):
10 |
11 | 
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-5/models/tag.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | var modelName = 'tag'
5 | var Types = mongoose.Schema.Types
6 | var Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String,
10 | required: true,
11 | unique: true
12 | }
13 | },
14 | { collection: modelName }
15 | )
16 |
17 | Schema.statics = {
18 | collectionName: modelName,
19 | routeOptions: {
20 | associations: {
21 | segments: {
22 | type: 'MANY_MANY',
23 | alias: 'segments',
24 | model: 'segment',
25 | embedAssociation: true,
26 | linkingModel: 'segment_tag'
27 | }
28 | }
29 | }
30 | }
31 |
32 | return Schema
33 | }
34 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/business.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'business'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String,
10 | required: true
11 | },
12 | description: {
13 | type: Types.String
14 | }
15 | },
16 | { collection: modelName }
17 | )
18 |
19 | Schema.statics = {
20 | collectionName: modelName,
21 | routeOptions: {
22 | associations: {
23 | roles: {
24 | type: 'ONE_MANY',
25 | alias: 'role',
26 | foreignField: 'company',
27 | model: 'role'
28 | }
29 | }
30 | }
31 | }
32 |
33 | return Schema
34 | }
35 |
--------------------------------------------------------------------------------
/seed/group.model.js:
--------------------------------------------------------------------------------
1 | module.exports = function(mongoose) {
2 | const modelName = 'group'
3 | const Types = mongoose.Schema.Types
4 | const Schema = new mongoose.Schema({
5 | name: {
6 | type: Types.String,
7 | required: true
8 | },
9 | description: {
10 | type: Types.String
11 | }
12 | })
13 |
14 | Schema.statics = {
15 | collectionName: modelName,
16 | routeOptions: {
17 | associations: {
18 | users: {
19 | type: 'MANY_MANY',
20 | alias: 'user',
21 | model: 'user'
22 | },
23 | permissions: {
24 | type: 'MANY_MANY',
25 | alias: 'permission',
26 | model: 'permission',
27 | linkingModel: 'group_permission'
28 | }
29 | }
30 | }
31 | }
32 |
33 | return Schema
34 | }
35 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/user-profile.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'userProfile'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | status: {
9 | type: Types.String,
10 | required: true
11 | },
12 | user: {
13 | type: Types.ObjectId,
14 | ref: 'user'
15 | }
16 | },
17 | { collection: modelName }
18 | )
19 |
20 | Schema.statics = {
21 | collectionName: modelName,
22 | routeOptions: {
23 | alias: 'user-profile',
24 | associations: {
25 | user: {
26 | type: 'ONE_ONE',
27 | model: 'user',
28 | duplicate: ['email']
29 | }
30 | }
31 | }
32 | }
33 |
34 | return Schema
35 | }
36 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-5/models/segment.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | var modelName = 'segment'
5 | var Types = mongoose.Schema.Types
6 | var Schema = new mongoose.Schema(
7 | {
8 | title: { type: Types.String, required: true },
9 | video: { type: Types.ObjectId, ref: 'video' }
10 | },
11 | { collection: modelName }
12 | )
13 |
14 | Schema.statics = {
15 | collectionName: modelName,
16 | routeOptions: {
17 | associations: {
18 | video: {
19 | type: 'MANY_ONE',
20 | model: 'video'
21 | },
22 | tags: {
23 | type: 'MANY_MANY',
24 | alias: 'tag',
25 | model: 'tag',
26 | linkingModel: 'segment_tag'
27 | }
28 | }
29 | }
30 | }
31 |
32 | return Schema
33 | }
34 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/swagger-documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-swagger-documentation
3 | title: Swagger Documentation
4 | sidebar_label: Swagger Documentation
5 | original_id: swagger-documentation
6 | ---
7 |
8 | Swagger documentation (via [hapi-swagger](https://github.com/glennjones/hapi-swagger)) is automatically generated for all endpoints and can be viewed by pointing a browser at the server URL. If you use the [intro script](introduction.md#using-the-plugin) this will be [http://localhost:8080/](http://localhost:8080/). The swagger docs provide quick access to testing your endpoints along with model schema descriptions and query options.
9 |
10 | Below is an example from [demo.resthapi.com](https://demo.resthapi.com):
11 |
12 | 
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-2/models/role.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'role'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String,
10 | required: true
11 | },
12 | description: {
13 | type: Types.String
14 | }
15 | },
16 | { collection: modelName }
17 | )
18 |
19 | Schema.statics = {
20 | collectionName: modelName,
21 | routeOptions: {
22 | documentScope: {
23 | rootScope: ['root'],
24 | createScope: ['create'],
25 | updateScope: ['update'],
26 | deleteScope: ['delete'],
27 | associateScope: ['associate']
28 | },
29 | authorizeDocumentCreator: true
30 | }
31 | }
32 |
33 | return Schema
34 | }
35 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/permission.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'permission'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String,
10 | required: true
11 | },
12 | description: {
13 | type: Types.String
14 | }
15 | },
16 | { collection: modelName }
17 | )
18 | Schema.statics = {
19 | collectionName: modelName,
20 | routeOptions: {
21 | associations: {
22 | users: {
23 | type: 'MANY_MANY',
24 | alias: 'user',
25 | model: 'user',
26 | linkingModel: 'user_permission'
27 | },
28 | roles: {
29 | type: 'MANY_MANY',
30 | alias: 'role',
31 | model: 'role'
32 | }
33 | }
34 | }
35 | }
36 |
37 | return Schema
38 | }
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/seed/role.model.js:
--------------------------------------------------------------------------------
1 | module.exports = function(mongoose) {
2 | const modelName = 'role'
3 | const Types = mongoose.Schema.Types
4 | const Schema = new mongoose.Schema(
5 | {
6 | name: {
7 | type: Types.String,
8 | enum: ['Account', 'Admin', 'SuperAdmin'],
9 | required: true
10 | },
11 | description: {
12 | type: Types.String
13 | }
14 | },
15 | { collection: modelName }
16 | )
17 |
18 | Schema.statics = {
19 | collectionName: modelName,
20 | routeOptions: {
21 | associations: {
22 | users: {
23 | type: 'ONE_MANY',
24 | alias: 'user',
25 | foreignField: 'role',
26 | model: 'user'
27 | },
28 | permissions: {
29 | type: 'MANY_MANY',
30 | alias: 'permission',
31 | model: 'permission',
32 | linkingModel: 'role_permission'
33 | }
34 | }
35 | }
36 | }
37 |
38 | return Schema
39 | }
40 |
--------------------------------------------------------------------------------
/seed/permission.model.js:
--------------------------------------------------------------------------------
1 | module.exports = function(mongoose) {
2 | const modelName = 'permission'
3 | const Types = mongoose.Schema.Types
4 | const Schema = new mongoose.Schema({
5 | name: {
6 | type: Types.String,
7 | required: true
8 | },
9 | description: {
10 | type: Types.String
11 | }
12 | })
13 | Schema.statics = {
14 | collectionName: modelName,
15 | routeOptions: {
16 | associations: {
17 | users: {
18 | type: 'MANY_MANY',
19 | alias: 'user',
20 | model: 'user',
21 | linkingModel: 'user_permission'
22 | },
23 | roles: {
24 | type: 'MANY_MANY',
25 | alias: 'role',
26 | model: 'role',
27 | linkingModel: 'role_permission'
28 | },
29 | groups: {
30 | type: 'MANY_MANY',
31 | alias: 'group',
32 | model: 'group',
33 | linkingModel: 'group_permission'
34 | }
35 | }
36 | }
37 | }
38 |
39 | return Schema
40 | }
41 |
--------------------------------------------------------------------------------
/docs/soft-delete.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: soft-delete
3 | title: Soft Delete
4 | sidebar_label: Soft Delete
5 | ---
6 |
7 | rest-hapi supports soft delete functionality for documents. When the [`config.enableSoftDelete`](configuration.md#enablesoftdelete) property is set to ``true``, documents will gain an ``isDeleted`` property when they are created that will be set to ``false``. Whenever that document is deleted (via a rest-hapi endpoint or method), the document will remain in the collection, its ``isDeleted`` property will be set to ``true``, and the ``deletedAt`` and ``deletedBy`` properties (if enabled) will be populated.
8 |
9 | "Hard" deletion is still possible when soft delete is enabled. In order to hard delete a document (i.e. remove a document from it's collection) via the api, a payload must be sent with the ``hardDelete`` property set to ``true``.
10 |
11 | The rest-hapi delete methods include a ``hardDelete`` flag as a parameter. The following is an example of a hard delete using a [rest-hapi method](mongoose-wrapper-methods.md):
12 |
13 | ``RestHapi.deleteOne(model, _id, true, Log);``
--------------------------------------------------------------------------------
/website/versioned_sidebars/version-3.0.x-sidebars.json:
--------------------------------------------------------------------------------
1 | {
2 | "version-3.0.x-docs": {
3 | "Getting Started": [
4 | "version-3.0.x-quick-start",
5 | "version-3.0.x-introduction",
6 | "version-3.0.x-swagger-documentation"
7 | ],
8 | "Usage": [
9 | "version-3.0.x-configuration",
10 | "version-3.0.x-creating-endpoints",
11 | "version-3.0.x-associations",
12 | "version-3.0.x-route-customization",
13 | "version-3.0.x-querying",
14 | "version-3.0.x-duplicate-fields",
15 | "version-3.0.x-validation",
16 | "version-3.0.x-middleware",
17 | "version-3.0.x-authentication",
18 | "version-3.0.x-authorization",
19 | "version-3.0.x-audit-logs",
20 | "version-3.0.x-policies",
21 | "version-3.0.x-mongoose-wrapper-methods",
22 | "version-3.0.x-soft-delete",
23 | "version-3.0.x-metadata",
24 | "version-3.0.x-model-generation"
25 | ],
26 | "Other": [
27 | "version-3.0.x-testing",
28 | "version-3.0.x-questions",
29 | "version-3.0.x-support"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Justin Headley
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/soft-delete.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-soft-delete
3 | title: Soft Delete
4 | sidebar_label: Soft Delete
5 | original_id: soft-delete
6 | ---
7 |
8 | rest-hapi supports soft delete functionality for documents. When the [`config.enableSoftDelete`](configuration.md#enablesoftdelete) property is set to ``true``, documents will gain an ``isDeleted`` property when they are created that will be set to ``false``. Whenever that document is deleted (via a rest-hapi endpoint or method), the document will remain in the collection, its ``isDeleted`` property will be set to ``true``, and the ``deletedAt`` and ``deletedBy`` properties (if enabled) will be populated.
9 |
10 | "Hard" deletion is still possible when soft delete is enabled. In order to hard delete a document (i.e. remove a document from it's collection) via the api, a payload must be sent with the ``hardDelete`` property set to ``true``.
11 |
12 | The rest-hapi delete methods include a ``hardDelete`` flag as a parameter. The following is an example of a hard delete using a [rest-hapi method](mongoose-wrapper-methods.md):
13 |
14 | ``RestHapi.deleteOne(model, _id, true, Log);``
--------------------------------------------------------------------------------
/website/versioned_sidebars/version-1.6.x-sidebars.json:
--------------------------------------------------------------------------------
1 | {
2 | "version-1.6.x-docs": {
3 | "Getting Started": [
4 | "version-1.6.x-quick-start",
5 | "version-1.6.x-introduction",
6 | "version-1.6.x-swagger-documentation"
7 | ],
8 | "Usage": [
9 | "version-1.6.x-configuration",
10 | "version-1.6.x-creating-endpoints",
11 | "version-1.6.x-associations",
12 | "version-1.6.x-route-customization",
13 | "version-1.6.x-querying",
14 | "version-1.6.x-duplicate-fields",
15 | "version-1.6.x-validation",
16 | "version-1.6.x-middleware",
17 | "version-1.6.x-authentication",
18 | "version-1.6.x-authorization",
19 | "version-1.6.x-audit-logs",
20 | "version-1.6.x-policies",
21 | "version-1.6.x-mongoose-wrapper-methods",
22 | "version-1.6.x-soft-delete",
23 | "version-1.6.x-metadata",
24 | "version-1.6.x-model-generation"
25 | ],
26 | "Other": [
27 | "version-1.6.x-testing",
28 | "version-1.6.x-questions",
29 | "version-1.6.x-support"
30 | ]
31 | },
32 | "version-1.6.x-docs-other": {
33 | "First Category": [
34 | "version-1.6.x-doc4",
35 | "version-1.6.x-doc5"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/utilities/validation-helper.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 |
3 | // TODO: verify routeOptions exist
4 |
5 | module.exports = {
6 | /**
7 | * Assert that a given model follows the mongoose model format.
8 | * @param model
9 | * @param logger
10 | * @returns {boolean}
11 | */
12 | validateModel: function(model, logger) {
13 | assert(
14 | model.schema,
15 | "model not mongoose format. 'schema' property required."
16 | )
17 | assert(
18 | model.schema.paths,
19 | "model not mongoose format. 'schema.paths' property required."
20 | )
21 | assert(
22 | model.schema.tree,
23 | "model not mongoose format. 'schema.tree' property required."
24 | )
25 |
26 | const fields = model.schema.paths
27 | const fieldNames = Object.keys(fields)
28 |
29 | assert(
30 | model.routeOptions,
31 | "model not mongoose format. 'routeOptions' property required."
32 | )
33 |
34 | for (let i = 0; i < fieldNames.length; i++) {
35 | const fieldName = fieldNames[i]
36 | assert(
37 | fields[fieldName].options,
38 | "field not mongoose format. 'options' parameter required."
39 | )
40 | }
41 |
42 | return true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: quick-start
3 | title: Quick Start
4 | sidebar_label: Quick Start
5 | ---
6 |
7 | ## Requirements
8 |
9 | You need [Node.js](https://nodejs.org/en/) >= 14 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
10 |
11 | ## Demo
12 |
13 | 
14 |
15 | The quickest way to get rest-hapi running on your machine is with the [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo) project:
16 |
17 | (**NOTE:** For an alternative quick start, check out his [awesome yeoman generator](https://github.com/vinaybedre/generator-resthapi) for rest-hapi.)
18 |
19 | 1) Clone the repo
20 | ```sh
21 | $ git clone https://github.com/JKHeadley/rest-hapi-demo.git
22 | $ cd rest-hapi-demo
23 | ```
24 |
25 | 2) Install the dependencies
26 | ```sh
27 | $ npm install
28 | ```
29 |
30 | 3) Seed the models
31 | ```sh
32 | $ ./node_modules/.bin/rest-hapi-cli seed
33 | ```
34 |
35 | 4) Start the server
36 | ```sh
37 | $ npm start
38 | ```
39 |
40 | 5) View the [API docs](swagger-documentation.md) at
41 |
42 | [http://localhost:8080/](http://localhost:8080/)
43 |
44 | ...have fun!
45 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/role.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function(mongoose) {
4 | const modelName = 'role'
5 | const Types = mongoose.Schema.Types
6 | const Schema = new mongoose.Schema(
7 | {
8 | name: {
9 | type: Types.String,
10 | enum: ['User', 'Admin', 'SuperAdmin'],
11 | required: true
12 | },
13 | description: {
14 | type: Types.String
15 | },
16 | company: {
17 | type: Types.ObjectId,
18 | ref: 'business'
19 | },
20 | companyName: {
21 | type: Types.String
22 | }
23 | },
24 | { collection: modelName }
25 | )
26 |
27 | Schema.statics = {
28 | collectionName: modelName,
29 | routeOptions: {
30 | associations: {
31 | company: {
32 | type: 'MANY_ONE',
33 | model: 'business',
34 | duplicate: 'name'
35 | },
36 | users: {
37 | type: 'ONE_MANY',
38 | alias: 'people',
39 | foreignField: 'title',
40 | model: 'user'
41 | },
42 | permissions: {
43 | type: 'MANY_MANY',
44 | model: 'permission'
45 | }
46 | }
47 | }
48 | }
49 |
50 | return Schema
51 | }
52 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-quick-start
3 | title: Quick Start
4 | sidebar_label: Quick Start
5 | original_id: quick-start
6 | ---
7 |
8 | ## Requirements
9 |
10 | You need [Node.js](https://nodejs.org/en/) ^8.10.0 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
11 |
12 | ## Demo
13 |
14 | 
15 |
16 | The quickest way to get rest-hapi running on your machine is with the [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo) project:
17 |
18 | (**NOTE:** For an alternative quick start, check out his [awesome yeoman generator](https://github.com/vinaybedre/generator-resthapi) for rest-hapi.)
19 |
20 | 1) Clone the repo
21 | ```sh
22 | $ git clone https://github.com/JKHeadley/rest-hapi-demo.git
23 | $ cd rest-hapi-demo
24 | ```
25 |
26 | 2) Install the dependencies
27 | ```sh
28 | $ npm install
29 | ```
30 |
31 | 3) Seed the models
32 | ```sh
33 | $ ./node_modules/.bin/rest-hapi-cli seed
34 | ```
35 |
36 | 4) Start the server
37 | ```sh
38 | $ npm start
39 | ```
40 |
41 | 5) View the [API docs](swagger-documentation.md) at
42 |
43 | [http://localhost:8080/](http://localhost:8080/)
44 |
45 | ...have fun!
46 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-2.0.x/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-2.0.x-quick-start
3 | title: Quick Start
4 | sidebar_label: Quick Start
5 | original_id: quick-start
6 | ---
7 |
8 | ## Requirements
9 |
10 | You need [Node.js](https://nodejs.org/en/) ^12.14.1 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
11 |
12 | ## Demo
13 |
14 | 
15 |
16 | The quickest way to get rest-hapi running on your machine is with the [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo) project:
17 |
18 | (**NOTE:** For an alternative quick start, check out his [awesome yeoman generator](https://github.com/vinaybedre/generator-resthapi) for rest-hapi.)
19 |
20 | 1) Clone the repo
21 | ```sh
22 | $ git clone https://github.com/JKHeadley/rest-hapi-demo.git
23 | $ cd rest-hapi-demo
24 | ```
25 |
26 | 2) Install the dependencies
27 | ```sh
28 | $ npm install
29 | ```
30 |
31 | 3) Seed the models
32 | ```sh
33 | $ ./node_modules/.bin/rest-hapi-cli seed
34 | ```
35 |
36 | 4) Start the server
37 | ```sh
38 | $ npm start
39 | ```
40 |
41 | 5) View the [API docs](swagger-documentation.md) at
42 |
43 | [http://localhost:8080/](http://localhost:8080/)
44 |
45 | ...have fun!
46 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-3.0.x/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-3.0.x-quick-start
3 | title: Quick Start
4 | sidebar_label: Quick Start
5 | original_id: quick-start
6 | ---
7 |
8 | ## Requirements
9 |
10 | You need [Node.js](https://nodejs.org/en/) >= 14 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
11 |
12 | ## Demo
13 |
14 | 
15 |
16 | The quickest way to get rest-hapi running on your machine is with the [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo) project:
17 |
18 | (**NOTE:** For an alternative quick start, check out his [awesome yeoman generator](https://github.com/vinaybedre/generator-resthapi) for rest-hapi.)
19 |
20 | 1) Clone the repo
21 | ```sh
22 | $ git clone https://github.com/JKHeadley/rest-hapi-demo.git
23 | $ cd rest-hapi-demo
24 | ```
25 |
26 | 2) Install the dependencies
27 | ```sh
28 | $ npm install
29 | ```
30 |
31 | 3) Seed the models
32 | ```sh
33 | $ ./node_modules/.bin/rest-hapi-cli seed
34 | ```
35 |
36 | 4) Start the server
37 | ```sh
38 | $ npm start
39 | ```
40 |
41 | 5) View the [API docs](swagger-documentation.md) at
42 |
43 | [http://localhost:8080/](http://localhost:8080/)
44 |
45 | ...have fun!
46 |
--------------------------------------------------------------------------------
/rest-hapi-cli.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | const userArgs = process.argv.slice(2)
4 |
5 | const command = userArgs[0]
6 |
7 | const args = userArgs
8 |
9 | args.shift()
10 |
11 | const exec = require('child_process').exec
12 |
13 | const isWindows = /^win/.test(process.platform)
14 |
15 | let cmdString = '$PWD/node_modules/rest-hapi/scripts/'
16 |
17 | if (isWindows) {
18 | cmdString = './node_modules/rest-hapi/scripts/'
19 | }
20 |
21 | switch (command) {
22 | case 'seed':
23 | exec('node ' + cmdString + 'seed.js ' + args, function(
24 | err,
25 | stdout,
26 | stderr
27 | ) {
28 | console.log(stdout)
29 | console.log(stderr)
30 | if (err) {
31 | throw err
32 | }
33 | })
34 | break
35 | case 'test':
36 | exec('npm run test', function(err, stdout, stderr) {
37 | console.log(stdout)
38 | console.log(stderr)
39 | if (err) {
40 | throw err
41 | }
42 | })
43 | break
44 | case 'update-associations':
45 | exec(
46 | 'node ' +
47 | cmdString +
48 | 'update-associations.js' +
49 | ' --options ' +
50 | args.join(' --options '),
51 | function(err, stdout, stderr) {
52 | console.log(stdout)
53 | console.log(stderr)
54 | if (err) {
55 | throw err
56 | }
57 | }
58 | )
59 | break
60 | default:
61 | console.error('error, unknown command:', command)
62 | break
63 | }
64 |
--------------------------------------------------------------------------------
/utilities/api-generator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 |
6 | /**
7 | * This module reads in all the files that define additional endpoints and generates those endpoints.
8 | * @param server
9 | * @param mongoose
10 | * @param logger
11 | * @param config
12 | * @returns {*|promise}
13 | */
14 | module.exports = async function(server, mongoose, logger, config) {
15 | const Log = logger.bind('api-generator')
16 |
17 | let apiPath = ''
18 |
19 | if (config.absoluteApiPath === true) {
20 | apiPath = config.apiPath
21 | } else {
22 | apiPath = path.join(__dirname, '/../../../', config.apiPath)
23 | }
24 |
25 | try {
26 | const files = fs.readdirSync(apiPath)
27 |
28 | for (const file of files) {
29 | const ext = path.extname(file)
30 | if (ext === '.js') {
31 | const fileName = path.basename(file, '.js')
32 |
33 | // EXPL: register all the additional endpoints
34 | require(apiPath + '/' + fileName)(server, mongoose, logger)
35 | }
36 | }
37 | } catch (err) {
38 | if (err.message.includes('no such file')) {
39 | if (config.absoluteApiPath === true) {
40 | Log.error(err)
41 | throw new Error(
42 | 'The api directory provided is either empty or does not exist. ' +
43 | "Try setting the 'apiPath' property of the config file."
44 | )
45 | }
46 | } else {
47 | throw err
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/docs/misc.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: misc
3 | title: Miscellaneous
4 | sidebar_label: Miscellaneous
5 | ---
6 |
7 | ## Model generation
8 | In some situations models may be required before or without endpoint generation. For example some hapi plugins may require models to exist before the routes are registered. In these cases rest-hapi provides a ``generateModels`` function that can be called independently.
9 |
10 | > **NOTE:** See the [appy seed file](https://github.com/JKHeadley/appy/blob/master/gulp/seed.js) (or [scripts/seed.js](https://github.com/JKHeadley/rest-hapi/blob/master/scripts/seed.js)) for another example usage of ``generateModels``.
11 |
12 |
13 | ## Testing
14 | If you have downloaded the source you can run the tests with:
15 | ```
16 | $ npm test
17 | ```
18 |
19 |
20 | ## License
21 | MIT
22 |
23 | ## Questions?
24 | If you have any questions/issues/feature requests, please feel free to open an [issue](https://github.com/JKHeadley/rest-hapi/issues/new). We'd love to hear from you!
25 |
26 | ## Support
27 | Like this project? Please star it!
28 |
29 | ## Projects
30 | Building a project with rest-hapi? [Open a PR](https://github.com/JKHeadley/rest-hapi/blob/master/README.md) and list it here!
31 |
32 | - [appy](https://github.com/JKHeadley/appy)
33 | * A ready-to-go user system built on rest-hapi.
34 | - [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo)
35 | * A simple demo project implementing rest-hapi in a hapi server.
36 |
37 | ## Contributing
38 | Please reference the contributing doc: https://github.com/JKHeadley/rest-hapi/blob/master/CONTRIBUTING.md
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-1/models/role.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // const _ = require('lodash');
4 | // const Config = require('../config');
5 |
6 | // const USER_ROLES = Config.get('/constants/USER_ROLES');
7 |
8 | module.exports = function(mongoose) {
9 | const modelName = 'role'
10 | const Types = mongoose.Schema.Types
11 | const Schema = new mongoose.Schema(
12 | {
13 | name: {
14 | type: Types.String,
15 | // enum: _.values(USER_ROLES),
16 | required: true,
17 | unique: true
18 | },
19 | description: {
20 | type: Types.String
21 | }
22 | },
23 | { collection: modelName }
24 | )
25 |
26 | Schema.statics = {
27 | collectionName: modelName,
28 | routeOptions: {
29 | // scope: {
30 | // scope: _.values(USER_ROLES),
31 | // },
32 | // documentScope: {
33 | // scope: ['root'],
34 | // },
35 | policies: {
36 | // policies: ['test']
37 | }
38 | // authorizeDocumentCreatorToUpdate: true,
39 | // authorizeDocumentCreatorToRead: true,
40 | // associations: {
41 | // users: {
42 | // type: "ONE_MANY",
43 | // alias: "user",
44 | // foreignField: "role",
45 | // model: "user"
46 | // },
47 | // permissions: {
48 | // type: "MANY_MANY",
49 | // alias: "permission",
50 | // model: "permission",
51 | // linkingModel: "role_permission",
52 | // }
53 | // }
54 | }
55 | }
56 |
57 | return Schema
58 | }
59 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/misc.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-misc
3 | title: Miscellaneous
4 | sidebar_label: Miscellaneous
5 | original_id: misc
6 | ---
7 |
8 | ## Model generation
9 | In some situations models may be required before or without endpoint generation. For example some hapi plugins may require models to exist before the routes are registered. In these cases rest-hapi provides a ``generateModels`` function that can be called independently.
10 |
11 | > **NOTE:** See the [appy seed file](https://github.com/JKHeadley/appy/blob/master/gulp/seed.js) (or [scripts/seed.js](https://github.com/JKHeadley/rest-hapi/blob/master/scripts/seed.js)) for another example usage of ``generateModels``.
12 |
13 |
14 | ## Testing
15 | If you have downloaded the source you can run the tests with:
16 | ```
17 | $ npm test
18 | ```
19 |
20 |
21 | ## License
22 | MIT
23 |
24 | ## Questions?
25 | If you have any questions/issues/feature requests, please feel free to open an [issue](https://github.com/JKHeadley/rest-hapi/issues/new). We'd love to hear from you!
26 |
27 | ## Support
28 | Like this project? Please star it!
29 |
30 | ## Projects
31 | Building a project with rest-hapi? [Open a PR](https://github.com/JKHeadley/rest-hapi/blob/master/README.md) and list it here!
32 |
33 | - [appy](https://github.com/JKHeadley/appy)
34 | * A ready-to-go user system built on rest-hapi.
35 | - [rest-hapi-demo](https://github.com/JKHeadley/rest-hapi-demo)
36 | * A simple demo project implementing rest-hapi in a hapi server.
37 |
38 | ## Contributing
39 | Please reference the contributing doc: https://github.com/JKHeadley/rest-hapi/blob/master/CONTRIBUTING.md
--------------------------------------------------------------------------------
/website/pages/en/users.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | const React = require('react')
9 |
10 | const CompLibrary = require('../../core/CompLibrary.js')
11 | const Container = CompLibrary.Container
12 |
13 | const siteConfig = require(process.cwd() + '/siteConfig.js')
14 |
15 | class Users extends React.Component {
16 | render() {
17 | if ((siteConfig.users || []).length === 0) {
18 | return null
19 | }
20 | const editUrl =
21 | siteConfig.repoUrl + '/edit/master/website/data/users.js'
22 | const showcase = siteConfig.users.map((user, i) => {
23 | return (
24 |
25 |
26 |
27 | )
28 | })
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
Who's Using rest-hapi?
36 |
rest-hapi is powering the APIs of these projects...
37 |
38 |
{showcase}
39 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | module.exports = Users
53 |
--------------------------------------------------------------------------------
/docs/support.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: support
3 | title: Support
4 | sidebar_label: Support
5 | ---
6 |
7 | Like this project? Please [star it](https://github.com/JKHeadley/rest-hapi)!
8 |
9 | ## Contributing
10 |
11 | We welcome contributions to rest-hapi! These are the many ways you can help:
12 |
13 | - Submit patches and features
14 | - Improve the documentation and website (created with [docusaurus](https://docusaurus.io/), check it out!)
15 | - Report bugs
16 | - Follow us on [Twitter](https://twitter.com/resthapi)
17 | - Participate in the [gitter community](https://gitter.im/rest-hapi/Lobby)
18 | - And [donate financially](https://opencollective.com/rest-hapi)!
19 |
20 | Please read our [contribution guide](https://github.com/JKHeadley/rest-hapi/blob/master/CONTRIBUTING.md) to get started. Also note
21 | that this project is released with a
22 | [Contributor Code of Conduct](https://github.com/JKHeadley/rest-hapi/blob/master/CODE_OF_CONDUCT.md), please make sure to review
23 | and follow it.
24 |
25 | ## Contributors
26 |
27 | Of course we want to thank all of our current contributors!
28 |
29 |
30 |
31 | ## Backers
32 |
33 | Support us with a monthly donation and help us continue our activities!
34 | [Become a backer](https://opencollective.com/rest-hapi#backers).
35 |
36 |
37 |
38 |
39 | ## License
40 | rest-hapi is licensed under a [MIT License](https://github.com/JKHeadley/rest-hapi/blob/master/LICENSE.txt).
41 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/support.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-support
3 | title: Support
4 | sidebar_label: Support
5 | original_id: support
6 | ---
7 |
8 | Like this project? Please [star it](https://github.com/JKHeadley/rest-hapi)!
9 |
10 | ## Contributing
11 |
12 | We welcome contributions to rest-hapi! These are the many ways you can help:
13 |
14 | - Submit patches and features
15 | - Improve the documentation and website (created with [docusaurus](https://docusaurus.io/), check it out!)
16 | - Report bugs
17 | - Follow us on [Twitter](https://twitter.com/resthapi)
18 | - Participate in the [gitter community](https://gitter.im/rest-hapi/Lobby)
19 | - And [donate financially](https://opencollective.com/rest-hapi)!
20 |
21 | Please read our [contribution guide](https://github.com/JKHeadley/rest-hapi/blob/master/CONTRIBUTING.md) to get started. Also note
22 | that this project is released with a
23 | [Contributor Code of Conduct](https://github.com/JKHeadley/rest-hapi/blob/master/CODE_OF_CONDUCT.md), please make sure to review
24 | and follow it.
25 |
26 | ## Contributors
27 |
28 | Of course we want to thank all of our current contributors!
29 |
30 |
31 |
32 | ## Backers
33 |
34 | Support us with a monthly donation and help us continue our activities!
35 | [Become a backer](https://opencollective.com/rest-hapi#backers).
36 |
37 |
38 |
39 |
40 | ## License
41 | rest-hapi is licensed under a [MIT License](https://github.com/JKHeadley/rest-hapi/blob/master/LICENSE.txt).
42 |
--------------------------------------------------------------------------------
/policies/add-document-scope.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Boom = require('@hapi/boom')
4 | const _ = require('lodash')
5 |
6 | const internals = {}
7 |
8 | /**
9 | * Policy to append any document scopes defined in the routeOptions to any existing scope.
10 | * @param model
11 | * @param logger
12 | * @returns {addDocumentScopeForModel}
13 | */
14 | internals.addDocumentScope = function(model, logger) {
15 | const addDocumentScopeForModel = function addDocumentScopeForModel(
16 | request,
17 | h
18 | ) {
19 | const Log = logger.bind('addDocumentScope')
20 | try {
21 | const scope = model.routeOptions.documentScope
22 |
23 | if (scope) {
24 | for (const scopeType in scope) {
25 | if (_.isArray(request.payload)) {
26 | request.payload.forEach(function(document) {
27 | document.scope = document.scope || {}
28 | document.scope[scopeType] = document.scope[scopeType] || []
29 | document.scope[scopeType] = document.scope[scopeType].concat(
30 | scope[scopeType]
31 | )
32 | })
33 | } else {
34 | request.payload.scope = request.payload.scope || {}
35 | request.payload.scope[scopeType] =
36 | request.payload.scope[scopeType] || []
37 | request.payload.scope[scopeType] = request.payload.scope[
38 | scopeType
39 | ].concat(scope[scopeType])
40 | }
41 | }
42 | }
43 |
44 | return h.continue
45 | } catch (err) {
46 | Log.error('ERROR:', err)
47 | throw Boom.badImplementation(err)
48 | }
49 | }
50 |
51 | addDocumentScopeForModel.applyPoint = 'onPreHandler'
52 |
53 | return addDocumentScopeForModel
54 | }
55 |
56 | internals.addDocumentScope.applyPoint = 'onPreHandler'
57 |
58 | module.exports = {
59 | addDocumentScope: internals.addDocumentScope
60 | }
61 |
--------------------------------------------------------------------------------
/.github/workflows/manual.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow that is manually triggered
2 |
3 | # HOW TO USE
4 | # One of the challenges with testing NEW github workflows is that you can't trigger them without first
5 | # having the workflow existing in the main branch of the repo.
6 | # Once that workflow is there, you can update the workflow on any branch, and run that branch's version
7 | # of the workflow. So to use this and test a manually triggered workflow:
8 | # 1. Check out a new branch and modify this file
9 | # 2. Push your branch, then in your browser, navigate to the Github workflow
10 | # 3. Click on "Manual workflow" and trigger it, specifying your branch.
11 |
12 | name: Manual workflow
13 |
14 | # Controls when the action will run. Workflow runs when manually triggered using the UI
15 | # or API.
16 | on:
17 | workflow_dispatch:
18 | # Inputs the workflow accepts.
19 | inputs:
20 | name:
21 | # Friendly description to be shown in the UI instead of 'name'
22 | description: 'Person to greet'
23 | # Default value if no value is explicitly provided
24 | default: 'World'
25 | # Input has to be provided for the workflow to run
26 | required: true
27 |
28 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
29 | jobs:
30 | # This workflow contains a single job that can be modified on a branch to test behaviors
31 | scratch-job:
32 | # The type of runner that the job will run on
33 | runs-on: ubuntu-latest
34 |
35 | # Steps represent a sequence of tasks that will be executed as part of the job
36 | steps:
37 | # Runs a single command using the runners shell
38 | - name: Send greeting
39 | run: echo "Hello ${{ github.event.inputs.name }}"
40 | - name: Exit if failed match
41 | run: |
42 | echo $MATCH;
43 | echo ${#MATCH};
44 | if [[ -z "$MATCH" ]]; then
45 | echo "this shouldn't print"
46 | fi
47 | env:
48 | MATCH: "asdf123"
49 |
--------------------------------------------------------------------------------
/docs/authentication.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: authentication
3 | title: Authentication
4 | sidebar_label: Authentication
5 | ---
6 |
7 | ## Route authentication
8 | Authentication for generated endpoints is configured through [`config.authStrategy`](configuration.md#authstrategy) property. If this property is set to a registered strategy, then that strategy is applied to all generated endpoints by default. For more details about authentication with hapi, see [the hapi docs](https://hapijs.com/tutorials/auth). For a working example of authentication with rest-hapi, see the [rest-hapi-demo-auth](https://github.com/JKHeadley/rest-hapi-demo/tree/feature/authentication) or [appy](https://github.com/JKHeadley/appy).
9 |
10 | You can disable authentication for generated CRUD endpoints by setting the correct property to ``false`` within the ``routeOptions`` object. Below is a list of properties and the endpoints they affect:
11 |
12 | Property | Affected endpoints when `false`
13 | --- | ---
14 | readAuth | ``GET /path`` and ``GET /path/{_id}`` endpoints
15 | createAuth | ``POST /path`` endpoint
16 | updateAuth | ``PUT /path/{_id}`` endpoint
17 | deleteAuth | ``DELETE /path`` and ``DELETE /path/{_id}`` endpoints
18 |
19 | Similarly, you can disable authentication for generated association endpoints through the following properties within each association object:
20 |
21 | Property | Affected endpoints when `false`
22 | --- | ---
23 | addAuth | ``POST /owner/{ownerId}/child`` and ``PUT /owner/{ownerId}/child/{childId}`` endpoints
24 | removeAuth | ``DELETE /owner/{ownerId}/child`` and ``DELETE /owner/{ownerId}/child/{childId}`` endpoints
25 | readAuth | ``GET /owner/{ownerId}/child`` endpoint
26 |
27 | For example, a routeOption object that disables authentication for creating objects and removing a specific association could look like this:
28 |
29 | ```javascript
30 | routeOptions: {
31 | createAuth: false,
32 | associations: {
33 | users: {
34 | type: "MANY_ONE",
35 | alias: "user",
36 | model: "user",
37 | removeAuth: false
38 | }
39 | }
40 | }
41 | ```
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/authentication.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-authentication
3 | title: Authentication
4 | sidebar_label: Authentication
5 | original_id: authentication
6 | ---
7 |
8 | ## Route authentication
9 | Authentication for generated endpoints is configured through [`config.authStrategy`](configuration.md#authstrategy) property. If this property is set to a registered strategy, then that strategy is applied to all generated endpoints by default. For more details about authentication with hapi, see [the hapi docs](https://hapijs.com/tutorials/auth). For a working example of authentication with rest-hapi, see the [rest-hapi-demo-auth](https://github.com/JKHeadley/rest-hapi-demo/tree/feature/authentication) or [appy](https://github.com/JKHeadley/appy).
10 |
11 | You can disable authentication for generated CRUD endpoints by setting the correct property to ``false`` within the ``routeOptions`` object. Below is a list of properties and the endpoints they affect:
12 |
13 | Property | Affected endpoints when `false`
14 | --- | ---
15 | readAuth | ``GET /path`` and ``GET /path/{_id}`` endpoints
16 | createAuth | ``POST /path`` endpoint
17 | updateAuth | ``PUT /path/{_id}`` endpoint
18 | deleteAuth | ``DELETE /path`` and ``DELETE /path/{_id}`` endpoints
19 |
20 | Similarly, you can disable authentication for generated association endpoints through the following properties within each association object:
21 |
22 | Property | Affected endpoints when `false`
23 | --- | ---
24 | addAuth | ``POST /owner/{ownerId}/child`` and ``PUT /owner/{ownerId}/child/{childId}`` endpoints
25 | removeAuth | ``DELETE /owner/{ownerId}/child`` and ``DELETE /owner/{ownerId}/child/{childId}`` endpoints
26 | readAuth | ``GET /owner/{ownerId}/child`` endpoint
27 |
28 | For example, a routeOption object that disables authentication for creating objects and removing a specific association could look like this:
29 |
30 | ```javascript
31 | routeOptions: {
32 | createAuth: false,
33 | associations: {
34 | users: {
35 | type: "MANY_ONE",
36 | alias: "user",
37 | model: "user",
38 | removeAuth: false
39 | }
40 | }
41 | }
42 | ```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to rest-hapi
2 |
3 | Hey there! We’re really excited that you are interested in contributing to
4 | rest-hapi. Before submitting your contribution though, please make sure to take
5 | a moment and read through the following guidelines.
6 |
7 | ## Code of Conduct
8 |
9 | Please note that this project has a [Code of Conduct](CODE_OF_CONDUCT.md). It's
10 | important that you review and enforce it.
11 |
12 | ## Code contributions
13 |
14 | Here is a quick guide to doing code contributions to the library.
15 |
16 | 1. Find some issue you're interested in, or a feature that you'd like to
17 | tackle. Also make sure that no one else is already working on it. If it's a
18 | feature you're requesting, make sure it's aligned with the direction of the
19 | project by creating an issue and discussing it with the core maintainers. We
20 | don't want you to be disappointed.
21 | 2. Fork the repo
22 | 3. Create a branch off of the *master* branch. Prefix your branch with either "feature/", "bugfix/", or something similar describing the type of update, and then add a descriptive name such as "bugfix/random\_files_erased".
23 | 4. Add your changes.
24 | * Please try to avoid monolithic commits.
25 | * Code must follow the [Javascript Standard Style](https://standardjs.com/).
26 | * Your code must pass eslint filters to be able to commit.
27 | 5. Make sure your master branch is [in sync/up-to-date with the original](https://help.github.com/articles/syncing-a-fork/).
28 | 6. Before submitting a pull request, merge in your synced master branch with your current branch and resolve any conflicts.
29 | 7. Once all conflicts are resolved, submit a pull request to the origin master branch.
30 | 8. Your pull request will be reviewed along with change requests or comments.
31 | 9. After all requests are complete, your pull request will be merged in.
32 | 10. Celebrate! :tada:
33 |
34 | **NOTE**: Don't forget to [update the docs](https://resthapi.com/docs/quick-start.html)! 😉
35 |
36 | ### This is my first Pull Request, where can I learn how to contribute?
37 |
38 | You can take this free course:
39 | [_How to Contribute to an Open Source Project on GitHub_](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).
40 |
--------------------------------------------------------------------------------
/tests/e2e/test-scenarios/scenario-3/models/user.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Boom = require('@hapi/boom')
4 |
5 | module.exports = function(mongoose) {
6 | const modelName = 'user'
7 | const Types = mongoose.Schema.Types
8 | const Schema = new mongoose.Schema(
9 | {
10 | email: {
11 | type: Types.String,
12 | unique: true
13 | },
14 | password: {
15 | type: Types.String,
16 | required: true,
17 | exclude: true,
18 | allowOnUpdate: false
19 | },
20 | firstName: {
21 | type: Types.String
22 | },
23 | lastName: {
24 | type: Types.String
25 | },
26 | title: {
27 | type: Types.ObjectId,
28 | ref: 'role'
29 | },
30 | profile: {
31 | type: Types.ObjectId,
32 | ref: 'userProfile'
33 | }
34 | },
35 | { collection: modelName }
36 | )
37 |
38 | Schema.statics = {
39 | collectionName: modelName,
40 | routeOptions: {
41 | associations: {
42 | profile: {
43 | type: 'ONE_ONE',
44 | model: 'userProfile',
45 | duplicate: {
46 | field: 'status',
47 | as: 'state'
48 | }
49 | },
50 | title: {
51 | type: 'MANY_ONE',
52 | model: 'role',
53 | duplicate: [
54 | {
55 | field: 'name'
56 | },
57 | {
58 | field: 'description',
59 | as: 'summary'
60 | },
61 | {
62 | field: 'companyName',
63 | as: 'businessName'
64 | }
65 | ]
66 | },
67 | permissions: {
68 | type: 'MANY_MANY',
69 | alias: 'permissions',
70 | model: 'permission',
71 | linkingModel: 'user_permission'
72 | },
73 | tags: {
74 | type: '_MANY',
75 | model: 'hashtag'
76 | }
77 | },
78 | update: {
79 | pre: function(_id, payload, request, Log) {
80 | if (payload.email === 'error@user.com') {
81 | throw Boom.badRequest('user error')
82 | }
83 |
84 | return payload
85 | }
86 | }
87 | }
88 | }
89 |
90 | return Schema
91 | }
92 |
--------------------------------------------------------------------------------
/website/pages/en/help.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | const React = require('react')
9 |
10 | const CompLibrary = require('../../core/CompLibrary.js')
11 | const Container = CompLibrary.Container
12 | const GridBlock = CompLibrary.GridBlock
13 |
14 | const siteConfig = require(process.cwd() + '/siteConfig.js')
15 |
16 | function docUrl(doc, language) {
17 | return siteConfig.baseUrl + 'docs/' + (language ? language + '/' : '') + doc
18 | }
19 |
20 | class Help extends React.Component {
21 | render() {
22 | let language = this.props.language || ''
23 | const supportLinks = [
24 | {
25 | content: `Learn more about rest-hapi using the [documentation on this site.](${docUrl(
26 | 'quick-start.html',
27 | null
28 | )})`,
29 | title: 'Browse the docs'
30 | },
31 | {
32 | content:
33 | 'Ask questions about the documentation and project. Join the conversation on [gitter](https://gitter.im/rest-hapi/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)!',
34 | title: 'Gitter'
35 | },
36 | {
37 | content:
38 | 'You can follow and contact us on [Twitter](https://twitter.com/resthapi).',
39 | title: 'Twitter'
40 | },
41 | {
42 | content:
43 | 'At our [GitHub repo](https://github.com/JKHeadley/rest-hapi) Browse and submit [issues](https://github.com/JKHeadley/rest-hapi/issues) or [pull requests](https://github.com/JKHeadley/rest-hapi/pulls) for bugs you find or any new features you may want implemented. Be sure to also check out our [contributing information](https://github.com/JKHeadley/rest-hapi/blob/master/CONTRIBUTING.md).',
44 | title: 'GitHub'
45 | }
46 | ]
47 |
48 | return (
49 |
50 |
51 |
52 |
55 |
56 | If you need help with rest-hapi, you can try one of the mechanisms
57 | below.
58 |
59 |
60 |
61 |
62 |
63 | )
64 | }
65 | }
66 |
67 | module.exports = Help
68 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: introduction
3 | title: Introduction
4 | sidebar_label: Introduction
5 | ---
6 |
7 | ## Requirements
8 |
9 | You need [Node.js](https://nodejs.org/en/) ^12.14.1 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
10 |
11 | ## Installation
12 |
13 | In your project directory, run:
14 |
15 | ```sh
16 | $ npm install rest-hapi
17 | ```
18 |
19 | ## Using the plugin
20 |
21 | As rest-hapi is a hapi plugin, you'll need to set up a hapi server to generate API endpoints. You'll also need to set up a [mongoose](https://github.com/Automattic/mongoose) instance and include it in the plugin's options when you register. Create a new file ``api.js`` and add the following code to set up an API with rest-hapi:
22 |
23 | ```javascript
24 | // api.js
25 | let Hapi = require('@hapi/hapi')
26 | let mongoose = require('mongoose')
27 | let RestHapi = require('rest-hapi')
28 |
29 | async function api(){
30 | try {
31 | let server = Hapi.Server({ port: 8080 })
32 |
33 | let config = {
34 | appTitle: "My API",
35 | };
36 |
37 | await server.register({
38 | plugin: RestHapi,
39 | options: {
40 | mongoose,
41 | config
42 | }
43 | })
44 |
45 | await server.start()
46 |
47 | console.log("Server ready", server.info)
48 |
49 | return server
50 | } catch (err) {
51 | console.log("Error starting server:", err);
52 | }
53 | }
54 |
55 | module.exports = api()
56 | ```
57 | You can then run
58 |
59 | ```sh
60 | $ node api.js
61 | ```
62 |
63 | and point your browser to [http://localhost:8080/](http://localhost:8080/) to view the swagger docs
64 |
65 | > **NOTE**: API endpoints will only be generated if you have provided models. See [Example Data](#example-data) or [Creating endpoints](creating-endpoints.md).
66 |
67 |
68 | ## Example Data
69 |
70 | **WARNING**: This will clear all data in the following MongoDB collections in the db defined in ``RestHapi.config`` (default ``mongodb://localhost:27017/rest_hapi``): ``users``, ``roles``.
71 |
72 | If you would like to seed your database with some demo models/data, run:
73 |
74 | ```sh
75 | $ ./node_modules/.bin/rest-hapi-cli seed
76 | ```
77 |
78 | If you need a db different than the default, you can add the URI as an argument to the command:
79 |
80 | ```sh
81 | $ ./node_modules/.bin/rest-hapi-cli seed mongodb://localhost:27017/other_db
82 | ```
83 |
84 | You can use these models as templates for your models or delete them later if you wish.
85 |
86 | For a ready-to-go demo project see [quick start](quick-start.md)
87 |
--------------------------------------------------------------------------------
/utilities/log-util.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const chalk = require('chalk')
3 |
4 | /**
5 | * Function that truncates the properties of an object to a certain length.
6 | */
7 | function truncatedProps(obj, truncateLength = 100) {
8 | const result = {}
9 | if (!_.isObject(obj)) return truncateProp(obj, truncateLength)
10 | for (const key in obj) {
11 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
12 | result[key] = truncateProp(obj[key], truncateLength)
13 | }
14 | }
15 | return result
16 | }
17 |
18 | function truncateProp(prop, truncateLength = 100) {
19 | let result = null
20 | let value = _.clone(prop)
21 | // if value is an array, truncate each element
22 | if (_.isArray(value)) {
23 | // First truncate the array to 10 elements
24 | if (value.length > 10) {
25 | value = value.slice(0, 10).concat(['.', '.', '.'])
26 | }
27 | result = value.map(v => truncatedProps(v, truncateLength))
28 | } else if (_.isObject(value)) {
29 | result = truncatedProps(value, truncateLength)
30 |
31 | // if value is a string, truncate it to truncateLength characters
32 | } else if (typeof value === 'string') {
33 | if (value.length <= truncateLength) {
34 | result = value
35 | } else {
36 | result = value.substring(0, truncateLength) + '...'
37 | }
38 |
39 | // otherwise, just return it
40 | } else {
41 | result = value
42 | }
43 |
44 | return result
45 | }
46 |
47 | function truncatedStringify(obj, truncateLength = 100) {
48 | return JSON.stringify(truncatedProps(obj, truncateLength), null, 2)
49 | }
50 |
51 | module.exports = {
52 | truncatedProps,
53 | truncatedStringify,
54 | bindHelper: function(logger, name) {
55 | return logger.bind(chalk.gray(name))
56 | },
57 | logActionStart: function(logger, message, data) {
58 | if (data) {
59 | logger.log(chalk.blue(message) + chalk.white('...:'))
60 | _.forIn(data, function(value, key) {
61 | logger.log(
62 | chalk.gray('\t%s: `%s`'),
63 | chalk.magenta(key),
64 | chalk.cyan(value)
65 | )
66 | })
67 | } else {
68 | logger.log(chalk.blue(message) + chalk.white('...'))
69 | }
70 | },
71 | logActionComplete: function(logger, message, data) {
72 | if (data) {
73 | logger.log(chalk.blue(message) + chalk.white(':'))
74 | _.forIn(data, function(value, key) {
75 | logger.log(
76 | chalk.gray('\t%s: `%s`'),
77 | chalk.magenta(key),
78 | chalk.cyan(value)
79 | )
80 | })
81 | } else {
82 | logger.log(chalk.blue(message))
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-introduction
3 | title: Introduction
4 | sidebar_label: Introduction
5 | original_id: introduction
6 | ---
7 |
8 | ## Requirements
9 |
10 | You need [Node.js](https://nodejs.org/en/) ^8.10.0 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
11 |
12 | ## Installation
13 |
14 | In your project directory, run:
15 |
16 | ```sh
17 | $ npm install rest-hapi
18 | ```
19 |
20 | ## Using the plugin
21 |
22 | As rest-hapi is a hapi plugin, you'll need to set up a hapi server to generate API endpoints. You'll also need to set up a [mongoose](https://github.com/Automattic/mongoose) instance and include it in the plugin's options when you register. Create a new file ``api.js`` and add the following code to set up an API with rest-hapi:
23 |
24 | ```javascript
25 | // api.js
26 | let Hapi = require('@hapi/hapi')
27 | let mongoose = require('mongoose')
28 | let RestHapi = require('rest-hapi')
29 |
30 | async function api(){
31 | try {
32 | let server = Hapi.Server({ port: 8080 })
33 |
34 | let config = {
35 | appTitle: "My API",
36 | };
37 |
38 | await server.register({
39 | plugin: RestHapi,
40 | options: {
41 | mongoose,
42 | config
43 | }
44 | })
45 |
46 | await server.start()
47 |
48 | console.log("Server ready", server.info)
49 |
50 | return server
51 | } catch (err) {
52 | console.log("Error starting server:", err);
53 | }
54 | }
55 |
56 | module.exports = api()
57 | ```
58 | You can then run
59 |
60 | ```sh
61 | $ node api.js
62 | ```
63 |
64 | and point your browser to [http://localhost:8080/](http://localhost:8080/) to view the swagger docs
65 |
66 | > **NOTE**: API endpoints will only be generated if you have provided models. See [Example Data](#example-data) or [Creating endpoints](creating-endpoints.md).
67 |
68 |
69 | ## Example Data
70 |
71 | **WARNING**: This will clear all data in the following MongoDB collections in the db defined in ``RestHapi.config`` (default ``mongodb://localhost:27017/rest_hapi``): ``users``, ``roles``.
72 |
73 | If you would like to seed your database with some demo models/data, run:
74 |
75 | ```sh
76 | $ ./node_modules/.bin/rest-hapi-cli seed
77 | ```
78 |
79 | If you need a db different than the default, you can add the URI as an argument to the command:
80 |
81 | ```sh
82 | $ ./node_modules/.bin/rest-hapi-cli seed mongodb://localhost:27017/other_db
83 | ```
84 |
85 | You can use these models as templates for your models or delete them later if you wish.
86 |
87 | For a ready-to-go demo project see [quick start](quick-start.md)
88 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-2.0.x/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-2.0.x-introduction
3 | title: Introduction
4 | sidebar_label: Introduction
5 | original_id: introduction
6 | ---
7 |
8 | ## Requirements
9 |
10 | You need [Node.js](https://nodejs.org/en/) ^12.14.1 installed and you'll need [MongoDB](https://docs.mongodb.com/manual/installation/) installed and running.
11 |
12 | ## Installation
13 |
14 | In your project directory, run:
15 |
16 | ```sh
17 | $ npm install rest-hapi
18 | ```
19 |
20 | ## Using the plugin
21 |
22 | As rest-hapi is a hapi plugin, you'll need to set up a hapi server to generate API endpoints. You'll also need to set up a [mongoose](https://github.com/Automattic/mongoose) instance and include it in the plugin's options when you register. Create a new file ``api.js`` and add the following code to set up an API with rest-hapi:
23 |
24 | ```javascript
25 | // api.js
26 | let Hapi = require('@hapi/hapi')
27 | let mongoose = require('mongoose')
28 | let RestHapi = require('rest-hapi')
29 |
30 | async function api(){
31 | try {
32 | let server = Hapi.Server({ port: 8080 })
33 |
34 | let config = {
35 | appTitle: "My API",
36 | };
37 |
38 | await server.register({
39 | plugin: RestHapi,
40 | options: {
41 | mongoose,
42 | config
43 | }
44 | })
45 |
46 | await server.start()
47 |
48 | console.log("Server ready", server.info)
49 |
50 | return server
51 | } catch (err) {
52 | console.log("Error starting server:", err);
53 | }
54 | }
55 |
56 | module.exports = api()
57 | ```
58 | You can then run
59 |
60 | ```sh
61 | $ node api.js
62 | ```
63 |
64 | and point your browser to [http://localhost:8080/](http://localhost:8080/) to view the swagger docs
65 |
66 | > **NOTE**: API endpoints will only be generated if you have provided models. See [Example Data](#example-data) or [Creating endpoints](creating-endpoints.md).
67 |
68 |
69 | ## Example Data
70 |
71 | **WARNING**: This will clear all data in the following MongoDB collections in the db defined in ``RestHapi.config`` (default ``mongodb://localhost:27017/rest_hapi``): ``users``, ``roles``.
72 |
73 | If you would like to seed your database with some demo models/data, run:
74 |
75 | ```sh
76 | $ ./node_modules/.bin/rest-hapi-cli seed
77 | ```
78 |
79 | If you need a db different than the default, you can add the URI as an argument to the command:
80 |
81 | ```sh
82 | $ ./node_modules/.bin/rest-hapi-cli seed mongodb://localhost:27017/other_db
83 | ```
84 |
85 | You can use these models as templates for your models or delete them later if you wish.
86 |
87 | For a ready-to-go demo project see [quick start](quick-start.md)
88 |
--------------------------------------------------------------------------------
/scripts/seed.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const mongoose = require('mongoose')
4 | const config = require('../config')
5 | const RestHapi = require('../rest-hapi')
6 | const path = require('path')
7 | const fs = require('fs-extra')
8 |
9 | let mongoURI = process.argv.slice(2)[0]
10 | ;(async function seed() {
11 | RestHapi.config.loglevel = 'DEBUG'
12 | const Log = RestHapi.getLogger('seed')
13 | try {
14 | await moveModels()
15 |
16 | mongoose.Promise = Promise
17 |
18 | mongoURI = mongoURI || RestHapi.config.mongo.URI
19 | mongoose.connect(mongoURI, {
20 | useNewUrlParser: true,
21 | useUnifiedTopology: true
22 | })
23 |
24 | const models = await RestHapi.generateModels(mongoose)
25 |
26 | const password = '1234'
27 |
28 | await dropCollections(models)
29 |
30 | Log.log('seeding roles')
31 | let roles = [
32 | {
33 | name: 'Account',
34 | description: 'A standard user account.'
35 | },
36 | {
37 | name: 'Admin',
38 | description: 'A user with advanced permissions.'
39 | },
40 | {
41 | name: 'SuperAdmin',
42 | description: 'A user with full permissions.'
43 | }
44 | ]
45 |
46 | roles = await RestHapi.create(models.role, roles, Log)
47 |
48 | Log.log('seeding users')
49 | const users = [
50 | {
51 | email: 'test@account.com',
52 | password: password,
53 | role: roles[0]._id
54 | },
55 | {
56 | email: 'test@admin.com',
57 | password: password,
58 | role: roles[1]._id
59 | },
60 | {
61 | email: 'test@superadmin.com',
62 | password: password,
63 | role: roles[2]._id
64 | }
65 | ]
66 | await RestHapi.create(models.user, users, Log)
67 | process.exit()
68 | } catch (err) {
69 | Log.error(err)
70 | process.exit()
71 | }
72 | })()
73 |
74 | function moveModels() {
75 | return new Promise((resolve, reject) => {
76 | fs.copy(
77 | path.join(__dirname, '../seed'),
78 | path.join(__dirname, '/../../../', config.modelPath),
79 | err => {
80 | if (err) {
81 | reject(err)
82 | }
83 | resolve()
84 | }
85 | )
86 | })
87 | }
88 |
89 | async function dropCollections(models) {
90 | RestHapi.config.loglevel = 'LOG'
91 | const Log = RestHapi.getLogger('unseed')
92 | try {
93 | await models.user.remove({})
94 | Log.log('roles removed')
95 | await models.role.remove({})
96 | Log.log('users removed')
97 | } catch (err) {
98 | Log.error(err)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/policies/populate-duplicate-fields.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Boom = require('@hapi/boom')
4 | const _ = require('lodash')
5 |
6 | const internals = {}
7 |
8 | /**
9 | * Policy to populate duplicate fields when an association is created or updated.
10 | * @param model
11 | * @param logger
12 | * @returns {populateDuplicateFields}
13 | */
14 | internals.populateDuplicateFields = function(model, mongoose, logger) {
15 | const populateDuplicateFieldsForModel = async function addDocumentScopeForModel(
16 | request,
17 | h
18 | ) {
19 | const Log = logger.bind('populateDuplicateFields')
20 | try {
21 | let payload = request.payload
22 | if (!_.isArray(request.payload)) {
23 | payload = [request.payload]
24 | }
25 |
26 | const associations = model.schema.statics.routeOptions.associations
27 | if (associations) {
28 | const promises = []
29 | for (const key in associations) {
30 | const association = associations[key]
31 | const duplicate = association.duplicate
32 | for (const doc of payload) {
33 | if (
34 | duplicate &&
35 | (association.type === 'MANY_ONE' ||
36 | association.type === 'ONE_ONE') &&
37 | doc[key]
38 | ) {
39 | const childModel = mongoose.model(association.model)
40 |
41 | const promise = childModel
42 | .findOne({ _id: doc[key] })
43 | .then(function(result) {
44 | const docsToUpdate = payload.filter(function(docToFind) {
45 | return docToFind[key] === result._id.toString()
46 | })
47 | // EXPL: Populate each duplicated field for this association.
48 | // NOTE: We are updating the original payload
49 | for (const prop of duplicate) {
50 | for (const docToUpdate of docsToUpdate) {
51 | docToUpdate[prop.as] = result[prop.field]
52 | }
53 | }
54 | })
55 | promises.push(promise)
56 | }
57 | }
58 | }
59 |
60 | await Promise.all(promises)
61 | return h.continue
62 | }
63 | return h.continue
64 | } catch (err) {
65 | if (err.isBoom) {
66 | throw err
67 | } else {
68 | Log.error(err)
69 | throw Boom.badImplementation(err)
70 | }
71 | }
72 | }
73 |
74 | populateDuplicateFieldsForModel.applyPoint = 'onPreHandler'
75 |
76 | return populateDuplicateFieldsForModel
77 | }
78 |
79 | internals.populateDuplicateFields.applyPoint = 'onPreHandler'
80 |
81 | module.exports = {
82 | populateDuplicateFields: internals.populateDuplicateFields
83 | }
84 |
--------------------------------------------------------------------------------
/docs/metadata.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: metadata
3 | title: Metadata
4 | sidebar_label: Metadata
5 | ---
6 |
7 | ## Timestamps
8 | rest-hapi supports the following optional timestamp metadata for documents:
9 | - createdAt (default enabled, activated via [`config.enableCreatedAt`](configuration.md#enablecreatedat))
10 | - updatedAt (default enabled, activated via [`config.enableUpdatedAt`](configuration.md#enableupdatedat))
11 | - deletedAt (default enabled, activated via [`config.enableDeletedAt`](configuration.md#enabledeletedat)) (see [Soft delete](soft-delete.md))
12 |
13 | When enabled, these properties will automatically be populated during CRUD operations. For example, say I create a user with a payload of:
14 |
15 | ```json
16 | {
17 | "email": "test@email.com",
18 | "password": "1234"
19 | }
20 | ```
21 |
22 | If I then query for this document I might get:
23 |
24 | ```json
25 | {
26 | "_id": "588077dfe8b75a830dc53e8b",
27 | "email": "test@email.com",
28 | "createdAt": "2017-01-19T08:25:03.577Z"
29 | }
30 | ```
31 |
32 | If I later update that user's email then an additional query might return:
33 |
34 | ```json
35 | {
36 | "_id": "588077dfe8b75a830dc53e8b",
37 | "email": "test2@email.com",
38 | "createdAt": "2017-01-19T08:25:03.577Z",
39 | "updatedAt": "2017-01-19T08:30:46.676Z"
40 | }
41 | ```
42 |
43 | The ``deletedAt`` property marks when a document was [soft deleted](soft-delete.md).
44 |
45 | > **NOTE**: Timestamp metadata properties are only set/updated if the document is created/modified using rest-hapi endpoints/methods.
46 | Ex:
47 |
48 | ``mongoose.model('user').findByIdAndUpdate(_id, payload)`` will not modify ``updatedAt`` whereas
49 |
50 | ``RestHapi.update(mongoose.model('user'), _id, payload)`` will. (see [Mongoose wrapper methods](mongoose-wrapper-methods.md))
51 |
52 | ## User tags
53 | In addition to timestamps, the following user tag metadata can be added to a document:
54 | - createdBy (default disabled, activated via [`config.enableCreatedBy`](configuration.md#enablecreatedby))
55 | - updatedBy (default disabled, activated via [`config.enableUpdatedBy`](configuration.md#enableupdatedby))
56 | - deletedBy (default disabled, activated via [`config.enableDeletedBy`](configuration.md#enabledeletedby)) (see [Soft delete](soft-delete.md))
57 |
58 | If enabled, these properties will record the `_id` of the user performing the corresponding action.
59 |
60 | This assumes that your authentication credentials (request.auth.credentials) will contain either a `user` object with a `_id` property, or the user's \_id stored in a property defined by [`config.userIdKey`](configuration.md#useridkey).
61 |
62 | > **NOTE**: Unlike timestamp metadata, user tag properties are only set/updated if the document is created/modified using rest-hapi endpoints, (not rest-hapi [methods](mongoose-wrapper-methods.md)).
63 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/metadata.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-metadata
3 | title: Metadata
4 | sidebar_label: Metadata
5 | original_id: metadata
6 | ---
7 |
8 | ## Timestamps
9 | rest-hapi supports the following optional timestamp metadata for documents:
10 | - createdAt (default enabled, activated via [`config.enableCreatedAt`](configuration.md#enablecreatedat))
11 | - updatedAt (default enabled, activated via [`config.enableUpdatedAt`](configuration.md#enableupdatedat))
12 | - deletedAt (default enabled, activated via [`config.enableDeletedAt`](configuration.md#enabledeletedat)) (see [Soft delete](soft-delete.md))
13 |
14 | When enabled, these properties will automatically be populated during CRUD operations. For example, say I create a user with a payload of:
15 |
16 | ```json
17 | {
18 | "email": "test@email.com",
19 | "password": "1234"
20 | }
21 | ```
22 |
23 | If I then query for this document I might get:
24 |
25 | ```json
26 | {
27 | "_id": "588077dfe8b75a830dc53e8b",
28 | "email": "test@email.com",
29 | "createdAt": "2017-01-19T08:25:03.577Z"
30 | }
31 | ```
32 |
33 | If I later update that user's email then an additional query might return:
34 |
35 | ```json
36 | {
37 | "_id": "588077dfe8b75a830dc53e8b",
38 | "email": "test2@email.com",
39 | "createdAt": "2017-01-19T08:25:03.577Z",
40 | "updatedAt": "2017-01-19T08:30:46.676Z"
41 | }
42 | ```
43 |
44 | The ``deletedAt`` property marks when a document was [soft deleted](soft-delete.md).
45 |
46 | > **NOTE**: Timestamp metadata properties are only set/updated if the document is created/modified using rest-hapi endpoints/methods.
47 | Ex:
48 |
49 | ``mongoose.model('user').findByIdAndUpdate(_id, payload)`` will not modify ``updatedAt`` whereas
50 |
51 | ``RestHapi.update(mongoose.model('user'), _id, payload)`` will. (see [Mongoose wrapper methods](mongoose-wrapper-methods.md))
52 |
53 | ## User tags
54 | In addition to timestamps, the following user tag metadata can be added to a document:
55 | - createdBy (default disabled, activated via [`config.enableCreatedBy`](configuration.md#enablecreatedby))
56 | - updatedBy (default disabled, activated via [`config.enableUpdatedBy`](configuration.md#enableupdatedby))
57 | - deletedBy (default disabled, activated via [`config.enableDeletedBy`](configuration.md#enabledeletedby)) (see [Soft delete](soft-delete.md))
58 |
59 | If enabled, these properties will record the `_id` of the user performing the corresponding action.
60 |
61 | This assumes that your authentication credentials (request.auth.credentials) will contain either a `user` object with a `_id` property, or the user's \_id stored in a property defined by [`config.userIdKey`](configuration.md#useridkey).
62 |
63 | > **NOTE**: Unlike timestamp metadata, user tag properties are only set/updated if the document is created/modified using rest-hapi endpoints, (not rest-hapi [methods](mongoose-wrapper-methods.md)).
64 |
--------------------------------------------------------------------------------
/docs/route-customization.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: route-customization
3 | title: Route Customization
4 | sidebar_label: Route Customization
5 | ---
6 |
7 | ## Custom path names
8 | By default route paths are constructed using model names, however aliases can be provided to customize the route paths. ``routeOptions.alias`` can be set to alter the base path name, and an ``alias`` property for an association can be set to alter the association path name. For example:
9 |
10 | ```javascript
11 | module.exports = function (mongoose) {
12 | let modelName = "user";
13 | let Types = mongoose.Schema.Types;
14 | let Schema = new mongoose.Schema({
15 | email: {
16 | type: Types.String,
17 | required: true,
18 | unique: true
19 | },
20 | password: {
21 | type: Types.String,
22 | required: true,
23 | exclude: true,
24 | allowOnUpdate: false
25 | }
26 | });
27 |
28 | Schema.statics = {
29 | collectionName: modelName
30 | routeOptions: {
31 | alias: "person"
32 | associations: {
33 | groups: {
34 | type: "MANY_MANY",
35 | model: "group",
36 | alias: "team"
37 | }
38 | }
39 | }
40 | };
41 |
42 | return Schema;
43 | };
44 | ```
45 |
46 | will result in the following endpoints:
47 |
48 | ```javascript
49 | DELETE /person
50 | POST /person
51 | GET /person
52 | DELETE /person/{_id}
53 | GET /person/{_id}
54 | PUT /person/{_id}
55 | GET /person/{ownerId}/team
56 | DELETE /person/{ownerId}/team
57 | POST /person/{ownerId}/team
58 | DELETE /person/{ownerId}/team/{childId}
59 | PUT /person/{ownerId}/team/{childId}
60 | ```
61 |
62 | ## Omitting routes
63 |
64 | You can prevent CRUD endpoints from generating by setting the correct property to ``false`` within the ``routeOptions`` object. Below is a list of properties and their effect:
65 |
66 | Property | Effect when false
67 | --- | ---
68 | allowList | omits ``GET /path`` endpoint
69 | allowRead | omits ``GET /path`` and ``GET /path/{_id}`` endpoints
70 | allowCreate | omits ``POST /path`` endpoint
71 | allowUpdate | omits ``PUT /path/{_id}`` endpoint
72 | allowDelete | omits ``DELETE /path`` and ``DELETE /path/{_id}`` endpoints
73 |
74 | Similarly, you can prevent association endpoints from generating through the following properties within each association object:
75 |
76 | Property | Effect when false
77 | --- | ---
78 | allowAdd | omits ``POST /owner/{ownerId}/child`` and ``PUT /owner/{ownerId}/child/{childId}`` endpoints
79 | allowRemove | omits ``DELETE /owner/{ownerId}/child`` and ``DELETE /owner/{ownerId}/child/{childId}`` endpoints
80 | allowRead | omits ``GET /owner/{ownerId}/child`` endpoint
81 |
82 | For example, a routeOption object that omits endpoints for creating objects and removing a specific association could look like this:
83 |
84 | ```javascript
85 | routeOptions: {
86 | allowCreate: false,
87 | associations: {
88 | users: {
89 | type: "MANY_ONE",
90 | alias: "user",
91 | model: "user",
92 | allowRemove: false
93 | }
94 | }
95 | }
96 | ```
--------------------------------------------------------------------------------
/website/versioned_docs/version-1.6.x/route-customization.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-1.6.x-route-customization
3 | title: Route Customization
4 | sidebar_label: Route Customization
5 | original_id: route-customization
6 | ---
7 |
8 | ## Custom path names
9 | By default route paths are constructed using model names, however aliases can be provided to customize the route paths. ``routeOptions.alias`` can be set to alter the base path name, and an ``alias`` property for an association can be set to alter the association path name. For example:
10 |
11 | ```javascript
12 | module.exports = function (mongoose) {
13 | let modelName = "user";
14 | let Types = mongoose.Schema.Types;
15 | let Schema = new mongoose.Schema({
16 | email: {
17 | type: Types.String,
18 | required: true,
19 | unique: true
20 | },
21 | password: {
22 | type: Types.String,
23 | required: true,
24 | exclude: true,
25 | allowOnUpdate: false
26 | }
27 | });
28 |
29 | Schema.statics = {
30 | collectionName: modelName
31 | routeOptions: {
32 | alias: "person"
33 | associations: {
34 | groups: {
35 | type: "MANY_MANY",
36 | model: "group",
37 | alias: "team"
38 | }
39 | }
40 | }
41 | };
42 |
43 | return Schema;
44 | };
45 | ```
46 |
47 | will result in the following endpoints:
48 |
49 | ```javascript
50 | DELETE /person
51 | POST /person
52 | GET /person
53 | DELETE /person/{_id}
54 | GET /person/{_id}
55 | PUT /person/{_id}
56 | GET /person/{ownerId}/team
57 | DELETE /person/{ownerId}/team
58 | POST /person/{ownerId}/team
59 | DELETE /person/{ownerId}/team/{childId}
60 | PUT /person/{ownerId}/team/{childId}
61 | ```
62 |
63 | ## Omitting routes
64 |
65 | You can prevent CRUD endpoints from generating by setting the correct property to ``false`` within the ``routeOptions`` object. Below is a list of properties and their effect:
66 |
67 | Property | Effect when false
68 | --- | ---
69 | allowRead | omits ``GET /path`` and ``GET /path/{_id}`` endpoints
70 | allowCreate | omits ``POST /path`` endpoint
71 | allowUpdate | omits ``PUT /path/{_id}`` endpoint
72 | allowDelete | omits ``DELETE /path`` and ``DELETE /path/{_id}`` endpoints
73 |
74 | Similarly, you can prevent association endpoints from generating through the following properties within each association object:
75 |
76 | Property | Effect when false
77 | --- | ---
78 | allowAdd | omits ``POST /owner/{ownerId}/child`` and ``PUT /owner/{ownerId}/child/{childId}`` endpoints
79 | allowRemove | omits ``DELETE /owner/{ownerId}/child`` and ``DELETE /owner/{ownerId}/child/{childId}`` endpoints
80 | allowRead | omits ``GET /owner/{ownerId}/child`` endpoint
81 |
82 | For example, a routeOption object that omits endpoints for creating objects and removing a specific association could look like this:
83 |
84 | ```javascript
85 | routeOptions: {
86 | allowCreate: false,
87 | associations: {
88 | users: {
89 | type: "MANY_ONE",
90 | alias: "user",
91 | model: "user",
92 | allowRemove: false
93 | }
94 | }
95 | }
96 | ```
--------------------------------------------------------------------------------
/models/audit-log.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Config = require('../config')
4 | const _ = require('lodash')
5 |
6 | module.exports = function(mongoose) {
7 | const modelName = 'auditLog'
8 | const Types = mongoose.Schema.Types
9 | const dateField = {
10 | type: Types.Date,
11 | default: () => {
12 | return Date.now()
13 | }
14 | }
15 | if (Config.auditLogTTL) {
16 | dateField.expires = Config.auditLogTTL
17 | }
18 |
19 | const Schema = new mongoose.Schema(
20 | {
21 | date: dateField,
22 | method: {
23 | type: Types.String,
24 | enum: ['POST', 'PUT', 'DELETE', 'GET', null],
25 | allowNull: true,
26 | default: null
27 | },
28 | action: {
29 | type: Types.String,
30 | allowNull: true,
31 | default: null
32 | },
33 | endpoint: {
34 | type: Types.String,
35 | allowNull: true,
36 | default: null
37 | },
38 | user: {
39 | type: Types.ObjectId,
40 | allowNull: true,
41 | default: null
42 | },
43 | collectionName: {
44 | type: Types.String,
45 | allowNull: true,
46 | default: null
47 | },
48 | childCollectionName: {
49 | type: Types.String,
50 | allowNull: true,
51 | default: null
52 | },
53 | associationType: {
54 | type: Types.String,
55 | enum: ['ONE_MANY', 'MANY_MANY', '_MANY', null],
56 | allowNull: true,
57 | default: null
58 | },
59 | documents: {
60 | type: [Types.ObjectId],
61 | allowNull: true,
62 | default: null
63 | },
64 | payload: {
65 | type: Types.Object,
66 | allowNull: true,
67 | default: null
68 | },
69 | params: {
70 | type: Types.Object,
71 | allowNull: true,
72 | default: null
73 | },
74 | result: {
75 | type: Types.Object,
76 | allowNull: true,
77 | default: null
78 | },
79 | statusCode: {
80 | type: Types.Number,
81 | allowNull: true,
82 | default: null
83 | },
84 | responseMessage: {
85 | type: Types.String,
86 | allowNull: true,
87 | default: null
88 | },
89 | isError: {
90 | type: Types.Boolean,
91 | default: false,
92 | required: true
93 | },
94 | ipAddress: {
95 | type: Types.String,
96 | allowNull: true,
97 | default: null
98 | },
99 | notes: {
100 | type: Types.String,
101 | allowNull: true,
102 | default: null
103 | }
104 | },
105 | { collection: modelName }
106 | )
107 |
108 | Schema.statics = {
109 | collectionName: modelName,
110 | routeOptions: {
111 | allowUpdate: false,
112 | allowDelete: false
113 | }
114 | }
115 |
116 | if (!_.isEmpty(Config.auditLogScope)) {
117 | Schema.statics.routeOptions.routeScope = {
118 | rootScope: Config.auditLogScope
119 | }
120 | }
121 |
122 | return Schema
123 | }
124 |
--------------------------------------------------------------------------------
/website/versioned_docs/version-2.2.x/route-customization.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: version-2.2.x-route-customization
3 | title: Route Customization
4 | sidebar_label: Route Customization
5 | original_id: route-customization
6 | ---
7 |
8 | ## Custom path names
9 | By default route paths are constructed using model names, however aliases can be provided to customize the route paths. ``routeOptions.alias`` can be set to alter the base path name, and an ``alias`` property for an association can be set to alter the association path name. For example:
10 |
11 | ```javascript
12 | module.exports = function (mongoose) {
13 | let modelName = "user";
14 | let Types = mongoose.Schema.Types;
15 | let Schema = new mongoose.Schema({
16 | email: {
17 | type: Types.String,
18 | required: true,
19 | unique: true
20 | },
21 | password: {
22 | type: Types.String,
23 | required: true,
24 | exclude: true,
25 | allowOnUpdate: false
26 | }
27 | });
28 |
29 | Schema.statics = {
30 | collectionName: modelName
31 | routeOptions: {
32 | alias: "person"
33 | associations: {
34 | groups: {
35 | type: "MANY_MANY",
36 | model: "group",
37 | alias: "team"
38 | }
39 | }
40 | }
41 | };
42 |
43 | return Schema;
44 | };
45 | ```
46 |
47 | will result in the following endpoints:
48 |
49 | ```javascript
50 | DELETE /person
51 | POST /person
52 | GET /person
53 | DELETE /person/{_id}
54 | GET /person/{_id}
55 | PUT /person/{_id}
56 | GET /person/{ownerId}/team
57 | DELETE /person/{ownerId}/team
58 | POST /person/{ownerId}/team
59 | DELETE /person/{ownerId}/team/{childId}
60 | PUT /person/{ownerId}/team/{childId}
61 | ```
62 |
63 | ## Omitting routes
64 |
65 | You can prevent CRUD endpoints from generating by setting the correct property to ``false`` within the ``routeOptions`` object. Below is a list of properties and their effect:
66 |
67 | Property | Effect when false
68 | --- | ---
69 | allowList | omits ``GET /path`` endpoint
70 | allowRead | omits ``GET /path`` and ``GET /path/{_id}`` endpoints
71 | allowCreate | omits ``POST /path`` endpoint
72 | allowUpdate | omits ``PUT /path/{_id}`` endpoint
73 | allowDelete | omits ``DELETE /path`` and ``DELETE /path/{_id}`` endpoints
74 |
75 | Similarly, you can prevent association endpoints from generating through the following properties within each association object:
76 |
77 | Property | Effect when false
78 | --- | ---
79 | allowAdd | omits ``POST /owner/{ownerId}/child`` and ``PUT /owner/{ownerId}/child/{childId}`` endpoints
80 | allowRemove | omits ``DELETE /owner/{ownerId}/child`` and ``DELETE /owner/{ownerId}/child/{childId}`` endpoints
81 | allowRead | omits ``GET /owner/{ownerId}/child`` endpoint
82 |
83 | For example, a routeOption object that omits endpoints for creating objects and removing a specific association could look like this:
84 |
85 | ```javascript
86 | routeOptions: {
87 | allowCreate: false,
88 | associations: {
89 | users: {
90 | type: "MANY_ONE",
91 | alias: "user",
92 | model: "user",
93 | allowRemove: false
94 | }
95 | }
96 | }
97 | ```
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at resthapi@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/tests/e2e/end-to-end.tests.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Test = require('blue-tape')
4 | const Logging = require('loggin')
5 | const Q = require('q')
6 | const Decache = require('decache')
7 |
8 | // Import test groups
9 | const BasicCrudTests = require('./basic-crud.tests')
10 | const DocAuthTests = require('./doc-auth.tests')
11 | const BasicEmbedRestTests = require('./basic-embed-rest.tests')
12 | const BasicEmbedWrapperTests = require('./basic-embed-wrapper.tests')
13 | const BasicNonEmbedTests = require('./basic-non-embed.tests')
14 | const AuditLogTests = require('./audit-log.tests')
15 | const AdvanceAssocTests = require('./advance-assoc.tests')
16 | const DuplicateFieldTests = require('./duplicate-field.tests')
17 | const MiscTests = require('./misc.tests')
18 |
19 | const MongoMemoryServer = require('mongodb-memory-server').MongoMemoryServer
20 | const mongoServer = new MongoMemoryServer({
21 | instance: {
22 | port: 27017,
23 | dbName: 'rest_hapi'
24 | }
25 | })
26 |
27 | // TODO: Possibly require this in every test and decache it to avoid unexpected
28 | // errors between tests.
29 | const Mongoose = require('mongoose')
30 | Mongoose.Promise = Promise
31 |
32 | let Log = Logging.getLogger('tests')
33 | Log.logLevel = 'DEBUG'
34 | Log = Log.bind('end-to-end')
35 |
36 | const internals = {
37 | previous: {}
38 | }
39 |
40 | internals.onFinish = function() {
41 | process.exit()
42 | }
43 |
44 | Test.onFinish(internals.onFinish)
45 |
46 | process.on('unhandledRejection', error => {
47 | console.log('Unhandled error:', error.message)
48 | })
49 |
50 | function restore(Mongoose) {
51 | Decache('../../rest-hapi')
52 |
53 | Decache('../config')
54 | Object.keys(Mongoose.models).forEach(function(key) {
55 | delete Mongoose.models[key]
56 | })
57 | Object.keys(Mongoose.modelSchemas || []).forEach(function(key) {
58 | delete Mongoose?.modelSchemas[key]
59 | })
60 |
61 | return Mongoose.connection.db.dropDatabase()
62 | }
63 |
64 | Test('end to end tests', function(t) {
65 | mongoServer
66 | .getConnectionString()
67 | .then(() => {
68 | return BasicCrudTests(t, Mongoose, internals, Log, restore)
69 | })
70 | .then(function() {
71 | return DocAuthTests(t, Mongoose, internals, Log, restore)
72 | })
73 | .then(function() {
74 | return BasicEmbedRestTests(t, Mongoose, internals, Log, restore)
75 | })
76 | .then(function() {
77 | return BasicEmbedWrapperTests(t, Mongoose, internals, Log, restore)
78 | })
79 | .then(function() {
80 | return BasicNonEmbedTests(t, Mongoose, internals, Log, restore)
81 | })
82 | .then(function() {
83 | return AuditLogTests(t, Mongoose, internals, Log, restore)
84 | })
85 | .then(function() {
86 | return AdvanceAssocTests(t, Mongoose, internals, Log, restore)
87 | })
88 | .then(function() {
89 | return DuplicateFieldTests(t, Mongoose, internals, Log, restore)
90 | })
91 | .then(function() {
92 | return MiscTests(t, Mongoose, internals, Log, restore)
93 | })
94 | .then(function() {
95 | return t.test('clearing cache', function(t) {
96 | return Q.when().then(function() {
97 | Object.keys(require.cache).forEach(function(key) {
98 | delete require.cache[key]
99 | })
100 | restore(Mongoose)
101 |
102 | t.ok(true, 'DONE')
103 | })
104 | })
105 | })
106 | })
107 |
--------------------------------------------------------------------------------
/utilities/model-generator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const modelHelper = require('./model-helper')
4 | const authHelper = require('./auth-helper')
5 | const fs = require('fs')
6 | const path = require('path')
7 |
8 | /**
9 | * This module reads in all the model files and generates the corresponding mongoose models.
10 | * @param mongoose
11 | * @param logger
12 | * @param config
13 | * @returns {*|promise}
14 | */
15 | module.exports = function(mongoose, logger, config) {
16 | const Log = logger.bind('model-generator')
17 |
18 | const models = {}
19 | const schemas = {}
20 | let modelPath = ''
21 |
22 | if (config.absoluteModelPath === true) {
23 | modelPath = config.modelPath
24 | } else {
25 | modelPath = path.join(__dirname, '/../../../', config.modelPath)
26 | }
27 |
28 | return new Promise((resolve, reject) => {
29 | fs.readdir(modelPath, (err, files) => {
30 | if (err) {
31 | if (err.message.includes('no such file')) {
32 | Log.error(err)
33 | reject(
34 | new Error(
35 | 'The model directory provided is either empty or does not exist. ' +
36 | "Try setting the 'modelPath' property of the config file."
37 | )
38 | )
39 | } else {
40 | reject(err)
41 | }
42 | return
43 | }
44 |
45 | for (const file of files) {
46 | // EXPL: Import all the model schemas
47 | const ext = path.extname(file)
48 | if (ext === '.js') {
49 | const modelName = path.basename(file, '.js')
50 | const schema = require(modelPath + '/' + modelName)(mongoose)
51 |
52 | // EXPL: Add text index if enabled
53 | if (config.enableTextSearch) {
54 | schema.index({ '$**': 'text' })
55 | }
56 | schemas[schema.statics.collectionName] = schema
57 | }
58 | }
59 |
60 | if (config.enableAuditLog) {
61 | const schema = require('../models/audit-log.model')(mongoose)
62 | schemas[schema.statics.collectionName] = schema
63 | }
64 |
65 | const extendedSchemas = {}
66 |
67 | for (const schemaKey in schemas) {
68 | const schema = schemas[schemaKey]
69 | extendedSchemas[schemaKey] = modelHelper.extendSchemaAssociations(
70 | schema,
71 | mongoose,
72 | modelPath
73 | )
74 | }
75 |
76 | for (const schemaKey in extendedSchemas) {
77 | const schema = extendedSchemas[schemaKey]
78 | extendedSchemas[schemaKey] = modelHelper.addDuplicateFields(
79 | schema,
80 | schemas
81 | )
82 | }
83 |
84 | for (const schemaKey in extendedSchemas) {
85 | // EXPL: Create models with final schemas
86 | const schema = extendedSchemas[schemaKey]
87 | models[schemaKey] = modelHelper.createModel(schema, mongoose)
88 | }
89 |
90 | for (const modelKey in models) {
91 | // EXPL: Populate internal model associations
92 | const model = models[modelKey]
93 | modelHelper.associateModels(model.schema, models)
94 | }
95 |
96 | for (const modelKey in models) {
97 | // EXPL: Generate scopes if enabled
98 | if (config.generateRouteScopes) {
99 | const model = models[modelKey]
100 | authHelper.generateScopeForModel(model, logger)
101 | }
102 | }
103 |
104 | resolve(models)
105 | })
106 | })
107 | }
108 |
--------------------------------------------------------------------------------
/policies/track-duplicated-fields.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Boom = require('@hapi/boom')
4 | const _ = require('lodash')
5 |
6 | const internals = {}
7 |
8 | /**
9 | * Policy to update any duplicate fields when the original field changes.
10 | * @param model
11 | * @param logger
12 | * @returns {trackDuplicatedFields}
13 | */
14 | internals.trackDuplicatedFields = function(model, mongoose, logger) {
15 | const trackDuplicatedFieldsForModel = async function addDocumentScopeForModel(
16 | request,
17 | h
18 | ) {
19 | const Log = logger.bind('trackDuplicatedFields')
20 | try {
21 | if (_.isError(request.response)) {
22 | return h.continue
23 | }
24 | await internals.trackFields(
25 | model,
26 | mongoose,
27 | request.payload,
28 | request.response.source,
29 | Log
30 | )
31 | return h.continue
32 | } catch (err) {
33 | Log.error(err)
34 | throw Boom.badImplementation(err)
35 | }
36 | }
37 |
38 | trackDuplicatedFieldsForModel.applyPoint = 'onPostHandler'
39 |
40 | return trackDuplicatedFieldsForModel
41 | }
42 |
43 | internals.trackDuplicatedFields.applyPoint = 'onPostHandler'
44 |
45 | /**
46 | * Recursively updates all the duplicate fields.
47 | * @param model
48 | * @param mongoose
49 | * @param payload
50 | * @param result
51 | * @param logger
52 | * @returns {*}
53 | */
54 | internals.trackFields = function(model, mongoose, payload, result, logger) {
55 | const Log = logger.bind('trackFields')
56 | const promises = []
57 | for (const key in payload) {
58 | const field = model.schema.obj[key]
59 | // EXPL: Check each field that was updated. If the field has been duplicated, update each duplicate
60 | // field to match the new value.
61 | if (field && field.duplicated) {
62 | field.duplicated.forEach(function(duplicate) {
63 | const childModel = mongoose.model(duplicate.model)
64 | const newProp = {}
65 | newProp[duplicate.as] = result[key]
66 | const query = {}
67 | query[duplicate.association] = result._id
68 |
69 | promises.push(
70 | internals.findAndUpdate(mongoose, childModel, query, newProp, Log)
71 | )
72 | })
73 | }
74 | }
75 |
76 | return Promise.all(promises)
77 | }
78 |
79 | /**
80 | * Find the documents with duplicate fields and update.
81 | * @param mongoose
82 | * @param childModel
83 | * @param query
84 | * @param newProp
85 | * @param logger
86 | */
87 | internals.findAndUpdate = async function(
88 | mongoose,
89 | childModel,
90 | query,
91 | newProp,
92 | logger
93 | ) {
94 | const result = await childModel.find(query)
95 | const promises = []
96 |
97 | result.forEach(function(doc) {
98 | promises.push(
99 | internals.updateField(mongoose, childModel, doc._id, newProp, logger)
100 | )
101 | })
102 |
103 | return Promise.all(promises)
104 | }
105 |
106 | /**
107 | * Update a duplicate field for a single doc, then call 'trackDuplicateFields' in case any other docs are duplicating
108 | * the duplicate field.
109 | * @param mongoose
110 | * @param childModel
111 | * @param _id
112 | * @param newProp
113 | * @param logger
114 | */
115 | internals.updateField = async function(
116 | mongoose,
117 | childModel,
118 | _id,
119 | newProp,
120 | logger
121 | ) {
122 | const result = await childModel.findByIdAndUpdate(_id, newProp, { new: true })
123 | return internals.trackFields(childModel, mongoose, newProp, result, logger)
124 | }
125 |
126 | module.exports = {
127 | trackDuplicatedFields: internals.trackDuplicatedFields
128 | }
129 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rest-hapi",
3 | "version": "3.2.0",
4 | "description": "A RESTful API generator for hapi",
5 | "main": "rest-hapi.js",
6 | "bin": {
7 | "rest-hapi-cli": "./rest-hapi-cli.js"
8 | },
9 | "engines": {
10 | "node": ">=16.13.2",
11 | "npm": ">=8.1.2"
12 | },
13 | "directories": {
14 | "test": "tests"
15 | },
16 | "scripts": {
17 | "test": "npm run cover",
18 | "posttest": "npm run report-coverage",
19 | "cover": "npm run cover:unit && npm run cover:e2e",
20 | "cover:unit": "nyc --reporter=lcov --silent npm run test-unit",
21 | "cover:e2e": "nyc --reporter=lcov --silent --clean=false npm run test-e2e",
22 | "test-all": "tape ./tests/unit/*.tests.js && tape ./tests/e2e/*.tests.js",
23 | "test-unit": "tape ./tests/unit/*.tests.js",
24 | "test-e2e": "tape ./tests/e2e/*.tests.js",
25 | "test-joi": "tape ./tests/unit/joi-mongoose-helper.tests.js",
26 | "test-rest-helper": "tape ./tests/unit/rest-helper-factory.tests.js",
27 | "report-coverage": "nyc report --reporter=html --reporter=text-lcov > coverage.lcov && codecov",
28 | "patch-release-git": "git add . && git commit -a -m 'patch release' && git push && npm version patch && npm publish",
29 | "patch-release": "npm version patch && npm publish",
30 | "lint": "eslint --fix **/*.js ./"
31 | },
32 | "lint-staged": {
33 | "**/*.js": [
34 | "eslint --fix",
35 | "git add"
36 | ]
37 | },
38 | "repository": {
39 | "type": "git",
40 | "url": "git+https://github.com/JKHeadley/rest-hapi.git"
41 | },
42 | "keywords": [
43 | "hapi",
44 | "API",
45 | "RESTful",
46 | "mongoose",
47 | "generator"
48 | ],
49 | "author": {
50 | "name": "Justin Headley",
51 | "email": "headley.justin@gmail.com"
52 | },
53 | "license": "MIT",
54 | "bugs": {
55 | "url": "https://github.com/JKHeadley/rest-hapi/issues/new",
56 | "email": "headley.justin@gmail.com"
57 | },
58 | "homepage": "https://github.com/JKHeadley/rest-hapi#readme",
59 | "dependencies": {
60 | "@hapi/boom": "^9.1.0",
61 | "@hapi/hapi": "^20.2.2",
62 | "@hapi/hoek": "^9.0.4",
63 | "@hapi/inert": "^6.0.1",
64 | "@hapi/vision": "^6.0.0",
65 | "blue-tape": "^1.0.0",
66 | "chalk": "^4.0.0",
67 | "extend": "^3.0.2",
68 | "fs-extra": "^8.1.0",
69 | "hapi-swagger": "^14.5.5",
70 | "joi": "^17.6.0",
71 | "lodash": "~4.17.15",
72 | "loggin": "^3.0.2",
73 | "mongoose": "^6.4.6",
74 | "mrhorse": "^6.0.0",
75 | "prettier-config-standard": "^1.0.1",
76 | "query-string": "^6.8.3",
77 | "require-all": "^3.0.0",
78 | "tape": "latest"
79 | },
80 | "devDependencies": {
81 | "babel-eslint": "^10.0.3",
82 | "clear-require": "^3.0.0",
83 | "codecov": "^3.7.0",
84 | "decache": "4.5.1",
85 | "eslint": "^6.4.0",
86 | "eslint-config-prettier": "^6.3.0",
87 | "eslint-config-prettier-standard": "^3.0.1",
88 | "eslint-config-standard": "^14.1.0",
89 | "eslint-plugin-import": "^2.18.2",
90 | "eslint-plugin-node": "^10.0.0",
91 | "eslint-plugin-prettier": "^3.1.0",
92 | "eslint-plugin-promise": "^4.2.1",
93 | "eslint-plugin-standard": "^4.0.1",
94 | "husky": "^3.0.5",
95 | "lint-staged": "^9.2.5",
96 | "mkdirp": "^1.0.3",
97 | "mongodb-memory-server": "^6.4.1",
98 | "nyc": "^15.0.0",
99 | "prettier": "1.18.2",
100 | "proxyquire": "^2.1.3",
101 | "q": "^1.5.1",
102 | "rewire": "^6.0.0",
103 | "sinon": "^7.0.0",
104 | "sinon-test": "^2.3.0"
105 | },
106 | "husky": {
107 | "hooks": {
108 | "pre-push": "lint-staged"
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/website/siteConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017-present, Facebook, Inc.
3 | *
4 | * This sourcecs/site-config.html for all the possible
5 | // site configuration options. code is licensed under the MIT license found in the
6 | // * LICENSE file in the root directory of this source tree.
7 | // */
8 | //
9 | // // See https://docusaurus.io/do
10 |
11 | /* List of projects/orgs using your project for the users page */
12 | const users = require('./data/users')
13 |
14 | const siteConfig = {
15 | title: 'rest-hapi' /* title for your website */,
16 | tagline: 'A RESTful API generator ',
17 | url: 'https://resthapi.com' /* your website url */,
18 | cname: 'resthapi.com',
19 | baseUrl: '/' /* base url for your project */,
20 | repoUrl: 'https://github.com/JKHeadley/rest-hapi',
21 | // For github.io type URLs, you would set the url and baseUrl like:
22 | // url: 'https://facebook.github.io',
23 | // baseUrl: '/test-site/',
24 |
25 | // Used for publishing and more
26 | projectName: 'rest-hapi',
27 | organizationName: 'JKHeadley',
28 | // For top-level user or org sites, the organization is still the same.
29 | // e.g., for the https://JoelMarcey.github.io site, it would be set like...
30 | // organizationName: 'JoelMarcey'
31 |
32 | // For no header links in the top nav bar -> headerLinks: [],
33 | headerLinks: [
34 | { doc: 'quick-start', label: 'Docs' },
35 | // {doc: 'doc4', label: 'API'},
36 | { page: 'help', label: 'Help' },
37 | { blog: true, label: 'Blog' },
38 | { page: 'users', label: 'Users' },
39 | {
40 | href: 'https://github.com/JKHeadley/rest-hapi',
41 | label: 'GitHub'
42 | }
43 | ],
44 |
45 | // If you have users set above, you add it here:
46 | users,
47 |
48 | /* path to images for header/footer */
49 | headerIcon: 'img/rest-hapi-logo.png',
50 | footerIcon: 'img/rest-hapi-logo-alt.png',
51 | favicon: 'img/rest-hapi-logo-alt.png',
52 |
53 | /* colors for website */
54 | // colors: {
55 | // primaryColor: '#195085',
56 | // secondaryColor: '#0d385c',
57 | // },
58 |
59 | colors: {
60 | primaryColor: '#f6941e',
61 | secondaryColor: '#f9ad00'
62 | },
63 |
64 | /* custom fonts for website */
65 | /* fonts: {
66 | myFont: [
67 | "Times New Roman",
68 | "Serif"
69 | ],
70 | myOtherFont: [
71 | "-apple-system",
72 | "system-ui"
73 | ]
74 | }, */
75 |
76 | // This copyright info is used in /core/Footer.js and blog rss/atom feeds.
77 | copyright: 'Copyright © ' + new Date().getFullYear() + ' Justin Headley',
78 |
79 | highlight: {
80 | // Highlight.js theme to use for syntax highlighting in code blocks
81 | theme: 'atom-one-dark'
82 | },
83 |
84 | // Add custom scripts here that would be placed in