├── .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 | [![Greenkeeper badge](https://badges.greenkeeper.io/rucken/core-nestjs.svg)](https://greenkeeper.io/) 4 | [![NPM version][npm-image]][npm-url] 5 | [![Build Status][travis-image]][travis-url] 6 | [![Appveyor CI](https://ci.appveyor.com/api/projects/status/sda540c6vpj47cx7/branch/master?svg=true)](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master) 7 | [![dependencies-release][dependencies-image]][dependencies-url] 8 | 9 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 | [![Greenkeeper badge](https://badges.greenkeeper.io/rucken/core-nestjs.svg)](https://greenkeeper.io/) 2 | [![NPM version][npm-image]][npm-url] 3 | [![Build Status][travis-image]][travis-url] 4 | [![Appveyor CI](https://ci.appveyor.com/api/projects/status/sda540c6vpj47cx7/branch/master?svg=true)](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master) 5 | [![dependencies-release][dependencies-image]][dependencies-url] 6 | 7 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 | [![Greenkeeper badge](https://badges.greenkeeper.io/rucken/core-nestjs.svg)](https://greenkeeper.io/) 2 | [![NPM version][npm-image]][npm-url] 3 | [![Build Status][travis-image]][travis-url] 4 | [![Appveyor CI](https://ci.appveyor.com/api/projects/status/sda540c6vpj47cx7/branch/master?svg=true)](https://ci.appveyor.com/project/EndyKaufman/core-nestjs/branch/master) 5 | [![dependencies-release][dependencies-image]][dependencies-url] 6 | 7 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 | } --------------------------------------------------------------------------------