├── .gitignore
├── .prettierrc
├── .travis.yml
├── .travis
├── deploy.key.enc
├── public-deploy.sh
├── public-patch.js
├── public-postinstall.sh
└── public-preinstall.sh
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README_DEV.md
├── angular.json
├── app.json
├── apps
└── demo
│ ├── jest.config.js
│ ├── src
│ ├── app
│ │ ├── app.module.ts
│ │ ├── config
│ │ │ ├── config.interface.ts
│ │ │ └── config.ts
│ │ ├── i18n
│ │ │ └── template.pot
│ │ └── index.ts
│ ├── assets
│ │ └── .gitkeep
│ └── main.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── appveyor.yml
├── bin
└── post_compile
├── client
└── .gitkeep
├── database
└── .gitkeep
├── develop._env
├── libs
└── rucken
│ ├── auth-nestjs
│ ├── LICENSE
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── auth.module.ts
│ │ ├── configs
│ │ │ ├── facebook.config.ts
│ │ │ ├── google-plus.config.ts
│ │ │ ├── index.ts
│ │ │ └── jwt.config.ts
│ │ ├── controllers
│ │ │ ├── auth.controller.ts
│ │ │ └── index.ts
│ │ ├── dto
│ │ │ ├── facebook-signIn.dto.ts
│ │ │ ├── facebook-token.dto.ts
│ │ │ ├── google-plus-signIn.dto.ts
│ │ │ ├── redirect-uri.dto.ts
│ │ │ ├── sign-in.dto.ts
│ │ │ ├── sign-up.dto.ts
│ │ │ ├── token.dto.ts
│ │ │ └── user-token.dto.ts
│ │ ├── entities
│ │ │ ├── index.ts
│ │ │ └── oauth-tokens-accesstoken.entity.ts
│ │ ├── filters
│ │ │ ├── custom-exception.filter.ts
│ │ │ └── index.ts
│ │ ├── guards
│ │ │ ├── access.guard.ts
│ │ │ └── index.ts
│ │ ├── i18n
│ │ │ └── template.pot
│ │ ├── index.ts
│ │ ├── interfaces
│ │ │ ├── facebook-config.interface.ts
│ │ │ ├── google-plus-config.interface.ts
│ │ │ ├── jwt-config.interface.ts
│ │ │ └── jwt-payload.interface.ts
│ │ ├── migrations
│ │ │ └── 1533634559617-AddOauthTokensAccesstokenTable.ts
│ │ ├── passport
│ │ │ ├── facebook.strategy.ts
│ │ │ ├── google-plus.strategy.ts
│ │ │ ├── index.ts
│ │ │ ├── jwt.strategy.ts
│ │ │ └── local.strategy.ts
│ │ └── services
│ │ │ ├── auth.service.ts
│ │ │ ├── index.ts
│ │ │ ├── oauth-tokens-accesstokens.service.ts
│ │ │ └── token.service.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
│ └── core-nestjs
│ ├── LICENSE
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── configs
│ │ ├── core.config.ts
│ │ └── index.ts
│ ├── controllers
│ │ ├── account.controller.ts
│ │ ├── content-types.controller.ts
│ │ ├── groups.controller.ts
│ │ ├── index.ts
│ │ ├── permissions.controller.ts
│ │ └── users.controller.ts
│ ├── core.module.ts
│ ├── decorators
│ │ ├── permissions.decorator.ts
│ │ └── roles.decorator.ts
│ ├── dto
│ │ ├── account.dto.ts
│ │ ├── content-type.dto.ts
│ │ ├── group-with-permissions.dto.ts
│ │ ├── group.dto.ts
│ │ ├── in-account.dto.ts
│ │ ├── in-content-type.dto.ts
│ │ ├── in-create-user.dto.ts
│ │ ├── in-group.dto.ts
│ │ ├── in-permission.dto.ts
│ │ ├── in-user.dto.ts
│ │ ├── meta.dto.ts
│ │ ├── out-account.dto.ts
│ │ ├── out-content-type.dto.ts
│ │ ├── out-content-types.dto.ts
│ │ ├── out-group.dto.ts
│ │ ├── out-groups.dto.ts
│ │ ├── out-permission.dto.ts
│ │ ├── out-permissions.dto.ts
│ │ ├── out-user.dto.ts
│ │ ├── out-users.dto.ts
│ │ ├── permission.dto.ts
│ │ └── user.dto.ts
│ ├── entities
│ │ ├── content-type.entity.ts
│ │ ├── group.entity.ts
│ │ ├── index.ts
│ │ ├── permission.entity.ts
│ │ └── user.entity.ts
│ ├── exceptions
│ │ ├── custom-validation.error.ts
│ │ └── custom.error.ts
│ ├── filters
│ │ ├── custom-exception.filter.ts
│ │ └── index.ts
│ ├── i18n
│ │ └── template.pot
│ ├── index.ts
│ ├── interfaces
│ │ └── core-config.interface.ts
│ ├── migrations
│ │ ├── 1524197725191-Init.ts
│ │ ├── 1524199022084-FillData.ts
│ │ └── 1524199144534-FillFrontendData.ts
│ ├── migrations_entities
│ │ └── 1524199022084
│ │ │ ├── content-type.entity.ts
│ │ │ ├── group.entity.ts
│ │ │ ├── permission.entity.ts
│ │ │ └── user.entity.ts
│ ├── pipes
│ │ ├── index.ts
│ │ ├── parse-int-with-default.pipe.ts
│ │ └── validation.pipe.ts
│ ├── services
│ │ ├── account.service.ts
│ │ ├── content-types.service.ts
│ │ ├── groups.service.ts
│ │ ├── index.ts
│ │ ├── permissions.service.ts
│ │ └── users.service.ts
│ └── utils
│ │ └── custom-transforms.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── nx.json
├── ormconfig.js
├── package-lock.json
├── package.json
├── scripts
├── patch.js
├── postinstall.sh
├── publish.sh
└── version-bump.sh
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Credentials
2 | .travis/*.key
3 | .travis/*.key.pub
4 |
5 | # config
6 | *.env
7 |
8 | deploy
9 |
10 | # IDE
11 | .idea/
12 | .vscode/
13 | node_modules/
14 | build/
15 | tmp/
16 | temp/
17 | /.idea
18 | /.awcache
19 | /.vscode
20 |
21 | # misc
22 | npm-debug.log
23 |
24 | # example
25 | /quick-start
26 |
27 | # tests
28 | /test
29 | /coverage
30 | /.nyc_output
31 |
32 | # dist
33 | /dist
34 | /database/sqlitedb.db
35 | /client/docs
36 | /client/index.html
37 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "singleQuote": true,
4 | "useTabs": false,
5 | "tabWidth": 2,
6 | "semi": true,
7 | "bracketSpacing": true
8 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '11'
4 | addons:
5 | ssh_known_hosts:
6 | - "$REMOTE_HOST"
7 | - "$REMOTE_HOST_IP"
8 | after_success:
9 | - chmod +x ".travis/public-deploy.sh"
10 | - ".travis/public-deploy.sh"
11 | before_install:
12 | - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then openssl aes-256-cbc -K $encrypted_bcbda17145e7_key -iv $encrypted_bcbda17145e7_iv
13 | -in .travis/deploy.key.enc -out .travis/deploy.key -d; fi'
14 |
--------------------------------------------------------------------------------
/.travis/deploy.key.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rucken/core-nestjs/39079cdec0932ae817b294a3972633fa7c68546e/.travis/deploy.key.enc
--------------------------------------------------------------------------------
/.travis/public-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # REMOTE_HOST=https://domain.name
4 | # REMOTE_HOST_IP=127.0.0.1
5 | # REMOTE_HOST_GIT_URL=dokku@127.0.0.1:dokku-app-name
6 |
7 | # For testing public-deploy.sh on local machine, set LOCAL=true
8 |
9 | setup_ssh() {
10 | eval "$(ssh-agent -s)"
11 | chmod 600 .travis/deploy.key
12 | ssh-add .travis/deploy.key
13 | ssh-keyscan ${REMOTE_HOST} >> $HOME/.ssh/known_hosts
14 | ssh-keyscan ${REMOTE_HOST_IP} >> $HOME/.ssh/known_hosts
15 | }
16 | create_folder(){
17 | rm -rf ./deploy
18 | mkdir deploy
19 | }
20 | setup_git() {
21 | git config user.email "travis@travis-ci.org"
22 | git config user.name "Travis CI"
23 | cd deploy
24 | git init
25 | git remote add deploy "${REMOTE_HOST_GIT_URL}" > /dev/null 2>&1
26 | git config push.default simple
27 | git remote -v
28 | git fetch deploy master
29 | git pull deploy master
30 | cd ..
31 | }
32 | copy_files() {
33 | ./scripts/postinstall.sh
34 | rm -rf ./deploy/scripts
35 | rm -rf ./deploy/dist
36 | rm -rf ./deploy/client
37 | rm -rf ./dist/node_modules
38 | rm -rf ./dist/rucken/core-nestjs/node_modules
39 | rm -rf ./dist/rucken/auth-nestjs/node_modules
40 | mkdir ./deploy/scripts
41 | mkdir ./deploy/dist
42 | mkdir ./deploy/client
43 | cp -rf ./scripts/* ./deploy/scripts
44 | cp -rf ./dist/* ./deploy/dist
45 | cp -rf ./client/* ./deploy/client
46 | cp -f ./package.json ./deploy/package.json
47 | cp -f ./.gitignore ./deploy/.gitignore
48 | cp -f ./ormconfig.js ./deploy/ormconfig.js
49 | cp -f ./angular.json ./deploy/angular.json
50 | cp -f ./tsconfig.json ./deploy/tsconfig.json
51 | cp -f ./README.md ./deploy/README.md
52 | cp -f ./.travis/public-postinstall.sh ./deploy/scripts/postinstall.sh
53 | cp -f ./.travis/public-preinstall.sh ./deploy/scripts/preinstall.sh
54 | node ./.travis/public-patch.js
55 | }
56 | commit_files() {
57 | cd deploy
58 | git add .
59 | git commit --message "Version: $PACKAGE_VERSION Commit: $TRAVIS_COMMIT"
60 | git push --quiet --set-upstream deploy master
61 | cd ..
62 | }
63 |
64 | upload_files() {
65 | rm -rf .git
66 | cd deploy
67 | git push deploy master
68 | cd ..
69 | }
70 |
71 | if [[ $TRAVIS_BRANCH == 'master' ]]
72 | then
73 | PACKAGE_VERSION=$(cat package.json \
74 | | grep version \
75 | | head -1 \
76 | | awk -F: '{ print $2 }' \
77 | | sed 's/[",]//g')
78 | export PACKAGE_VERSION=$PACKAGE_VERSION
79 |
80 | setup_ssh
81 | create_folder
82 | setup_git
83 | copy_files
84 | commit_files
85 | upload_files
86 | fi
87 | if [[ $LOCAL == 'true' ]]
88 | then
89 | create_folder
90 | copy_files
91 | fi
--------------------------------------------------------------------------------
/.travis/public-patch.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const f_gitignore = './deploy/.gitignore';
3 | const f_package_json = './deploy/package.json';
4 | const f_tsconfig_json = './deploy/tsconfig.json';
5 |
6 | var data = '', obj = {}, txt = '';
7 |
8 | data = fs.readFileSync(f_gitignore, 'utf8');
9 | txt = data.replace(/\/dist/g, '').replace(/\/client\/docs/g, '').replace(/\/client\/index.html/g, '');
10 | fs.writeFileSync(f_gitignore, txt, 'utf8');
11 |
12 | console.log('Updated .gitignore');
13 |
14 | data = fs.readFileSync(f_package_json, 'utf8');
15 | obj = JSON.parse(data);
16 | delete obj['devDependencies'];
17 | obj.scripts.test = 'exit 0';
18 | obj.scripts.build = 'npm run migrate';
19 | obj.scripts.preinstall = 'sh ./scripts/preinstall.sh';
20 | fs.writeFileSync(f_package_json, JSON.stringify(obj), 'utf8');
21 |
22 | console.log('Updated package.json');
23 |
24 | data = fs.readFileSync(f_tsconfig_json, 'utf8');
25 | obj = JSON.parse(data);
26 | externalLibs = obj.externalLibs || [];
27 | for (var i = 0; i < externalLibs.length; i++) {
28 | const externalLib = externalLibs[i];
29 | try {
30 | const externalLibData = fs.readFileSync(f_tsconfig_json, 'utf8');
31 | const externalLibObj = JSON.parse(externalLibData);
32 | const externalLibName = externalLibObj.name;
33 | if (
34 | obj.compilerOptions &&
35 | obj.compilerOptions.paths &&
36 | !obj.compilerOptions.paths[name]
37 | ) {
38 | obj.compilerOptions[name] = [
39 | externalLib
40 | ];
41 | obj.compilerOptions[name + '/*'] = [
42 | externalLib + '/*'
43 | ];
44 | }
45 | } catch (error) {
46 |
47 | }
48 | }
49 | fs.writeFileSync(f_tsconfig_json, JSON.stringify(obj), 'utf8');
50 |
51 | console.log('Updated tsconfig.json');
52 |
--------------------------------------------------------------------------------
/.travis/public-postinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | node ./scripts/patch.js
--------------------------------------------------------------------------------
/.travis/public-preinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | npm i --save ./dist/rucken/core-nestjs
3 | npm i --save ./dist/rucken/auth-nestjs
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rucken-core-nestjs
2 |
3 | [](https://greenkeeper.io/)
4 | [![NPM version][npm-image]][npm-url]
5 | [![Build Status][travis-image]][travis-url]
6 | [](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master)
7 | [![dependencies-release][dependencies-image]][dependencies-url]
8 |
9 | [](https://heroku.com/deploy?template=https://github.com/rucken/core-nestjs)
10 |
11 | A simple application demonstrating the basic usage of permissions with [NestJS](https://github.com/nestjs/nest) (JWT, Passport, Facebook, Google+, User, Group, Permission) based on [Rucken](https://rucken.ru) template
12 |
13 | ## Screenshots
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## Features
21 |
22 | - [NestJS](https://github.com/nestjs/nest) - a JS backend framework providing architecture out of the box with a syntax similar to Angular
23 | - [TypeORM](http://typeorm.io/) - ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases.
24 | - [TypeScript](https://github.com/Microsoft/TypeScript) - superset of JS which compiles to JS, providing compile-time type checking
25 | - [Passport](https://github.com/jaredhanson/passport) - a popular library used to implement JavaScript authentication (Facebook, Google+)
26 | - [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) - a JavaScript json web tokens implementation by auth0
27 | - [@nrwl/schematics](https://github.com/nrwl/nx/blob/master/packages/schematics/src/collection.json) - Angular CLI power-ups for modern development, example usage: `ng g @nrwl/schematics:node-app app-name --framework nestjs`
28 | - [@nestjs/schematics](https://github.com/nestjs/schematics/blob/master/src/collection.json) - Nest architecture element generation based on Angular schematics, example usage: `ng g @nestjs/schematics:library lib-name`
29 |
30 | ## Usage
31 |
32 | - clone or fork [repository](https://github.com/rucken/core-nestjs.git) `git clone --recursive https://github.com/rucken/core-nestjs.git`
33 | - make sure you have [node.js](https://nodejs.org/) installed version 11+
34 | - copy `develop._env` to `develop.env` and set environments for use (on Windows copy with IDE)
35 | - run `npm install` to install project dependencies
36 | - run `npm run build` to install project dependencies
37 | - run `npm run start:prod` to fire up prod server (`npm run start:dev` - dev server)
38 | - Open browser to [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
39 |
40 | ## Demo
41 |
42 | [https://core-nestjs.rucken.ru](https://core-nestjs.rucken.ru) - Application with [Sqlite](https://www.sqlite.org/index.html) Database on VPS with [Dokku](http://dokku.viewdocs.io/dokku/)
43 |
44 | ### Users
45 |
46 | - user with admin group: admin@admin.com, password: 12345678
47 | - user with user group: user1@user1.com, password: 12345678
48 | - user with user group: user2@user2.com, password: 12345678
49 |
50 | ### Swagger
51 |
52 | - local: [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
53 | - online: [`https://core-nestjs.rucken.ru/swagger`](https://core-nestjs.rucken.ru/swagger)
54 | - apiKey template: `JWT `
55 |
56 | ## Typedoc documentations
57 |
58 | - local: [`http://localhost:5000/docs`](http://localhost:5000/docs)
59 | - online: [`https://core-nestjs.rucken.ru/docs`](https://core-nestjs.rucken.ru/docs)
60 |
61 | ## Quick links
62 |
63 | ### Frontend (with core)
64 |
65 | [@rucken/core](https://github.com/rucken/core) - Core with Admin UI for web application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
66 |
67 | [@rucken/todo](https://github.com/rucken/todo) - Core with UI for web todo application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
68 |
69 | ### Backend
70 |
71 | [@rucken/core-nestjs](https://github.com/rucken/core-nestjs) - A simple application demonstrating the basic usage of permissions with [NestJS](https://nestjs.com/) (JWT, Passport, Facebook, Google+, User, Group, Permission).
72 |
73 | [@rucken/todo-nestjs](https://github.com/rucken/todo-nestjs) - A simple todo application with [NestJS](https://nestjs.com/) (Projects, Tasks, Statuses).
74 |
75 | ### Mobile
76 |
77 | [@rucken/ionic](https://github.com/rucken/ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile application.
78 |
79 | [@rucken/todo-ionic](https://github.com/rucken/todo-ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile todo application
80 |
81 | ### Console
82 |
83 | [@rucken/cli](https://github.com/rucken/cli) - Console tools to create and build [Angular7+](https://angular.io/) and [NestJS](https://nestjs.com/) application based on [Rucken](https://github.com/rucken) template
84 |
85 | ## License
86 |
87 | MIT
88 |
89 | [travis-image]: https://travis-ci.org/rucken/core-nestjs.svg?branch=master
90 | [travis-url]: https://travis-ci.org/rucken/core-nestjs
91 | [dependencies-image]: https://david-dm.org/rucken/core-nestjs/status.svg
92 | [dependencies-url]: https://david-dm.org/rucken/core-nestjs
93 | [npm-image]: https://badge.fury.io/js/%40rucken%2Fcore-nestjs.svg
94 | [npm-url]: https://npmjs.org/package/@rucken/core-nestjs
95 |
--------------------------------------------------------------------------------
/README_DEV.md:
--------------------------------------------------------------------------------
1 | ## Create migration
2 |
3 | ```bash
4 | npm run migrate:create -- -n MigrationName
5 | npm run migrate:generate -- -n MigrationName
6 | npm run migrate:run
7 | ```
8 |
9 | ## Run with custom env file
10 | ```bash
11 | ./node_modules/.bin/cross-env NODE_ENV=production npm run start
12 | ./node_modules/.bin/cross-env NODE_ENV=postgres npm run build
13 | ./node_modules/.bin/cross-env NODE_ENV=postgres npm run start:dev
14 | ./node_modules/.bin/cross-env NODE_ENV=postgres npm run start
15 | ```
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "",
5 | "projects": {
6 | "core": {
7 | "root": "libs/rucken/core-nestjs",
8 | "sourceRoot": "libs/rucken/core-nestjs/src",
9 | "projectType": "library",
10 | "prefix": "",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@nrwl/builders:run-commands",
15 | "options": {
16 | "outputPath": "dist/rucken/core-nestjs",
17 | "commands": [
18 | {
19 | "command": "del-cli ./dist/rucken/core-nestjs"
20 | },
21 | {
22 | "command": "tsc --project ./libs/rucken/core-nestjs/tsconfig.lib.json -d --outDir ./dist/rucken/core-nestjs"
23 | },
24 | {
25 | "command": "cp-cli ./libs/rucken/core-nestjs/package.json ./dist/rucken/core-nestjs/package.json"
26 | },
27 | {
28 | "command": "cp-cli ./libs/rucken/core-nestjs/README.md ./dist/rucken/core-nestjs/README.md"
29 | },
30 | {
31 | "command": "cp-cli ./libs/rucken/core-nestjs/LICENSE ./dist/rucken/core-nestjs/LICENSE"
32 | },
33 | {
34 | "command": "npm link ./dist/rucken/core-nestjs"
35 | }
36 | ],
37 | "parallel": false
38 | }
39 | },
40 | "lint": {
41 | "builder": "@angular-devkit/build-angular:tslint",
42 | "options": {
43 | "tsConfig": [
44 | "libs/rucken/core-nestjs/tsconfig.lib.json",
45 | "libs/rucken/core-nestjs/tsconfig.spec.json"
46 | ],
47 | "exclude": [
48 | "**/node_modules/**"
49 | ]
50 | }
51 | },
52 | "test": {
53 | "builder": "@nrwl/builders:jest",
54 | "options": {
55 | "jestConfig": "libs/rucken/core-nestjs/jest.config.js",
56 | "tsConfig": "libs/rucken/core-nestjs/tsconfig.spec.json"
57 | }
58 | }
59 | }
60 | },
61 | "auth": {
62 | "root": "libs/rucken/auth-nestjs",
63 | "sourceRoot": "libs/rucken/auth-nestjs/src",
64 | "projectType": "library",
65 | "prefix": "",
66 | "schematics": {},
67 | "architect": {
68 | "build": {
69 | "builder": "@nrwl/builders:run-commands",
70 | "options": {
71 | "outputPath": "dist/rucken/auth-nestjs",
72 | "commands": [
73 | {
74 | "command": "del-cli ./dist/rucken/auth-nestjs"
75 | },
76 | {
77 | "command": "tsc --project ./libs/rucken/auth-nestjs/tsconfig.lib.json -d --outDir ./dist/rucken/auth-nestjs"
78 | },
79 | {
80 | "command": "cp-cli ./libs/rucken/auth-nestjs/package.json ./dist/rucken/auth-nestjs/package.json"
81 | },
82 | {
83 | "command": "cp-cli ./libs/rucken/auth-nestjs/README.md ./dist/rucken/auth-nestjs/README.md"
84 | },
85 | {
86 | "command": "cp-cli ./libs/rucken/auth-nestjs/LICENSE ./dist/rucken/auth-nestjs/LICENSE"
87 | },
88 | {
89 | "command": "npm link ./dist/rucken/auth-nestjs"
90 | }
91 | ],
92 | "parallel": false
93 | }
94 | },
95 | "lint": {
96 | "builder": "@angular-devkit/build-angular:tslint",
97 | "options": {
98 | "tsConfig": [
99 | "libs/rucken/auth-nestjs/tsconfig.lib.json",
100 | "libs/rucken/auth-nestjs/tsconfig.spec.json"
101 | ],
102 | "exclude": [
103 | "**/node_modules/**"
104 | ]
105 | }
106 | },
107 | "test": {
108 | "builder": "@nrwl/builders:jest",
109 | "options": {
110 | "jestConfig": "libs/rucken/auth-nestjs/jest.config.js",
111 | "tsConfig": "libs/rucken/auth-nestjs/tsconfig.spec.json"
112 | }
113 | }
114 | }
115 | },
116 | "demo": {
117 | "root": "apps/demo",
118 | "sourceRoot": "apps/demo/src",
119 | "projectType": "application",
120 | "prefix": "",
121 | "schematics": {},
122 | "architect": {
123 | "build": {
124 | "builder": "@nrwl/builders:run-commands",
125 | "options": {
126 | "outputPath": "dist/demo",
127 | "commands": [
128 | {
129 | "command": "del-cli ./dist/demo"
130 | },
131 | {
132 | "command": "tsc --project ./apps/demo/tsconfig.app.json --outDir ./dist/demo"
133 | },
134 | {
135 | "command": "showdown makehtml -i README.md -o client/index.html"
136 | }
137 | ],
138 | "parallel": false
139 | }
140 | },
141 | "serve": {
142 | "builder": "@nrwl/builders:run-commands",
143 | "options": {
144 | "commands": [
145 | {
146 | "command": "rucken prepare -m dev"
147 | },
148 | {
149 | "command": "nodemon --ext 'ts' --watch 'apps' --watch 'libs' --ignore 'apps/**/*.spec.ts' --ignore 'libs/**/*.spec.ts' --exec ts-node -r tsconfig-paths/register ./apps/demo/src/main.ts"
150 | }
151 | ],
152 | "parallel": false
153 | }
154 | },
155 | "serve-prod": {
156 | "builder": "@nrwl/builders:run-commands",
157 | "options": {
158 | "commands": [
159 | {
160 | "command": "rucken prepare -m prod"
161 | },
162 | {
163 | "command": "cross-env NODE_ENV=production node dist/demo/main.js"
164 | }
165 | ],
166 | "parallel": false
167 | }
168 | },
169 | "lint": {
170 | "builder": "@angular-devkit/build-angular:tslint",
171 | "options": {
172 | "tsConfig": [
173 | "apps/demo/tsconfig.app.json",
174 | "apps/demo/tsconfig.spec.json"
175 | ],
176 | "exclude": [
177 | "**/node_modules/**"
178 | ]
179 | }
180 | },
181 | "test": {
182 | "builder": "@nrwl/builders:jest",
183 | "options": {
184 | "jestConfig": "apps/demo/jest.config.js",
185 | "tsConfig": "apps/demo/tsconfig.spec.json"
186 | }
187 | }
188 | }
189 | }
190 | },
191 | "cli": {
192 | "warnings": {
193 | "typescriptMismatch": false,
194 | "versionMismatch": false
195 | },
196 | "defaultCollection": "@nrwl/schematics",
197 | "packageManager": "npm"
198 | },
199 | "schematics": {
200 | "@nrwl/schematics:application": {
201 | "style": "scss"
202 | },
203 | "@nrwl/schematics:library": {
204 | "style": "scss"
205 | }
206 | },
207 | "defaultProject": "demo"
208 | }
209 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rucken-core-nestjs",
3 | "description": "A simple application demonstrating the basic usage of permissions with NestJS (JWT, Passport, Facebook, Google+, User, Group, Permission)",
4 | "repository": "https://github.com/rucken/core-nestjs",
5 | "logo": "https://avatars0.githubusercontent.com/u/32410498?s=200&v=4",
6 | "keywords": [
7 | "jwt",
8 | "oauth",
9 | "passport",
10 | "facebook",
11 | "google+",
12 | "user",
13 | "group",
14 | "permission",
15 | "contenttype",
16 | "bootstrap",
17 | "nestjs",
18 | "heroku",
19 | "typedoc"
20 | ],
21 | "env": {
22 | "DEBUG": "false",
23 | "DEMO": "false",
24 | "DOMAIN": "localhost",
25 | "PROTOCOL": "https",
26 | "EXTERNAL_PORT": "5000",
27 | "NODE_MODULES_CACHE": "false",
28 | "NPM_CONFIG_PRODUCTION": "false",
29 | "JWT_SECRET_KEY": "secret_key",
30 | "JWT_EXPIRATION_DELTA": "7 days",
31 | "JWT_AUTH_HEADER_PREFIX": "JWT",
32 | "FACEBOOK_CLIENT_ID": "none",
33 | "FACEBOOK_CLIENT_SECRET": "none",
34 | "FACEBOOK_OAUTH_REDIRECT_URI": "http://localhost:5000/",
35 | "GOOGLE_CLIENT_ID": "none",
36 | "GOOGLE_CLIENT_SECRET": "none",
37 | "GOOGLE_OAUTH_REDIRECT_URI": "http://localhost:5000/",
38 | "DATABASE_URL": "sqlite://database/sqlitedb.db"
39 | }
40 | }
--------------------------------------------------------------------------------
/apps/demo/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'demo',
3 | preset: '../../jest.config.js',
4 | coverageDirectory: '../../coverage/demo'
5 | };
6 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module, Provider } from '@nestjs/common';
2 | import { PassportModule } from '@nestjs/passport';
3 | import { TypeOrmModule } from '@nestjs/typeorm';
4 | import { RuckenAuthModule } from '@rucken/auth-nestjs';
5 | import { RuckenCoreModule } from '@rucken/core-nestjs';
6 | @Module({})
7 | export class AppModule {
8 | static forRoot(options: { providers: Provider[]; passportProviders: Provider[] }): DynamicModule {
9 | return {
10 | module: AppModule,
11 | imports: [
12 | PassportModule.register({ defaultStrategy: 'jwt' }),
13 | RuckenCoreModule.forRoot(options),
14 | RuckenAuthModule.forRoot(options),
15 | TypeOrmModule.forRoot()
16 | ],
17 | providers: [...options.providers, ...(options.passportProviders ? options.passportProviders : [])]
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/demo/src/app/config/config.interface.ts:
--------------------------------------------------------------------------------
1 | import { Provider } from '@nestjs/common';
2 | import { ConnectionString } from 'connection-string';
3 |
4 | export interface IDemoConfig {
5 | env: {
6 | name: string;
7 | port: number;
8 | protocol: 'http' | 'https';
9 | };
10 | project: {
11 | path: string;
12 | tsconfig: {
13 | compilerOptions: { paths: { [key: string]: string[] } };
14 | };
15 | package: any;
16 | staticFolders: string[];
17 | };
18 | db: {
19 | connectionString: ConnectionString;
20 | file: string;
21 | };
22 | core: {
23 | providers: () => Provider[];
24 | };
25 | auth: {
26 | providers: () => Provider[];
27 | passportProviders: () => Provider[];
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/apps/demo/src/app/config/config.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from '@nestjs/common';
2 | import {
3 | AUTH_APP_FILTERS,
4 | AUTH_APP_GUARDS,
5 | AUTH_PASSPORT_STRATEGIES,
6 | DEFAULT_FACEBOOK_CONFIG,
7 | DEFAULT_GOOGLE_PLUS_CONFIG,
8 | DEFAULT_JWT_CONFIG,
9 | FACEBOOK_CONFIG_TOKEN,
10 | GOOGLE_PLUS_CONFIG_TOKEN,
11 | JWT_CONFIG_TOKEN
12 | } from '@rucken/auth-nestjs';
13 | import { CORE_APP_FILTERS, CORE_APP_PIPES, CORE_CONFIG_TOKEN, DEFAULT_CORE_CONFIG } from '@rucken/core-nestjs';
14 | import { ConnectionString } from 'connection-string';
15 | import { config as load } from 'dotenv';
16 | import { accessSync, readFileSync } from 'fs';
17 | import * as path from 'path';
18 | import { IDemoConfig } from './config.interface';
19 |
20 | const NODE_ENV = process.env.NODE_ENV || 'develop';
21 | const envFile = resolveRootFile(`${NODE_ENV}.env`);
22 | const connectionString = new ConnectionString(process.env.DATABASE_URL || 'sqlite://database/sqlitedb.db');
23 | const dbFile =
24 | connectionString.protocol === 'sqlite'
25 | ? './' +
26 | (connectionString.hosts ? connectionString.hosts[0].name : '') +
27 | (connectionString.path ? '/' + connectionString.path[0] : '')
28 | : '';
29 | try {
30 | accessSync(envFile);
31 | load({ path: envFile });
32 | Logger.log(`env file: ${envFile}`, 'Main');
33 | } catch (error) {
34 | Logger.log(`error on get env file: ${envFile}`, 'Main');
35 | try {
36 | accessSync(`.env`);
37 | load();
38 | Logger.log(`env file: .env`, 'Main');
39 | } catch (error) {
40 | Logger.log(`error on get env file: .env`, 'Main');
41 | }
42 | }
43 |
44 | export const config: IDemoConfig = {
45 | env: {
46 | name: NODE_ENV,
47 | port: process.env.PORT ? +process.env.PORT : 5000,
48 | protocol: process.env.PROTOCOL === 'https' ? 'https' : 'http'
49 | },
50 | project: {
51 | path: getRootPath(),
52 | tsconfig: loadRootJson('tsconfig.json'),
53 | package: loadRootJson('package.json'),
54 | staticFolders: [resolveRootFile('client')]
55 | },
56 | db: {
57 | connectionString: connectionString,
58 | file: dbFile
59 | },
60 | core: {
61 | providers: () => [
62 | {
63 | provide: CORE_CONFIG_TOKEN,
64 | useValue: {
65 | ...DEFAULT_CORE_CONFIG,
66 | demo: process.env.DEMO === 'true',
67 | port: process.env.PORT ? +process.env.PORT : 5000,
68 | protocol: process.env.PROTOCOL === 'https' ? 'https' : 'http',
69 | externalPort: process.env.EXTERNAL_PORT ? +process.env.EXTERNAL_PORT : undefined,
70 | domain: process.env.DOMAIN || ''
71 | }
72 | },
73 | ...CORE_APP_FILTERS,
74 | ...CORE_APP_PIPES
75 | ]
76 | },
77 | auth: {
78 | providers: () => [
79 | {
80 | provide: JWT_CONFIG_TOKEN,
81 | useValue: {
82 | ...DEFAULT_JWT_CONFIG,
83 | authHeaderPrefix: process.env.JWT_AUTH_HEADER_PREFIX || 'JWT',
84 | expirationDelta: process.env.JWT_EXPIRATION_DELTA || '7 days',
85 | secretKey: process.env.JWT_SECRET_KEY || 'secret_key'
86 | }
87 | },
88 | {
89 | provide: FACEBOOK_CONFIG_TOKEN,
90 | useValue: {
91 | ...DEFAULT_FACEBOOK_CONFIG,
92 | client_id: process.env.FACEBOOK_CLIENT_ID || 'none',
93 | client_secret: process.env.FACEBOOK_CLIENT_SECRET || 'none',
94 | oauth_redirect_uri: process.env.FACEBOOK_OAUTH_REDIRECT_URI || 'none'
95 | }
96 | },
97 | {
98 | provide: GOOGLE_PLUS_CONFIG_TOKEN,
99 | useValue: {
100 | ...DEFAULT_GOOGLE_PLUS_CONFIG,
101 | client_id: process.env.GOOGLE_CLIENT_ID || 'none',
102 | client_secret: process.env.GOOGLE_CLIENT_SECRET || 'none',
103 | oauth_redirect_uri: process.env.GOOGLE_OAUTH_REDIRECT_URI || 'none'
104 | }
105 | },
106 | ...AUTH_APP_GUARDS,
107 | ...AUTH_APP_FILTERS
108 | ],
109 | passportProviders: () => AUTH_PASSPORT_STRATEGIES
110 | }
111 | };
112 | export function getRootPath() {
113 | return NODE_ENV === 'develop' ? '../../../../../' : '../../../../';
114 | }
115 | export function resolveRootFile(fileName: string) {
116 | return path.resolve(__dirname, getRootPath(), fileName);
117 | }
118 | export function loadRootJson(fileName: string) {
119 | return JSON.parse(readFileSync(resolveRootFile(fileName)).toString()) as T;
120 | }
121 |
--------------------------------------------------------------------------------
/apps/demo/src/app/i18n/template.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "MIME-Version: 1.0\n"
4 | "Content-Type: text/plain; charset=utf-8\n"
5 | "Content-Transfer-Encoding: 8bit\n"
--------------------------------------------------------------------------------
/apps/demo/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app.module';
2 | export * from './config/config.interface';
3 | export * from './config/config';
4 |
--------------------------------------------------------------------------------
/apps/demo/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rucken/core-nestjs/39079cdec0932ae817b294a3972633fa7c68546e/apps/demo/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/demo/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
3 | import 'reflect-metadata';
4 | import { register } from 'tsconfig-paths';
5 | import { AppModule } from './app/app.module';
6 | import { config } from './app/config/config';
7 | import { NestExpressApplication } from '@nestjs/platform-express';
8 |
9 | /* hmr
10 | declare const module: any;
11 | */
12 |
13 | /**
14 | * Link tsconfig paths
15 | */
16 | if (config.env.name !== 'develop') {
17 | register({
18 | baseUrl: config.project.path,
19 | paths: config.project.tsconfig.compilerOptions.paths
20 | });
21 | }
22 | async function bootstrap() {
23 | /**
24 | * Create nest application
25 | */
26 | const app = await NestFactory.create(
27 | AppModule.forRoot({
28 | providers: [...config.core.providers(), ...config.auth.providers()],
29 | passportProviders: config.auth.passportProviders()
30 | }),
31 | { cors: true }
32 | );
33 |
34 | /**
35 | * Add static folders
36 | */
37 | config.project.staticFolders.forEach(folder => {
38 | app.useStaticAssets(folder);
39 | });
40 |
41 | /**
42 | * Init swagger
43 | */
44 | let documentBuilder = new DocumentBuilder()
45 | .setTitle(config.project.package.name)
46 | .setDescription(config.project.package.description)
47 | .setContactEmail(config.project.package.author.email)
48 | .setExternalDoc('Project on Github', config.project.package.homepage)
49 | .setLicense(config.project.package.license, '')
50 | .setVersion(config.project.package.version)
51 | .addBearerAuth('Authorization', 'header');
52 | documentBuilder = documentBuilder.setSchemes(
53 | config.env.protocol === 'https' ? 'https' : 'http',
54 | config.env.protocol === 'https' ? 'http' : 'https'
55 | );
56 | SwaggerModule.setup('/swagger', app, SwaggerModule.createDocument(app, documentBuilder.build()));
57 |
58 | /**
59 | * Start nest application
60 | */
61 | await app.listen(config.env.port);
62 |
63 | /* hmr
64 | if (module.hot) {
65 | module.hot.accept();
66 | module.hot.dispose(() => app.close());
67 | }
68 | */
69 | }
70 | bootstrap();
71 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc/demo",
5 | "types": ["node"]
6 | },
7 | "exclude": ["**/*.spec.ts"],
8 | "include": ["**/*.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "jest"]
5 | },
6 | "include": ["**/*.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc/demo",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": ["**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/demo/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {}
4 | }
5 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | nodejs_version: "10"
3 | cache:
4 | - '%LOCALAPPDATA%\Yarn -> appveyor.yml'
5 | - node_modules -> yarn.lock
6 |
7 | install:
8 | - ps: Install-Product node $env:nodejs_version x64
9 | - npm install
10 | test_script:
11 | - yarn test
12 |
13 | build: off
14 |
15 |
--------------------------------------------------------------------------------
/bin/post_compile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | npm i
3 | npm run build
--------------------------------------------------------------------------------
/client/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rucken/core-nestjs/39079cdec0932ae817b294a3972633fa7c68546e/client/.gitkeep
--------------------------------------------------------------------------------
/database/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rucken/core-nestjs/39079cdec0932ae817b294a3972633fa7c68546e/database/.gitkeep
--------------------------------------------------------------------------------
/develop._env:
--------------------------------------------------------------------------------
1 | DEBUG=true
2 | DEMO=false
3 | NODE_MODULES_CACHE=false
4 | DOMAIN=localhost
5 | PORT=5000
6 | EXTERNAL_PORT=5000
7 | JWT_SECRET_KEY=secret_key
8 | JWT_EXPIRATION_DELTA=7 days
9 | JWT_AUTH_HEADER_PREFIX=JWT
10 |
11 | # https://developers.facebook.com/apps//settings/basic/
12 | FACEBOOK_CLIENT_ID=none
13 | FACEBOOK_CLIENT_SECRET=none
14 | FACEBOOK_OAUTH_REDIRECT_URI={host}/auth/facebook/
15 |
16 | # https://console.developers.google.com/apis/credentials/oauthclient/
17 | GOOGLE_CLIENT_ID=none
18 | GOOGLE_CLIENT_SECRET=none
19 | GOOGLE_OAUTH_REDIRECT_URI={host}/auth/google-plus/
20 |
21 | DATABASE_URL=sqlite://database/sqlitedb.db
22 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:1105/NAME
23 | #DATABASE_URL=sqlite://db.sqlite3
24 | #DATABASE_URL=oracle://USER:PASSWORD@HOST:1105/NAME
25 | #DATABASE_URL=mysql://USER:PASSWORD@HOST:1105/NAME
26 | #DATABASE_URL=redis://USER:PASSWORD@HOST:1105
27 | #DATABASE_URL=memcached://USER:PASSWORD@HOST:1105
28 | #DATABASE_URL=ocmem://HOST:1105/PATH
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017
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 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/README.md:
--------------------------------------------------------------------------------
1 | [](https://greenkeeper.io/)
2 | [![NPM version][npm-image]][npm-url]
3 | [![Build Status][travis-image]][travis-url]
4 | [](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master)
5 | [![dependencies-release][dependencies-image]][dependencies-url]
6 |
7 | [](https://heroku.com/deploy?template=https://github.com/rucken/core-nestjs)
8 |
9 | Basic entities, services and controllers for oauth authorization in [NestJS](https://github.com/nestjs/nest) REST backend
10 |
11 | ## Features
12 |
13 | - [NestJS](https://github.com/nestjs/nest) - a JS backend framework providing architecture out of the box with a syntax similar to Angular
14 | - [TypeORM](http://typeorm.io/) - ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases.
15 | - [TypeScript](https://github.com/Microsoft/TypeScript) - superset of JS which compiles to JS, providing compile-time type checking
16 | - [Passport](https://github.com/jaredhanson/passport) - a popular library used to implement JavaScript authentication (Facebook, Google+)
17 | - [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) - a JavaScript json web tokens implementation by auth0
18 | - [@nrwl/schematics](https://github.com/nrwl/nx/blob/master/packages/schematics/src/collection.json) - Angular CLI power-ups for modern development, example usage: `ng g @nrwl/schematics:node-app app-name --framework nestjs`
19 | - [@nestjs/schematics](https://github.com/nestjs/schematics/blob/master/src/collection.json) - Nest architecture element generation based on Angular schematics, example usage: `ng g @nestjs/schematics:library lib-name`
20 |
21 | ## Usage
22 |
23 | - clone or fork [repository](https://github.com/rucken/core-nestjs.git) `git clone --recursive https://github.com/rucken/core-nestjs.git`
24 | - make sure you have [node.js](https://nodejs.org/) installed version 11+
25 | - copy `develop._env` to `develop.env` and set environments for use (on Windows copy with IDE)
26 | - run `npm install` to install project dependencies
27 | - run `npm run build` to install project dependencies
28 | - run `npm run start:prod` to fire up prod server (`npm run start:dev` - dev server)
29 | - Open browser to [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
30 |
31 | ## Demo
32 |
33 | [https://core-nestjs.rucken.ru](https://core-nestjs.rucken.ru) - Application with [Sqlite](https://www.sqlite.org/index.html) Database on VPS with [Dokku](http://dokku.viewdocs.io/dokku/)
34 |
35 | ### Users
36 |
37 | - user with admin group: admin@admin.com, password: 12345678
38 | - user with user group: user1@user1.com, password: 12345678
39 | - user with user group: user2@user2.com, password: 12345678
40 |
41 | ### Swagger
42 |
43 | - local: [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
44 | - online: [`https://core-nestjs.rucken.ru/swagger`](https://core-nestjs.rucken.ru/swagger)
45 | - apiKey template: `JWT `
46 |
47 | ## Typedoc documentations
48 |
49 | - local: [`http://localhost:5000/docs`](http://localhost:5000/docs)
50 | - online: [`https://core-nestjs.rucken.ru/docs`](https://core-nestjs.rucken.ru/docs)
51 |
52 | ## Quick links
53 |
54 | ### Frontend (with core)
55 |
56 | [@rucken/core](https://github.com/rucken/core) - Core with Admin UI for web application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
57 |
58 | [@rucken/todo](https://github.com/rucken/todo) - Core with UI for web todo application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
59 |
60 | ### Backend
61 |
62 | [@rucken/core-nestjs](https://github.com/rucken/core-nestjs) - A simple application demonstrating the basic usage of permissions with [NestJS](https://nestjs.com/) (JWT, Passport, Facebook, Google+, User, Group, Permission).
63 |
64 | [@rucken/todo-nestjs](https://github.com/rucken/todo-nestjs) - A simple todo application with [NestJS](https://nestjs.com/) (Projects, Tasks, Statuses).
65 |
66 | ### Mobile
67 |
68 | [@rucken/ionic](https://github.com/rucken/ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile application.
69 |
70 | [@rucken/todo-ionic](https://github.com/rucken/todo-ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile todo application
71 |
72 | ### Console
73 |
74 | [@rucken/cli](https://github.com/rucken/cli) - Console tools to create and build [Angular7+](https://angular.io/) and [NestJS](https://nestjs.com/) application based on [Rucken](https://github.com/rucken) template
75 |
76 | ## License
77 |
78 | MIT
79 |
80 | [travis-image]: https://travis-ci.org/rucken/core-nestjs.svg?branch=master
81 | [travis-url]: https://travis-ci.org/rucken/core-nestjs
82 | [dependencies-image]: https://david-dm.org/rucken/core-nestjs/status.svg
83 | [dependencies-url]: https://david-dm.org/rucken/core-nestjs
84 | [npm-image]: https://badge.fury.io/js/%40rucken%2Fauth-nestjs.svg
85 | [npm-url]: https://npmjs.org/package/@rucken/auth-nestjs
86 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'auth-nestjs',
3 | preset: '../../../jest.config.js',
4 | coverageDirectory: '../../../coverage/libs/rucken/auth-nestjs'
5 | };
6 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rucken/auth-nestjs",
3 | "version": "1.0.12",
4 | "engines": {
5 | "node": ">=11",
6 | "npm": ">=6.5.0"
7 | },
8 | "license": "MIT",
9 | "author": {
10 | "name": "EndyKaufman",
11 | "email": "admin@site15.ru"
12 | },
13 | "description": "Basic entities, services and controllers for oauth authorization in NestJS REST backend",
14 | "bugs": {
15 | "url": "https://github.com/rucken/core-nestjs/issues"
16 | },
17 | "homepage": "https://github.com/rucken/core-nestjs",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/rucken/core-nestjs.git"
21 | },
22 | "keywords": [
23 | "nestjs",
24 | "backend",
25 | "jwt",
26 | "oauth",
27 | "passport",
28 | "facebook",
29 | "google+",
30 | "user",
31 | "group",
32 | "permission",
33 | "bootstrap",
34 | "node",
35 | "nodejs",
36 | "angular",
37 | "crud",
38 | "rest",
39 | "nest",
40 | "auth",
41 | "rucken",
42 | "nrwl",
43 | "nx",
44 | "angular",
45 | "schematics",
46 | "ngx"
47 | ],
48 | "maintainers": [
49 | {
50 | "name": "EndyKaufman",
51 | "email": "admin@site15.ru"
52 | }
53 | ],
54 | "main": "index.js",
55 | "types": "index.d.ts",
56 | "dependencies": {
57 | "@nestjs/common": "6.1.1",
58 | "@nestjs/core": "6.1.1",
59 | "@nestjs/passport": "6.0.0",
60 | "@nestjs/swagger": "^3.0.2",
61 | "@nestjs/typeorm": "6.1.0",
62 | "@types/passport-facebook-token": "0.4.33",
63 | "@types/passport-local": "1.0.33",
64 | "class-transformer": "^0.2.0",
65 | "class-validator": "0.9.1",
66 | "jsonwebtoken": "8.5.1",
67 | "passport": "0.4.0",
68 | "passport-facebook-token": "3.3.0",
69 | "passport-google-plus-token": "2.1.0",
70 | "passport-http-bearer": "1.0.1",
71 | "passport-jwt": "4.0.0",
72 | "passport-local": "1.0.0",
73 | "rxjs": "6.5.2",
74 | "typeorm": "0.2.17",
75 | "highlight.js": "9.15.6"
76 | }
77 | }
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, HttpModule, MiddlewareConsumer, Module, NestModule, Provider } from '@nestjs/common';
2 | import { TypeOrmModule } from '@nestjs/typeorm';
3 | import { RuckenCoreModule } from '@rucken/core-nestjs';
4 | import { authenticate } from 'passport';
5 | import { AUTH_CONTROLLERS } from './controllers';
6 | import { AUTH_ENTITIES } from './entities';
7 | import { AUTH_SERVICES } from './services';
8 |
9 | @Module({})
10 | export class RuckenAuthModule implements NestModule {
11 | static forFeature(options?: { providers: Provider[] }): DynamicModule {
12 | const providers = options && options.providers ? options.providers : [];
13 | return {
14 | module: RuckenAuthModule,
15 | imports: [HttpModule, RuckenCoreModule.forFeature(options), TypeOrmModule.forFeature([...AUTH_ENTITIES])],
16 | providers: [...providers, ...AUTH_SERVICES],
17 | exports: [...AUTH_SERVICES]
18 | };
19 | }
20 | static forRoot(options?: { providers: Provider[] }): DynamicModule {
21 | const providers = options && options.providers ? options.providers : [];
22 | return {
23 | module: RuckenAuthModule,
24 | imports: [HttpModule, RuckenCoreModule.forFeature(options), TypeOrmModule.forFeature([...AUTH_ENTITIES])],
25 | controllers: [...AUTH_CONTROLLERS],
26 | providers: [...providers, ...AUTH_SERVICES],
27 | exports: [...AUTH_SERVICES]
28 | };
29 | }
30 |
31 | public configure(consumer: MiddlewareConsumer) {
32 | consumer.apply(authenticate('signup', { session: false, passReqToCallback: true })).forRoutes('api/auth/signup');
33 | consumer.apply(authenticate('signin', { session: false, passReqToCallback: true })).forRoutes('api/auth/signin');
34 | consumer
35 | .apply(authenticate('facebook', { session: false, passReqToCallback: true }))
36 | .forRoutes('api/auth/facebook/token');
37 | consumer
38 | .apply(authenticate('google', { session: false, passReqToCallback: true }))
39 | .forRoutes('api/auth/google-plus/token');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/configs/facebook.config.ts:
--------------------------------------------------------------------------------
1 | import { IFacebookConfig } from '../interfaces/facebook-config.interface';
2 |
3 | export const DEFAULT_FACEBOOK_CONFIG: IFacebookConfig = {
4 | login_dialog_uri: 'https://www.facebook.com/v2.12/dialog/oauth',
5 | access_token_uri: 'https://graph.facebook.com/v2.12/oauth/access_token',
6 | client_id: '',
7 | client_secret: '',
8 | oauth_redirect_uri: '',
9 | state: '{fbstate}'
10 | };
11 | export const FACEBOOK_CONFIG_TOKEN = 'FacebookConfig';
12 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/configs/google-plus.config.ts:
--------------------------------------------------------------------------------
1 | import { IGooglePlusConfig } from '../interfaces/google-plus-config.interface';
2 |
3 | export const DEFAULT_GOOGLE_PLUS_CONFIG: IGooglePlusConfig = {
4 | login_dialog_uri: 'https://accounts.google.com/o/oauth2/auth',
5 | client_id: '',
6 | client_secret: '',
7 | oauth_redirect_uri: '',
8 | access_token_uri: 'https://accounts.google.com/o/oauth2/token',
9 | response_type: 'code',
10 | scopes: ['https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/plus.profile.emails.read'],
11 | grant_type: 'authorization_code'
12 | };
13 | export const GOOGLE_PLUS_CONFIG_TOKEN = 'GooglePlusConfig';
14 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/configs/index.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_FACEBOOK_CONFIG, FACEBOOK_CONFIG_TOKEN } from '../configs/facebook.config';
2 | import { DEFAULT_GOOGLE_PLUS_CONFIG, GOOGLE_PLUS_CONFIG_TOKEN } from '../configs/google-plus.config';
3 | import { DEFAULT_JWT_CONFIG, JWT_CONFIG_TOKEN } from '../configs/jwt.config';
4 |
5 | export const AUTH_CONFIGS = [
6 | {
7 | provide: JWT_CONFIG_TOKEN,
8 | useValue: DEFAULT_JWT_CONFIG
9 | },
10 | {
11 | provide: FACEBOOK_CONFIG_TOKEN,
12 | useValue: DEFAULT_FACEBOOK_CONFIG
13 | },
14 | {
15 | provide: GOOGLE_PLUS_CONFIG_TOKEN,
16 | useValue: DEFAULT_GOOGLE_PLUS_CONFIG
17 | }
18 | ];
19 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/configs/jwt.config.ts:
--------------------------------------------------------------------------------
1 | import { IJwtConfig } from '../interfaces/jwt-config.interface';
2 |
3 | export const DEFAULT_JWT_CONFIG: IJwtConfig = {
4 | authHeaderPrefix: 'JWT',
5 | expirationDelta: '7 days'
6 | };
7 | export const JWT_CONFIG_TOKEN = 'JwtConfigToken';
8 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/controllers/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Get, HttpCode, HttpStatus, Inject, Logger, Post, Req } from '@nestjs/common';
2 | import { ApiResponse, ApiUseTags } from '@nestjs/swagger';
3 | import { CORE_CONFIG_TOKEN, ICoreConfig, OutAccountDto } from '@rucken/core-nestjs';
4 | import { plainToClass } from 'class-transformer';
5 | import { JsonWebTokenError } from 'jsonwebtoken';
6 | import { FacebookSignInDto } from '../dto/facebook-signIn.dto';
7 | import { FacebookTokenDto } from '../dto/facebook-token.dto';
8 | import { GooglePlusSignInDto } from '../dto/google-plus-signIn.dto';
9 | import { RedirectUriDto } from '../dto/redirect-uri.dto';
10 | import { SignInDto } from '../dto/sign-in.dto';
11 | import { SignUpDto } from '../dto/sign-up.dto';
12 | import { TokenDto } from '../dto/token.dto';
13 | import { UserTokenDto } from '../dto/user-token.dto';
14 | import { IJwtPayload } from '../interfaces/jwt-payload.interface';
15 | import { AuthService } from '../services/auth.service';
16 | import { TokenService } from '../services/token.service';
17 |
18 | @ApiUseTags('auth')
19 | @Controller('/api/auth')
20 | export class AuthController {
21 | constructor(
22 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
23 | private readonly authService: AuthService,
24 | private readonly tokenService: TokenService
25 | ) {}
26 |
27 | @HttpCode(HttpStatus.OK)
28 | @Post('signin')
29 | @ApiResponse({
30 | status: HttpStatus.OK,
31 | type: UserTokenDto,
32 | description: 'API View that checks the veracity of a token, returning the token if it is valid.'
33 | })
34 | async requestJsonWebTokenAfterSignIn(@Req() req, @Body() signInDto: SignInDto): Promise {
35 | const token = await this.tokenService.create(req.user);
36 | return plainToClass(UserTokenDto, { user: req.user, token });
37 | }
38 |
39 | @HttpCode(HttpStatus.CREATED)
40 | @Post('signup')
41 | @ApiResponse({
42 | status: HttpStatus.OK,
43 | type: UserTokenDto,
44 | description: `API View that receives a POST with a user's username and password.
45 | Returns a JSON Web Token that can be used for authenticated requests.`
46 | })
47 | async requestJsonWebTokenAfterSignUp(@Req() req, @Body() signUpDto: SignUpDto): Promise {
48 | const token = await this.tokenService.create(req.user);
49 | return plainToClass(UserTokenDto, { user: req.user, token });
50 | }
51 |
52 | @HttpCode(HttpStatus.OK)
53 | @Post('info')
54 | @ApiResponse({
55 | status: HttpStatus.OK,
56 | type: UserTokenDto,
57 | description: 'API View that checks the veracity of a token, returning the token if it is valid.'
58 | })
59 | async requestJsonWebTokenAfterInfo(@Req() req, @Body() tokenDto: TokenDto): Promise {
60 | try {
61 | const validateTokenResult = await this.tokenService.validate(tokenDto.token);
62 | if (validateTokenResult) {
63 | const jwtPayload: IJwtPayload = await this.tokenService.decode(tokenDto.token);
64 | const { user } = await this.authService.info({ id: jwtPayload.id });
65 | const token = await this.tokenService.create(user);
66 | return plainToClass(UserTokenDto, { user, token });
67 | } else {
68 | throw new JsonWebTokenError('invalid token');
69 | }
70 | } catch (error) {
71 | throw error;
72 | }
73 | }
74 |
75 | @HttpCode(HttpStatus.OK)
76 | @ApiResponse({
77 | status: HttpStatus.OK,
78 | description: 'facebook/uri'
79 | })
80 | @Get('facebook/uri')
81 | async requestFacebookRedirectUrl(@Req() req): Promise {
82 | // Logger.log(req.get('origin'), AuthController.name + ':requestFacebookRedirectUrl#origin');
83 | return this.authService.requestFacebookRedirectUri(
84 | req.get('origin') || this.coreConfig.protocol + '://' + req.get('host')
85 | );
86 | }
87 |
88 | @HttpCode(HttpStatus.OK)
89 | @ApiResponse({
90 | status: HttpStatus.OK,
91 | description: 'facebook/signin'
92 | })
93 | @Post('facebook/signin')
94 | async facebookSignIn(@Req() req, @Body() facebookSignInDto: FacebookSignInDto): Promise {
95 | // Logger.log(req.get('origin'), AuthController.name + ':facebookSignIn#origin');
96 | return this.authService.facebookSignIn(
97 | facebookSignInDto.code,
98 | req.get('origin') || this.coreConfig.protocol + '://' + req.get('host')
99 | );
100 | }
101 |
102 | @HttpCode(HttpStatus.OK)
103 | @ApiResponse({
104 | status: HttpStatus.OK,
105 | description: 'facebook/token'
106 | })
107 | @Post('facebook/token')
108 | async requestJsonWebTokenAfterFacebookSignIn(
109 | @Req() req,
110 | @Body() facebookTokenDto: FacebookTokenDto
111 | ): Promise {
112 | const token = await this.tokenService.create(req.user);
113 | return plainToClass(UserTokenDto, { user: req.user, token });
114 | }
115 |
116 | @HttpCode(HttpStatus.OK)
117 | @ApiResponse({
118 | status: HttpStatus.OK,
119 | description: 'google-plus/uri'
120 | })
121 | @Get('google-plus/uri')
122 | async requestGoogleRedirectUri(@Req() req): Promise {
123 | // Logger.log(req.get('origin'), AuthController.name + ':requestGoogleRedirectUri#origin');
124 | return this.authService.requestGoogleRedirectUri(
125 | req.get('origin') || this.coreConfig.protocol + '://' + req.get('host')
126 | );
127 | }
128 | @HttpCode(HttpStatus.OK)
129 | @ApiResponse({
130 | status: HttpStatus.OK,
131 | description: 'google-plus/signin'
132 | })
133 | @Post('google-plus/signin')
134 | async googleSignIn(@Req() req, @Body() googleSignInDto: GooglePlusSignInDto): Promise {
135 | // Logger.log(req.get('origin'), AuthController.name + ':googleSignIn#origin');
136 | return this.authService.googleSignIn(
137 | googleSignInDto.code,
138 | req.get('origin') || this.coreConfig.protocol + '://' + req.get('host')
139 | );
140 | }
141 |
142 | @HttpCode(HttpStatus.OK)
143 | @ApiResponse({
144 | status: HttpStatus.OK,
145 | description: 'google-plus/token'
146 | })
147 | @Post('google-plus/token')
148 | async requestJsonWebTokenAfterGoogleSignIn(@Req() req): Promise {
149 | const token = await this.tokenService.create(req.user);
150 | return plainToClass(UserTokenDto, { user: req.user, token });
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/controllers/index.ts:
--------------------------------------------------------------------------------
1 | import { AuthController } from './auth.controller';
2 |
3 | export const AUTH_CONTROLLERS = [AuthController];
4 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/facebook-signIn.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsNotEmpty } from 'class-validator';
3 |
4 | export class FacebookSignInDto {
5 | @IsNotEmpty()
6 | @ApiModelProperty()
7 | code: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/facebook-token.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsNotEmpty } from 'class-validator';
3 |
4 | export class FacebookTokenDto {
5 | @IsNotEmpty()
6 | @ApiModelProperty()
7 | access_token: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/google-plus-signIn.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsNotEmpty } from 'class-validator';
3 |
4 | export class GooglePlusSignInDto {
5 | @IsNotEmpty()
6 | @ApiModelProperty()
7 | code: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/redirect-uri.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | export class RedirectUriDto {
3 | @ApiModelProperty()
4 | redirect_uri: string;
5 | }
6 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/sign-in.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsNotEmpty, MaxLength } from 'class-validator';
3 |
4 | export class SignInDto {
5 | @IsNotEmpty()
6 | @MaxLength(150)
7 | @ApiModelProperty()
8 | email: string;
9 |
10 | @IsNotEmpty()
11 | @MaxLength(128)
12 | @ApiModelProperty()
13 | password: string;
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/sign-up.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsEmail, IsNotEmpty, MaxLength, IsOptional } from 'class-validator';
3 |
4 | export class SignUpDto {
5 | @IsNotEmpty()
6 | @IsEmail()
7 | @MaxLength(254)
8 | @ApiModelProperty()
9 | email: string;
10 |
11 | @IsNotEmpty()
12 | @MaxLength(150)
13 | @ApiModelProperty()
14 | username: string;
15 |
16 | @IsNotEmpty()
17 | @MaxLength(128)
18 | @ApiModelProperty()
19 | password: string;
20 |
21 | @MaxLength(30)
22 | @IsOptional()
23 | firstName?: string = undefined;
24 |
25 | @MaxLength(30)
26 | @IsOptional()
27 | lastName?: string = undefined;
28 | }
29 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/token.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty } from 'class-validator';
2 | import { ApiModelProperty } from '@nestjs/swagger';
3 |
4 | export class TokenDto {
5 | @ApiModelProperty()
6 | @IsNotEmpty()
7 | token: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/dto/user-token.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { AccountDto } from '@rucken/core-nestjs';
4 |
5 | export class UserTokenDto {
6 | @ApiModelProperty()
7 | token: string;
8 |
9 | @Type(() => AccountDto)
10 | @ApiModelProperty({ type: AccountDto })
11 | user: AccountDto;
12 | }
13 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/entities/index.ts:
--------------------------------------------------------------------------------
1 | import { OauthTokensAccesstoken } from './oauth-tokens-accesstoken.entity';
2 |
3 | export const AUTH_ENTITIES = [OauthTokensAccesstoken];
4 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/entities/oauth-tokens-accesstoken.entity.ts:
--------------------------------------------------------------------------------
1 | import { CustomValidationError, User } from '@rucken/core-nestjs';
2 | import { IsNotEmpty, IsOptional, MaxLength, validateSync } from 'class-validator';
3 | import {
4 | BeforeInsert,
5 | BeforeUpdate,
6 | Column,
7 | CreateDateColumn,
8 | Entity,
9 | JoinColumn,
10 | ManyToOne,
11 | PrimaryGeneratedColumn
12 | } from 'typeorm';
13 |
14 | @Entity({ name: 'oauth_tokens_accesstokens' })
15 | export class OauthTokensAccesstoken {
16 | @PrimaryGeneratedColumn()
17 | id: number = undefined;
18 |
19 | @Column({ length: 20 })
20 | @IsNotEmpty()
21 | @MaxLength(20)
22 | provider: string = undefined;
23 |
24 | @Column({ name: 'provider_client_id', length: 200 })
25 | @IsNotEmpty()
26 | @MaxLength(200)
27 | providerClientId: string = undefined;
28 |
29 | @CreateDateColumn({ name: 'granted_at' })
30 | grantedAt: Date = undefined;
31 |
32 | @Column({ name: 'access_token', length: 500 })
33 | @IsNotEmpty()
34 | @MaxLength(500)
35 | accessToken: string = undefined;
36 |
37 | @Column({ name: 'refresh_token', length: 200, nullable: true })
38 | @MaxLength(200)
39 | @IsOptional()
40 | refreshToken: string = undefined;
41 |
42 | @Column({ type: Date, name: 'expires_at', nullable: true })
43 | expiresAt: Date = undefined;
44 |
45 | @Column({ name: 'token_type', length: 200, nullable: true })
46 | @MaxLength(200)
47 | @IsOptional()
48 | tokenType: string = undefined;
49 |
50 | @Column({ length: 512, nullable: true })
51 | @MaxLength(512)
52 | @IsOptional()
53 | scope: string = undefined;
54 |
55 | @ManyToOne(type => User, { eager: true })
56 | @IsNotEmpty()
57 | @JoinColumn({ name: 'user_id' })
58 | user: User = undefined;
59 |
60 | @BeforeInsert()
61 | doBeforeInsertion() {
62 | const errors = validateSync(this, { validationError: { target: false } });
63 | if (errors.length > 0) {
64 | throw new CustomValidationError(errors);
65 | }
66 | }
67 |
68 | @BeforeUpdate()
69 | doBeforeUpdate() {
70 | const errors = validateSync(this, { validationError: { target: false } });
71 | if (errors.length > 0) {
72 | throw new CustomValidationError(errors);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/filters/custom-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus, Logger } from '@nestjs/common';
2 | import { JsonWebTokenError } from 'jsonwebtoken';
3 |
4 | @Catch(JsonWebTokenError)
5 | export class CustomExceptionFilter implements ExceptionFilter {
6 | private response(exception: JsonWebTokenError, host: ArgumentsHost, data: any, status?: number) {
7 | const ctx = host.switchToHttp();
8 | const response = ctx.getResponse();
9 | const request = ctx.getRequest();
10 | Logger.error(JSON.stringify(exception), undefined, CustomExceptionFilter.name);
11 | response.status(status ? status : HttpStatus.BAD_REQUEST).json(data);
12 | }
13 |
14 | catch(exception: JsonWebTokenError, host: ArgumentsHost) {
15 | if (exception instanceof JsonWebTokenError) {
16 | this.response(exception, host, {
17 | message: exception.message
18 | });
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/filters/index.ts:
--------------------------------------------------------------------------------
1 | import { APP_FILTER } from '@nestjs/core';
2 | import { CustomExceptionFilter } from '../filters/custom-exception.filter';
3 |
4 | export const AUTH_APP_FILTERS = [{ provide: APP_FILTER, useClass: CustomExceptionFilter, multi: true }];
5 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/guards/access.guard.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, Injectable, Logger } from '@nestjs/common';
2 | import { Reflector } from '@nestjs/core';
3 | import { AuthGuard } from '@nestjs/passport';
4 | import { User } from '@rucken/core-nestjs';
5 |
6 | @Injectable()
7 | export class AccessGuard extends AuthGuard('jwt') {
8 | constructor(private readonly reflector: Reflector) {
9 | super();
10 | }
11 |
12 | async canActivate(context: ExecutionContext) {
13 | try {
14 | await super.canActivate(context);
15 | } catch (error) {
16 | Logger.error('Error in canActivate', error.message, AccessGuard.name);
17 | }
18 | const roles = this.reflector.get('roles', context.getHandler());
19 | const permissions = this.reflector.get('permissions', context.getHandler());
20 | const request = context.switchToHttp().getRequest();
21 | const user: User = request.user;
22 | // Logger.log(JSON.stringify(user), AccessGuard.name);
23 | const hasRole = roles ? roles.filter(roleName => user && user instanceof User && user[roleName]).length > 0 : null;
24 | const hasPermission = permissions ? user && user instanceof User && user.checkPermissions(permissions) : null;
25 | return hasRole === true || hasPermission === true || (hasRole === null && hasPermission === null);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/guards/index.ts:
--------------------------------------------------------------------------------
1 | import { APP_GUARD } from '@nestjs/core';
2 | import { AccessGuard } from './access.guard';
3 |
4 | export const AUTH_APP_GUARDS = [{ provide: APP_GUARD, useClass: AccessGuard, multi: true }];
5 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/i18n/template.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "MIME-Version: 1.0\n"
4 | "Content-Type: text/plain; charset=utf-8\n"
5 | "Content-Transfer-Encoding: 8bit\n"
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.module';
2 | export * from './configs/facebook.config';
3 | export * from './configs/google-plus.config';
4 | export * from './configs/index';
5 | export * from './configs/jwt.config';
6 | export * from './controllers/auth.controller';
7 | export * from './controllers/index';
8 | export * from './dto/facebook-signIn.dto';
9 | export * from './dto/facebook-token.dto';
10 | export * from './dto/google-plus-signIn.dto';
11 | export * from './dto/redirect-uri.dto';
12 | export * from './dto/sign-in.dto';
13 | export * from './dto/sign-up.dto';
14 | export * from './dto/token.dto';
15 | export * from './dto/user-token.dto';
16 | export * from './entities/index';
17 | export * from './entities/oauth-tokens-accesstoken.entity';
18 | export * from './filters/custom-exception.filter';
19 | export * from './filters/index';
20 | export * from './guards/access.guard';
21 | export * from './guards/index';
22 | export * from './interfaces/facebook-config.interface';
23 | export * from './interfaces/google-plus-config.interface';
24 | export * from './interfaces/jwt-config.interface';
25 | export * from './interfaces/jwt-payload.interface';
26 | export * from './migrations/1533634559617-AddOauthTokensAccesstokenTable';
27 | export * from './passport/facebook.strategy';
28 | export * from './passport/google-plus.strategy';
29 | export * from './passport/index';
30 | export * from './passport/jwt.strategy';
31 | export * from './passport/local.strategy';
32 | export * from './services/auth.service';
33 | export * from './services/index';
34 | export * from './services/oauth-tokens-accesstokens.service';
35 | export * from './services/token.service';
36 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/interfaces/facebook-config.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IFacebookConfig {
2 | readonly login_dialog_uri?: string;
3 | readonly access_token_uri?: string;
4 | readonly client_id: string;
5 | readonly client_secret: string;
6 | readonly oauth_redirect_uri: string;
7 | readonly state?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/interfaces/google-plus-config.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IGooglePlusConfig {
2 | readonly login_dialog_uri: string;
3 | readonly client_id: string;
4 | readonly client_secret: string;
5 | readonly oauth_redirect_uri: string;
6 | readonly access_token_uri: string;
7 | readonly response_type: string;
8 | readonly scopes: string[];
9 | readonly grant_type: string;
10 | }
11 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/interfaces/jwt-config.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IJwtConfig {
2 | authHeaderPrefix: string;
3 | expirationDelta: string;
4 | secretKey?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/interfaces/jwt-payload.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IJwtPayload {
2 | readonly id: number;
3 | readonly isStaff: boolean;
4 | readonly isActive: boolean;
5 | readonly isSuperuser: boolean;
6 | readonly groups: { name: string }[];
7 | readonly iat: number;
8 | readonly exp: number;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/migrations/1533634559617-AddOauthTokensAccesstokenTable.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner, Table, TableColumn, TableForeignKey, TableIndex } from 'typeorm';
2 |
3 | export class AddOauthTokensAccesstokenTable1533634559617 implements MigrationInterface {
4 | public async up(queryRunner: QueryRunner): Promise {
5 | await queryRunner.createTable(
6 | new Table({
7 | name: 'oauth_tokens_accesstokens',
8 | columns: [
9 | {
10 | name: 'id',
11 | type: 'integer'
12 | },
13 | {
14 | name: 'provider',
15 | type: 'varchar(20)',
16 | isNullable: false
17 | },
18 | {
19 | name: 'provider_client_id',
20 | type: 'varchar(200)',
21 | isNullable: false
22 | },
23 | {
24 | name: 'granted_at',
25 | type: queryRunner.connection.driver.mappedDataTypes.createDate.toString(),
26 | isNullable: false,
27 | default: queryRunner.connection.driver.mappedDataTypes.createDateDefault
28 | },
29 | {
30 | name: 'access_token',
31 | type: 'varchar(500)',
32 | isNullable: false
33 | },
34 | {
35 | name: 'refresh_token',
36 | type: 'varchar(500)'
37 | },
38 | {
39 | name: 'expires_at',
40 | type: queryRunner.connection.driver.mappedDataTypes.createDate.toString(),
41 | isNullable: true
42 | },
43 | {
44 | name: 'token_type',
45 | type: 'varchar(200)'
46 | },
47 | {
48 | name: 'scope',
49 | type: 'varchar(512)'
50 | },
51 | {
52 | name: 'user_id',
53 | type: 'integer'
54 | }
55 | ]
56 | }),
57 | true
58 | );
59 |
60 | await queryRunner.changeColumn(
61 | 'oauth_tokens_accesstokens',
62 | 'id',
63 | new TableColumn({
64 | name: 'id',
65 | type: 'integer',
66 | isPrimary: true,
67 | isGenerated: true,
68 | generationStrategy: 'increment'
69 | })
70 | );
71 |
72 | if (!(queryRunner.connection.driver as any).sqlite) {
73 | await queryRunner.createForeignKey(
74 | 'oauth_tokens_accesstokens',
75 | new TableForeignKey({
76 | name: 'FK_TOK_ACC_U_ID',
77 | columnNames: ['user_id'],
78 | referencedColumnNames: ['id'],
79 | referencedTableName: 'users',
80 | onDelete: 'CASCADE'
81 | })
82 | );
83 | await queryRunner.createIndex(
84 | 'oauth_tokens_accesstokens',
85 | new TableIndex({
86 | name: 'IDX_TOK_ACC_U_ID',
87 | columnNames: ['user_id']
88 | })
89 | );
90 | }
91 | }
92 |
93 | public async down(queryRunner: QueryRunner): Promise {}
94 | }
95 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/passport/facebook.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable } from '@nestjs/common';
2 | import { plainToClass } from 'class-transformer';
3 | import { use } from 'passport';
4 | import * as FacebookTokenStrategy from 'passport-facebook-token';
5 | import { FACEBOOK_CONFIG_TOKEN } from '../configs/facebook.config';
6 | import { SignUpDto } from '../dto/sign-up.dto';
7 | import { OauthTokensAccesstoken } from '../entities/oauth-tokens-accesstoken.entity';
8 | import { IFacebookConfig } from '../interfaces/facebook-config.interface';
9 | import { AuthService } from '../services/auth.service';
10 | import { OauthTokensAccesstokensService } from '../services/oauth-tokens-accesstokens.service';
11 |
12 | @Injectable()
13 | export class FacebookStrategy {
14 | constructor(
15 | @Inject(FACEBOOK_CONFIG_TOKEN) private readonly fbConfig: IFacebookConfig,
16 | private readonly oauthTokensAccesstokensService: OauthTokensAccesstokensService,
17 | private readonly authService: AuthService
18 | ) {
19 | this.init();
20 | }
21 |
22 | private init(): void {
23 | use(
24 | 'facebook',
25 | new FacebookTokenStrategy(
26 | {
27 | clientID: this.fbConfig.client_id,
28 | clientSecret: this.fbConfig.client_secret
29 | },
30 | async (accessToken: string, refreshToken: string, profile: any, done) => {
31 | // Logger.log(JSON.stringify(profile), FacebookStrategy.name);
32 | if (!profile.id) {
33 | done(null, null);
34 | }
35 | try {
36 | try {
37 | const { oauthTokensAccesstoken } = await this.oauthTokensAccesstokensService.findByProviderClientId({
38 | id: profile.id
39 | });
40 | const { user } = await this.authService.info({
41 | id: oauthTokensAccesstoken.user.id
42 | });
43 | done(null, user);
44 | } catch (err) {
45 | const email =
46 | profile.emails && profile.emails.length && profile.emails[0].value
47 | ? profile.emails[0].value
48 | : `${profile.id}@facebook.com`;
49 | const username = `facebook_${profile.id}`;
50 | const firstName = profile.name.givenName;
51 | const lastName = profile.name.familyName;
52 | const password = `facebook_${profile.id}`;
53 | const { user } = await this.authService.signUp(
54 | plainToClass(SignUpDto, {
55 | email,
56 | username,
57 | password,
58 | firstName,
59 | lastName
60 | })
61 | );
62 | const newOauthTokensAccesstoken = new OauthTokensAccesstoken();
63 | newOauthTokensAccesstoken.user = user;
64 | newOauthTokensAccesstoken.providerClientId = profile.id;
65 | newOauthTokensAccesstoken.provider = profile.provider;
66 | newOauthTokensAccesstoken.accessToken = accessToken;
67 | await this.oauthTokensAccesstokensService.create({
68 | item: newOauthTokensAccesstoken
69 | });
70 | done(null, user);
71 | }
72 | } catch (err) {
73 | done(err, null);
74 | }
75 | }
76 | )
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/passport/google-plus.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable } from '@nestjs/common';
2 | import { plainToClass } from 'class-transformer';
3 | import { use } from 'passport';
4 | import * as GoogleTokenStrategy from 'passport-google-plus-token';
5 | import { GOOGLE_PLUS_CONFIG_TOKEN } from '../configs/google-plus.config';
6 | import { SignUpDto } from '../dto/sign-up.dto';
7 | import { OauthTokensAccesstoken } from '../entities/oauth-tokens-accesstoken.entity';
8 | import { IGooglePlusConfig } from '../interfaces/google-plus-config.interface';
9 | import { AuthService } from '../services/auth.service';
10 | import { OauthTokensAccesstokensService } from '../services/oauth-tokens-accesstokens.service';
11 |
12 | @Injectable()
13 | export class GooglePlusStrategy {
14 | constructor(
15 | @Inject(GOOGLE_PLUS_CONFIG_TOKEN)
16 | private readonly googlePlusConfig: IGooglePlusConfig,
17 | private readonly oauthTokensAccesstokensService: OauthTokensAccesstokensService,
18 | private readonly authService: AuthService
19 | ) {
20 | this.init();
21 | }
22 |
23 | private init(): void {
24 | use(
25 | 'google',
26 | new GoogleTokenStrategy(
27 | {
28 | clientID: this.googlePlusConfig.client_id,
29 | clientSecret: this.googlePlusConfig.client_secret
30 | },
31 | async (accessToken: string, refreshToken: string, profile: any, done) => {
32 | // Logger.log(JSON.stringify(profile), GooglePlusStrategy.name);
33 | if (!profile.id) {
34 | done(null, null);
35 | }
36 | try {
37 | try {
38 | const { oauthTokensAccesstoken } = await this.oauthTokensAccesstokensService.findByProviderClientId({
39 | id: profile.id
40 | });
41 | const { user } = await this.authService.info({
42 | id: oauthTokensAccesstoken.user.id
43 | });
44 | done(null, user);
45 | } catch (err) {
46 | const email =
47 | profile.emails && profile.emails.length && profile.emails[0].value
48 | ? profile.emails[0].value
49 | : `${profile.id}@google.com`;
50 | const username = `google_${profile.id}`;
51 | const firstName = profile.name ? profile.name.givenName : `google_${profile.id}`;
52 | const lastName = profile.name ? profile.name.familyName : `google_${profile.id}`;
53 | const password = `google_${profile.id}`;
54 | const { user } = await this.authService.signUp(
55 | plainToClass(SignUpDto, {
56 | email,
57 | username,
58 | password,
59 | firstName,
60 | lastName
61 | })
62 | );
63 | const newOauthTokensAccesstoken = new OauthTokensAccesstoken();
64 | newOauthTokensAccesstoken.user = user;
65 | newOauthTokensAccesstoken.providerClientId = profile.id;
66 | newOauthTokensAccesstoken.provider = profile.provider;
67 | newOauthTokensAccesstoken.accessToken = accessToken;
68 | await this.oauthTokensAccesstokensService.create({
69 | item: newOauthTokensAccesstoken
70 | });
71 | done(null, user);
72 | }
73 | } catch (err) {
74 | done(err, null);
75 | }
76 | }
77 | )
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/passport/index.ts:
--------------------------------------------------------------------------------
1 | import { FacebookStrategy } from './facebook.strategy';
2 | import { JwtStrategy } from './jwt.strategy';
3 | import { LocalStrategySignIn, LocalStrategySignUp } from './local.strategy';
4 | import { GooglePlusStrategy } from './google-plus.strategy';
5 |
6 | export const AUTH_PASSPORT_STRATEGIES = [
7 | LocalStrategySignIn,
8 | LocalStrategySignUp,
9 | JwtStrategy,
10 | FacebookStrategy,
11 | GooglePlusStrategy
12 | ];
13 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/passport/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { GroupsService, User } from '@rucken/core-nestjs';
4 | import { plainToClass } from 'class-transformer';
5 | import { Strategy } from 'passport-jwt';
6 | import { IJwtPayload } from '../interfaces/jwt-payload.interface';
7 | import { TokenService } from '../services/token.service';
8 |
9 | @Injectable()
10 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
11 | constructor(private readonly tokenService: TokenService, private readonly groupsService: GroupsService) {
12 | super({
13 | passReqToCallback: true,
14 | jwtFromRequest: req => {
15 | const token = this.tokenService.extractTokenFromRequest(req);
16 | // Logger.log(token, JwtStrategy.name);
17 | return token;
18 | },
19 | secretOrKeyProvider: (req, token, done) => {
20 | const secretKey = this.tokenService.createSecretKey(plainToClass(User, this.tokenService.decode(token)));
21 | done(null, secretKey);
22 | }
23 | });
24 | }
25 |
26 | public async validate(req, payload: IJwtPayload) {
27 | try {
28 | await this.groupsService.preloadAll();
29 | } catch (error) {
30 | throw new BadRequestException('Error in load groups');
31 | }
32 | try {
33 | // Logger.log(JSON.stringify(payload), JwtStrategy.name);
34 | // const { user } = await this.userService.findById({ id: payload.id });
35 | const user = plainToClass(User, payload);
36 | user.groups = user.groups.map(group => this.groupsService.getGroupByName({ name: group.name }));
37 | // Logger.log(JSON.stringify(user), JwtStrategy.name);
38 | return user;
39 | } catch (error) {
40 | throw new UnauthorizedException();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/passport/local.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { User } from '@rucken/core-nestjs';
4 | import { Strategy } from 'passport-local';
5 | import { AuthService } from '../services/auth.service';
6 |
7 | @Injectable()
8 | export class LocalStrategySignIn extends PassportStrategy(Strategy, 'signin') {
9 | constructor(private readonly authService: AuthService) {
10 | super({
11 | usernameField: 'email',
12 | passwordField: 'password',
13 | passReqToCallback: true
14 | });
15 | }
16 |
17 | public async validate(req, email: string, password: string) {
18 | const { user }: { user: User } = await this.authService.signIn({ email, password });
19 | return user;
20 | }
21 | }
22 | // tslint:disable-next-line:max-classes-per-file
23 | @Injectable()
24 | export class LocalStrategySignUp extends PassportStrategy(Strategy, 'signup') {
25 | constructor(private readonly authService: AuthService) {
26 | super({
27 | usernameField: 'email',
28 | passwordField: 'password',
29 | passReqToCallback: true
30 | });
31 | }
32 |
33 | public async validate(req, email: string, password: string) {
34 | if (req.user) {
35 | return req.user;
36 | }
37 | const { user } = await this.authService.signUp({
38 | email,
39 | password,
40 | username: req.body.username
41 | });
42 | return user;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/services/index.ts:
--------------------------------------------------------------------------------
1 | import { AuthService } from '../services/auth.service';
2 | import { OauthTokensAccesstokensService } from '../services/oauth-tokens-accesstokens.service';
3 | import { TokenService } from '../services/token.service';
4 |
5 | export const AUTH_SERVICES = [TokenService, AuthService, OauthTokensAccesstokensService];
6 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/services/oauth-tokens-accesstokens.service.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { Repository } from 'typeorm';
4 | import { OauthTokensAccesstoken } from '../entities/oauth-tokens-accesstoken.entity';
5 | import { CORE_CONFIG_TOKEN, ICoreConfig } from '@rucken/core-nestjs';
6 |
7 | @Injectable()
8 | export class OauthTokensAccesstokensService {
9 | constructor(
10 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
11 | @InjectRepository(OauthTokensAccesstoken)
12 | private readonly repository: Repository
13 | ) {}
14 |
15 | async create(options: { item: OauthTokensAccesstoken }) {
16 | try {
17 | options.item = await this.repository.save(options.item);
18 | return { oauthTokensAccesstoken: options.item };
19 | } catch (error) {
20 | throw error;
21 | }
22 | }
23 |
24 | async update(options: { id: number; item: OauthTokensAccesstoken }) {
25 | options.item.id = options.id;
26 | try {
27 | options.item = await this.repository.save(options.item);
28 | return { oauthTokensAccesstoken: options.item };
29 | } catch (error) {
30 | throw error;
31 | }
32 | }
33 |
34 | async delete(options: { id: number }) {
35 | try {
36 | let item = await this.repository.findOneOrFail(options.id);
37 | item = await this.repository.save(item);
38 | await this.repository.delete(options.id);
39 | return { oauthTokensAccesstoken: null };
40 | } catch (error) {
41 | throw error;
42 | }
43 | }
44 |
45 | async findByProviderClientId(options: { id: number }) {
46 | try {
47 | const item = await this.repository.findOneOrFail({
48 | where: {
49 | providerClientId: options.id
50 | }
51 | });
52 | return { oauthTokensAccesstoken: item };
53 | } catch (error) {
54 | throw error;
55 | }
56 | }
57 |
58 | async findById(options: { id: number }) {
59 | try {
60 | const item = await this.repository.findOneOrFail(options.id);
61 | return { oauthTokensAccesstoken: item };
62 | } catch (error) {
63 | throw error;
64 | }
65 | }
66 |
67 | async findAll(options: { curPage: number; perPage: number; q?: string; sort?: string }) {
68 | try {
69 | let objects: [OauthTokensAccesstoken[], number];
70 | let qb = this.repository.createQueryBuilder('oauthTokensAccesstoken');
71 | if (options.q) {
72 | qb = qb.where(
73 | 'oauthTokensAccesstoken.name like :q or oauthTokensAccesstoken.title like :q or oauthTokensAccesstoken.id = :id',
74 | {
75 | q: `%${options.q}%`,
76 | id: +options.q
77 | }
78 | );
79 | }
80 | options.sort =
81 | options.sort && new OauthTokensAccesstoken().hasOwnProperty(options.sort.replace('-', ''))
82 | ? options.sort
83 | : '-id';
84 | const field = options.sort.replace('-', '');
85 | if (options.sort) {
86 | if (options.sort[0] === '-') {
87 | qb = qb.orderBy('oauthTokensAccesstoken.' + field, 'DESC');
88 | } else {
89 | qb = qb.orderBy('oauthTokensAccesstoken.' + field, 'ASC');
90 | }
91 | }
92 | qb = qb.skip((options.curPage - 1) * options.perPage).take(options.perPage);
93 | objects = await qb.getManyAndCount();
94 | return {
95 | contentTypes: objects[0],
96 | meta: {
97 | perPage: options.perPage,
98 | totalPages: options.perPage > objects[1] ? 1 : Math.ceil(objects[1] / options.perPage),
99 | totalResults: objects[1],
100 | curPage: options.curPage
101 | }
102 | };
103 | } catch (error) {
104 | throw error;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/src/services/token.service.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable } from '@nestjs/common';
2 | import { User } from '@rucken/core-nestjs';
3 | import { decode, sign, verify } from 'jsonwebtoken';
4 | import { JWT_CONFIG_TOKEN } from '../configs/jwt.config';
5 | import { IJwtConfig } from '../interfaces/jwt-config.interface';
6 | import { IJwtPayload } from '../interfaces/jwt-payload.interface';
7 |
8 | @Injectable()
9 | export class TokenService {
10 | constructor(@Inject(JWT_CONFIG_TOKEN) private readonly jwtConfig: IJwtConfig) {}
11 |
12 | create(user: User) {
13 | return sign(
14 | {
15 | id: user.id,
16 | isStaff: user.isStaff,
17 | isActive: user.isActive,
18 | isSuperuser: user.isSuperuser,
19 | groups: user.groups.map(group => {
20 | return { name: group.name };
21 | })
22 | },
23 | this.createSecretKey(user),
24 | {
25 | expiresIn: this.jwtConfig.expirationDelta
26 | }
27 | );
28 | }
29 |
30 | validate(token: string) {
31 | const data: any = this.decode(token);
32 | return verify(this.removeHeaderPrefix(token), this.createSecretKey(data));
33 | }
34 |
35 | decode(token: string) {
36 | return decode(this.removeHeaderPrefix(token)) as IJwtPayload;
37 | }
38 |
39 | removeHeaderPrefix(token: string) {
40 | return this.jwtConfig.authHeaderPrefix && token && token.split(this.jwtConfig.authHeaderPrefix + ' ').length > 1
41 | ? token.split(this.jwtConfig.authHeaderPrefix + ' ')[1]
42 | : token;
43 | }
44 |
45 | extractTokenFromRequest(request) {
46 | const authorizationHeader = request.headers.authorization ? String(request.headers.authorization) : null;
47 | const token = this.removeHeaderPrefix(authorizationHeader);
48 | return token;
49 | }
50 |
51 | createSecretKey(user: User) {
52 | return (
53 | this.jwtConfig.secretKey +
54 | (user
55 | ? '$' +
56 | user.id +
57 | '$' +
58 | user.isStaff +
59 | '$' +
60 | user.isActive +
61 | '$' +
62 | user.isSuperuser +
63 | (user.groups ? user.groups.map(group => '$' + group.name) : '')
64 | : '')
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest"]
5 | },
6 | "include": ["**/*.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../../dist/out-tsc/auth",
5 | "module": "commonjs",
6 | "declaration": true,
7 | "noImplicitAny": false,
8 | "removeComments": true,
9 | "noLib": false,
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "target": "es6",
13 | "sourceMap": true,
14 | "baseUrl": "./"
15 | },
16 | "include": ["src/**/*"],
17 | "exclude": ["node_modules", "**/*.spec.ts"]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["**/*.spec.ts", "**/*.d.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/libs/rucken/auth-nestjs/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tslint.json"
3 | }
4 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017
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 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/README.md:
--------------------------------------------------------------------------------
1 | [](https://greenkeeper.io/)
2 | [![NPM version][npm-image]][npm-url]
3 | [![Build Status][travis-image]][travis-url]
4 | [](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master)
5 | [![dependencies-release][dependencies-image]][dependencies-url]
6 |
7 | [](https://heroku.com/deploy?template=https://github.com/rucken/core-nestjs)
8 |
9 | Basic entities, services and controllers for [NestJS](https://github.com/nestjs/nest) REST backend
10 |
11 | ## Features
12 |
13 | - [NestJS](https://github.com/nestjs/nest) - a JS backend framework providing architecture out of the box with a syntax similar to Angular
14 | - [TypeORM](http://typeorm.io/) - ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases.
15 | - [TypeScript](https://github.com/Microsoft/TypeScript) - superset of JS which compiles to JS, providing compile-time type checking
16 | - [Passport](https://github.com/jaredhanson/passport) - a popular library used to implement JavaScript authentication (Facebook, Google+)
17 | - [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) - a JavaScript json web tokens implementation by auth0
18 | - [@nrwl/schematics](https://github.com/nrwl/nx/blob/master/packages/schematics/src/collection.json) - Angular CLI power-ups for modern development, example usage: `ng g @nrwl/schematics:node-app app-name --framework nestjs`
19 | - [@nestjs/schematics](https://github.com/nestjs/schematics/blob/master/src/collection.json) - Nest architecture element generation based on Angular schematics, example usage: `ng g @nestjs/schematics:library lib-name`
20 |
21 | ## Usage
22 |
23 | - clone or fork [repository](https://github.com/rucken/core-nestjs.git) `git clone --recursive https://github.com/rucken/core-nestjs.git`
24 | - make sure you have [node.js](https://nodejs.org/) installed version 11+
25 | - copy `develop._env` to `develop.env` and set environments for use (on Windows copy with IDE)
26 | - run `npm install` to install project dependencies
27 | - run `npm run build` to install project dependencies
28 | - run `npm run start:prod` to fire up prod server (`npm run start:dev` - dev server)
29 | - Open browser to [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
30 |
31 | ## Demo
32 |
33 | [https://core-nestjs.rucken.ru](https://core-nestjs.rucken.ru) - Application with [Sqlite](https://www.sqlite.org/index.html) Database on VPS with [Dokku](http://dokku.viewdocs.io/dokku/)
34 |
35 | ### Users
36 |
37 | - user with admin group: admin@admin.com, password: 12345678
38 | - user with user group: user1@user1.com, password: 12345678
39 | - user with user group: user2@user2.com, password: 12345678
40 |
41 | ### Swagger
42 |
43 | - local: [`http://localhost:5000/swagger`](http://localhost:5000/swagger)
44 | - online: [`https://core-nestjs.rucken.ru/swagger`](https://core-nestjs.rucken.ru/swagger)
45 | - apiKey template: `JWT `
46 |
47 | ## Typedoc documentations
48 |
49 | - local: [`http://localhost:5000/docs`](http://localhost:5000/docs)
50 | - online: [`https://core-nestjs.rucken.ru/docs`](https://core-nestjs.rucken.ru/docs)
51 |
52 | ## Quick links
53 |
54 | ### Frontend (with core)
55 |
56 | [@rucken/core](https://github.com/rucken/core) - Core with Admin UI for web application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
57 |
58 | [@rucken/todo](https://github.com/rucken/todo) - Core with UI for web todo application maked on [Angular7+](https://angular.io) and [Bootstrap3](https://valor-software.com/ngx-bootstrap/).
59 |
60 | ### Backend
61 |
62 | [@rucken/core-nestjs](https://github.com/rucken/core-nestjs) - A simple application demonstrating the basic usage of permissions with [NestJS](https://nestjs.com/) (JWT, Passport, Facebook, Google+, User, Group, Permission).
63 |
64 | [@rucken/todo-nestjs](https://github.com/rucken/todo-nestjs) - A simple todo application with [NestJS](https://nestjs.com/) (Projects, Tasks, Statuses).
65 |
66 | ### Mobile
67 |
68 | [@rucken/ionic](https://github.com/rucken/ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile application.
69 |
70 | [@rucken/todo-ionic](https://github.com/rucken/todo-ionic) - Admin UI for [Ionic4](https://beta.ionicframework.com) with [Angular7+](https://angular.io) mobile todo application
71 |
72 | ### Console
73 |
74 | [@rucken/cli](https://github.com/rucken/cli) - Console tools to create and build [Angular7+](https://angular.io/) and [NestJS](https://nestjs.com/) application based on [Rucken](https://github.com/rucken) template
75 |
76 | ## License
77 |
78 | MIT
79 |
80 | [travis-image]: https://travis-ci.org/rucken/core-nestjs.svg?branch=master
81 | [travis-url]: https://travis-ci.org/rucken/core-nestjs
82 | [dependencies-image]: https://david-dm.org/rucken/core-nestjs/status.svg
83 | [dependencies-url]: https://david-dm.org/rucken/core-nestjs
84 | [npm-image]: https://badge.fury.io/js/%40rucken%2Fcore-nestjs.svg
85 | [npm-url]: https://npmjs.org/package/@rucken/core-nestjs
86 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'core-nestjs',
3 | preset: '../../../jest.config.js',
4 | coverageDirectory: '../../../coverage/rucken/core-nestjs/core'
5 | };
6 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rucken/core-nestjs",
3 | "version": "1.0.12",
4 | "engines": {
5 | "node": ">=11",
6 | "npm": ">=6.5.0"
7 | },
8 | "license": "MIT",
9 | "author": {
10 | "name": "EndyKaufman",
11 | "email": "admin@site15.ru"
12 | },
13 | "description": "Basic entities, services and controllers for NestJS REST backend",
14 | "bugs": {
15 | "url": "https://github.com/rucken/core-nestjs/issues"
16 | },
17 | "homepage": "https://github.com/rucken/core-nestjs",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/rucken/core-nestjs.git"
21 | },
22 | "keywords": [
23 | "nestjs",
24 | "backend",
25 | "jwt",
26 | "user",
27 | "group",
28 | "permission",
29 | "contenttype",
30 | "bootstrap",
31 | "node",
32 | "nodejs",
33 | "angular",
34 | "crud",
35 | "rest",
36 | "nest",
37 | "core",
38 | "rucken",
39 | "nrwl",
40 | "nx",
41 | "angular",
42 | "schematics",
43 | "ngx"
44 | ],
45 | "maintainers": [
46 | {
47 | "name": "EndyKaufman",
48 | "email": "admin@site15.ru"
49 | }
50 | ],
51 | "main": "index.js",
52 | "types": "index.d.ts",
53 | "dependencies": {
54 | "@nestjs/common": "6.1.1",
55 | "@nestjs/core": "6.1.1",
56 | "@nestjs/passport": "6.0.0",
57 | "@nestjs/swagger": "^3.0.2",
58 | "@nestjs/typeorm": "6.1.0",
59 | "class-transformer": "^0.2.0",
60 | "class-validator": "0.9.1",
61 | "node-django-hashers": "1.1.6",
62 | "highlight.js": "9.15.6"
63 | }
64 | }
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/configs/core.config.ts:
--------------------------------------------------------------------------------
1 | import { ICoreConfig } from '../interfaces/core-config.interface';
2 |
3 | export const DEFAULT_CORE_CONFIG: ICoreConfig = {
4 | demo: false,
5 | port: 5000,
6 | protocol: 'http',
7 | domain: 'localhost'
8 | };
9 | export const CORE_CONFIG_TOKEN = 'CoreConfigToken';
10 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/configs/index.ts:
--------------------------------------------------------------------------------
1 | import { CORE_CONFIG_TOKEN, DEFAULT_CORE_CONFIG } from '../configs/core.config';
2 |
3 | export const CORE_CONFIGS = [
4 | {
5 | provide: CORE_CONFIG_TOKEN,
6 | useValue: DEFAULT_CORE_CONFIG
7 | }
8 | ];
9 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/account.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, HttpCode, HttpStatus, Inject, MethodNotAllowedException, Post, Req } from '@nestjs/common';
2 | import { ApiBearerAuth, ApiResponse, ApiUseTags } from '@nestjs/swagger';
3 | import { plainToClass } from 'class-transformer';
4 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
5 | import { Permissions } from '../decorators/permissions.decorator';
6 | import { Roles } from '../decorators/roles.decorator';
7 | import { InAccountDto } from '../dto/in-account.dto';
8 | import { OutAccountDto } from '../dto/out-account.dto';
9 | import { User } from '../entities/user.entity';
10 | import { ICoreConfig } from '../interfaces/core-config.interface';
11 | import { AccountService } from '../services/account.service';
12 |
13 | @ApiUseTags('account')
14 | @Controller('/api/account')
15 | export class AccountController {
16 | constructor(
17 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
18 | private accountService: AccountService
19 | ) {}
20 |
21 | @ApiBearerAuth()
22 | @Roles('isActive')
23 | @Permissions('change_profile')
24 | @HttpCode(HttpStatus.OK)
25 | @Post('/update')
26 | @ApiResponse({
27 | status: HttpStatus.OK,
28 | type: OutAccountDto,
29 | description: ''
30 | })
31 | async update(@Req() req, @Body() accountDto: InAccountDto) {
32 | if (this.coreConfig.demo) {
33 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
34 | }
35 | try {
36 | return plainToClass(
37 | OutAccountDto,
38 | await this.accountService.update({
39 | id: req.user && req.user.id,
40 | user: plainToClass(User, accountDto)
41 | })
42 | );
43 | } catch (error) {
44 | throw error;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/content-types.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Delete,
5 | Get,
6 | HttpCode,
7 | HttpStatus,
8 | Inject,
9 | MethodNotAllowedException,
10 | Param,
11 | ParseIntPipe,
12 | Post,
13 | Put,
14 | Query
15 | } from '@nestjs/common';
16 | import { ApiBearerAuth, ApiImplicitParam, ApiImplicitQuery, ApiResponse, ApiUseTags } from '@nestjs/swagger';
17 | import { plainToClass } from 'class-transformer';
18 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
19 | import { Permissions } from '../decorators/permissions.decorator';
20 | import { Roles } from '../decorators/roles.decorator';
21 | import { InContentTypeDto } from '../dto/in-content-type.dto';
22 | import { OutContentTypeDto } from '../dto/out-content-type.dto';
23 | import { OutContentTypesDto } from '../dto/out-content-types.dto';
24 | import { ContentType } from '../entities/content-type.entity';
25 | import { ICoreConfig } from '../interfaces/core-config.interface';
26 | import { ParseIntWithDefaultPipe } from '../pipes/parse-int-with-default.pipe';
27 | import { ContentTypesService } from '../services/content-types.service';
28 |
29 | @ApiUseTags('content-types')
30 | @ApiBearerAuth()
31 | @Controller('/api/content_types')
32 | export class ContentTypesController {
33 | constructor(
34 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
35 | private readonly service: ContentTypesService
36 | ) {}
37 |
38 | @Roles('isSuperuser')
39 | @Permissions('add_content-type')
40 | @HttpCode(HttpStatus.CREATED)
41 | @ApiResponse({
42 | status: HttpStatus.CREATED,
43 | type: OutContentTypeDto,
44 | description: 'The record has been successfully created.'
45 | })
46 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
47 | @Post()
48 | async create(@Body() dto: InContentTypeDto) {
49 | try {
50 | return plainToClass(
51 | OutContentTypeDto,
52 | await this.service.create({
53 | item: plainToClass(ContentType, dto)
54 | })
55 | );
56 | } catch (error) {
57 | throw error;
58 | }
59 | }
60 |
61 | @Roles('isSuperuser')
62 | @Permissions('change_content-type')
63 | @HttpCode(HttpStatus.OK)
64 | @ApiResponse({
65 | status: HttpStatus.OK,
66 | type: OutContentTypeDto,
67 | description: 'The record has been successfully updated.'
68 | })
69 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
70 | @ApiImplicitParam({ name: 'id', type: Number })
71 | @Put(':id')
72 | async update(@Param('id', new ParseIntPipe()) id, @Body() dto: InContentTypeDto) {
73 | if (this.coreConfig.demo) {
74 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
75 | }
76 | try {
77 | return plainToClass(
78 | OutContentTypeDto,
79 | await this.service.update({
80 | id,
81 | item: plainToClass(ContentType, dto)
82 | })
83 | );
84 | } catch (error) {
85 | throw error;
86 | }
87 | }
88 |
89 | @Roles('isSuperuser')
90 | @Permissions('delete_content-type')
91 | @HttpCode(HttpStatus.NO_CONTENT)
92 | @ApiResponse({
93 | status: HttpStatus.NO_CONTENT,
94 | description: 'The record has been successfully deleted.'
95 | })
96 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
97 | @ApiImplicitParam({ name: 'id', type: Number })
98 | @Delete(':id')
99 | async delete(@Param('id', new ParseIntPipe()) id) {
100 | if (this.coreConfig.demo) {
101 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
102 | }
103 | try {
104 | return plainToClass(
105 | OutContentTypeDto,
106 | await this.service.delete({
107 | id
108 | })
109 | );
110 | } catch (error) {
111 | throw error;
112 | }
113 | }
114 |
115 | @Roles('isSuperuser')
116 | @Permissions('read_content-type')
117 | @HttpCode(HttpStatus.OK)
118 | @ApiResponse({
119 | status: HttpStatus.OK,
120 | type: OutContentTypeDto,
121 | description: ''
122 | })
123 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
124 | @ApiImplicitParam({ name: 'id', type: Number })
125 | @Get(':id')
126 | async findById(@Param('id', new ParseIntPipe()) id) {
127 | try {
128 | return plainToClass(
129 | OutContentTypeDto,
130 | await this.service.findById({
131 | id
132 | })
133 | );
134 | } catch (error) {
135 | throw error;
136 | }
137 | }
138 |
139 | @Roles('isSuperuser')
140 | @Permissions('read_content-type')
141 | @HttpCode(HttpStatus.OK)
142 | @ApiResponse({
143 | status: HttpStatus.OK,
144 | type: OutContentTypesDto,
145 | description: ''
146 | })
147 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
148 | @ApiImplicitQuery({
149 | name: 'q',
150 | required: false,
151 | type: String,
152 | description: 'Text for search (default: empty)'
153 | })
154 | @ApiImplicitQuery({
155 | name: 'sort',
156 | required: false,
157 | type: String,
158 | description: 'Column name for sort (default: -id)'
159 | })
160 | @ApiImplicitQuery({
161 | name: 'per_page',
162 | required: false,
163 | type: Number,
164 | description: 'Number of results to return per page. (default: 10)'
165 | })
166 | @ApiImplicitQuery({
167 | name: 'cur_page',
168 | required: false,
169 | type: Number,
170 | description: 'A page number within the paginated result set. (default: 1)'
171 | })
172 | @Get()
173 | async findAll(
174 | @Query('cur_page', new ParseIntWithDefaultPipe(1)) curPage,
175 | @Query('per_page', new ParseIntWithDefaultPipe(10)) perPage,
176 | @Query('q') q,
177 | @Query('sort') sort
178 | ) {
179 | try {
180 | return plainToClass(
181 | OutContentTypesDto,
182 | await this.service.findAll({
183 | curPage,
184 | perPage,
185 | q,
186 | sort
187 | })
188 | );
189 | } catch (error) {
190 | throw error;
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/groups.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Delete,
5 | Get,
6 | HttpCode,
7 | HttpStatus,
8 | Inject,
9 | MethodNotAllowedException,
10 | Param,
11 | ParseIntPipe,
12 | Post,
13 | Put,
14 | Query
15 | } from '@nestjs/common';
16 | import { ApiBearerAuth, ApiImplicitParam, ApiImplicitQuery, ApiResponse, ApiUseTags } from '@nestjs/swagger';
17 | import { plainToClass } from 'class-transformer';
18 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
19 | import { Permissions } from '../decorators/permissions.decorator';
20 | import { Roles } from '../decorators/roles.decorator';
21 | import { InGroupDto } from '../dto/in-group.dto';
22 | import { OutGroupDto } from '../dto/out-group.dto';
23 | import { OutGroupsDto } from '../dto/out-groups.dto';
24 | import { Group } from '../entities/group.entity';
25 | import { ICoreConfig } from '../interfaces/core-config.interface';
26 | import { ParseIntWithDefaultPipe } from '../pipes/parse-int-with-default.pipe';
27 | import { GroupsService } from '../services/groups.service';
28 |
29 | @ApiUseTags('groups')
30 | @ApiBearerAuth()
31 | @Controller('/api/groups')
32 | export class GroupsController {
33 | constructor(
34 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
35 | private readonly service: GroupsService
36 | ) {}
37 |
38 | @Roles('isSuperuser')
39 | @Permissions('add_group')
40 | @HttpCode(HttpStatus.CREATED)
41 | @ApiResponse({
42 | status: HttpStatus.CREATED,
43 | type: OutGroupDto,
44 | description: 'The record has been successfully created.'
45 | })
46 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
47 | @Post()
48 | async create(@Body() dto: InGroupDto) {
49 | try {
50 | return plainToClass(
51 | OutGroupDto,
52 | await this.service.create({
53 | item: plainToClass(Group, dto)
54 | })
55 | );
56 | } catch (error) {
57 | throw error;
58 | }
59 | }
60 |
61 | @Roles('isSuperuser')
62 | @Permissions('change_group')
63 | @HttpCode(HttpStatus.OK)
64 | @ApiResponse({
65 | status: HttpStatus.OK,
66 | type: OutGroupDto,
67 | description: 'The record has been successfully updated.'
68 | })
69 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
70 | @ApiImplicitParam({ name: 'id', type: Number })
71 | @Put(':id')
72 | async update(@Param('id', new ParseIntPipe()) id, @Body() dto: InGroupDto) {
73 | if (this.coreConfig.demo) {
74 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
75 | }
76 | try {
77 | return plainToClass(
78 | OutGroupDto,
79 | await this.service.update({
80 | id,
81 | item: plainToClass(Group, dto)
82 | })
83 | );
84 | } catch (error) {
85 | throw error;
86 | }
87 | }
88 |
89 | @Roles('isSuperuser')
90 | @Permissions('delete_group')
91 | @HttpCode(HttpStatus.NO_CONTENT)
92 | @ApiResponse({
93 | status: HttpStatus.NO_CONTENT,
94 | description: 'The record has been successfully deleted.'
95 | })
96 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
97 | @ApiImplicitParam({ name: 'id', type: Number })
98 | @Delete(':id')
99 | async delete(@Param('id', new ParseIntPipe()) id) {
100 | if (this.coreConfig.demo) {
101 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
102 | }
103 | try {
104 | return plainToClass(
105 | OutGroupDto,
106 | await this.service.delete({
107 | id
108 | })
109 | );
110 | } catch (error) {
111 | throw error;
112 | }
113 | }
114 |
115 | @Roles('isSuperuser')
116 | @Permissions('read_group')
117 | @HttpCode(HttpStatus.OK)
118 | @ApiResponse({
119 | status: HttpStatus.OK,
120 | type: OutGroupDto,
121 | description: ''
122 | })
123 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
124 | @ApiImplicitParam({ name: 'id', type: Number })
125 | @Get(':id')
126 | async findById(@Param('id', new ParseIntPipe()) id) {
127 | try {
128 | return plainToClass(
129 | OutGroupDto,
130 | await this.service.findById({
131 | id
132 | })
133 | );
134 | } catch (error) {
135 | throw error;
136 | }
137 | }
138 |
139 | @Roles('isSuperuser')
140 | @Permissions('read_group')
141 | @HttpCode(HttpStatus.OK)
142 | @ApiResponse({
143 | status: HttpStatus.OK,
144 | type: OutGroupsDto,
145 | description: ''
146 | })
147 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
148 | @ApiImplicitQuery({
149 | name: 'q',
150 | required: false,
151 | type: String,
152 | description: 'Text for search (default: empty)'
153 | })
154 | @ApiImplicitQuery({
155 | name: 'sort',
156 | required: false,
157 | type: String,
158 | description: 'Column name for sort (default: -id)'
159 | })
160 | @ApiImplicitQuery({
161 | name: 'per_page',
162 | required: false,
163 | type: Number,
164 | description: 'Number of results to return per page. (default: 10)'
165 | })
166 | @ApiImplicitQuery({
167 | name: 'cur_page',
168 | required: false,
169 | type: Number,
170 | description: 'A page number within the paginated result set. (default: 1)'
171 | })
172 | @Get()
173 | async findAll(
174 | @Query('cur_page', new ParseIntWithDefaultPipe(1)) curPage,
175 | @Query('per_page', new ParseIntWithDefaultPipe(10)) perPage,
176 | @Query('q') q,
177 | @Query('sort') sort
178 | ) {
179 | try {
180 | return plainToClass(
181 | OutGroupsDto,
182 | await this.service.findAll({
183 | curPage,
184 | perPage,
185 | q,
186 | sort
187 | })
188 | );
189 | } catch (error) {
190 | throw error;
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/index.ts:
--------------------------------------------------------------------------------
1 | import { AccountController } from '../controllers/account.controller';
2 | import { ContentTypesController } from '../controllers/content-types.controller';
3 | import { GroupsController } from '../controllers/groups.controller';
4 | import { PermissionsController } from '../controllers/permissions.controller';
5 | import { UsersController } from '../controllers/users.controller';
6 |
7 | export const CORE_CONTROLLERS = [
8 | AccountController,
9 | ContentTypesController,
10 | PermissionsController,
11 | UsersController,
12 | GroupsController
13 | ];
14 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/permissions.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Delete,
5 | Get,
6 | HttpCode,
7 | HttpStatus,
8 | Inject,
9 | MethodNotAllowedException,
10 | Param,
11 | ParseIntPipe,
12 | Post,
13 | Put,
14 | Query
15 | } from '@nestjs/common';
16 | import { ApiBearerAuth, ApiImplicitParam, ApiImplicitQuery, ApiResponse, ApiUseTags } from '@nestjs/swagger';
17 | import { plainToClass } from 'class-transformer';
18 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
19 | import { Permissions } from '../decorators/permissions.decorator';
20 | import { Roles } from '../decorators/roles.decorator';
21 | import { InPermissionDto } from '../dto/in-permission.dto';
22 | import { OutPermissionDto } from '../dto/out-permission.dto';
23 | import { OutPermissionsDto } from '../dto/out-permissions.dto';
24 | import { Permission } from '../entities/permission.entity';
25 | import { ICoreConfig } from '../interfaces/core-config.interface';
26 | import { ParseIntWithDefaultPipe } from '../pipes/parse-int-with-default.pipe';
27 | import { PermissionsService } from '../services/permissions.service';
28 |
29 | @ApiUseTags('permissions')
30 | @ApiBearerAuth()
31 | @Controller('/api/permissions')
32 | export class PermissionsController {
33 | constructor(
34 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
35 | private readonly service: PermissionsService
36 | ) {}
37 |
38 | @Roles('isSuperuser')
39 | @Permissions('add_permission')
40 | @HttpCode(HttpStatus.CREATED)
41 | @ApiResponse({
42 | status: HttpStatus.CREATED,
43 | type: OutPermissionDto,
44 | description: 'The record has been successfully created.'
45 | })
46 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
47 | @Post()
48 | async create(@Body() dto: InPermissionDto) {
49 | try {
50 | return plainToClass(
51 | OutPermissionDto,
52 | await this.service.create({
53 | item: plainToClass(Permission, dto)
54 | })
55 | );
56 | } catch (error) {
57 | throw error;
58 | }
59 | }
60 |
61 | @Roles('isSuperuser')
62 | @Permissions('change_permission')
63 | @HttpCode(HttpStatus.OK)
64 | @ApiResponse({
65 | status: HttpStatus.OK,
66 | type: OutPermissionDto,
67 | description: 'The record has been successfully updated.'
68 | })
69 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
70 | @ApiImplicitParam({ name: 'id', type: Number })
71 | @Put(':id')
72 | async update(@Param('id', new ParseIntPipe()) id, @Body() dto: InPermissionDto) {
73 | if (this.coreConfig.demo) {
74 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
75 | }
76 | try {
77 | return plainToClass(
78 | OutPermissionDto,
79 | await this.service.update({
80 | id,
81 | item: plainToClass(Permission, dto)
82 | })
83 | );
84 | } catch (error) {
85 | throw error;
86 | }
87 | }
88 |
89 | @Roles('isSuperuser')
90 | @Permissions('delete_permission')
91 | @HttpCode(HttpStatus.NO_CONTENT)
92 | @ApiResponse({
93 | status: HttpStatus.NO_CONTENT,
94 | description: 'The record has been successfully deleted.'
95 | })
96 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
97 | @ApiImplicitParam({ name: 'id', type: Number })
98 | @Delete(':id')
99 | async delete(@Param('id', new ParseIntPipe()) id) {
100 | if (this.coreConfig.demo) {
101 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
102 | }
103 | try {
104 | return plainToClass(
105 | OutPermissionDto,
106 | await this.service.delete({
107 | id
108 | })
109 | );
110 | } catch (error) {
111 | throw error;
112 | }
113 | }
114 |
115 | @Roles('isSuperuser')
116 | @Permissions('read_permission')
117 | @HttpCode(HttpStatus.OK)
118 | @ApiResponse({
119 | status: HttpStatus.OK,
120 | type: OutPermissionDto,
121 | description: ''
122 | })
123 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
124 | @ApiImplicitParam({ name: 'id', type: Number })
125 | @Get(':id')
126 | async findById(@Param('id', new ParseIntPipe()) id) {
127 | try {
128 | return plainToClass(
129 | OutPermissionDto,
130 | await this.service.findById({
131 | id
132 | })
133 | );
134 | } catch (error) {
135 | throw error;
136 | }
137 | }
138 |
139 | @Roles('isSuperuser')
140 | @Permissions('read_permission')
141 | @HttpCode(HttpStatus.OK)
142 | @ApiResponse({
143 | status: HttpStatus.OK,
144 | type: OutPermissionsDto,
145 | description: ''
146 | })
147 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
148 | @ApiImplicitQuery({
149 | name: 'q',
150 | required: false,
151 | type: String,
152 | description: 'Text for search (default: empty)'
153 | })
154 | @ApiImplicitQuery({
155 | name: 'sort',
156 | required: false,
157 | type: String,
158 | description: 'Column name for sort (default: -id)'
159 | })
160 | @ApiImplicitQuery({
161 | name: 'per_page',
162 | required: false,
163 | type: Number,
164 | description: 'Number of results to return per page. (default: 10)'
165 | })
166 | @ApiImplicitQuery({
167 | name: 'cur_page',
168 | required: false,
169 | type: Number,
170 | description: 'A page number within the paginated result set. (default: 1)'
171 | })
172 | @ApiImplicitQuery({
173 | name: 'group',
174 | required: false,
175 | type: Number,
176 | description: 'Group id for filter data by group. (default: empty)'
177 | })
178 | @ApiImplicitQuery({
179 | name: 'content_type',
180 | required: false,
181 | type: Number,
182 | description: 'Content type id for filter data by content type. (default: empty)'
183 | })
184 | @Get()
185 | async findAll(
186 | @Query('cur_page', new ParseIntWithDefaultPipe(1)) curPage,
187 | @Query('per_page', new ParseIntWithDefaultPipe(10)) perPage,
188 | @Query('q') q,
189 | @Query('group') group,
190 | @Query('content_type') contentType,
191 | @Query('sort') sort
192 | ) {
193 | try {
194 | return plainToClass(
195 | OutPermissionsDto,
196 | await this.service.findAll({
197 | curPage,
198 | perPage,
199 | q,
200 | sort,
201 | group,
202 | contentType
203 | })
204 | );
205 | } catch (error) {
206 | throw error;
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/controllers/users.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Body,
3 | Controller,
4 | Delete,
5 | Get,
6 | HttpCode,
7 | HttpStatus,
8 | Inject,
9 | MethodNotAllowedException,
10 | Param,
11 | ParseIntPipe,
12 | Post,
13 | Put,
14 | Query
15 | } from '@nestjs/common';
16 | import { ApiBearerAuth, ApiImplicitParam, ApiImplicitQuery, ApiResponse, ApiUseTags } from '@nestjs/swagger';
17 | import { plainToClass } from 'class-transformer';
18 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
19 | import { Permissions } from '../decorators/permissions.decorator';
20 | import { Roles } from '../decorators/roles.decorator';
21 | import { InCreateUserDto } from '../dto/in-create-user.dto';
22 | import { InUserDto } from '../dto/in-user.dto';
23 | import { OutUserDto } from '../dto/out-user.dto';
24 | import { OutUsersDto } from '../dto/out-users.dto';
25 | import { User } from '../entities/user.entity';
26 | import { ICoreConfig } from '../interfaces/core-config.interface';
27 | import { ParseIntWithDefaultPipe } from '../pipes/parse-int-with-default.pipe';
28 | import { UsersService } from '../services/users.service';
29 |
30 | @ApiUseTags('users')
31 | @ApiBearerAuth()
32 | @Controller('/api/users')
33 | export class UsersController {
34 | constructor(
35 | @Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig,
36 | private readonly service: UsersService
37 | ) {}
38 |
39 | @Roles('isSuperuser')
40 | @Permissions('add_user')
41 | @HttpCode(HttpStatus.CREATED)
42 | @ApiResponse({
43 | status: HttpStatus.CREATED,
44 | type: OutUserDto,
45 | description: 'The record has been successfully created.'
46 | })
47 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
48 | @Post()
49 | async create(@Body() dto: InCreateUserDto) {
50 | if (dto.isSuperuser && this.coreConfig.demo) {
51 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
52 | }
53 | try {
54 | return plainToClass(
55 | OutUserDto,
56 | await this.service.create({
57 | item: await plainToClass(User, dto).setPassword(dto.password)
58 | })
59 | );
60 | } catch (error) {
61 | throw error;
62 | }
63 | }
64 |
65 | @Roles('isSuperuser')
66 | @Permissions('change_user')
67 | @HttpCode(HttpStatus.OK)
68 | @ApiResponse({
69 | status: HttpStatus.OK,
70 | type: OutUserDto,
71 | description: 'The record has been successfully updated.'
72 | })
73 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
74 | @ApiImplicitParam({ name: 'id', type: Number })
75 | @Put(':id')
76 | async update(@Param('id', new ParseIntPipe()) id, @Body() dto: InUserDto) {
77 | if (this.coreConfig.demo) {
78 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
79 | }
80 | try {
81 | return plainToClass(
82 | OutUserDto,
83 | await this.service.update({
84 | id,
85 | item: await plainToClass(User, dto).setPassword(dto.password)
86 | })
87 | );
88 | } catch (error) {
89 | throw error;
90 | }
91 | }
92 |
93 | @Roles('isSuperuser')
94 | @Permissions('delete_user')
95 | @HttpCode(HttpStatus.NO_CONTENT)
96 | @ApiResponse({
97 | status: HttpStatus.NO_CONTENT,
98 | description: 'The record has been successfully deleted.'
99 | })
100 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
101 | @ApiImplicitParam({ name: 'id', type: Number })
102 | @Delete(':id')
103 | async delete(@Param('id', new ParseIntPipe()) id) {
104 | if (this.coreConfig.demo) {
105 | throw new MethodNotAllowedException('Not allowed in DEMO mode');
106 | }
107 | try {
108 | return plainToClass(
109 | OutUserDto,
110 | await this.service.delete({
111 | id
112 | })
113 | );
114 | } catch (error) {
115 | throw error;
116 | }
117 | }
118 |
119 | @Roles('isSuperuser')
120 | @Permissions('read_user')
121 | @HttpCode(HttpStatus.OK)
122 | @ApiResponse({
123 | status: HttpStatus.OK,
124 | type: OutUserDto,
125 | description: ''
126 | })
127 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
128 | @ApiImplicitParam({ name: 'id', type: Number })
129 | @Get(':id')
130 | async findById(@Param('id', new ParseIntPipe()) id) {
131 | try {
132 | return plainToClass(
133 | OutUserDto,
134 | await this.service.findById({
135 | id
136 | })
137 | );
138 | } catch (error) {
139 | throw error;
140 | }
141 | }
142 |
143 | @Roles('isSuperuser')
144 | @Permissions('read_user')
145 | @HttpCode(HttpStatus.OK)
146 | @ApiResponse({
147 | status: HttpStatus.OK,
148 | type: OutUsersDto,
149 | description: ''
150 | })
151 | @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden.' })
152 | @ApiImplicitQuery({
153 | name: 'q',
154 | required: false,
155 | type: String,
156 | description: 'Text for search (default: empty)'
157 | })
158 | @ApiImplicitQuery({
159 | name: 'sort',
160 | required: false,
161 | type: String,
162 | description: 'Column name for sort (default: -id)'
163 | })
164 | @ApiImplicitQuery({
165 | name: 'per_page',
166 | required: false,
167 | type: Number,
168 | description: 'Number of results to return per page. (default: 10)'
169 | })
170 | @ApiImplicitQuery({
171 | name: 'cur_page',
172 | required: false,
173 | type: Number,
174 | description: 'A page number within the paginated result set. (default: 1)'
175 | })
176 | @ApiImplicitQuery({
177 | name: 'group',
178 | required: false,
179 | type: Number,
180 | description: 'Group id for filter data by group. (default: empty)'
181 | })
182 | @Get()
183 | async findAll(
184 | @Query('cur_page', new ParseIntWithDefaultPipe(1)) curPage,
185 | @Query('per_page', new ParseIntWithDefaultPipe(10)) perPage,
186 | @Query('q') q,
187 | @Query('group') group,
188 | @Query('sort') sort
189 | ) {
190 | try {
191 | return plainToClass(
192 | OutUsersDto,
193 | await this.service.findAll({
194 | curPage,
195 | perPage,
196 | q,
197 | sort,
198 | group
199 | })
200 | );
201 | } catch (error) {
202 | throw error;
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/core.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module, Provider } from '@nestjs/common';
2 | import { TypeOrmModule } from '@nestjs/typeorm';
3 | import { CORE_CONTROLLERS } from './controllers';
4 | import { CORE_ENTITIES } from './entities';
5 | import { CORE_SERVICES } from './services';
6 |
7 | @Module({})
8 | export class RuckenCoreModule {
9 | static forFeature(options?: { providers: Provider[] }): DynamicModule {
10 | const providers = options && options.providers ? options.providers : [];
11 | return {
12 | module: RuckenCoreModule,
13 | imports: [TypeOrmModule.forFeature([...CORE_ENTITIES])],
14 | providers: [...providers, ...CORE_SERVICES],
15 | exports: [...CORE_SERVICES]
16 | };
17 | }
18 | static forRoot(options?: { providers: Provider[] }): DynamicModule {
19 | const providers = options && options.providers ? options.providers : [];
20 | return {
21 | module: RuckenCoreModule,
22 | imports: [TypeOrmModule.forFeature([...CORE_ENTITIES])],
23 | controllers: [...CORE_CONTROLLERS],
24 | providers: [...providers, ...CORE_SERVICES],
25 | exports: [...CORE_SERVICES]
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/decorators/permissions.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata } from '@nestjs/common';
2 |
3 | export const Permissions = (...permissions: string[]) => SetMetadata('permissions', permissions);
4 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/decorators/roles.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata } from '@nestjs/common';
2 |
3 | export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
4 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/account.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { GroupWithPermissionsDto } from '../dto/group-with-permissions.dto';
4 | import { UserDto } from '../dto/user.dto';
5 |
6 | export class AccountDto extends UserDto {
7 | @Type(() => GroupWithPermissionsDto)
8 | @ApiModelProperty({ type: GroupWithPermissionsDto, isArray: true })
9 | groups: GroupWithPermissionsDto[];
10 | }
11 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/content-type.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { MaxLength } from 'class-validator';
3 |
4 | export class ContentTypeDto {
5 | @ApiModelProperty({ type: Number })
6 | id: number;
7 |
8 | @MaxLength(100)
9 | @ApiModelProperty()
10 | name: string;
11 |
12 | @MaxLength(255)
13 | @ApiModelProperty()
14 | title: string;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/group-with-permissions.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { MaxLength } from 'class-validator';
4 | import { PermissionDto } from '../dto/permission.dto';
5 |
6 | export class GroupWithPermissionsDto {
7 | @ApiModelProperty({ type: Number })
8 | id: number;
9 |
10 | @MaxLength(100)
11 | @ApiModelProperty()
12 | name: string;
13 |
14 | @MaxLength(255)
15 | @ApiModelProperty()
16 | title: string;
17 |
18 | @Type(() => PermissionDto)
19 | @ApiModelProperty({ type: PermissionDto, isArray: true })
20 | permissions: PermissionDto[];
21 | }
22 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/group.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { MaxLength } from 'class-validator';
3 |
4 | export class GroupDto {
5 | @ApiModelProperty({ type: Number })
6 | id: number;
7 |
8 | @MaxLength(100)
9 | @ApiModelProperty()
10 | name: string;
11 |
12 | @MaxLength(255)
13 | @ApiModelProperty()
14 | title: string;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-account.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
2 | import { Exclude } from 'class-transformer';
3 | import { IsEmail, IsOptional, MaxLength } from 'class-validator';
4 |
5 | export class InAccountDto {
6 | @IsOptional()
7 | id: number;
8 |
9 | @IsEmail()
10 | @MaxLength(254)
11 | @ApiModelProperty()
12 | email: string;
13 |
14 | @Exclude()
15 | @MaxLength(128)
16 | @IsOptional()
17 | @ApiModelPropertyOptional()
18 | password: string;
19 |
20 | @MaxLength(150)
21 | @ApiModelProperty()
22 | username: string;
23 |
24 | @MaxLength(30)
25 | @ApiModelProperty()
26 | firstName: string;
27 |
28 | @MaxLength(30)
29 | @ApiModelProperty()
30 | lastName: string;
31 | }
32 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-content-type.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsOptional, MaxLength } from 'class-validator';
3 |
4 | export class InContentTypeDto {
5 | @IsOptional()
6 | id: number;
7 |
8 | @MaxLength(100)
9 | @ApiModelProperty()
10 | name: string;
11 |
12 | @MaxLength(255)
13 | @ApiModelProperty()
14 | title: string;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-create-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { IsOptional, MaxLength } from 'class-validator';
4 | import { GroupDto } from '../dto/group.dto';
5 |
6 | export class InCreateUserDto {
7 | @IsOptional()
8 | id: number;
9 |
10 | @MaxLength(128)
11 | @ApiModelPropertyOptional()
12 | password: string;
13 |
14 | @ApiModelPropertyOptional({ type: String })
15 | lastLogin: Date;
16 |
17 | @ApiModelProperty({ type: Boolean })
18 | isSuperuser: boolean;
19 |
20 | @MaxLength(150)
21 | @ApiModelProperty()
22 | username: string;
23 |
24 | @MaxLength(30)
25 | @ApiModelProperty()
26 | firstName: string;
27 |
28 | @MaxLength(30)
29 | @ApiModelProperty()
30 | lastName: string;
31 |
32 | @MaxLength(254)
33 | @ApiModelProperty()
34 | email: string;
35 |
36 | @ApiModelProperty({ type: Boolean })
37 | isStaff: boolean;
38 |
39 | @ApiModelProperty({ type: Boolean })
40 | isActive: boolean;
41 |
42 | @ApiModelProperty({ type: String })
43 | dateJoined: Date;
44 |
45 | @ApiModelPropertyOptional({ type: String })
46 | dateOfBirth: Date;
47 |
48 | @Type(() => GroupDto)
49 | @ApiModelProperty({ type: GroupDto, isArray: true })
50 | groups: GroupDto[];
51 | }
52 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-group.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { IsOptional, MaxLength } from 'class-validator';
3 |
4 | export class InGroupDto {
5 | @IsOptional()
6 | id: number;
7 |
8 | @MaxLength(100)
9 | @ApiModelProperty()
10 | name: string;
11 |
12 | @MaxLength(255)
13 | @ApiModelProperty()
14 | title: string;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-permission.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { IsOptional, MaxLength } from 'class-validator';
4 | import { ContentTypeDto } from '../dto/content-type.dto';
5 |
6 | export class InPermissionDto {
7 | @IsOptional()
8 | id: number;
9 |
10 | @MaxLength(100)
11 | @ApiModelProperty()
12 | name: string;
13 |
14 | @MaxLength(255)
15 | @ApiModelProperty()
16 | title: string;
17 |
18 | @Type(() => ContentTypeDto)
19 | @ApiModelProperty({ type: ContentTypeDto })
20 | contentType: ContentTypeDto;
21 | }
22 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/in-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { UserDto } from '../dto/user.dto';
2 |
3 | export class InUserDto extends UserDto {}
4 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/meta.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 |
3 | export class MetaDto {
4 | @ApiModelProperty({ type: Number })
5 | perPage: number;
6 |
7 | @ApiModelProperty({ type: Number })
8 | totalPages: number;
9 |
10 | @ApiModelProperty({ type: Number })
11 | totalResults: number;
12 |
13 | @ApiModelProperty({ type: Number })
14 | curPage: number;
15 | }
16 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-account.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { AccountDto } from '../dto/account.dto';
4 |
5 | export class OutAccountDto {
6 | @Type(() => AccountDto)
7 | @ApiModelProperty({ type: AccountDto })
8 | user: AccountDto;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-content-type.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { ContentTypeDto } from '../dto/content-type.dto';
4 | export class OutContentTypeDto {
5 | @Type(() => ContentTypeDto)
6 | @ApiModelProperty({ type: ContentTypeDto })
7 | contentType: ContentTypeDto;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-content-types.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { ContentTypeDto } from '../dto/content-type.dto';
4 | import { MetaDto } from '../dto/meta.dto';
5 |
6 | export class OutContentTypesDto {
7 | @Type(() => ContentTypeDto)
8 | @ApiModelProperty({ type: ContentTypeDto, isArray: true })
9 | contentTypes: ContentTypeDto[];
10 |
11 | @Type(() => MetaDto)
12 | @ApiModelProperty({ type: MetaDto })
13 | meta: MetaDto;
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-group.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { GroupWithPermissionsDto } from '../dto/group-with-permissions.dto';
4 |
5 | export class OutGroupDto {
6 | @Type(() => GroupWithPermissionsDto)
7 | @ApiModelProperty({ type: GroupWithPermissionsDto })
8 | group: GroupWithPermissionsDto;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-groups.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { GroupWithPermissionsDto } from '../dto/group-with-permissions.dto';
4 | import { MetaDto } from '../dto/meta.dto';
5 |
6 | export class OutGroupsDto {
7 | @Type(() => GroupWithPermissionsDto)
8 | @ApiModelProperty({ type: GroupWithPermissionsDto, isArray: true })
9 | groups: GroupWithPermissionsDto[];
10 |
11 | @Type(() => MetaDto)
12 | @ApiModelProperty({ type: MetaDto })
13 | meta: MetaDto;
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-permission.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { PermissionDto } from '../dto/permission.dto';
4 |
5 | export class OutPermissionDto {
6 | @Type(() => PermissionDto)
7 | @ApiModelProperty({ type: PermissionDto })
8 | permission: PermissionDto;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-permissions.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { MetaDto } from '../dto/meta.dto';
4 | import { PermissionDto } from '../dto/permission.dto';
5 |
6 | export class OutPermissionsDto {
7 | @Type(() => PermissionDto)
8 | @ApiModelProperty({ type: PermissionDto, isArray: true })
9 | permissions: PermissionDto[];
10 |
11 | @Type(() => MetaDto)
12 | @ApiModelProperty({ type: MetaDto })
13 | meta: MetaDto;
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { UserDto } from '../dto/user.dto';
4 |
5 | export class OutUserDto {
6 | @Type(() => UserDto)
7 | @ApiModelProperty({ type: UserDto })
8 | user: UserDto;
9 | }
10 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/out-users.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { MetaDto } from '../dto/meta.dto';
4 | import { UserDto } from '../dto/user.dto';
5 |
6 | export class OutUsersDto {
7 | @Type(() => UserDto)
8 | @ApiModelProperty({ type: UserDto, isArray: true })
9 | users: UserDto[];
10 |
11 | @Type(() => MetaDto)
12 | @ApiModelProperty({ type: MetaDto })
13 | meta: MetaDto;
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/permission.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty } from '@nestjs/swagger';
2 | import { Type } from 'class-transformer';
3 | import { MaxLength } from 'class-validator';
4 | import { ContentTypeDto } from '../dto/content-type.dto';
5 |
6 | export class PermissionDto {
7 | @ApiModelProperty({ type: Number })
8 | id: number;
9 |
10 | @MaxLength(100)
11 | @ApiModelProperty()
12 | name: string;
13 |
14 | @MaxLength(255)
15 | @ApiModelProperty()
16 | title: string;
17 |
18 | @Type(() => ContentTypeDto)
19 | @ApiModelProperty({ type: ContentTypeDto })
20 | contentType: ContentTypeDto;
21 | }
22 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/dto/user.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
2 | import { Exclude, Type } from 'class-transformer';
3 | import { MaxLength } from 'class-validator';
4 | import { GroupDto } from '../dto/group.dto';
5 |
6 | export class UserDto {
7 | @ApiModelProperty({ type: Number })
8 | id: number;
9 |
10 | @Exclude()
11 | @ApiModelPropertyOptional()
12 | password: string;
13 |
14 | @ApiModelPropertyOptional({ type: String })
15 | lastLogin: Date;
16 |
17 | @ApiModelPropertyOptional({ type: Boolean })
18 | isSuperuser: boolean;
19 |
20 | @MaxLength(150)
21 | @ApiModelProperty()
22 | username: string;
23 |
24 | @MaxLength(30)
25 | @ApiModelProperty()
26 | firstName: string;
27 |
28 | @MaxLength(30)
29 | @ApiModelProperty()
30 | lastName: string;
31 |
32 | @MaxLength(254)
33 | @ApiModelProperty()
34 | email: string;
35 |
36 | @ApiModelPropertyOptional({ type: Boolean })
37 | isStaff: boolean;
38 |
39 | @ApiModelPropertyOptional({ type: Boolean })
40 | isActive: boolean;
41 |
42 | @ApiModelProperty({ type: String })
43 | dateJoined: Date;
44 |
45 | @ApiModelPropertyOptional({ type: String })
46 | dateOfBirth: Date;
47 |
48 | @Type(() => GroupDto)
49 | @ApiModelProperty({ type: GroupDto, isArray: true })
50 | groups: GroupDto[];
51 | }
52 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/entities/content-type.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
3 | import { Permission } from '../entities/permission.entity';
4 | import { CustomValidationError } from '../exceptions/custom-validation.error';
5 |
6 | @Entity({ name: 'content_types' })
7 | export class ContentType {
8 | @PrimaryGeneratedColumn()
9 | id: number = undefined;
10 |
11 | @Column({ length: 100 })
12 | @IsNotEmpty()
13 | @MaxLength(100)
14 | name: string = undefined;
15 |
16 | @Column({ length: 255 })
17 | @IsNotEmpty()
18 | @MaxLength(255)
19 | title: string = undefined;
20 |
21 | @OneToMany(type => Permission, permission => permission.contentType)
22 | permissions: Permission[];
23 |
24 | @BeforeInsert()
25 | doBeforeInsertion() {
26 | const errors = validateSync(this, { validationError: { target: false } });
27 | if (errors.length > 0) {
28 | throw new CustomValidationError(errors);
29 | }
30 | }
31 |
32 | @BeforeUpdate()
33 | doBeforeUpdate() {
34 | const errors = validateSync(this, { validationError: { target: false } });
35 | if (errors.length > 0) {
36 | throw new CustomValidationError(errors);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/entities/group.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import { BeforeInsert, BeforeUpdate, Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
3 | import { Permission } from '../entities/permission.entity';
4 | import { User } from '../entities/user.entity';
5 | import { CustomValidationError } from '../exceptions/custom-validation.error';
6 |
7 | @Entity({ name: 'groups' })
8 | export class Group {
9 | @PrimaryGeneratedColumn()
10 | id: number = undefined;
11 |
12 | @Column({ length: 100, unique: true })
13 | @IsNotEmpty()
14 | @MaxLength(100)
15 | name: string = undefined;
16 |
17 | @Column({ length: 255, unique: true })
18 | @IsNotEmpty()
19 | @MaxLength(255)
20 | title: string = undefined;
21 |
22 | @ManyToMany(type => Permission, {
23 | cascade: ['remove']
24 | })
25 | @JoinTable({
26 | // not work on run cli migration:
27 | name: 'group_permissions',
28 | joinColumn: {
29 | name: 'group_id',
30 | referencedColumnName: 'id'
31 | },
32 | inverseJoinColumn: {
33 | name: 'permission_id',
34 | referencedColumnName: 'id'
35 | }
36 | })
37 | permissions: Permission[];
38 |
39 | @ManyToMany(type => User)
40 | @JoinTable({
41 | // not work on run cli migration:
42 | name: 'user_groups',
43 | joinColumn: {
44 | name: 'group_id',
45 | referencedColumnName: 'id'
46 | },
47 | inverseJoinColumn: {
48 | name: 'user_id',
49 | referencedColumnName: 'id'
50 | }
51 | })
52 | users: User[];
53 |
54 | @BeforeInsert()
55 | doBeforeInsertion() {
56 | const errors = validateSync(this, { validationError: { target: false } });
57 | if (errors.length > 0) {
58 | throw new CustomValidationError(errors);
59 | }
60 | }
61 |
62 | @BeforeUpdate()
63 | doBeforeUpdate() {
64 | const errors = validateSync(this, { validationError: { target: false } });
65 | if (errors.length > 0) {
66 | throw new CustomValidationError(errors);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/entities/index.ts:
--------------------------------------------------------------------------------
1 | import { ContentType } from './content-type.entity';
2 | import { Group } from './group.entity';
3 | import { Permission } from './permission.entity';
4 | import { User } from './user.entity';
5 |
6 | export const CORE_ENTITIES = [Permission, ContentType, Group, User];
7 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/entities/permission.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import {
3 | BeforeInsert,
4 | BeforeUpdate,
5 | Column,
6 | Entity,
7 | JoinColumn,
8 | JoinTable,
9 | ManyToMany,
10 | ManyToOne,
11 | PrimaryGeneratedColumn
12 | } from 'typeorm';
13 | import { ContentType } from '../entities/content-type.entity';
14 | import { Group } from '../entities/group.entity';
15 | import { CustomValidationError } from '../exceptions/custom-validation.error';
16 |
17 | @Entity({ name: 'permissions' })
18 | export class Permission {
19 | @PrimaryGeneratedColumn()
20 | id: number = undefined;
21 |
22 | @Column({ length: 100 })
23 | @IsNotEmpty()
24 | @MaxLength(100)
25 | name: string = undefined;
26 |
27 | @Column({ length: 255 })
28 | @IsNotEmpty()
29 | @MaxLength(255)
30 | title: string = undefined;
31 |
32 | @ManyToOne(type => ContentType, { eager: true, nullable: true })
33 | @JoinColumn({ name: 'content_type_id' })
34 | contentType: ContentType = undefined;
35 |
36 | @ManyToMany(type => Group)
37 | @JoinTable({
38 | // not work on run cli migration:
39 | name: 'group_permissions',
40 | joinColumn: {
41 | name: 'permission_id',
42 | referencedColumnName: 'id'
43 | },
44 | inverseJoinColumn: {
45 | name: 'group_id',
46 | referencedColumnName: 'id'
47 | }
48 | })
49 | groups: Group[];
50 |
51 | @BeforeInsert()
52 | doBeforeInsertion() {
53 | const errors = validateSync(this, { validationError: { target: false } });
54 | if (errors.length > 0) {
55 | throw new CustomValidationError(errors);
56 | }
57 | }
58 |
59 | @BeforeUpdate()
60 | doBeforeUpdate() {
61 | const errors = validateSync(this, { validationError: { target: false } });
62 | if (errors.length > 0) {
63 | throw new CustomValidationError(errors);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/entities/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsNotEmpty, IsOptional, MaxLength, validateSync } from 'class-validator';
2 | import * as hashers from 'node-django-hashers';
3 | import {
4 | BeforeInsert,
5 | BeforeUpdate,
6 | Column,
7 | CreateDateColumn,
8 | Entity,
9 | JoinTable,
10 | ManyToMany,
11 | PrimaryGeneratedColumn,
12 | UpdateDateColumn
13 | } from 'typeorm';
14 | import { Group } from '../entities/group.entity';
15 | import { CustomValidationError } from '../exceptions/custom-validation.error';
16 |
17 | @Entity({ name: 'users' })
18 | export class User {
19 | @PrimaryGeneratedColumn()
20 | id: number = undefined;
21 |
22 | @Column({ length: 128 })
23 | @MaxLength(128)
24 | @IsOptional()
25 | password: string = undefined;
26 |
27 | @UpdateDateColumn({ name: 'last_login', nullable: true })
28 | lastLogin: Date = undefined;
29 |
30 | @Column({ name: 'is_superuser', default: false })
31 | isSuperuser: boolean = undefined;
32 |
33 | @Column({ length: 150, unique: true })
34 | @MaxLength(150)
35 | username: string = undefined;
36 |
37 | @Column({ name: 'first_name', length: 30, nullable: true })
38 | @MaxLength(30)
39 | @IsOptional()
40 | firstName: string = undefined;
41 |
42 | @Column({ name: 'last_name', length: 30, nullable: true })
43 | @MaxLength(30)
44 | @IsOptional()
45 | lastName: string = undefined;
46 |
47 | @Column({ length: 254, unique: true })
48 | @IsNotEmpty()
49 | @IsEmail()
50 | @MaxLength(254)
51 | email: string = undefined;
52 |
53 | @Column({ name: 'is_staff', default: false })
54 | isStaff: boolean = undefined;
55 |
56 | @Column({ name: 'is_active', default: false })
57 | isActive: boolean = undefined;
58 |
59 | @CreateDateColumn({ name: 'date_joined' })
60 | dateJoined: Date = undefined;
61 |
62 | @Column({ type: Date, name: 'date_of_birth', nullable: true })
63 | dateOfBirth: Date = undefined;
64 |
65 | @ManyToMany(type => Group)
66 | @JoinTable({
67 | // not work on run cli migration:
68 | name: 'user_groups',
69 | joinColumn: {
70 | name: 'user_id',
71 | referencedColumnName: 'id'
72 | },
73 | inverseJoinColumn: {
74 | name: 'group_id',
75 | referencedColumnName: 'id'
76 | }
77 | })
78 | groups: Group[];
79 |
80 | @BeforeInsert()
81 | doBeforeInsertion() {
82 | const errors = validateSync(this, { validationError: { target: false } });
83 | if (errors.length > 0) {
84 | throw new CustomValidationError(errors);
85 | }
86 | }
87 |
88 | @BeforeUpdate()
89 | doBeforeUpdate() {
90 | const errors = validateSync(this, { validationError: { target: false } });
91 | if (errors.length > 0) {
92 | throw new CustomValidationError(errors);
93 | }
94 | }
95 |
96 | async createPassword(password: string) {
97 | const h = new hashers.PBKDF2PasswordHasher();
98 | const hash = await h.encode(password, h.salt());
99 | return hash;
100 | }
101 |
102 | async validatePassword(password: string) {
103 | const h = new hashers.PBKDF2PasswordHasher();
104 | return h.verify(password, this.password);
105 | }
106 |
107 | async setPassword(password: string) {
108 | if (password) {
109 | this.password = await this.createPassword(password);
110 | }
111 | return this;
112 | }
113 |
114 | checkPermissions(permissions: string[]) {
115 | permissions = permissions.map(permission => permission.toLowerCase());
116 | return (
117 | this.groups.filter(
118 | group =>
119 | group &&
120 | group.permissions.filter(permission => permissions.indexOf(permission.name.toLowerCase()) !== -1).length > 0
121 | ).length > 0
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/exceptions/custom-validation.error.ts:
--------------------------------------------------------------------------------
1 | import { ValidationError } from 'class-validator';
2 |
3 | export class CustomValidationError extends Error {
4 | errors: ValidationError[];
5 |
6 | constructor(errors: ValidationError[]) {
7 | super();
8 | this.errors = errors;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/exceptions/custom.error.ts:
--------------------------------------------------------------------------------
1 | export class CustomError extends Error {
2 | constructor(error: string) {
3 | super(error);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/filters/custom-exception.filter.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Inject, Logger } from '@nestjs/common';
2 | import { ValidationError } from 'class-validator';
3 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
4 | import { CustomValidationError } from '../exceptions/custom-validation.error';
5 | import { CustomError } from '../exceptions/custom.error';
6 | import { ICoreConfig } from '../interfaces/core-config.interface';
7 |
8 | @Catch(SyntaxError, CustomValidationError, CustomError, HttpException)
9 | export class CustomExceptionFilter implements ExceptionFilter {
10 | constructor(@Inject(CORE_CONFIG_TOKEN) private readonly coreConfig: ICoreConfig) {}
11 |
12 | private response(
13 | exception: CustomValidationError | SyntaxError | Error | HttpException,
14 | host: ArgumentsHost,
15 | data: any,
16 | status?: number
17 | ) {
18 | const ctx = host.switchToHttp();
19 | const response = ctx.getResponse();
20 | const request = ctx.getRequest();
21 | Logger.error(JSON.stringify(exception), undefined, CustomExceptionFilter.name);
22 | if (request.originalUrl.indexOf('/api/') !== 0 && request.accepts('html') && this.coreConfig.indexFile) {
23 | response.sendFile(this.coreConfig.indexFile);
24 | } else {
25 | response.status(status ? status : HttpStatus.BAD_REQUEST).json(data);
26 | }
27 | }
28 |
29 | catch(exception: CustomValidationError | SyntaxError | Error | HttpException, host: ArgumentsHost) {
30 | const errors = {};
31 | if (exception instanceof CustomValidationError) {
32 | exception.errors.forEach((error: ValidationError) => {
33 | Object.keys(error.constraints).forEach((key: string) => {
34 | if (!errors[error.property]) {
35 | errors[error.property] = [];
36 | }
37 | errors[error.property].push(error.constraints[key]);
38 | });
39 | });
40 | this.response(exception, host, {
41 | validationErrors: errors
42 | });
43 | }
44 | if (exception instanceof CustomError) {
45 | this.response(exception, host, {
46 | message: exception.message
47 | });
48 | }
49 | if (exception instanceof SyntaxError) {
50 | this.response(exception, host, {
51 | message: 'Syntax error'
52 | });
53 | }
54 | if (exception instanceof HttpException) {
55 | this.response(
56 | exception,
57 | host,
58 | {
59 | message: exception.message && exception.message.message ? exception.message.message : 'Http exception'
60 | },
61 | exception.getStatus()
62 | );
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/filters/index.ts:
--------------------------------------------------------------------------------
1 | import { APP_FILTER } from '@nestjs/core';
2 | import { CustomExceptionFilter } from '../filters/custom-exception.filter';
3 |
4 | export const CORE_APP_FILTERS = [{ provide: APP_FILTER, useClass: CustomExceptionFilter, multi: true }];
5 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/i18n/template.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "MIME-Version: 1.0\n"
4 | "Content-Type: text/plain; charset=utf-8\n"
5 | "Content-Transfer-Encoding: 8bit\n"
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './core.module';
2 | export * from './configs/core.config';
3 | export * from './configs/index';
4 | export * from './controllers/account.controller';
5 | export * from './controllers/content-types.controller';
6 | export * from './controllers/groups.controller';
7 | export * from './controllers/index';
8 | export * from './controllers/permissions.controller';
9 | export * from './controllers/users.controller';
10 | export * from './decorators/permissions.decorator';
11 | export * from './decorators/roles.decorator';
12 | export * from './dto/account.dto';
13 | export * from './dto/content-type.dto';
14 | export * from './dto/group-with-permissions.dto';
15 | export * from './dto/group.dto';
16 | export * from './dto/in-account.dto';
17 | export * from './dto/in-content-type.dto';
18 | export * from './dto/in-create-user.dto';
19 | export * from './dto/in-group.dto';
20 | export * from './dto/in-permission.dto';
21 | export * from './dto/in-user.dto';
22 | export * from './dto/meta.dto';
23 | export * from './dto/out-account.dto';
24 | export * from './dto/out-content-type.dto';
25 | export * from './dto/out-content-types.dto';
26 | export * from './dto/out-group.dto';
27 | export * from './dto/out-groups.dto';
28 | export * from './dto/out-permission.dto';
29 | export * from './dto/out-permissions.dto';
30 | export * from './dto/out-user.dto';
31 | export * from './dto/out-users.dto';
32 | export * from './dto/permission.dto';
33 | export * from './dto/user.dto';
34 | export * from './entities/content-type.entity';
35 | export * from './entities/group.entity';
36 | export * from './entities/index';
37 | export * from './entities/permission.entity';
38 | export * from './entities/user.entity';
39 | export * from './exceptions/custom-validation.error';
40 | export * from './exceptions/custom.error';
41 | export * from './filters/custom-exception.filter';
42 | export * from './filters/index';
43 | export * from './interfaces/core-config.interface';
44 | export * from './migrations/1524197725191-Init';
45 | export * from './migrations/1524199022084-FillData';
46 | export * from './migrations/1524199144534-FillFrontendData';
47 | export * from './migrations_entities/1524199022084/content-type.entity';
48 | export * from './migrations_entities/1524199022084/group.entity';
49 | export * from './migrations_entities/1524199022084/permission.entity';
50 | export * from './migrations_entities/1524199022084/user.entity';
51 | export * from './pipes/index';
52 | export * from './pipes/parse-int-with-default.pipe';
53 | export * from './pipes/validation.pipe';
54 | export * from './services/account.service';
55 | export * from './services/content-types.service';
56 | export * from './services/groups.service';
57 | export * from './services/index';
58 | export * from './services/permissions.service';
59 | export * from './services/users.service';
60 | export * from './utils/custom-transforms';
61 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/interfaces/core-config.interface.ts:
--------------------------------------------------------------------------------
1 | export interface ICoreConfig {
2 | demo: boolean;
3 | port?: number;
4 | externalPort?: number;
5 | domain?: string;
6 | protocol?: 'http' | 'https';
7 | indexFile?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations/1524199022084-FillData.ts:
--------------------------------------------------------------------------------
1 | import { plainToClass } from 'class-transformer';
2 | import { MigrationInterface, QueryRunner } from 'typeorm';
3 | import { ContentType1524199022084 } from '../migrations_entities/1524199022084/content-type.entity';
4 | import { Group1524199022084 } from '../migrations_entities/1524199022084/group.entity';
5 | import { Permission1524199022084 } from '../migrations_entities/1524199022084/permission.entity';
6 | import { User1524199022084 } from '../migrations_entities/1524199022084/user.entity';
7 |
8 | export class FillData1524199022084 implements MigrationInterface {
9 | public async up(queryRunner: QueryRunner): Promise {
10 | const ctPermission = await queryRunner.manager
11 | .getRepository(ContentType1524199022084)
12 | .save(plainToClass(ContentType1524199022084, { name: 'permission', title: 'Permission' }));
13 | const ctGroup = await queryRunner.manager
14 | .getRepository(ContentType1524199022084)
15 | .save(plainToClass(ContentType1524199022084, { name: 'group', title: 'Group' }));
16 | const ctContentTtype = await queryRunner.manager
17 | .getRepository(ContentType1524199022084)
18 | .save(
19 | plainToClass(ContentType1524199022084, {
20 | name: 'content-type',
21 | title: 'Content type'
22 | })
23 | );
24 | const ctUser = await queryRunner.manager
25 | .getRepository(ContentType1524199022084)
26 | .save(plainToClass(ContentType1524199022084, { name: 'user', title: 'User' }));
27 | const pPermissions = await queryRunner.manager.getRepository(Permission1524199022084).save(
28 | plainToClass(Permission1524199022084, [
29 | {
30 | title: 'Can add permission',
31 | name: 'add_permission',
32 | contentType: ctPermission
33 | },
34 | {
35 | title: 'Can change permission',
36 | name: 'change_permission',
37 | contentType: ctPermission
38 | },
39 | {
40 | title: 'Can delete permission',
41 | name: 'delete_permission',
42 | contentType: ctPermission
43 | },
44 | {
45 | title: 'Can add group',
46 | name: 'add_group',
47 | contentType: ctGroup
48 | },
49 | {
50 | title: 'Can change group',
51 | name: 'change_group',
52 | contentType: ctGroup
53 | },
54 | {
55 | title: 'Can delete group',
56 | name: 'delete_group',
57 | contentType: ctGroup
58 | },
59 | {
60 | title: 'Can add content type',
61 | name: 'add_content-type',
62 | contentType: ctContentTtype
63 | },
64 | {
65 | title: 'Can change content type',
66 | name: 'change_content-type',
67 | contentType: ctContentTtype
68 | },
69 | {
70 | title: 'Can delete content type',
71 | name: 'delete_content-type',
72 | contentType: ctContentTtype
73 | },
74 | {
75 | title: 'Can add user',
76 | name: 'add_user',
77 | contentType: ctUser
78 | },
79 | {
80 | title: 'Can change user',
81 | name: 'change_user',
82 | contentType: ctUser
83 | },
84 | {
85 | title: 'Can delete user',
86 | name: 'delete_user',
87 | contentType: ctUser
88 | },
89 | {
90 | title: 'Can read user',
91 | name: 'read_user',
92 | contentType: ctUser
93 | },
94 | {
95 | title: 'Can read group',
96 | name: 'read_group',
97 | contentType: ctGroup
98 | },
99 | {
100 | title: 'Can read permission',
101 | name: 'read_permission',
102 | contentType: ctPermission
103 | },
104 | {
105 | title: 'Can read content type',
106 | name: 'read_content-type',
107 | contentType: ctContentTtype
108 | },
109 | {
110 | title: 'Can change profile',
111 | name: 'change_profile',
112 | contentType: ctUser
113 | }
114 | ])
115 | );
116 | const gUser = await queryRunner.manager.getRepository(Group1524199022084).save(
117 | plainToClass(Group1524199022084, {
118 | name: 'user',
119 | title: 'User',
120 | permissions: pPermissions.filter(item => item.name === 'change_profile')
121 | })
122 | );
123 | const gAdmin = await queryRunner.manager.getRepository(Group1524199022084).save(
124 | plainToClass(Group1524199022084, {
125 | name: 'admin',
126 | title: 'Admin',
127 | permissions: pPermissions
128 | })
129 | );
130 | const tempUser = new User1524199022084();
131 | const uUsers = await queryRunner.manager.getRepository(User1524199022084).save(
132 | plainToClass(User1524199022084, [
133 | {
134 | username: 'admin',
135 | email: 'admin@admin.com',
136 | password: await tempUser.createPassword('12345678'),
137 | firstName: 'AdminFirstName',
138 | lastName: 'AdminLastName',
139 | isSuperuser: false,
140 | isStaff: false,
141 | isActive: true,
142 | groups: [gAdmin]
143 | },
144 | {
145 | username: 'user1',
146 | email: 'user1@user1.com',
147 | password: await tempUser.createPassword('12345678'),
148 | firstName: 'User1FirstName',
149 | lastName: 'User1LastName',
150 | isSuperuser: false,
151 | isStaff: false,
152 | isActive: true,
153 | groups: [gUser]
154 | },
155 | {
156 | username: 'user2',
157 | email: 'user2@user2.com',
158 | password: await tempUser.createPassword('12345678'),
159 | firstName: 'User2FirstName',
160 | lastName: 'User2LastName',
161 | isSuperuser: false,
162 | isStaff: false,
163 | isActive: true,
164 | groups: [gUser]
165 | }
166 | ])
167 | );
168 | }
169 |
170 | public async down(queryRunner: QueryRunner): Promise {}
171 | }
172 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations/1524199144534-FillFrontendData.ts:
--------------------------------------------------------------------------------
1 | import { plainToClass } from 'class-transformer';
2 | import { MigrationInterface, QueryRunner } from 'typeorm';
3 | import { ContentType1524199022084 } from '../migrations_entities/1524199022084/content-type.entity';
4 | import { Group1524199022084 } from '../migrations_entities/1524199022084/group.entity';
5 | import { Permission1524199022084 } from '../migrations_entities/1524199022084/permission.entity';
6 |
7 | export class FillFrontendData1524199144534 implements MigrationInterface {
8 | public async up(queryRunner: QueryRunner): Promise {
9 | const gUser = await queryRunner.manager.getRepository(Group1524199022084).findOneOrFail({
10 | where: {
11 | name: 'user'
12 | },
13 | relations: ['permissions']
14 | });
15 | const gAdmin = await queryRunner.manager.getRepository(Group1524199022084).findOneOrFail({
16 | where: {
17 | name: 'admin'
18 | },
19 | relations: ['permissions']
20 | });
21 | const ctUser = await queryRunner.manager
22 | .getRepository(ContentType1524199022084)
23 | .findOneOrFail({
24 | where: {
25 | name: 'user'
26 | }
27 | });
28 | const permissionTitles = {
29 | 'read_themes-page': 'Can read themes page',
30 | 'read_account-page': 'Can read account page',
31 | 'read_profile-frame': 'Can read profile frame',
32 | 'read_admin-page': 'Can read admin page',
33 | 'read_groups-frame': 'Can read groups frame',
34 | 'read_users-frame': 'Can read users frame'
35 | };
36 | const permissionsObjects = [];
37 | for (const permissionTitle in permissionTitles) {
38 | if (permissionTitles.hasOwnProperty(permissionTitle)) {
39 | permissionsObjects.push({
40 | title: permissionTitles[permissionTitle],
41 | name: permissionTitle,
42 | contentType: ctUser
43 | });
44 | }
45 | }
46 | const permissions = await queryRunner.manager
47 | .getRepository(Permission1524199022084)
48 | .save(plainToClass(Permission1524199022084, permissionsObjects));
49 | gUser.permissions = [
50 | ...gUser.permissions.filter(permission => !permissionTitles[permission.name]),
51 | ...permissions.filter(
52 | permission => ['read_themes-page', 'read_account-page', 'read_profile-frame'].indexOf(permission.name) !== -1
53 | )
54 | ];
55 | gAdmin.permissions = [
56 | ...gAdmin.permissions.filter(permission => !permissionTitles[permission.name]),
57 | ...permissions
58 | ];
59 | const groups = await queryRunner.manager
60 | .getRepository(Group1524199022084)
61 | .save(plainToClass(Group1524199022084, [gUser, gAdmin]));
62 | }
63 |
64 | public async down(queryRunner: QueryRunner): Promise {}
65 | }
66 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations_entities/1524199022084/content-type.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
3 | import { CustomValidationError } from '../../exceptions/custom-validation.error';
4 | import { Permission1524199022084 } from './permission.entity';
5 |
6 | @Entity({ name: 'content_types' })
7 | export class ContentType1524199022084 {
8 | @PrimaryGeneratedColumn()
9 | id: number = undefined;
10 |
11 | @Column({ length: 100 })
12 | @IsNotEmpty()
13 | @MaxLength(100)
14 | name: string = undefined;
15 |
16 | @Column({ length: 255 })
17 | @IsNotEmpty()
18 | @MaxLength(255)
19 | title: string = undefined;
20 |
21 | @OneToMany(type => Permission1524199022084, permission => permission.contentType)
22 | permissions: Permission1524199022084[];
23 |
24 | @BeforeInsert()
25 | doBeforeInsertion() {
26 | const errors = validateSync(this, { validationError: { target: false } });
27 | if (errors.length > 0) {
28 | throw new CustomValidationError(errors);
29 | }
30 | }
31 |
32 | @BeforeUpdate()
33 | doBeforeUpdate() {
34 | const errors = validateSync(this, { validationError: { target: false } });
35 | if (errors.length > 0) {
36 | throw new CustomValidationError(errors);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations_entities/1524199022084/group.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import { BeforeInsert, BeforeUpdate, Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
3 | import { CustomValidationError } from '../../exceptions/custom-validation.error';
4 | import { Permission1524199022084 } from './permission.entity';
5 | import { User1524199022084 } from './user.entity';
6 |
7 | @Entity({ name: 'groups' })
8 | export class Group1524199022084 {
9 | @PrimaryGeneratedColumn()
10 | id: number = undefined;
11 |
12 | @Column({ length: 100, unique: true })
13 | @IsNotEmpty()
14 | @MaxLength(100)
15 | name: string = undefined;
16 |
17 | @Column({ length: 255, unique: true })
18 | @IsNotEmpty()
19 | @MaxLength(255)
20 | title: string = undefined;
21 |
22 | @ManyToMany(type => Permission1524199022084, {
23 | cascade: ['remove']
24 | })
25 | @JoinTable({
26 | // not work on run cli migration:
27 | name: 'group_permissions',
28 | joinColumn: {
29 | name: 'group_id',
30 | referencedColumnName: 'id'
31 | },
32 | inverseJoinColumn: {
33 | name: 'permission_id',
34 | referencedColumnName: 'id'
35 | }
36 | })
37 | permissions: Permission1524199022084[];
38 |
39 | @ManyToMany(type => User1524199022084)
40 | @JoinTable({
41 | // not work on run cli migration:
42 | name: 'user_groups',
43 | joinColumn: {
44 | name: 'group_id',
45 | referencedColumnName: 'id'
46 | },
47 | inverseJoinColumn: {
48 | name: 'user_id',
49 | referencedColumnName: 'id'
50 | }
51 | })
52 | users: User1524199022084[];
53 |
54 | @BeforeInsert()
55 | doBeforeInsertion() {
56 | const errors = validateSync(this, { validationError: { target: false } });
57 | if (errors.length > 0) {
58 | throw new CustomValidationError(errors);
59 | }
60 | }
61 |
62 | @BeforeUpdate()
63 | doBeforeUpdate() {
64 | const errors = validateSync(this, { validationError: { target: false } });
65 | if (errors.length > 0) {
66 | throw new CustomValidationError(errors);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations_entities/1524199022084/permission.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, MaxLength, validateSync } from 'class-validator';
2 | import {
3 | BeforeInsert,
4 | BeforeUpdate,
5 | Column,
6 | Entity,
7 | JoinColumn,
8 | JoinTable,
9 | ManyToMany,
10 | ManyToOne,
11 | PrimaryGeneratedColumn
12 | } from 'typeorm';
13 | import { CustomValidationError } from '../../exceptions/custom-validation.error';
14 | import { ContentType1524199022084 } from './content-type.entity';
15 | import { Group1524199022084 } from './group.entity';
16 |
17 | @Entity({ name: 'permissions' })
18 | export class Permission1524199022084 {
19 | @PrimaryGeneratedColumn()
20 | id: number = undefined;
21 |
22 | @Column({ length: 100 })
23 | @IsNotEmpty()
24 | @MaxLength(100)
25 | name: string = undefined;
26 |
27 | @Column({ length: 255 })
28 | @IsNotEmpty()
29 | @MaxLength(255)
30 | title: string = undefined;
31 |
32 | @ManyToOne(type => ContentType1524199022084, { eager: true, nullable: true })
33 | @JoinColumn({ name: 'content_type_id' })
34 | contentType: ContentType1524199022084 = undefined;
35 |
36 | @ManyToMany(type => Group1524199022084)
37 | @JoinTable({
38 | // not work on run cli migration:
39 | name: 'group_permissions',
40 | joinColumn: {
41 | name: 'permission_id',
42 | referencedColumnName: 'id'
43 | },
44 | inverseJoinColumn: {
45 | name: 'group_id',
46 | referencedColumnName: 'id'
47 | }
48 | })
49 | groups: Group1524199022084[];
50 |
51 | @BeforeInsert()
52 | doBeforeInsertion() {
53 | const errors = validateSync(this, { validationError: { target: false } });
54 | if (errors.length > 0) {
55 | throw new CustomValidationError(errors);
56 | }
57 | }
58 |
59 | @BeforeUpdate()
60 | doBeforeUpdate() {
61 | const errors = validateSync(this, { validationError: { target: false } });
62 | if (errors.length > 0) {
63 | throw new CustomValidationError(errors);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/migrations_entities/1524199022084/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsNotEmpty, IsOptional, MaxLength, validateSync } from 'class-validator';
2 | import * as hashers from 'node-django-hashers';
3 | import {
4 | BeforeInsert,
5 | BeforeUpdate,
6 | Column,
7 | CreateDateColumn,
8 | Entity,
9 | JoinTable,
10 | ManyToMany,
11 | PrimaryGeneratedColumn,
12 | UpdateDateColumn
13 | } from 'typeorm';
14 | import { CustomValidationError } from '../../exceptions/custom-validation.error';
15 | import { Group1524199022084 } from './group.entity';
16 |
17 | @Entity({ name: 'users' })
18 | export class User1524199022084 {
19 | @PrimaryGeneratedColumn()
20 | id: number = undefined;
21 |
22 | @Column({ length: 128 })
23 | @MaxLength(128)
24 | @IsOptional()
25 | password: string = undefined;
26 |
27 | @UpdateDateColumn({ name: 'last_login', nullable: true })
28 | lastLogin: Date = undefined;
29 |
30 | @Column({ name: 'is_superuser', default: false })
31 | isSuperuser: boolean = undefined;
32 |
33 | @Column({ length: 150, unique: true })
34 | @MaxLength(150)
35 | username: string = undefined;
36 |
37 | @Column({ name: 'first_name', length: 30, nullable: true })
38 | @MaxLength(30)
39 | @IsOptional()
40 | firstName: string = undefined;
41 |
42 | @Column({ name: 'last_name', length: 30, nullable: true })
43 | @MaxLength(30)
44 | @IsOptional()
45 | lastName: string = undefined;
46 |
47 | @Column({ length: 254, unique: true })
48 | @IsNotEmpty()
49 | @IsEmail()
50 | @MaxLength(254)
51 | email: string = undefined;
52 |
53 | @Column({ name: 'is_staff', default: false })
54 | isStaff: boolean = undefined;
55 |
56 | @Column({ name: 'is_active', default: false })
57 | isActive: boolean = undefined;
58 |
59 | @CreateDateColumn({ name: 'date_joined' })
60 | dateJoined: Date = undefined;
61 |
62 | @Column({ type: Date, name: 'date_of_birth', nullable: true })
63 | dateOfBirth: Date = undefined;
64 |
65 | @ManyToMany(type => Group1524199022084)
66 | @JoinTable({
67 | // not work on run cli migration:
68 | name: 'user_groups',
69 | joinColumn: {
70 | name: 'user_id',
71 | referencedColumnName: 'id'
72 | },
73 | inverseJoinColumn: {
74 | name: 'group_id',
75 | referencedColumnName: 'id'
76 | }
77 | })
78 | groups: Group1524199022084[];
79 |
80 | @BeforeInsert()
81 | doBeforeInsertion() {
82 | const errors = validateSync(this, { validationError: { target: false } });
83 | if (errors.length > 0) {
84 | throw new CustomValidationError(errors);
85 | }
86 | }
87 |
88 | @BeforeUpdate()
89 | doBeforeUpdate() {
90 | const errors = validateSync(this, { validationError: { target: false } });
91 | if (errors.length > 0) {
92 | throw new CustomValidationError(errors);
93 | }
94 | }
95 |
96 | async createPassword(password: string) {
97 | const h = new hashers.PBKDF2PasswordHasher();
98 | const hash = await h.encode(password, h.salt());
99 | return hash;
100 | }
101 |
102 | async validatePassword(password: string) {
103 | const h = new hashers.PBKDF2PasswordHasher();
104 | return h.verify(password, this.password);
105 | }
106 |
107 | async setPassword(password: string) {
108 | if (password) {
109 | this.password = await this.createPassword(password);
110 | }
111 | return this;
112 | }
113 |
114 | checkPermissions(permissions: string[]) {
115 | permissions = permissions.map(permission => permission.toLowerCase());
116 | return (
117 | this.groups.filter(
118 | group =>
119 | group &&
120 | group.permissions.filter(permission => permissions.indexOf(permission.name.toLowerCase()) !== -1).length > 0
121 | ).length > 0
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/pipes/index.ts:
--------------------------------------------------------------------------------
1 | import { APP_PIPE } from '@nestjs/core';
2 | import { ValidationPipe } from '../pipes/validation.pipe';
3 |
4 | export const CORE_APP_PIPES = [{ provide: APP_PIPE, useClass: ValidationPipe, multi: true }];
5 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/pipes/parse-int-with-default.pipe.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class ParseIntWithDefaultPipe implements PipeTransform {
5 | constructor(private readonly defaultValue: number) {}
6 |
7 | async transform(value: string, metadata: ArgumentMetadata) {
8 | let val = parseInt(value, 10);
9 | if (isNaN(val)) {
10 | val = this.defaultValue;
11 | }
12 | return val;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/pipes/validation.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { ArgumentMetadata, PipeTransform } from '@nestjs/common/interfaces';
3 | import { plainToClass } from 'class-transformer';
4 | import { validate } from 'class-validator';
5 | import { CustomValidationError } from '../exceptions/custom-validation.error';
6 |
7 | @Injectable()
8 | export class ValidationPipe implements PipeTransform {
9 | public async transform(value, metadata: ArgumentMetadata) {
10 | const { metatype } = metadata;
11 | if (!metatype || !this.toValidate(metatype)) {
12 | return value;
13 | }
14 | const entity = plainToClass(metatype, value);
15 | const errors = await validate(entity, {
16 | validationError: { target: false }
17 | });
18 | if (errors.length > 0) {
19 | throw new CustomValidationError(errors);
20 | }
21 | return entity;
22 | }
23 |
24 | private toValidate(metatype): boolean {
25 | const types = [String, Boolean, Number, Array, Object];
26 | return !types.find(type => metatype === type) && metatype;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/account.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { plainToClassFromExist } from 'class-transformer';
3 | import { User } from '../entities/user.entity';
4 | import { UsersService } from './users.service';
5 |
6 | @Injectable()
7 | export class AccountService {
8 | constructor(private readonly usersService: UsersService) {}
9 |
10 | async update(options: { id: number; user: User }) {
11 | try {
12 | await this.usersService.assertUsernameAndEmail({
13 | id: options.id,
14 | email: options.user.email,
15 | username: options.user.username
16 | });
17 | let { user } = await this.usersService.findById(options);
18 | user = plainToClassFromExist(user, {
19 | email: options.user.email,
20 | password: options.user.password,
21 | username: options.user.username,
22 | firstName: options.user.firstName,
23 | lastName: options.user.lastName
24 | });
25 | await user.setPassword(options.user.password);
26 | return await this.usersService.update({ id: options.id, item: user });
27 | } catch (error) {
28 | throw error;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/content-types.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { Repository } from 'typeorm';
4 | import { ContentType } from '../entities/content-type.entity';
5 |
6 | @Injectable()
7 | export class ContentTypesService {
8 | constructor(
9 | @InjectRepository(ContentType)
10 | private readonly repository: Repository
11 | ) {}
12 |
13 | async create(options: { item: ContentType }) {
14 | try {
15 | options.item = await this.repository.save(options.item);
16 | return { contentType: options.item };
17 | } catch (error) {
18 | throw error;
19 | }
20 | }
21 |
22 | async update(options: { id: number; item: ContentType }) {
23 | options.item.id = options.id;
24 | try {
25 | options.item = await this.repository.save(options.item);
26 | return { contentType: options.item };
27 | } catch (error) {
28 | throw error;
29 | }
30 | }
31 |
32 | async delete(options: { id: number }) {
33 | try {
34 | let item = await this.repository.findOneOrFail(options.id, {
35 | relations: ['permissions']
36 | });
37 | item.permissions = [];
38 | item = await this.repository.save(item);
39 | await this.repository.delete(options.id);
40 | return { contentType: null };
41 | } catch (error) {
42 | throw error;
43 | }
44 | }
45 |
46 | async findById(options: { id: number }) {
47 | try {
48 | const item = await this.repository.findOneOrFail(options.id, {
49 | relations: ['permissions']
50 | });
51 | return { contentType: item };
52 | } catch (error) {
53 | throw error;
54 | }
55 | }
56 |
57 | async findAll(options: { curPage: number; perPage: number; q?: string; sort?: string }) {
58 | try {
59 | let objects: [ContentType[], number];
60 | let qb = this.repository.createQueryBuilder('contentType');
61 | if (options.q) {
62 | qb = qb.where('contentType.name like :q or contentType.title like :q or contentType.id = :id', {
63 | q: `%${options.q}%`,
64 | id: +options.q
65 | });
66 | }
67 | options.sort =
68 | options.sort && new ContentType().hasOwnProperty(options.sort.replace('-', '')) ? options.sort : '-id';
69 | const field = options.sort.replace('-', '');
70 | if (options.sort)
71 | if (options.sort[0] === '-') {
72 | qb = qb.orderBy('contentType.' + field, 'DESC');
73 | } else {
74 | qb = qb.orderBy('contentType.' + field, 'ASC');
75 | }
76 | qb = qb.skip((options.curPage - 1) * options.perPage).take(options.perPage);
77 | objects = await qb.getManyAndCount();
78 | return {
79 | contentTypes: objects[0],
80 | meta: {
81 | perPage: options.perPage,
82 | totalPages: options.perPage > objects[1] ? 1 : Math.ceil(objects[1] / options.perPage),
83 | totalResults: objects[1],
84 | curPage: options.curPage
85 | }
86 | };
87 | } catch (error) {
88 | throw error;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/groups.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NotFoundException } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { plainToClass } from 'class-transformer';
4 | import { Repository } from 'typeorm';
5 | import { Group } from '../entities/group.entity';
6 |
7 | @Injectable()
8 | export class GroupsService {
9 | items: Group[];
10 |
11 | constructor(@InjectRepository(Group) private readonly repository: Repository) {}
12 | async create(options: { item: Group }) {
13 | try {
14 | options.item = await this.repository.save(options.item);
15 | return { group: options.item };
16 | } catch (error) {
17 | throw error;
18 | }
19 | }
20 |
21 | async update(options: { id: number; item: Group }) {
22 | options.item.id = options.id;
23 | try {
24 | options.item = await this.repository.save(options.item);
25 | return { group: options.item };
26 | } catch (error) {
27 | throw error;
28 | }
29 | }
30 |
31 | async delete(options: { id: number }) {
32 | try {
33 | let item = await this.repository.findOneOrFail(options.id);
34 | item.permissions = [];
35 | item = await this.repository.save(item);
36 | await this.repository.delete(options.id);
37 | return { group: null };
38 | } catch (error) {
39 | throw error;
40 | }
41 | }
42 |
43 | async findById(options: { id: number }) {
44 | try {
45 | const item = await this.repository.findOneOrFail(options.id, {
46 | relations: ['permissions']
47 | });
48 | return { group: item };
49 | } catch (error) {
50 | throw error;
51 | }
52 | }
53 |
54 | async findAll(options: { curPage: number; perPage: number; q?: string; sort?: string }) {
55 | try {
56 | let objects: [Group[], number];
57 | let qb = this.repository.createQueryBuilder('group');
58 | qb = qb.leftJoinAndSelect('group.permissions', 'permission');
59 | qb = qb.leftJoinAndSelect('permission.contentType', 'contentType');
60 | if (options.q) {
61 | qb = qb.where('group.title like :q or group.name like :q or group.id = :id', {
62 | q: `%${options.q}%`,
63 | id: +options.q
64 | });
65 | }
66 | options.sort = options.sort && new Group().hasOwnProperty(options.sort.replace('-', '')) ? options.sort : '-id';
67 | const field = options.sort.replace('-', '');
68 | if (options.sort) {
69 | if (options.sort[0] === '-') {
70 | qb = qb.orderBy('group.' + field, 'DESC');
71 | } else {
72 | qb = qb.orderBy('group.' + field, 'ASC');
73 | }
74 | }
75 | qb = qb.skip((options.curPage - 1) * options.perPage).take(options.perPage);
76 | objects = await qb.getManyAndCount();
77 | return {
78 | groups: objects[0],
79 | meta: {
80 | perPage: options.perPage,
81 | totalPages: options.perPage > objects[1] ? 1 : Math.ceil(objects[1] / options.perPage),
82 | totalResults: objects[1],
83 | curPage: options.curPage
84 | }
85 | };
86 | } catch (error) {
87 | throw error;
88 | }
89 | }
90 |
91 | getGroupByName(options: { name: string }) {
92 | const groups = (this.items ? this.items : []).filter(group => group.name === options.name);
93 | if (groups.length) {
94 | return groups[0];
95 | }
96 | throw new NotFoundException(`Group with name "${options.name}" not exists`);
97 | }
98 |
99 | async preloadAll() {
100 | if (!this.items) {
101 | try {
102 | const groups = await this.repository
103 | .createQueryBuilder('group')
104 | .leftJoinAndSelect('group.permissions', 'permission')
105 | .getMany();
106 | this.items = plainToClass(Group, groups);
107 | // Logger.log(JSON.stringify(groups.map(group => group.name)), GroupsService.name);
108 | } catch (error) {
109 | throw error;
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/index.ts:
--------------------------------------------------------------------------------
1 | import { AccountService } from '../services/account.service';
2 | import { ContentTypesService } from '../services/content-types.service';
3 | import { GroupsService } from '../services/groups.service';
4 | import { PermissionsService } from '../services/permissions.service';
5 | import { UsersService } from '../services/users.service';
6 |
7 | export const CORE_SERVICES = [AccountService, GroupsService, UsersService, ContentTypesService, PermissionsService];
8 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/permissions.service.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable, MethodNotAllowedException } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { Repository } from 'typeorm';
4 | import { CORE_CONFIG_TOKEN } from '../configs/core.config';
5 | import { Permission } from '../entities/permission.entity';
6 | import { ICoreConfig } from '../interfaces/core-config.interface';
7 |
8 | @Injectable()
9 | export class PermissionsService {
10 | constructor(
11 | @InjectRepository(Permission)
12 | private readonly repository: Repository
13 | ) {}
14 |
15 | async create(options: { item: Permission }) {
16 | try {
17 | options.item = await this.repository.save(options.item);
18 | return { permission: options.item };
19 | } catch (error) {
20 | throw error;
21 | }
22 | }
23 |
24 | async update(options: { id: number; item: Permission }) {
25 | options.item.id = options.id;
26 | try {
27 | options.item = await this.repository.save(options.item);
28 | return { permission: options.item };
29 | } catch (error) {
30 | throw error;
31 | }
32 | }
33 |
34 | async delete(options: { id: number }) {
35 | try {
36 | await this.repository.delete(options.id);
37 | return { permission: null };
38 | } catch (error) {
39 | throw error;
40 | }
41 | }
42 |
43 | async findById(options: { id: number }) {
44 | try {
45 | const item = await this.repository.findOneOrFail(options.id, {
46 | relations: ['contentType']
47 | });
48 | return { permission: item };
49 | } catch (error) {
50 | throw error;
51 | }
52 | }
53 |
54 | async findAll(options: {
55 | curPage: number;
56 | perPage: number;
57 | q?: string;
58 | group?: number;
59 | contentType?: number;
60 | sort?: string;
61 | }) {
62 | try {
63 | let objects: [Permission[], number];
64 | let qb = this.repository.createQueryBuilder('permission');
65 | qb = qb.leftJoinAndSelect('permission.contentType', 'contentType');
66 | if (options.group) {
67 | qb = qb.leftJoin('permission.groups', 'group').where('group.id = :group', { group: options.group });
68 | }
69 | if (options.q) {
70 | qb = qb.where('permission.name like :q or permission.title like :q or permission.id = :id', {
71 | q: `%${options.q}%`,
72 | id: +options.q
73 | });
74 | }
75 | if (options.contentType) {
76 | qb = qb.where('contentType.id = :contentType', {
77 | contentType: options.contentType
78 | });
79 | }
80 | options.sort =
81 | options.sort && new Permission().hasOwnProperty(options.sort.replace('-', '')) ? options.sort : '-id';
82 | const field = options.sort.replace('-', '');
83 | if (options.sort) {
84 | if (options.sort[0] === '-') {
85 | qb = qb.orderBy('permission.' + field, 'DESC');
86 | } else {
87 | qb = qb.orderBy('permission.' + field, 'ASC');
88 | }
89 | }
90 | qb = qb.skip((options.curPage - 1) * options.perPage).take(options.perPage);
91 | objects = await qb.getManyAndCount();
92 | return {
93 | permissions: objects[0],
94 | meta: {
95 | perPage: options.perPage,
96 | totalPages: options.perPage > objects[1] ? 1 : Math.ceil(objects[1] / options.perPage),
97 | totalResults: objects[1],
98 | curPage: options.curPage
99 | }
100 | };
101 | } catch (error) {
102 | throw error;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/services/users.service.ts:
--------------------------------------------------------------------------------
1 | import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
2 | import { InjectRepository } from '@nestjs/typeorm';
3 | import { Repository } from 'typeorm';
4 | import { User } from '../entities/user.entity';
5 |
6 | @Injectable()
7 | export class UsersService {
8 | constructor(@InjectRepository(User) private readonly repository: Repository) {}
9 |
10 | async assertUsernameAndEmail(options: { id?: number; email: string; username: string }) {
11 | if (options.email) {
12 | let userOfEmail: { user };
13 | try {
14 | userOfEmail = await this.findByEmail(options);
15 | } catch (error) {
16 | userOfEmail = undefined;
17 | }
18 | if (userOfEmail && userOfEmail.user.id !== options.id) {
19 | throw new ConflictException(`User with email "${options.email}" is exists`);
20 | }
21 | }
22 | if (options.username) {
23 | let userOfUsername: { user };
24 | try {
25 | userOfUsername = await this.findByUserName(options);
26 | } catch (error) {
27 | userOfUsername = undefined;
28 | }
29 | if (userOfUsername && userOfUsername.user.id !== options.id) {
30 | throw new ConflictException(`User with username "${options.username}" is exists`);
31 | }
32 | }
33 | }
34 |
35 | async create(options: { item: User }) {
36 | try {
37 | await this.assertUsernameAndEmail({
38 | id: options.item.id,
39 | email: options.item.email,
40 | username: options.item.username
41 | });
42 | options.item = await this.repository.save(options.item);
43 | const { user } = await this.findById({ id: options.item.id });
44 | return { user };
45 | } catch (error) {
46 | throw error;
47 | }
48 | }
49 |
50 | async update(options: { id: number; item: User }) {
51 | try {
52 | await this.assertUsernameAndEmail({
53 | id: options.item.id,
54 | email: options.item.email,
55 | username: options.item.username
56 | });
57 | options.item.lastLogin = new Date();
58 | options.item.id = options.id;
59 | options.item = await this.repository.save(options.item);
60 | const { user } = await this.findById({ id: options.item.id });
61 | return { user };
62 | } catch (error) {
63 | throw error;
64 | }
65 | }
66 |
67 | async delete(options: { id: number }) {
68 | try {
69 | let object = await this.repository.findOneOrFail(options.id);
70 | object.groups = [];
71 | object = await this.repository.save(object);
72 | await this.repository.delete(options.id);
73 | return { user: null };
74 | } catch (error) {
75 | throw error;
76 | }
77 | }
78 |
79 | async findById(options: { id: number }) {
80 | try {
81 | const item = await this.repository.findOneOrFail(options.id, {
82 | relations: ['groups', 'groups.permissions']
83 | });
84 | return { user: item };
85 | } catch (error) {
86 | throw error;
87 | }
88 | }
89 |
90 | async findAll(options: { curPage: number; perPage: number; q?: string; group?: number; sort?: string }) {
91 | try {
92 | let objects: [User[], number];
93 | let qb = this.repository.createQueryBuilder('user');
94 | if (options.group) {
95 | qb = qb.leftJoinAndSelect('user.groups', 'group').where('group.id = :group', { group: options.group });
96 | } else {
97 | qb = qb.leftJoinAndSelect('user.groups', 'group');
98 | qb = qb.leftJoinAndSelect('group.permissions', 'permission');
99 | qb = qb.leftJoinAndSelect('permission.contentType', 'contentType');
100 | }
101 | if (options.q) {
102 | qb = qb.where('user.first_name like :q or user.last_name like :q or user.username like :q or user.id = :id', {
103 | q: `%${options.q}%`,
104 | id: +options.q
105 | });
106 | }
107 | options.sort = options.sort && new User().hasOwnProperty(options.sort.replace('-', '')) ? options.sort : '-id';
108 | const field = options.sort.replace('-', '');
109 | if (options.sort) {
110 | if (options.sort[0] === '-') {
111 | qb = qb.orderBy('user.' + field, 'DESC');
112 | } else {
113 | qb = qb.orderBy('user.' + field, 'ASC');
114 | }
115 | }
116 | qb = qb.skip((options.curPage - 1) * options.perPage).take(options.perPage);
117 | objects = await qb.getManyAndCount();
118 | return {
119 | users: objects[0],
120 | meta: {
121 | perPage: options.perPage,
122 | totalPages: options.perPage > objects[1] ? 1 : Math.ceil(objects[1] / options.perPage),
123 | totalResults: objects[1],
124 | curPage: options.curPage
125 | }
126 | };
127 | } catch (error) {
128 | throw error;
129 | }
130 | }
131 |
132 | async findByEmail(options: { email: string }) {
133 | try {
134 | const item = await this.repository.findOneOrFail({
135 | where: {
136 | email: options.email
137 | },
138 | relations: ['groups', 'groups.permissions']
139 | });
140 | return {
141 | user: item
142 | };
143 | } catch (error) {
144 | throw new NotFoundException(`User with email "${options.email}" not founded`);
145 | }
146 | }
147 |
148 | async findByUserName(options: { username: string }) {
149 | try {
150 | const item = await this.repository.findOneOrFail({
151 | where: {
152 | username: options.username
153 | },
154 | relations: ['groups', 'groups.permissions']
155 | });
156 | return {
157 | user: item
158 | };
159 | } catch (error) {
160 | throw new NotFoundException(`User with username "${options.username}" not founded`);
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/src/utils/custom-transforms.ts:
--------------------------------------------------------------------------------
1 | export function transformToBoolean(value: boolean | undefined) {
2 | return !!value;
3 | }
4 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest"]
5 | },
6 | "include": ["**/*.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../../dist/out-tsc/core",
5 | "module": "commonjs",
6 | "declaration": true,
7 | "noImplicitAny": false,
8 | "removeComments": true,
9 | "noLib": false,
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "target": "es6",
13 | "sourceMap": true,
14 | "baseUrl": "./"
15 | },
16 | "include": ["src/**/*"],
17 | "exclude": ["node_modules", "**/*.spec.ts"]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"]
5 | },
6 | "include": ["**/*.spec.ts", "**/*.d.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/libs/rucken/core-nestjs/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tslint.json"
3 | }
4 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmScope": "rucken",
3 | "implicitDependencies": {
4 | "angular.json": "*",
5 | "package.json": "*",
6 | "tsconfig.json": "*",
7 | "tslint.json": "*",
8 | "nx.json": "*"
9 | },
10 | "projects": {
11 | "core": {
12 | "tags": [
13 | "nestjs",
14 | "server",
15 | "core"
16 | ]
17 | },
18 | "auth": {
19 | "tags": [
20 | "nestjs",
21 | "server",
22 | "auth"
23 | ]
24 | },
25 | "demo": {
26 | "tags": [
27 | "nestjs",
28 | "server",
29 | "demo"
30 | ]
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/ormconfig.js:
--------------------------------------------------------------------------------
1 | const fg = require('fast-glob');
2 | const ConnectionString = require('connection-string');
3 | const load = require('dotenv').load;
4 | const fs = require('fs');
5 | const path = require('path');
6 | const runnedInMigration = process.env.MIGRATIONS === 'true';
7 | const envName = process.env.NODE_ENV || 'develop';
8 | const isDevelop = envName === 'develop';
9 | const sourceRootKey = isDevelop ? 'sourceRoot' : 'outputPath';
10 | const entitiesExt = '{.js,.ts}';;
11 | const angularJson = JSON.parse(fs.readFileSync('angular.json'));
12 | const packageJson = JSON.parse(fs.readFileSync('package.json'));
13 | const vendors = packageJson.externalLibs || [];
14 | try {
15 | fs.accessSync(`${envName}.env`);
16 | load({ path: `${envName}.env` });
17 | console.log(`env file: ${envName}.env`);
18 | } catch (error) {
19 | console.log(`error on get env file: ${envName}.env`);
20 | try {
21 | fs.accessSync(`.env`);
22 | load();
23 | console.log(`env file: .env`);
24 | } catch (error) {
25 | console.log(`error on get env file: .env`);
26 | }
27 | }
28 |
29 | const connectionString = new ConnectionString(process.env.DATABASE_URL || 'sqlite://database/sqlitedb.db');
30 | const vendorsLibs = Object.keys(vendors).map(index => {
31 | const vendorConfig = {};
32 | vendorConfig[sourceRootKey] = vendors[index];
33 | return vendorConfig;
34 | });
35 | const libs = Object.keys(angularJson.projects).filter(key => angularJson.projects[key].projectType === 'library').filter(lib =>
36 | isDevelop ||
37 | lib[sourceRootKey] !== undefined ||
38 | (
39 | lib.architect &&
40 | lib.architect.build &&
41 | lib.architect.build.options &&
42 | lib.architect.build.options[sourceRootKey] !== undefined
43 | )
44 | ).map(key => angularJson.projects[key]);
45 | const apps = Object.keys(angularJson.projects).filter(key => angularJson.projects[key].projectType === 'application').filter(lib =>
46 | isDevelop ||
47 | lib[sourceRootKey] !== undefined ||
48 | (
49 | lib.architect &&
50 | lib.architect.build &&
51 | lib.architect.build.options &&
52 | lib.architect.build.options[sourceRootKey] !== undefined
53 | )
54 | ).map(key => angularJson.projects[key]);
55 | const defaultProject = angularJson.defaultProject;
56 | const defaultApp = angularJson.projects[defaultProject];
57 | const migrationsDir = `${defaultApp[sourceRootKey]}/migrations`;
58 |
59 | const entities = (
60 | fg.sync(
61 | [
62 | ...(
63 | runnedInMigration ? [
64 | ...vendorsLibs,
65 | ...libs,
66 | ...apps
67 | ].map(lib =>
68 | `${lib[sourceRootKey] || lib.architect.build.options[sourceRootKey]}/**/migrations_entities/**/*.entity${entitiesExt}`
69 | ) : []
70 | ),
71 | ...(
72 | !runnedInMigration ? [
73 | ...vendorsLibs,
74 | ...libs,
75 | ...apps
76 | ].map(lib =>
77 | `${lib[sourceRootKey] || lib.architect.build.options[sourceRootKey]}/**/entities/**/*.entity${entitiesExt}`
78 | ) : []
79 | )
80 | ]
81 | )
82 | );
83 | const migrations = normalizationFileList(
84 | fg.sync(
85 | runnedInMigration ? [
86 | ...vendorsLibs,
87 | ...libs,
88 | ...apps
89 | ].map(lib =>
90 | `${lib[sourceRootKey] || lib.architect.build.options[sourceRootKey]}/**/migrations/**/*${entitiesExt}`
91 | ) : []
92 | )
93 | );
94 | const subscribers = (
95 | fg.sync(
96 | [
97 | ...vendorsLibs,
98 | ...libs,
99 | ...apps
100 | ].map(lib =>
101 | `${lib[sourceRootKey] || lib.architect.build.options[sourceRootKey]}/**/subscribers/**/*${entitiesExt}`
102 | )
103 | )
104 | );
105 | if (connectionString.protocol === 'sqlite') {
106 | const dbFile =
107 | './' +
108 | (connectionString.hosts ? connectionString.hosts[0].name : '') +
109 | (connectionString.path ? '/' + connectionString.path[0] : '');
110 | module.exports = {
111 | type: 'sqlite',
112 | database: dbFile,
113 | entities: entities,
114 | migrations: migrations,
115 | subscribers: subscribers,
116 | logging: 'all',
117 | synchronize: false,
118 | cli: {
119 | migrationsDir: migrationsDir
120 | }
121 | }
122 | } else {
123 | module.exports = {
124 | type: connectionString.protocol,
125 | host: connectionString.hosts && connectionString.hosts[0].name,
126 | port: connectionString.hosts && +connectionString.hosts[0].port,
127 | username: connectionString.user,
128 | password: connectionString.password,
129 | database: connectionString.path && connectionString.path[0],
130 | entities: entities,
131 | migrations: migrations,
132 | subscribers: subscribers,
133 | logging: 'all',
134 | synchronize: false,
135 | cli: {
136 | migrationsDir: migrationsDir
137 | }
138 | }
139 | }
140 |
141 | function normalizationFileList(files) {
142 | const newFiles = [];
143 | for (var i = 0; i < files.length; i++) {
144 | const filename = files[i];
145 | if (filename.indexOf('.d.ts') === -1) {
146 | var founded = false;
147 | for (var j = 0; j < newFiles.length; j++) {
148 | const filename = newFiles[j];
149 | if (
150 | path.basename(filename, path.parse(filename).ext) ===
151 | path.basename(files[i], path.parse(files[i]).ext)
152 | ) {
153 | founded = true;
154 | }
155 | }
156 | if (!founded) {
157 | newFiles.push(files[i]);
158 | }
159 | }
160 | }
161 | return newFiles;
162 | }
163 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rucken-core-nestjs",
3 | "version": "1.0.12",
4 | "engines": {
5 | "node": ">=11",
6 | "npm": ">=6.5.0"
7 | },
8 | "license": "MIT",
9 | "author": {
10 | "name": "EndyKaufman",
11 | "email": "admin@site15.ru"
12 | },
13 | "description": "A simple application demonstrating the basic usage of permissions with NestJS (JWT, Passport, Facebook, Google+, User, Group, Permission)",
14 | "bugs": {
15 | "url": "https://github.com/rucken/core-nestjs/issues"
16 | },
17 | "homepage": "https://github.com/rucken/core-nestjs",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/rucken/core-nestjs.git"
21 | },
22 | "keywords": [
23 | "nestjs",
24 | "backend",
25 | "jwt",
26 | "oauth",
27 | "passport",
28 | "facebook",
29 | "google+",
30 | "user",
31 | "group",
32 | "permission",
33 | "contenttype",
34 | "bootstrap",
35 | "heroku",
36 | "typedoc",
37 | "node",
38 | "nodejs",
39 | "angular",
40 | "crud",
41 | "rest",
42 | "nest",
43 | "core",
44 | "rucken",
45 | "nrwl",
46 | "nx",
47 | "angular",
48 | "schematics",
49 | "ngx"
50 | ],
51 | "maintainers": [
52 | {
53 | "name": "EndyKaufman",
54 | "email": "admin@site15.ru"
55 | }
56 | ],
57 | "externalLibs": [
58 | "./dist/rucken/core-nestjs",
59 | "./dist/rucken/auth-nestjs"
60 | ],
61 | "scripts": {
62 | "typeorm": "./node_modules/.bin/ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
63 | "clean": "./node_modules/.bin/del-cli dist client/docs database/sqlitedb.db",
64 | "start:dev": "ng run demo:serve",
65 | "start:prod": "ng run demo:serve-prod",
66 | "start": "node dist/demo/main.js",
67 | "test": "npm run build",
68 | "test:cov": "jest --coverage",
69 | "test:e2e": "jest --config ./test/jest-e2e.json",
70 | "migrate:create": "npm run typeorm migration:create",
71 | "migrate:generate": "npm run typeorm migration:generate",
72 | "migrate:prod": "cross-env MIGRATIONS=true NODE_ENV=production npm run typeorm migration:run",
73 | "migrate": "cross-env MIGRATIONS=true npm run typeorm migration:run",
74 | "docs": "./node_modules/.bin/typedoc --ignoreCompilerErrors --excludeExternals --out client/docs libs",
75 | "build": "cross-env NODE_ENV=production ./node_modules/.bin/run-s clean format affected:lint build:all docs migrate",
76 | "affected:apps": "./node_modules/.bin/nx affected:apps --all",
77 | "affected:libs": "./node_modules/.bin/nx affected:libs --all",
78 | "affected:build": "./node_modules/.bin/nx affected:build --all",
79 | "affected:e2e": "./node_modules/.bin/nx affected:e2e --all",
80 | "affected:test": "./node_modules/.bin/nx affected:test --all",
81 | "affected:lint": "./node_modules/.bin/nx affected:lint --all",
82 | "affected:dep-graph": "./node_modules/.bin/nx affected:dep-graph --all",
83 | "affected:prod": "./node_modules/.bin/rucken prepare -m prod",
84 | "affected": "./node_modules/.bin/nx affected",
85 | "format": "./node_modules/.bin/nx format:write",
86 | "format:write": "./node_modules/.bin/nx format:write",
87 | "format:check": "./node_modules/.bin/nx format:check",
88 | "workspace-schematic": "./node_modules/.bin/nx workspace-schematic",
89 | "dep-graph": "./node_modules/.bin/nx dep-graph",
90 | "postinstall": "sh ./scripts/postinstall.sh",
91 | "pm2:start": "cross-env NODE_ENV=production ./node_modules/.bin/pm2 start dist/demo/main.js --name demo",
92 | "pm2:stop": "./node_modules/.bin/pm2 kill --name demo",
93 | "pm2:logs": "./node_modules/.bin/pm2 logs --name demo",
94 | "pm2:logs:flush": "./node_modules/.bin/pm2 flush --name demo",
95 | "heroku-postbuild": "npm run build",
96 | "ng": "ng",
97 | "build:libs": "npm run ng build core && npm run ng build auth",
98 | "build:apps": "npm run ng build demo --prod",
99 | "build:all": "run-s affected:prod build:libs build:apps"
100 | },
101 | "dependencies": {
102 | "@angular-devkit/architect": "0.13.9",
103 | "@angular-devkit/build-angular": "0.13.9",
104 | "@angular-devkit/core": "7.3.9",
105 | "@angular-devkit/schematics": "7.3.9",
106 | "@nestjs/common": "6.1.1",
107 | "@nestjs/core": "6.1.1",
108 | "@nestjs/passport": "6.0.0",
109 | "@nestjs/platform-express": "^6.1.1",
110 | "@nestjs/swagger": "^3.0.2",
111 | "@nestjs/typeorm": "6.1.0",
112 | "@nrwl/nx": "7.8.5",
113 | "@types/passport-facebook-token": "0.4.33",
114 | "@types/passport-local": "1.0.33",
115 | "chmod": "0.2.1",
116 | "class-transformer": "^0.2.0",
117 | "class-validator": "0.9.1",
118 | "connection-string": "2.4.0",
119 | "cp-cli": "2.0.0",
120 | "cross-env": "5.2.0",
121 | "del-cli": "1.1.0",
122 | "dotenv": "8.0.0",
123 | "fast-glob": "2.2.6",
124 | "fastify-formbody": "3.1.0",
125 | "highlight.js": "9.15.6",
126 | "jsonwebtoken": "8.5.1",
127 | "node-django-hashers": "1.1.6",
128 | "passport": "0.4.0",
129 | "passport-facebook-token": "3.3.0",
130 | "passport-google-plus-token": "2.1.0",
131 | "passport-http-bearer": "1.0.1",
132 | "passport-jwt": "4.0.0",
133 | "passport-local": "1.0.0",
134 | "pg": "7.10.0",
135 | "reflect-metadata": "0.1.13",
136 | "rxjs": "6.5.2",
137 | "sqlite3": "4.0.7",
138 | "swagger-ui-express": "^4.0.2",
139 | "ts-node": "8.1.0",
140 | "tsconfig-paths": "3.8.0",
141 | "typedoc": "0.14.2",
142 | "typeorm": "0.2.17",
143 | "typescript": "3.2.2"
144 | },
145 | "devDependencies": {
146 | "@angular/cli": "7.3.9",
147 | "@angular/compiler": "7.2.15",
148 | "@angular/compiler-cli": "7.2.15",
149 | "@angular/core": "7.2.15",
150 | "@nestjs/schematics": "6.2.0",
151 | "@nestjs/testing": "6.1.1",
152 | "@nrwl/builders": "7.8.5",
153 | "@nrwl/schematics": "7.8.5",
154 | "@rucken/cli": "4.1.3",
155 | "@types/dotenv": "6.1.1",
156 | "@types/express": "4.16.1",
157 | "@types/jasmine": "3.3.12",
158 | "@types/jest": "24.0.12",
159 | "@types/jsonwebtoken": "8.3.2",
160 | "@types/node": "12.0.0",
161 | "@types/supertest": "2.0.7",
162 | "codelyzer": "5.0.1",
163 | "conventional-changelog-cli": "2.0.21",
164 | "conventional-commits-detector": "1.0.2",
165 | "conventional-recommended-bump": "5.0.0",
166 | "jest": "24.8.0",
167 | "nodemon": "1.19.0",
168 | "npm-run-all": "4.1.5",
169 | "pm2": "3.5.0",
170 | "prettier": "1.17.0",
171 | "showdown": "1.9.0",
172 | "supertest": "4.0.2",
173 | "ts-jest": "24.0.2",
174 | "ts-loader": "6.0.0",
175 | "tslint": "5.16.0",
176 | "tslint-config-prettier": "1.18.0",
177 | "webpack-cli": "3.1.0",
178 | "webpack-node-externals": "1.7.2"
179 | },
180 | "greenkeeper": {
181 | "ignore": [
182 | "typescript"
183 | ]
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/scripts/patch.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const f = 'node_modules/typeorm/migration/MigrationExecutor.js';
3 |
4 | fs.readFile(f, 'utf8', function (err, data) {
5 | if (err) {
6 | return console.log(err);
7 | }
8 | var result = data.replace(/.connection.migrations.map/g, '.connection.migrations.filter(function (migration) { return migration.constructor.name !== \'Function\' }).map');
9 |
10 | fs.writeFile(f, result, 'utf8', function (err) {
11 | if (err) return console.log(err);
12 | });
13 | });
--------------------------------------------------------------------------------
/scripts/postinstall.sh:
--------------------------------------------------------------------------------
1 | node ./scripts/patch.js
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | npm publish ./dist/rucken/core-nestjs
3 | npm publish ./dist/rucken/auth-nestjs
4 | read -p "Press any key to continue... " -n1 -s
--------------------------------------------------------------------------------
/scripts/version-bump.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | preset=`node_modules/.bin/conventional-commits-detector`
3 | echo $preset
4 | bump=`node_modules/.bin/conventional-recommended-bump -p angular`
5 | echo ${1:-$bump}
6 | npm --no-git-tag-version version ${1:-$bump} &>/dev/null
7 | node_modules/.bin/conventional-changelog -i CHANGELOG.md -s -p ${2:-$preset}
8 | read -p "Press any key to continue... " -n1 -s
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "sourceMap": true,
5 | "inlineSources": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "importHelpers": true,
11 | "lib": [
12 | "es5",
13 | "es6"
14 | ],
15 | "target": "es6",
16 | "module": "commonjs",
17 | "typeRoots": [
18 | "node_modules/@types"
19 | ],
20 | "baseUrl": ".",
21 | "paths": {
22 | "@rucken/core-nestjs": [
23 | "dist/rucken/core-nestjs"
24 | ],
25 | "@rucken/core-nestjs/*": [
26 | "dist/rucken/core-nestjs/*"
27 | ],
28 | "@rucken/auth-nestjs": [
29 | "dist/rucken/auth-nestjs"
30 | ],
31 | "@rucken/auth-nestjs/*": [
32 | "dist/rucken/auth-nestjs/*"
33 | ]
34 | }
35 | },
36 | "exclude": [
37 | "client",
38 | "dist",
39 | "node_modules",
40 | "**/*.spec.ts",
41 | "**/*-spec.ts",
42 | "**/*.js"
43 | ]
44 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer",
4 | "node_modules/@nrwl/schematics/src/tslint"
5 | ],
6 | "rules": {
7 | "arrow-return-shorthand": true,
8 | "callable-types": true,
9 | "class-name": true,
10 | "deprecation": {
11 | "severity": "warn"
12 | },
13 | "forin": true,
14 | "import-blacklist": [
15 | true,
16 | "rxjs/Rx"
17 | ],
18 | "interface-over-type-literal": true,
19 | "member-access": false,
20 | "member-ordering": [
21 | true,
22 | {
23 | "order": [
24 | "static-field",
25 | "instance-field",
26 | "static-method",
27 | "instance-method"
28 | ]
29 | }
30 | ],
31 | "no-arg": true,
32 | "no-bitwise": true,
33 | "no-console": [
34 | true,
35 | "debug",
36 | "info",
37 | "time",
38 | "timeEnd",
39 | "trace"
40 | ],
41 | "no-construct": true,
42 | "no-debugger": true,
43 | "no-duplicate-super": true,
44 | "no-empty": false,
45 | "no-empty-interface": true,
46 | "no-eval": true,
47 | "no-inferrable-types": [
48 | true,
49 | "ignore-params"
50 | ],
51 | "no-misused-new": true,
52 | "no-non-null-assertion": true,
53 | "no-shadowed-variable": true,
54 | "no-string-literal": false,
55 | "no-string-throw": true,
56 | "no-switch-case-fall-through": true,
57 | "no-unnecessary-initializer": true,
58 | "no-unused-expression": true,
59 | "no-use-before-declare": true,
60 | "no-var-keyword": true,
61 | "object-literal-sort-keys": false,
62 | "prefer-const": true,
63 | "radix": true,
64 | "triple-equals": [
65 | true,
66 | "allow-null-check"
67 | ],
68 | "unified-signatures": true,
69 | "variable-name": false,
70 | "directive-selector": [
71 | true,
72 | "attribute",
73 | "app",
74 | "camelCase"
75 | ],
76 | "component-selector": [
77 | true,
78 | "element",
79 | "app",
80 | "kebab-case"
81 | ],
82 | "no-output-on-prefix": true,
83 | "no-input-rename": true,
84 | "no-output-rename": true,
85 | "use-pipe-transform-interface": true,
86 | "component-class-suffix": true,
87 | "directive-class-suffix": true,
88 | "nx-enforce-module-boundaries": [
89 | true,
90 | {
91 | "allow": [],
92 | "depConstraints": [
93 | {
94 | "sourceTag": "*",
95 | "onlyDependOnLibsWithTags": [
96 | "*"
97 | ]
98 | }
99 | ]
100 | }
101 | ]
102 | }
103 | }
--------------------------------------------------------------------------------