├── .editorconfig ├── .env.example ├── .gitignore ├── .graphqlconfig ├── .prettierignore ├── .prettierrc ├── README.md ├── angular.json ├── apps ├── api │ ├── jest.config.js │ ├── src │ │ ├── app │ │ │ └── app.module.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── web-e2e │ ├── cypress.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ ├── tsconfig.e2e.json │ ├── tsconfig.json │ └── tslint.json └── web │ ├── .browserslistrc │ ├── jest.config.js │ ├── src │ ├── app │ │ ├── app.component.ts │ │ └── app.module.ts │ ├── assets │ │ ├── .gitkeep │ │ └── logo.png │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── decorate-angular-cli.js ├── docker-compose.yml ├── graphql.schema.json ├── jest.config.js ├── libs ├── api │ ├── data-access │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ ├── api-data-access.helper.ts │ │ │ │ ├── api-data-access.module.ts │ │ │ │ ├── api-data-access.service.ts │ │ │ │ └── sample-data │ │ │ │ │ └── sample-users.ts │ │ │ ├── migrations │ │ │ │ ├── 20200925071132-init │ │ │ │ │ ├── README.md │ │ │ │ │ ├── schema.prisma │ │ │ │ │ └── steps.json │ │ │ │ ├── 20200925083105-add-bio-field │ │ │ │ │ ├── README.md │ │ │ │ │ ├── schema.prisma │ │ │ │ │ └── steps.json │ │ │ │ ├── 20200925164238-location │ │ │ │ │ ├── README.md │ │ │ │ │ ├── schema.prisma │ │ │ │ │ └── steps.json │ │ │ │ └── migrate.lock │ │ │ └── schema.prisma │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── feature-auth │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── api-feature-auth.helper.ts │ │ │ │ ├── api-feature-auth.module.ts │ │ │ │ ├── api-feature-auth.resolver.ts │ │ │ │ ├── api-feature-auth.service.ts │ │ │ │ ├── decorators │ │ │ │ └── ctx-user.decorator.ts │ │ │ │ ├── dto │ │ │ │ ├── create-user.input.ts │ │ │ │ ├── jwt.dto.ts │ │ │ │ ├── login.input.ts │ │ │ │ ├── register.input.ts │ │ │ │ ├── update-user-password.input.ts │ │ │ │ └── update-user.input.ts │ │ │ │ ├── guards │ │ │ │ └── gql-auth.guard.ts │ │ │ │ ├── models │ │ │ │ ├── role.ts │ │ │ │ ├── user-token.ts │ │ │ │ └── user.ts │ │ │ │ └── strategies │ │ │ │ └── jwt.strategy.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── feature-comment │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── api-feature-comment.module.ts │ │ │ │ ├── api-feature-comment.resolver.ts │ │ │ │ ├── api-feature-comment.service.ts │ │ │ │ ├── dto │ │ │ │ └── create-comment.input.ts │ │ │ │ └── models │ │ │ │ └── comment.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── feature-core │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── api-feature-core.controller.ts │ │ │ │ ├── api-feature-core.module.ts │ │ │ │ ├── api-feature-core.resolver.ts │ │ │ │ ├── api-feature-core.service.ts │ │ │ │ └── config │ │ │ │ ├── configuration.ts │ │ │ │ └── validation.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── feature-post │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── api-feature-post-profile.resolver.ts │ │ │ │ ├── api-feature-post.module.ts │ │ │ │ ├── api-feature-post.resolver.ts │ │ │ │ ├── api-feature-post.service.ts │ │ │ │ ├── dto │ │ │ │ └── create-post.input.ts │ │ │ │ └── models │ │ │ │ └── post.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ └── feature-profile │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── api-feature-profile.controller.ts │ │ │ ├── api-feature-profile.module.ts │ │ │ ├── api-feature-profile.resolver.ts │ │ │ ├── api-feature-profile.service.ts │ │ │ └── models │ │ │ └── profile.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json └── web │ ├── data-access-auth │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── web-data-access-auth.module.ts │ │ │ └── web-data-access-auth.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── data-access │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── codegen.yml │ │ ├── generated │ │ │ └── graphql.ts │ │ ├── graphql │ │ │ ├── auth-queries.graphql │ │ │ ├── core.graphql │ │ │ ├── post-queries.graphql │ │ │ └── profile-queries.graphql │ │ ├── index.ts │ │ ├── lib │ │ │ ├── web-data-access.module.ts │ │ │ └── web-data-access.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── feature-auth │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── components │ │ │ │ └── auth-page │ │ │ │ │ ├── auth-page.component.html │ │ │ │ │ ├── auth-page.component.scss │ │ │ │ │ └── auth-page.component.ts │ │ │ ├── containers │ │ │ │ ├── login.component.ts │ │ │ │ ├── logout.component.ts │ │ │ │ ├── profile.component.ts │ │ │ │ └── register.component.ts │ │ │ ├── guards │ │ │ │ └── auth.guard.ts │ │ │ ├── web-feature-auth.module.ts │ │ │ └── web-feature-auth.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── feature-core │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── graphql.module.ts │ │ │ └── web-feature-core.module.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── feature-post │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── actions │ │ │ │ └── post.actions.ts │ │ │ ├── components │ │ │ │ └── post-modal.component.ts │ │ │ ├── containers │ │ │ │ └── post-index.component.ts │ │ │ ├── web-feature-post.component.ts │ │ │ ├── web-feature-post.module.ts │ │ │ └── web-feature-post.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── feature-profile │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── components │ │ │ │ └── profile-card.component.ts │ │ │ ├── containers │ │ │ │ └── profile-detail.component.ts │ │ │ ├── web-feature-profile.module.ts │ │ │ └── web-feature-profile.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json │ └── feature-shell │ ├── README.md │ ├── jest.config.js │ ├── src │ ├── index.ts │ ├── lib │ │ ├── app.config.ts │ │ ├── components │ │ │ ├── app-header-dropdown.component.ts │ │ │ ├── app-header-links.component.ts │ │ │ ├── app-header.component.ts │ │ │ ├── app-layout.component.ts │ │ │ └── not-found.component.ts │ │ └── web-feature-shell.module.ts │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── nx.json ├── package.json ├── tools ├── schematics │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3000 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | .env 41 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Untitled GraphQL Schema", 3 | "schemaPath": "graphql.schema.json", 4 | "extensions": { 5 | "endpoints": { 6 | "Default GraphQL Endpoint": { 7 | "url": "http://localhost:3000/graphql", 8 | "headers": { 9 | "user-agent": "JS GraphQL" 10 | }, 11 | "introspect": false 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | dist 3 | coverage 4 | tmp -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "semi": false, 5 | "trailingComma": "all", 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularGraphql 2 | 3 | This project was generated using [Nx](https://nx.dev). 4 | 5 |

6 | 7 | 🔎 **Nx is a set of Extensible Dev Tools for Monorepos.** 8 | 9 | ## Quick Start & Documentation 10 | 11 | [Nx Documentation](https://nx.dev/angular) 12 | 13 | [10-minute video showing all Nx features](https://nx.dev/angular/getting-started/what-is-nx) 14 | 15 | [Interactive Tutorial](https://nx.dev/angular/tutorial/01-create-application) 16 | 17 | ## Adding capabilities to your workspace 18 | 19 | Nx supports many plugins which add capabilities for developing different types of applications and different tools. 20 | 21 | These capabilities include generating applications, libraries, etc as well as the devtools to test, and build projects as well. 22 | 23 | Below are our core plugins: 24 | 25 | - [Angular](https://angular.io) 26 | - `ng add @nrwl/angular` 27 | - [React](https://reactjs.org) 28 | - `ng add @nrwl/react` 29 | - Web (no framework frontends) 30 | - `ng add @nrwl/web` 31 | - [Nest](https://nestjs.com) 32 | - `ng add @nrwl/nest` 33 | - [Express](https://expressjs.com) 34 | - `ng add @nrwl/express` 35 | - [Node](https://nodejs.org) 36 | - `ng add @nrwl/node` 37 | 38 | There are also many [community plugins](https://nx.dev/nx-community) you could add. 39 | 40 | ## Generate an application 41 | 42 | Run `ng g @nrwl/angular:app my-app` to generate an application. 43 | 44 | > You can use any of the plugins above to generate applications as well. 45 | 46 | When using Nx, you can create multiple applications and libraries in the same workspace. 47 | 48 | ## Generate a library 49 | 50 | Run `ng g @nrwl/angular:lib my-lib` to generate a library. 51 | 52 | > You can also use any of the plugins above to generate libraries as well. 53 | 54 | Libraries are sharable across libraries and applications. They can be imported from `@angular-graphql/mylib`. 55 | 56 | ## Development server 57 | 58 | Run `ng serve my-app` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. 59 | 60 | ## Code scaffolding 61 | 62 | Run `ng g component my-component --project=my-app` to generate a new component. 63 | 64 | ## Build 65 | 66 | Run `ng build my-app` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 67 | 68 | ## Running unit tests 69 | 70 | Run `ng test my-app` to execute the unit tests via [Jest](https://jestjs.io). 71 | 72 | Run `nx affected:test` to execute the unit tests affected by a change. 73 | 74 | ## Running end-to-end tests 75 | 76 | Run `ng e2e my-app` to execute the end-to-end tests via [Cypress](https://www.cypress.io). 77 | 78 | Run `nx affected:e2e` to execute the end-to-end tests affected by a change. 79 | 80 | ## Understand your workspace 81 | 82 | Run `nx dep-graph` to see a diagram of the dependencies of your projects. 83 | 84 | ## Further help 85 | 86 | Visit the [Nx Documentation](https://nx.dev/angular) to learn more. 87 | 88 | ## ☁ Nx Cloud 89 | 90 | ### Computation Memoization in the Cloud 91 | 92 |

93 | 94 | Nx Cloud pairs with Nx in order to enable you to build and test code more rapidly, by up to 10 times. Even teams that are new to Nx can connect to Nx Cloud and start saving time instantly. 95 | 96 | Teams using Nx gain the advantage of building full-stack applications with their preferred framework alongside Nx’s advanced code generation and project dependency graph, plus a unified experience for both frontend and backend developers. 97 | 98 | Visit [Nx Cloud](https://nx.app/) to learn more. 99 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "projects": { 4 | "api": { 5 | "root": "apps/api", 6 | "sourceRoot": "apps/api/src", 7 | "projectType": "application", 8 | "prefix": "api", 9 | "schematics": {}, 10 | "architect": { 11 | "build": { 12 | "builder": "@nrwl/node:build", 13 | "options": { 14 | "outputPath": "dist/apps/api", 15 | "main": "apps/api/src/main.ts", 16 | "tsConfig": "apps/api/tsconfig.app.json", 17 | "assets": ["apps/api/src/assets"] 18 | }, 19 | "configurations": { 20 | "production": { 21 | "optimization": true, 22 | "extractLicenses": true, 23 | "inspect": false, 24 | "fileReplacements": [ 25 | { 26 | "replace": "apps/api/src/environments/environment.ts", 27 | "with": "apps/api/src/environments/environment.prod.ts" 28 | } 29 | ] 30 | } 31 | } 32 | }, 33 | "serve": { 34 | "builder": "@nrwl/node:execute", 35 | "options": { 36 | "buildTarget": "api:build" 37 | } 38 | }, 39 | "lint": { 40 | "builder": "@angular-devkit/build-angular:tslint", 41 | "options": { 42 | "tsConfig": ["apps/api/tsconfig.app.json", "apps/api/tsconfig.spec.json"], 43 | "exclude": ["**/node_modules/**", "!apps/api/**/*"] 44 | } 45 | }, 46 | "test": { 47 | "builder": "@nrwl/jest:jest", 48 | "options": { 49 | "jestConfig": "apps/api/jest.config.js", 50 | "passWithNoTests": true 51 | } 52 | } 53 | } 54 | }, 55 | "api-data-access": { 56 | "root": "libs/api/data-access", 57 | "sourceRoot": "libs/api/data-access/src", 58 | "projectType": "library", 59 | "schematics": {}, 60 | "architect": { 61 | "lint": { 62 | "builder": "@angular-devkit/build-angular:tslint", 63 | "options": { 64 | "tsConfig": ["libs/api/data-access/tsconfig.lib.json", "libs/api/data-access/tsconfig.spec.json"], 65 | "exclude": ["**/node_modules/**", "!libs/api/data-access/**/*"] 66 | } 67 | }, 68 | "test": { 69 | "builder": "@nrwl/jest:jest", 70 | "options": { 71 | "jestConfig": "libs/api/data-access/jest.config.js", 72 | "passWithNoTests": true 73 | } 74 | } 75 | } 76 | }, 77 | "api-feature-core": { 78 | "root": "libs/api/feature-core", 79 | "sourceRoot": "libs/api/feature-core/src", 80 | "projectType": "library", 81 | "schematics": {}, 82 | "architect": { 83 | "lint": { 84 | "builder": "@angular-devkit/build-angular:tslint", 85 | "options": { 86 | "tsConfig": ["libs/api/feature-core/tsconfig.lib.json", "libs/api/feature-core/tsconfig.spec.json"], 87 | "exclude": ["**/node_modules/**", "!libs/api/feature-core/**/*"] 88 | } 89 | }, 90 | "test": { 91 | "builder": "@nrwl/jest:jest", 92 | "options": { 93 | "jestConfig": "libs/api/feature-core/jest.config.js", 94 | "passWithNoTests": true 95 | } 96 | } 97 | } 98 | }, 99 | "api-feature-auth": { 100 | "root": "libs/api/feature-auth", 101 | "sourceRoot": "libs/api/feature-auth/src", 102 | "projectType": "library", 103 | "schematics": {}, 104 | "architect": { 105 | "lint": { 106 | "builder": "@angular-devkit/build-angular:tslint", 107 | "options": { 108 | "tsConfig": ["libs/api/feature-auth/tsconfig.lib.json", "libs/api/feature-auth/tsconfig.spec.json"], 109 | "exclude": ["**/node_modules/**", "!libs/api/feature-auth/**/*"] 110 | } 111 | }, 112 | "test": { 113 | "builder": "@nrwl/jest:jest", 114 | "options": { 115 | "jestConfig": "libs/api/feature-auth/jest.config.js", 116 | "passWithNoTests": true 117 | } 118 | } 119 | } 120 | }, 121 | "web": { 122 | "projectType": "application", 123 | "schematics": { 124 | "@schematics/angular:component": { 125 | "style": "scss" 126 | } 127 | }, 128 | "root": "apps/web", 129 | "sourceRoot": "apps/web/src", 130 | "prefix": "angular-graphql", 131 | "architect": { 132 | "build": { 133 | "builder": "@angular-devkit/build-angular:browser", 134 | "options": { 135 | "outputPath": "dist/apps/web", 136 | "index": "apps/web/src/index.html", 137 | "main": "apps/web/src/main.ts", 138 | "polyfills": "apps/web/src/polyfills.ts", 139 | "tsConfig": "apps/web/tsconfig.app.json", 140 | "aot": true, 141 | "assets": ["apps/web/src/favicon.ico", "apps/web/src/assets"], 142 | "styles": ["apps/web/src/styles.scss"], 143 | "scripts": [], 144 | "allowedCommonJsDependencies": ["graphql-tag", "zen-observable"] 145 | }, 146 | "configurations": { 147 | "production": { 148 | "fileReplacements": [ 149 | { 150 | "replace": "libs/web/feature-core/src/environments/environment.ts", 151 | "with": "libs/web/feature-core/src/environments/environment.prod.ts" 152 | } 153 | ], 154 | "optimization": true, 155 | "outputHashing": "all", 156 | "sourceMap": false, 157 | "extractCss": true, 158 | "namedChunks": false, 159 | "extractLicenses": true, 160 | "vendorChunk": false, 161 | "buildOptimizer": true, 162 | "budgets": [ 163 | { 164 | "type": "initial", 165 | "maximumWarning": "2mb", 166 | "maximumError": "5mb" 167 | }, 168 | { 169 | "type": "anyComponentStyle", 170 | "maximumWarning": "6kb", 171 | "maximumError": "10kb" 172 | } 173 | ] 174 | } 175 | } 176 | }, 177 | "serve": { 178 | "builder": "@angular-devkit/build-angular:dev-server", 179 | "options": { 180 | "browserTarget": "web:build" 181 | }, 182 | "configurations": { 183 | "production": { 184 | "browserTarget": "web:build:production" 185 | } 186 | } 187 | }, 188 | "extract-i18n": { 189 | "builder": "@angular-devkit/build-angular:extract-i18n", 190 | "options": { 191 | "browserTarget": "web:build" 192 | } 193 | }, 194 | "lint": { 195 | "builder": "@angular-devkit/build-angular:tslint", 196 | "options": { 197 | "tsConfig": ["apps/web/tsconfig.app.json", "apps/web/tsconfig.spec.json"], 198 | "exclude": ["**/node_modules/**", "!apps/web/**/*"] 199 | } 200 | }, 201 | "test": { 202 | "builder": "@nrwl/jest:jest", 203 | "options": { 204 | "jestConfig": "apps/web/jest.config.js", 205 | "passWithNoTests": true 206 | } 207 | } 208 | } 209 | }, 210 | "web-e2e": { 211 | "root": "apps/web-e2e", 212 | "sourceRoot": "apps/web-e2e/src", 213 | "projectType": "application", 214 | "architect": { 215 | "e2e": { 216 | "builder": "@nrwl/cypress:cypress", 217 | "options": { 218 | "cypressConfig": "apps/web-e2e/cypress.json", 219 | "tsConfig": "apps/web-e2e/tsconfig.e2e.json", 220 | "devServerTarget": "web:serve" 221 | }, 222 | "configurations": { 223 | "production": { 224 | "devServerTarget": "web:serve:production" 225 | } 226 | } 227 | }, 228 | "lint": { 229 | "builder": "@angular-devkit/build-angular:tslint", 230 | "options": { 231 | "tsConfig": ["apps/web-e2e/tsconfig.e2e.json"], 232 | "exclude": ["**/node_modules/**", "!apps/web-e2e/**/*"] 233 | } 234 | } 235 | } 236 | }, 237 | "web-data-access": { 238 | "projectType": "library", 239 | "root": "libs/web/data-access", 240 | "sourceRoot": "libs/web/data-access/src", 241 | "prefix": "data-access", 242 | "architect": { 243 | "lint": { 244 | "builder": "@angular-devkit/build-angular:tslint", 245 | "options": { 246 | "tsConfig": ["libs/web/data-access/tsconfig.lib.json", "libs/web/data-access/tsconfig.spec.json"], 247 | "exclude": ["**/node_modules/**", "!libs/web/data-access/**/*"] 248 | } 249 | }, 250 | "test": { 251 | "builder": "@nrwl/jest:jest", 252 | "options": { 253 | "jestConfig": "libs/web/data-access/jest.config.js", 254 | "passWithNoTests": true 255 | } 256 | } 257 | }, 258 | "schematics": { 259 | "@schematics/angular:component": { 260 | "style": "scss" 261 | } 262 | } 263 | }, 264 | "web-feature-shell": { 265 | "projectType": "library", 266 | "root": "libs/web/feature-shell", 267 | "sourceRoot": "libs/web/feature-shell/src", 268 | "prefix": "shell", 269 | "architect": { 270 | "lint": { 271 | "builder": "@angular-devkit/build-angular:tslint", 272 | "options": { 273 | "tsConfig": ["libs/web/feature-shell/tsconfig.lib.json", "libs/web/feature-shell/tsconfig.spec.json"], 274 | "exclude": ["**/node_modules/**", "!libs/web/feature-shell/**/*"] 275 | } 276 | }, 277 | "test": { 278 | "builder": "@nrwl/jest:jest", 279 | "options": { 280 | "jestConfig": "libs/web/feature-shell/jest.config.js", 281 | "passWithNoTests": true 282 | } 283 | } 284 | }, 285 | "schematics": { 286 | "@schematics/angular:component": { 287 | "style": "scss" 288 | } 289 | } 290 | }, 291 | "web-feature-auth": { 292 | "projectType": "library", 293 | "root": "libs/web/feature-auth", 294 | "sourceRoot": "libs/web/feature-auth/src", 295 | "prefix": "auth", 296 | "architect": { 297 | "lint": { 298 | "builder": "@angular-devkit/build-angular:tslint", 299 | "options": { 300 | "tsConfig": ["libs/web/feature-auth/tsconfig.lib.json", "libs/web/feature-auth/tsconfig.spec.json"], 301 | "exclude": ["**/node_modules/**", "!libs/web/feature-auth/**/*"] 302 | } 303 | }, 304 | "test": { 305 | "builder": "@nrwl/jest:jest", 306 | "options": { 307 | "jestConfig": "libs/web/feature-auth/jest.config.js", 308 | "passWithNoTests": true 309 | } 310 | } 311 | }, 312 | "schematics": { 313 | "@schematics/angular:component": { 314 | "style": "scss" 315 | } 316 | } 317 | }, 318 | "api-feature-post": { 319 | "root": "libs/api/feature-post", 320 | "sourceRoot": "libs/api/feature-post/src", 321 | "projectType": "library", 322 | "schematics": {}, 323 | "architect": { 324 | "lint": { 325 | "builder": "@angular-devkit/build-angular:tslint", 326 | "options": { 327 | "tsConfig": ["libs/api/feature-post/tsconfig.lib.json", "libs/api/feature-post/tsconfig.spec.json"], 328 | "exclude": ["**/node_modules/**", "!libs/api/feature-post/**/*"] 329 | } 330 | }, 331 | "test": { 332 | "builder": "@nrwl/jest:jest", 333 | "options": { 334 | "jestConfig": "libs/api/feature-post/jest.config.js", 335 | "passWithNoTests": true 336 | } 337 | } 338 | } 339 | }, 340 | "api-feature-comment": { 341 | "root": "libs/api/feature-comment", 342 | "sourceRoot": "libs/api/feature-comment/src", 343 | "projectType": "library", 344 | "schematics": {}, 345 | "architect": { 346 | "lint": { 347 | "builder": "@angular-devkit/build-angular:tslint", 348 | "options": { 349 | "tsConfig": ["libs/api/feature-comment/tsconfig.lib.json", "libs/api/feature-comment/tsconfig.spec.json"], 350 | "exclude": ["**/node_modules/**", "!libs/api/feature-comment/**/*"] 351 | } 352 | }, 353 | "test": { 354 | "builder": "@nrwl/jest:jest", 355 | "options": { 356 | "jestConfig": "libs/api/feature-comment/jest.config.js", 357 | "passWithNoTests": true 358 | } 359 | } 360 | } 361 | }, 362 | "api-feature-profile": { 363 | "root": "libs/api/feature-profile", 364 | "sourceRoot": "libs/api/feature-profile/src", 365 | "projectType": "library", 366 | "schematics": {}, 367 | "architect": { 368 | "lint": { 369 | "builder": "@angular-devkit/build-angular:tslint", 370 | "options": { 371 | "tsConfig": ["libs/api/feature-profile/tsconfig.lib.json", "libs/api/feature-profile/tsconfig.spec.json"], 372 | "exclude": ["**/node_modules/**", "!libs/api/feature-profile/**/*"] 373 | } 374 | }, 375 | "test": { 376 | "builder": "@nrwl/jest:jest", 377 | "options": { 378 | "jestConfig": "libs/api/feature-profile/jest.config.js", 379 | "passWithNoTests": true 380 | } 381 | } 382 | } 383 | }, 384 | "web-feature-core": { 385 | "projectType": "library", 386 | "root": "libs/web/feature-core", 387 | "sourceRoot": "libs/web/feature-core/src", 388 | "prefix": "core", 389 | "architect": { 390 | "lint": { 391 | "builder": "@angular-devkit/build-angular:tslint", 392 | "options": { 393 | "tsConfig": ["libs/web/feature-core/tsconfig.lib.json", "libs/web/feature-core/tsconfig.spec.json"], 394 | "exclude": ["**/node_modules/**", "!libs/web/feature-core/**/*"] 395 | } 396 | }, 397 | "test": { 398 | "builder": "@nrwl/jest:jest", 399 | "options": { 400 | "jestConfig": "libs/web/feature-core/jest.config.js", 401 | "passWithNoTests": true 402 | } 403 | } 404 | }, 405 | "schematics": { 406 | "@schematics/angular:component": { 407 | "style": "scss" 408 | } 409 | } 410 | }, 411 | "web-feature-post": { 412 | "projectType": "library", 413 | "root": "libs/web/feature-post", 414 | "sourceRoot": "libs/web/feature-post/src", 415 | "prefix": "post", 416 | "architect": { 417 | "lint": { 418 | "builder": "@angular-devkit/build-angular:tslint", 419 | "options": { 420 | "tsConfig": ["libs/web/feature-post/tsconfig.lib.json", "libs/web/feature-post/tsconfig.spec.json"], 421 | "exclude": ["**/node_modules/**", "!libs/web/feature-post/**/*"] 422 | } 423 | }, 424 | "test": { 425 | "builder": "@nrwl/jest:jest", 426 | "options": { 427 | "jestConfig": "libs/web/feature-post/jest.config.js", 428 | "passWithNoTests": true 429 | } 430 | } 431 | }, 432 | "schematics": { 433 | "@schematics/angular:component": { 434 | "style": "scss" 435 | } 436 | } 437 | }, 438 | "web-feature-profile": { 439 | "projectType": "library", 440 | "root": "libs/web/feature-profile", 441 | "sourceRoot": "libs/web/feature-profile/src", 442 | "prefix": "profile", 443 | "architect": { 444 | "lint": { 445 | "builder": "@angular-devkit/build-angular:tslint", 446 | "options": { 447 | "tsConfig": ["libs/web/feature-profile/tsconfig.lib.json", "libs/web/feature-profile/tsconfig.spec.json"], 448 | "exclude": ["**/node_modules/**", "!libs/web/feature-profile/**/*"] 449 | } 450 | }, 451 | "test": { 452 | "builder": "@nrwl/jest:jest", 453 | "options": { 454 | "jestConfig": "libs/web/feature-profile/jest.config.js", 455 | "passWithNoTests": true 456 | } 457 | } 458 | }, 459 | "schematics": { 460 | "@schematics/angular:component": { 461 | "style": "scss" 462 | } 463 | } 464 | }, 465 | "web-data-access-auth": { 466 | "projectType": "library", 467 | "root": "libs/web/data-access-auth", 468 | "sourceRoot": "libs/web/data-access-auth/src", 469 | "prefix": "auth", 470 | "architect": { 471 | "lint": { 472 | "builder": "@angular-devkit/build-angular:tslint", 473 | "options": { 474 | "tsConfig": ["libs/web/data-access-auth/tsconfig.lib.json", "libs/web/data-access-auth/tsconfig.spec.json"], 475 | "exclude": ["**/node_modules/**", "!libs/web/data-access-auth/**/*"] 476 | } 477 | }, 478 | "test": { 479 | "builder": "@nrwl/jest:jest", 480 | "options": { 481 | "jestConfig": "libs/web/data-access-auth/jest.config.js", 482 | "passWithNoTests": true 483 | } 484 | } 485 | }, 486 | "schematics": { 487 | "@schematics/angular:component": { 488 | "style": "scss" 489 | } 490 | } 491 | } 492 | }, 493 | "cli": { 494 | "defaultCollection": "@nrwl/nest" 495 | }, 496 | "schematics": { 497 | "@nrwl/workspace": { 498 | "library": { 499 | "linter": "tslint" 500 | } 501 | }, 502 | "@nrwl/cypress": { 503 | "cypress-project": { 504 | "linter": "tslint" 505 | } 506 | }, 507 | "@nrwl/node": { 508 | "application": { 509 | "linter": "tslint" 510 | }, 511 | "library": { 512 | "linter": "tslint" 513 | } 514 | }, 515 | "@nrwl/nest": { 516 | "application": { 517 | "linter": "tslint" 518 | }, 519 | "library": { 520 | "linter": "tslint" 521 | } 522 | }, 523 | "@nrwl/express": { 524 | "application": { 525 | "linter": "tslint" 526 | }, 527 | "library": { 528 | "linter": "tslint" 529 | } 530 | }, 531 | "@nrwl/angular:application": { 532 | "unitTestRunner": "jest", 533 | "e2eTestRunner": "cypress" 534 | }, 535 | "@nrwl/angular:library": { 536 | "unitTestRunner": "jest" 537 | } 538 | }, 539 | "defaultProject": "api" 540 | } 541 | -------------------------------------------------------------------------------- /apps/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api', 3 | preset: '../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | transform: { 10 | '^.+\\.[tj]s$': 'ts-jest', 11 | }, 12 | moduleFileExtensions: ['ts', 'js', 'html'], 13 | coverageDirectory: '../../coverage/apps/api', 14 | } 15 | -------------------------------------------------------------------------------- /apps/api/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { ApiFeatureAuthModule } from '@angular-graphql/api/feature-auth' 2 | import { ApiFeatureCommentModule } from '@angular-graphql/api/feature-comment' 3 | import { ApiFeatureCoreModule } from '@angular-graphql/api/feature-core' 4 | import { ApiFeaturePostModule } from '@angular-graphql/api/feature-post' 5 | import { ApiFeatureProfileModule } from '@angular-graphql/api/feature-profile' 6 | import { Module } from '@nestjs/common' 7 | 8 | @Module({ 9 | imports: [ 10 | ApiFeatureAuthModule, 11 | ApiFeatureCommentModule, 12 | ApiFeatureCoreModule, 13 | ApiFeaturePostModule, 14 | ApiFeatureProfileModule, 15 | ], 16 | }) 17 | export class AppModule {} 18 | -------------------------------------------------------------------------------- /apps/api/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/panng-stack/35aae3ea5358ff641e8e7c32b05541fae4e4261d/apps/api/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | } 4 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | } 4 | -------------------------------------------------------------------------------- /apps/api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common' 2 | import { NestFactory } from '@nestjs/core' 3 | 4 | import { AppModule } from './app/app.module' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule) 8 | const globalPrefix = 'api' 9 | app.setGlobalPrefix(globalPrefix) 10 | const port = process.env.PORT || 3333 11 | await app.listen(port, () => { 12 | Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix) 13 | Logger.log('Listening at http://localhost:' + port + '/graphql') 14 | }) 15 | } 16 | 17 | bootstrap() 18 | -------------------------------------------------------------------------------- /apps/api/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"], 6 | "emitDecoratorMetadata": true, 7 | "target": "es2015" 8 | }, 9 | "exclude": ["**/*.spec.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /apps/web-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "pluginsFile": "./src/plugins/index", 7 | "supportFile": "./src/support/index.ts", 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/web-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/web-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/web-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/web-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po' 2 | 3 | describe('web', () => { 4 | beforeEach(() => cy.visit('/')) 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword') 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to web!') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /apps/web-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor') 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript file using Nx helper 21 | on('file:preprocessor', preprocessTypescript(config)) 22 | } 23 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1') 2 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // eslint-disable-next-line @typescript-eslint/no-namespace 11 | declare namespace Cypress { 12 | interface Chainable { 13 | login(email: string, password: string): void 14 | } 15 | } 16 | // 17 | // -- This is a parent command -- 18 | Cypress.Commands.add('login', (email, password) => { 19 | console.log('Custom command example: Login', email, password) 20 | }) 21 | // 22 | // -- This is a child command -- 23 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 24 | // 25 | // 26 | // -- This is a dual command -- 27 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 28 | // 29 | // 30 | // -- This will overwrite an existing command -- 31 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 32 | -------------------------------------------------------------------------------- /apps/web-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | -------------------------------------------------------------------------------- /apps/web-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web-e2e/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /apps/web/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../coverage/apps/web', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'angular-graphql-root', 5 | template: '', 6 | }) 7 | export class AppComponent {} 8 | -------------------------------------------------------------------------------- /apps/web/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser' 2 | import { NgModule } from '@angular/core' 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations' 4 | import { RouterModule } from '@angular/router' 5 | import { WebFeatureShellModule } from '@angular-graphql/web/feature-shell' 6 | 7 | import { AppComponent } from './app.component' 8 | 9 | @NgModule({ 10 | declarations: [AppComponent], 11 | imports: [BrowserModule, BrowserAnimationsModule, RouterModule, WebFeatureShellModule], 12 | bootstrap: [AppComponent], 13 | }) 14 | export class AppModule {} 15 | -------------------------------------------------------------------------------- /apps/web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/panng-stack/35aae3ea5358ff641e8e7c32b05541fae4e4261d/apps/web/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/panng-stack/35aae3ea5358ff641e8e7c32b05541fae4e4261d/apps/web/src/assets/logo.png -------------------------------------------------------------------------------- /apps/web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/panng-stack/35aae3ea5358ff641e8e7c32b05541fae4e4261d/apps/web/src/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /apps/web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { environment } from '@angular-graphql/web/feature-core' 2 | import { enableProdMode } from '@angular/core' 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' 4 | 5 | import { AppModule } from './app/app.module' 6 | 7 | if (environment.production) { 8 | enableProdMode() 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)) 14 | -------------------------------------------------------------------------------- /apps/web/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone' // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /apps/web/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~@kikstart-ui/themes/scss/pirate'; 2 | -------------------------------------------------------------------------------- /apps/web/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /apps/web/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/web/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "angularGraphql", "camelCase"], 5 | "component-selector": [true, "element", "angular-graphql", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /decorate-angular-cli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching 3 | * and faster execution of tasks. 4 | * 5 | * It does this by: 6 | * 7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. 8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI 9 | * - Updating the package.json postinstall script to give you control over this script 10 | * 11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. 12 | * Every command you run should work the same when using the Nx CLI, except faster. 13 | * 14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, 15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. 16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI. 17 | * 18 | * To opt out of this patch: 19 | * - Replace occurrences of nx with ng in your package.json 20 | * - Remove the script from your postinstall script in your package.json 21 | * - Delete and reinstall your node_modules 22 | */ 23 | 24 | const fs = require('fs') 25 | const os = require('os') 26 | const cp = require('child_process') 27 | const isWindows = os.platform() === 'win32' 28 | const { output } = require('@nrwl/workspace') 29 | 30 | /** 31 | * Paths to files being patched 32 | */ 33 | const angularCLIInitPath = 'node_modules/@angular/cli/lib/cli/index.js' 34 | 35 | /** 36 | * Patch index.js to warn you if you invoke the undecorated Angular CLI. 37 | */ 38 | function patchAngularCLI(initPath) { 39 | const angularCLIInit = fs.readFileSync(initPath, 'utf-8').toString() 40 | 41 | if (!angularCLIInit.includes('NX_CLI_SET')) { 42 | fs.writeFileSync( 43 | initPath, 44 | ` 45 | if (!process.env['NX_CLI_SET']) { 46 | const { output } = require('@nrwl/workspace'); 47 | output.warn({ title: 'The Angular CLI was invoked instead of the Nx CLI. Use "npx ng [command]" or "nx [command]" instead.' }); 48 | } 49 | ${angularCLIInit} 50 | `, 51 | ) 52 | } 53 | } 54 | 55 | /** 56 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still 57 | * invoke the Nx CLI and get the benefits of computation caching. 58 | */ 59 | function symlinkNgCLItoNxCLI() { 60 | try { 61 | const ngPath = './node_modules/.bin/ng' 62 | const nxPath = './node_modules/.bin/nx' 63 | if (isWindows) { 64 | /** 65 | * This is the most reliable way to create symlink-like behavior on Windows. 66 | * Such that it works in all shells and works with npx. 67 | */ 68 | ;['', '.cmd', '.ps1'].forEach((ext) => { 69 | if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)) 70 | }) 71 | } else { 72 | // If unix-based, symlink 73 | cp.execSync(`ln -sf ./nx ${ngPath}`) 74 | } 75 | } catch (e) { 76 | output.error({ 77 | title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message, 78 | }) 79 | throw e 80 | } 81 | } 82 | 83 | try { 84 | symlinkNgCLItoNxCLI() 85 | patchAngularCLI(angularCLIInitPath) 86 | output.log({ 87 | title: 'Angular CLI has been decorated to enable computation caching.', 88 | }) 89 | } catch (e) { 90 | output.error({ 91 | title: 'Decoration of the Angular CLI did not complete successfully', 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres: 4 | image: postgres 5 | ports: 6 | - "5432:5432" 7 | environment: 8 | POSTGRES_DB: prisma 9 | POSTGRES_USER: prisma 10 | POSTGRES_PASSWORD: prisma 11 | volumes: 12 | - ./tmp/postgres:/var/lib/postgresql/data 13 | volumes: 14 | postgres: 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 3 | transform: { 4 | '^.+\\.(ts|js|html)$': 'ts-jest', 5 | }, 6 | resolver: '@nrwl/jest/plugins/resolver', 7 | moduleFileExtensions: ['ts', 'js', 'html'], 8 | coverageReporters: ['html'], 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/data-access/README.md: -------------------------------------------------------------------------------- 1 | # api-data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-data-access` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-data-access', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/data-access', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-data-access.module' 2 | export * from './lib/api-data-access.service' 3 | export * from './lib/api-data-access.helper' 4 | -------------------------------------------------------------------------------- /libs/api/data-access/src/lib/api-data-access.helper.ts: -------------------------------------------------------------------------------- 1 | import { compare, hash } from 'bcryptjs' 2 | import { createHash } from 'crypto' 3 | 4 | const getHash = (str) => createHash('md5').update(str).digest('hex') 5 | 6 | const gravatarUrl = 'https://www.gravatar.com/avatar/' 7 | const gravatarSize = 460 8 | 9 | export const getGravatarUrl = (email = '') => `${gravatarUrl}${getHash(email)}?s=${gravatarSize}` 10 | 11 | export const validatePassword = (password: string, hashedPassword: string): Promise => { 12 | return compare(password, hashedPassword) 13 | } 14 | 15 | export const hashPassword = (password: string): Promise => { 16 | return hash(password, 10) 17 | } 18 | -------------------------------------------------------------------------------- /libs/api/data-access/src/lib/api-data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { ApiDataAccessService } from './api-data-access.service' 4 | 5 | @Module({ 6 | exports: [ApiDataAccessService], 7 | providers: [ApiDataAccessService], 8 | }) 9 | export class ApiDataAccessModule {} 10 | -------------------------------------------------------------------------------- /libs/api/data-access/src/lib/api-data-access.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleDestroy, OnModuleInit, Logger } from '@nestjs/common' 2 | import { PrismaClient, UserCreateInput } from '@prisma/client' 3 | import { getGravatarUrl, hashPassword } from './api-data-access.helper' 4 | import { sampleUsers } from './sample-data/sample-users' 5 | 6 | @Injectable() 7 | export class ApiDataAccessService extends PrismaClient implements OnModuleInit, OnModuleDestroy { 8 | constructor() { 9 | super() 10 | } 11 | 12 | public async onModuleDestroy() { 13 | await this.$disconnect() 14 | } 15 | 16 | public async onModuleInit() { 17 | await this.$connect() 18 | await this.sampleData() 19 | } 20 | 21 | async createUser(input: UserCreateInput) { 22 | const password = await hashPassword(input.password) 23 | 24 | return this.user.create({ 25 | data: { 26 | username: input.username, 27 | name: input.name || input.username, 28 | email: input.email.toLowerCase(), 29 | avatarUrl: input.avatarUrl || getGravatarUrl(input.email.toLowerCase()), 30 | password, 31 | }, 32 | }) 33 | } 34 | 35 | public findUserByEmail(email: string) { 36 | return this.user.findOne({ where: { email } }) 37 | } 38 | 39 | public findUserById(userId: string) { 40 | return this.user.findOne({ where: { id: userId } }) 41 | } 42 | 43 | public findUserByUsername(username: string) { 44 | return this.user.findOne({ where: { username } }) 45 | } 46 | 47 | private async sampleData() { 48 | await this.deleteData() 49 | 50 | for (const data of sampleUsers) { 51 | await this.user.create({ data }) 52 | } 53 | 54 | const comments = await this.comment.count() 55 | const posts = await this.post.count() 56 | const userCount = await this.user.count() 57 | 58 | console.log('Sample data:', { comments, posts, users: userCount }) 59 | } 60 | 61 | private async deleteData() { 62 | await this.comment.deleteMany({ where: {} }) 63 | await this.post.deleteMany({ where: {} }) 64 | await this.user.deleteMany({ where: {} }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /libs/api/data-access/src/lib/sample-data/sample-users.ts: -------------------------------------------------------------------------------- 1 | import { UserCreateInput } from '@prisma/client' 2 | 3 | const password = '$2b$10$dqyYw5XovLjpmkYNiRDEWuwKaRAvLaG45fnXE5b3KTccKZcRPka2m' // "secret42", 4 | 5 | export const sampleUsers: UserCreateInput[] = [ 6 | { 7 | id: 'summer', 8 | username: 'summer', 9 | password, 10 | email: 'summer@example.com', 11 | avatarUrl: 'https://imgur.com/Y4dT5W0.png', 12 | name: 'Summer Smith', 13 | role: 'User', 14 | location: 'Who cares...', 15 | bio: 'Hey!', 16 | 17 | posts: { 18 | create: [ 19 | { 20 | text: 'Hi! Summer here! Nice to meet you all!', 21 | }, 22 | ], 23 | }, 24 | }, 25 | { 26 | id: 'morty', 27 | username: 'morty', 28 | password, 29 | email: 'morty@example.com', 30 | avatarUrl: 'https://imgur.com/Ca4c884.png', 31 | name: 'Morty Smith', 32 | role: 'User', 33 | location: 'School!', 34 | bio: "Hi there I'm Morty 🎉!", 35 | posts: { 36 | create: [ 37 | { 38 | text: 'Morty Represents! 😎', 39 | }, 40 | ], 41 | }, 42 | }, 43 | { 44 | id: 'rick', 45 | username: 'rick', 46 | password, 47 | email: 'rick@example.com', 48 | avatarUrl: 'https://imgur.com/0Ao2f63.png', 49 | name: 'Rick Sanchez', 50 | role: 'Admin', 51 | location: 'Earth C-137', 52 | bio: 'I am the smartest guy in all universes!', 53 | posts: { 54 | create: { 55 | text: "Hello, I'm Rick and I love GraphQL", 56 | commentCount: 2, 57 | comments: { 58 | create: [ 59 | { 60 | text: 'Welcome to the site Rick!!', 61 | author: { connect: { username: 'summer' } }, 62 | }, 63 | { 64 | text: 'I hope you enjoy your stay!!!', 65 | author: { connect: { username: 'morty' } }, 66 | }, 67 | ], 68 | }, 69 | }, 70 | }, 71 | }, 72 | { 73 | id: 'beth', 74 | username: 'beth', 75 | password, 76 | email: 'beth@example.com', 77 | avatarUrl: 'https://imgur.com/o28uw9C.png', 78 | name: 'Beth Smith', 79 | role: 'User', 80 | location: 'Earth', 81 | bio: 'Veterinarian', 82 | posts: { 83 | create: [ 84 | { 85 | text: 'I love horses!', 86 | }, 87 | ], 88 | }, 89 | }, 90 | { 91 | id: 'jerry', 92 | username: 'jerry', 93 | password, 94 | email: 'jerry@example.com', 95 | avatarUrl: 'https://imgur.com/Yr4YiTi.png', 96 | name: 'Jerry Smith', 97 | role: 'User', 98 | location: 'Earth', 99 | bio: 'Husband of @beth', 100 | posts: { 101 | create: [ 102 | { 103 | text: 'Something with apples!', 104 | }, 105 | ], 106 | }, 107 | }, 108 | ] 109 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925071132-init/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200925071132-init` 2 | 3 | This migration has been generated by Bram Borggreve at 9/25/2020, 2:11:32 AM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | CREATE TYPE "angular-graphql"."Role" AS ENUM ('Admin', 'User') 10 | 11 | CREATE TABLE "angular-graphql"."User" ( 12 | "id" text NOT NULL , 13 | "createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 14 | "updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 15 | "username" text NOT NULL , 16 | "email" text NOT NULL , 17 | "password" text NOT NULL , 18 | "role" "Role" NOT NULL DEFAULT E'User', 19 | "name" text , 20 | "avatarUrl" text , 21 | PRIMARY KEY ("id") 22 | ) 23 | 24 | CREATE TABLE "angular-graphql"."Post" ( 25 | "id" text NOT NULL , 26 | "createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 27 | "updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 28 | "text" text NOT NULL , 29 | "authorId" text NOT NULL , 30 | "commentCount" integer NOT NULL DEFAULT 0, 31 | "likeCount" integer NOT NULL DEFAULT 0, 32 | PRIMARY KEY ("id") 33 | ) 34 | 35 | CREATE TABLE "angular-graphql"."Comment" ( 36 | "id" text NOT NULL , 37 | "createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 38 | "updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 39 | "text" text NOT NULL , 40 | "postId" text NOT NULL , 41 | "userId" text NOT NULL , 42 | PRIMARY KEY ("id") 43 | ) 44 | 45 | CREATE TABLE "angular-graphql"."_LikedPostsByUser" ( 46 | "A" text NOT NULL , 47 | "B" text NOT NULL 48 | ) 49 | 50 | CREATE UNIQUE INDEX "User.username_unique" ON "angular-graphql"."User"("username") 51 | 52 | CREATE UNIQUE INDEX "User.email_unique" ON "angular-graphql"."User"("email") 53 | 54 | CREATE UNIQUE INDEX "_LikedPostsByUser_AB_unique" ON "angular-graphql"."_LikedPostsByUser"("A", "B") 55 | 56 | CREATE INDEX "_LikedPostsByUser_B_index" ON "angular-graphql"."_LikedPostsByUser"("B") 57 | 58 | ALTER TABLE "angular-graphql"."Post" ADD FOREIGN KEY ("authorId")REFERENCES "angular-graphql"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE 59 | 60 | ALTER TABLE "angular-graphql"."Comment" ADD FOREIGN KEY ("postId")REFERENCES "angular-graphql"."Post"("id") ON DELETE CASCADE ON UPDATE CASCADE 61 | 62 | ALTER TABLE "angular-graphql"."Comment" ADD FOREIGN KEY ("userId")REFERENCES "angular-graphql"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE 63 | 64 | ALTER TABLE "angular-graphql"."_LikedPostsByUser" ADD FOREIGN KEY ("A")REFERENCES "angular-graphql"."Post"("id") ON DELETE CASCADE ON UPDATE CASCADE 65 | 66 | ALTER TABLE "angular-graphql"."_LikedPostsByUser" ADD FOREIGN KEY ("B")REFERENCES "angular-graphql"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE 67 | ``` 68 | 69 | ## Changes 70 | 71 | ```diff 72 | diff --git schema.prisma schema.prisma 73 | migration ..20200925071132-init 74 | --- datamodel.dml 75 | +++ datamodel.dml 76 | @@ -1,0 +1,57 @@ 77 | +// This is your Prisma schema file, 78 | +// learn more about it in the docs: https://pris.ly/d/prisma-schema 79 | + 80 | +datasource db { 81 | + provider = "postgresql" 82 | + url = "***" 83 | +} 84 | + 85 | +generator client { 86 | + provider = "prisma-client-js" 87 | +} 88 | + 89 | +model User { 90 | + id String @default(cuid()) @id 91 | + createdAt DateTime @default(now()) 92 | + updatedAt DateTime @default(now()) @updatedAt 93 | + username String @unique 94 | + email String @unique 95 | + password String 96 | + role Role @default(value: User) 97 | + name String? 98 | + avatarUrl String? 99 | + // bio String? 100 | + // location String? 101 | + posts Post[] @relation("PostsByUser") 102 | + liked Post[] @relation("LikedPostsByUser") 103 | + comments Comment[] @relation("CommentsByUser") 104 | +} 105 | + 106 | +enum Role { 107 | + Admin 108 | + User 109 | +} 110 | + 111 | +model Post { 112 | + id String @default(cuid()) @id 113 | + createdAt DateTime @default(now()) 114 | + updatedAt DateTime @default(now()) @updatedAt 115 | + text String 116 | + author User @relation("PostsByUser", fields: [authorId], references: [id]) 117 | + authorId String 118 | + comments Comment[] @relation("CommentsByPost") 119 | + commentCount Int @default(value: 0) 120 | + likedBy User[] @relation("LikedPostsByUser") 121 | + likeCount Int @default(value: 0) 122 | +} 123 | + 124 | +model Comment { 125 | + id String @default(cuid()) @id 126 | + createdAt DateTime @default(now()) 127 | + updatedAt DateTime @default(now()) @updatedAt 128 | + text String 129 | + post Post @relation("CommentsByPost", fields: [postId], references: [id]) 130 | + postId String 131 | + author User @relation("CommentsByUser", fields: [userId], references: [id]) 132 | + userId String 133 | +} 134 | ``` 135 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925071132-init/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model User { 14 | id String @default(cuid()) @id 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @default(now()) @updatedAt 17 | username String @unique 18 | email String @unique 19 | password String 20 | role Role @default(value: User) 21 | name String? 22 | avatarUrl String? 23 | // bio String? 24 | // location String? 25 | posts Post[] @relation("PostsByUser") 26 | liked Post[] @relation("LikedPostsByUser") 27 | comments Comment[] @relation("CommentsByUser") 28 | } 29 | 30 | enum Role { 31 | Admin 32 | User 33 | } 34 | 35 | model Post { 36 | id String @default(cuid()) @id 37 | createdAt DateTime @default(now()) 38 | updatedAt DateTime @default(now()) @updatedAt 39 | text String 40 | author User @relation("PostsByUser", fields: [authorId], references: [id]) 41 | authorId String 42 | comments Comment[] @relation("CommentsByPost") 43 | commentCount Int @default(value: 0) 44 | likedBy User[] @relation("LikedPostsByUser") 45 | likeCount Int @default(value: 0) 46 | } 47 | 48 | model Comment { 49 | id String @default(cuid()) @id 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @default(now()) @updatedAt 52 | text String 53 | post Post @relation("CommentsByPost", fields: [postId], references: [id]) 54 | postId String 55 | author User @relation("CommentsByUser", fields: [userId], references: [id]) 56 | userId String 57 | } 58 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925083105-add-bio-field/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200925083105-add-bio-field` 2 | 3 | This migration has been generated by Bram Borggreve at 9/25/2020, 3:31:05 AM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | ALTER TABLE "angular-graphql"."User" ADD COLUMN "bio" text 10 | ``` 11 | 12 | ## Changes 13 | 14 | ```diff 15 | diff --git schema.prisma schema.prisma 16 | migration 20200925071132-init..20200925083105-add-bio-field 17 | --- datamodel.dml 18 | +++ datamodel.dml 19 | @@ -2,9 +2,9 @@ 20 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 21 | datasource db { 22 | provider = "postgresql" 23 | - url = "***" 24 | + url = "***" 25 | } 26 | generator client { 27 | provider = "prisma-client-js" 28 | @@ -19,9 +19,9 @@ 29 | password String 30 | role Role @default(value: User) 31 | name String? 32 | avatarUrl String? 33 | - // bio String? 34 | + bio String? 35 | // location String? 36 | posts Post[] @relation("PostsByUser") 37 | liked Post[] @relation("LikedPostsByUser") 38 | comments Comment[] @relation("CommentsByUser") 39 | ``` 40 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925083105-add-bio-field/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model User { 14 | id String @default(cuid()) @id 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @default(now()) @updatedAt 17 | username String @unique 18 | email String @unique 19 | password String 20 | role Role @default(value: User) 21 | name String? 22 | avatarUrl String? 23 | bio String? 24 | // location String? 25 | posts Post[] @relation("PostsByUser") 26 | liked Post[] @relation("LikedPostsByUser") 27 | comments Comment[] @relation("CommentsByUser") 28 | } 29 | 30 | enum Role { 31 | Admin 32 | User 33 | } 34 | 35 | model Post { 36 | id String @default(cuid()) @id 37 | createdAt DateTime @default(now()) 38 | updatedAt DateTime @default(now()) @updatedAt 39 | text String 40 | author User @relation("PostsByUser", fields: [authorId], references: [id]) 41 | authorId String 42 | comments Comment[] @relation("CommentsByPost") 43 | commentCount Int @default(value: 0) 44 | likedBy User[] @relation("LikedPostsByUser") 45 | likeCount Int @default(value: 0) 46 | } 47 | 48 | model Comment { 49 | id String @default(cuid()) @id 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @default(now()) @updatedAt 52 | text String 53 | post Post @relation("CommentsByPost", fields: [postId], references: [id]) 54 | postId String 55 | author User @relation("CommentsByUser", fields: [userId], references: [id]) 56 | userId String 57 | } 58 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925083105-add-bio-field/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateField", 6 | "model": "User", 7 | "field": "bio", 8 | "type": "String", 9 | "arity": "Optional" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925164238-location/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200925164238-location` 2 | 3 | This migration has been generated by Bram Borggreve at 9/25/2020, 11:42:38 AM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | ALTER TABLE "angular-graphql"."User" ADD COLUMN "location" text 10 | ``` 11 | 12 | ## Changes 13 | 14 | ```diff 15 | diff --git schema.prisma schema.prisma 16 | migration 20200925083105-add-bio-field..20200925164238-location 17 | --- datamodel.dml 18 | +++ datamodel.dml 19 | @@ -2,9 +2,9 @@ 20 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 21 | datasource db { 22 | provider = "postgresql" 23 | - url = "***" 24 | + url = "***" 25 | } 26 | generator client { 27 | provider = "prisma-client-js" 28 | @@ -20,9 +20,9 @@ 29 | role Role @default(value: User) 30 | name String? 31 | avatarUrl String? 32 | bio String? 33 | - // location String? 34 | + location String? 35 | posts Post[] @relation("PostsByUser") 36 | liked Post[] @relation("LikedPostsByUser") 37 | comments Comment[] @relation("CommentsByUser") 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925164238-location/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model User { 14 | id String @default(cuid()) @id 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @default(now()) @updatedAt 17 | username String @unique 18 | email String @unique 19 | password String 20 | role Role @default(value: User) 21 | name String? 22 | avatarUrl String? 23 | bio String? 24 | location String? 25 | posts Post[] @relation("PostsByUser") 26 | liked Post[] @relation("LikedPostsByUser") 27 | comments Comment[] @relation("CommentsByUser") 28 | } 29 | 30 | enum Role { 31 | Admin 32 | User 33 | } 34 | 35 | model Post { 36 | id String @default(cuid()) @id 37 | createdAt DateTime @default(now()) 38 | updatedAt DateTime @default(now()) @updatedAt 39 | text String 40 | author User @relation("PostsByUser", fields: [authorId], references: [id]) 41 | authorId String 42 | comments Comment[] @relation("CommentsByPost") 43 | commentCount Int @default(value: 0) 44 | likedBy User[] @relation("LikedPostsByUser") 45 | likeCount Int @default(value: 0) 46 | } 47 | 48 | model Comment { 49 | id String @default(cuid()) @id 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @default(now()) @updatedAt 52 | text String 53 | post Post @relation("CommentsByPost", fields: [postId], references: [id]) 54 | postId String 55 | author User @relation("CommentsByUser", fields: [userId], references: [id]) 56 | userId String 57 | } 58 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/20200925164238-location/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateField", 6 | "model": "User", 7 | "field": "location", 8 | "type": "String", 9 | "arity": "Optional" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/data-access/src/migrations/migrate.lock: -------------------------------------------------------------------------------- 1 | # Prisma Migrate lockfile v1 2 | 3 | 20200925071132-init 4 | 20200925083105-add-bio-field 5 | 20200925164238-location -------------------------------------------------------------------------------- /libs/api/data-access/src/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model User { 14 | id String @default(cuid()) @id 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @default(now()) @updatedAt 17 | username String @unique 18 | email String @unique 19 | password String 20 | role Role @default(value: User) 21 | name String? 22 | avatarUrl String? 23 | bio String? 24 | location String? 25 | posts Post[] @relation("PostsByUser") 26 | liked Post[] @relation("LikedPostsByUser") 27 | comments Comment[] @relation("CommentsByUser") 28 | } 29 | 30 | enum Role { 31 | Admin 32 | User 33 | } 34 | 35 | model Post { 36 | id String @default(cuid()) @id 37 | createdAt DateTime @default(now()) 38 | updatedAt DateTime @default(now()) @updatedAt 39 | text String 40 | author User @relation("PostsByUser", fields: [authorId], references: [id]) 41 | authorId String 42 | comments Comment[] @relation("CommentsByPost") 43 | commentCount Int @default(value: 0) 44 | likedBy User[] @relation("LikedPostsByUser") 45 | likeCount Int @default(value: 0) 46 | } 47 | 48 | model Comment { 49 | id String @default(cuid()) @id 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @default(now()) @updatedAt 52 | text String 53 | post Post @relation("CommentsByPost", fields: [postId], references: [id]) 54 | postId String 55 | author User @relation("CommentsByUser", fields: [userId], references: [id]) 56 | userId String 57 | } 58 | -------------------------------------------------------------------------------- /libs/api/data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/data-access/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /libs/api/feature-auth/README.md: -------------------------------------------------------------------------------- 1 | # api-feature-auth 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-feature-auth` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/feature-auth/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-feature-auth', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/feature-auth', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/decorators/ctx-user.decorator' 2 | export * from './lib/guards/gql-auth.guard' 3 | export * from './lib/api-feature-auth.module' 4 | export * from './lib/api-feature-auth.service' 5 | export * from './lib/models/user' 6 | export * from './lib/models/role' 7 | export * from './lib/models/user-token' 8 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/api-feature-auth.helper.ts: -------------------------------------------------------------------------------- 1 | import { compare, hash } from 'bcryptjs' 2 | 3 | export class ApiFeatureAuthHelper { 4 | static validatePassword(password: string, hashedPassword: string): Promise { 5 | return compare(password, hashedPassword) 6 | } 7 | 8 | static hashPassword(password: string): Promise { 9 | return hash(password, 10) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/api-feature-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessModule } from '@angular-graphql/api/data-access' 2 | import { Module } from '@nestjs/common' 3 | import { JwtModule } from '@nestjs/jwt' 4 | import { PassportModule } from '@nestjs/passport' 5 | import { ApiFeatureAuthResolver } from './api-feature-auth.resolver' 6 | import { ApiFeatureAuthService } from './api-feature-auth.service' 7 | import { JwtStrategy } from './strategies/jwt.strategy' 8 | 9 | @Module({ 10 | imports: [ 11 | PassportModule.register({ defaultStrategy: 'jwt' }), 12 | JwtModule.register({ 13 | secret: 'PANNG_STACK_SECRET', 14 | }), 15 | ApiDataAccessModule, 16 | ], 17 | providers: [ApiFeatureAuthResolver, ApiFeatureAuthService, JwtStrategy], 18 | }) 19 | export class ApiFeatureAuthModule {} 20 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/api-feature-auth.resolver.ts: -------------------------------------------------------------------------------- 1 | import { UseGuards } from '@nestjs/common' 2 | import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql' 3 | 4 | import { ApiFeatureAuthService } from './api-feature-auth.service' 5 | import { CtxUser } from './decorators/ctx-user.decorator' 6 | 7 | import { LoginInput } from './dto/login.input' 8 | import { RegisterInput } from './dto/register.input' 9 | import { GqlAuthGuard } from './guards/gql-auth.guard' 10 | import { User } from './models/user' 11 | import { UserToken } from './models/user-token' 12 | 13 | @Resolver(() => UserToken) 14 | export class ApiFeatureAuthResolver { 15 | constructor(private readonly service: ApiFeatureAuthService) {} 16 | 17 | @Query(() => User, { nullable: true }) 18 | @UseGuards(GqlAuthGuard) 19 | async me(@CtxUser() user: User) { 20 | console.log('user', user) 21 | return user 22 | } 23 | 24 | @Mutation(() => UserToken, { nullable: true }) 25 | async register(@Args('data') data: RegisterInput) { 26 | return this.service.register(data) 27 | } 28 | 29 | @Mutation(() => UserToken, { nullable: true }) 30 | async login(@Args('data') { username, password }: LoginInput) { 31 | return this.service.login(username.trim(), password) 32 | } 33 | 34 | @ResolveField('user') 35 | user(@Parent() auth: UserToken) { 36 | return this.service.getUserFromToken(auth.token) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/api-feature-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessService } from '@angular-graphql/api/data-access' 2 | import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common' 3 | import { JwtService } from '@nestjs/jwt' 4 | import { ApiFeatureAuthHelper } from './api-feature-auth.helper' 5 | import { RegisterInput } from './dto/register.input' 6 | import { UserToken } from './models/user-token' 7 | 8 | @Injectable() 9 | export class ApiFeatureAuthService { 10 | constructor(private readonly jwtService: JwtService, private readonly data: ApiDataAccessService) {} 11 | 12 | async register(payload: RegisterInput) { 13 | const user = await this.data.createUser({ 14 | ...payload, 15 | }) 16 | 17 | return this.signUser(user) 18 | } 19 | 20 | async login(username: string, password: string) { 21 | const user = await this.data.findUserByUsername(username) 22 | 23 | if (!user) { 24 | throw new NotFoundException(`No user found for username: ${username}`) 25 | } 26 | 27 | const passwordValid = await ApiFeatureAuthHelper.validatePassword(password, user.password) 28 | 29 | if (!passwordValid) { 30 | throw new BadRequestException('Invalid password') 31 | } 32 | 33 | return this.signUser(user) 34 | } 35 | 36 | signUser(user): UserToken { 37 | const token = this.jwtService.sign({ userId: user.id }) 38 | return { token, user } 39 | } 40 | 41 | validateUser(userId: string) { 42 | return this.data.findUserById(userId) 43 | } 44 | 45 | getUserFromToken(token: string) { 46 | const userId = this.jwtService.decode(token)['userId'] 47 | console.log('getUserFromToken userId', userId) 48 | return this.data.findUserById(userId) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/decorators/ctx-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '@nestjs/common' 2 | import { GqlExecutionContext } from '@nestjs/graphql' 3 | 4 | export const CtxUser = createParamDecorator((data, ctx) => GqlExecutionContext.create(ctx).getContext().req.user) 5 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/create-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql' 2 | import { IsNotEmpty, MinLength } from 'class-validator' 3 | import { UpdateUserInput } from './update-user.input' 4 | 5 | @InputType() 6 | export class CreateUserInput extends UpdateUserInput { 7 | @Field() 8 | @IsNotEmpty() 9 | @MinLength(8) 10 | password: string 11 | } 12 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/jwt.dto.ts: -------------------------------------------------------------------------------- 1 | export interface JwtDto { 2 | userId: string 3 | } 4 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/login.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql' 2 | import { IsNotEmpty, MinLength } from 'class-validator' 3 | 4 | @InputType() 5 | export class LoginInput { 6 | @Field() 7 | @IsNotEmpty() 8 | username: string 9 | 10 | @Field() 11 | @IsNotEmpty() 12 | @MinLength(8) 13 | password: string 14 | } 15 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/register.input.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, MinLength } from 'class-validator' 2 | import { InputType, Field } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class RegisterInput { 6 | @Field() 7 | @IsNotEmpty() 8 | @MinLength(3) 9 | username: string 10 | 11 | @Field() 12 | @IsNotEmpty() 13 | @IsEmail() 14 | email: string 15 | 16 | @Field({ nullable: true }) 17 | name?: string 18 | 19 | @Field({ nullable: true }) 20 | avatarUrl?: string 21 | 22 | // @Field({ nullable: true }) 23 | // location?: string; 24 | 25 | @Field() 26 | @IsNotEmpty() 27 | @MinLength(8) 28 | password: string 29 | } 30 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/update-user-password.input.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, MinLength } from 'class-validator' 2 | import { InputType, Field } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class UpdateUserPasswordInput { 6 | @Field() 7 | // @IsNotEmpty() 8 | // @MinLength(8) 9 | password: string 10 | } 11 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/dto/update-user.input.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, MinLength } from 'class-validator' 2 | import { InputType, Field } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class UpdateUserInput { 6 | @Field() 7 | @IsNotEmpty() 8 | @MinLength(3) 9 | username: string 10 | 11 | @Field() 12 | @IsNotEmpty() 13 | @IsEmail() 14 | email: string 15 | 16 | @Field({ nullable: true }) 17 | name?: string 18 | 19 | @Field({ nullable: true }) 20 | avatarUrl?: string 21 | 22 | // @Field({ nullable: true }) 23 | // location?: string; 24 | } 25 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/guards/gql-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from '@nestjs/common' 2 | import { GqlExecutionContext } from '@nestjs/graphql' 3 | import { AuthGuard } from '@nestjs/passport' 4 | 5 | @Injectable() 6 | export class GqlAuthGuard extends AuthGuard('jwt') { 7 | getRequest(context: ExecutionContext) { 8 | const ctx = GqlExecutionContext.create(context) 9 | 10 | console.log('ctx') 11 | 12 | return ctx.getContext().req 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/models/role.ts: -------------------------------------------------------------------------------- 1 | import { registerEnumType } from '@nestjs/graphql' 2 | 3 | export enum Role { 4 | Admin = 'Admin', 5 | User = 'User', 6 | } 7 | 8 | registerEnumType(Role, { 9 | name: 'Role', 10 | description: 'User role', 11 | }) 12 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/models/user-token.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | import { User } from './user' 3 | 4 | @ObjectType() 5 | export class UserToken { 6 | @Field({ description: 'JWT Bearer token' }) 7 | token: string 8 | 9 | @Field(() => User) 10 | user: User 11 | } 12 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/models/user.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | import { Role } from './role' 3 | 4 | @ObjectType() 5 | export class User { 6 | @Field({ nullable: true }) 7 | id: string 8 | 9 | @Field({ nullable: true }) 10 | created: Date 11 | 12 | @Field({ nullable: true }) 13 | updated: Date 14 | 15 | @Field({ nullable: true }) 16 | email: string 17 | 18 | @Field({ nullable: true }) 19 | username?: string 20 | 21 | @Field({ nullable: true }) 22 | name?: string 23 | 24 | @Field({ nullable: true }) 25 | avatarUrl?: string 26 | 27 | // @Field({ nullable: true }) 28 | // location?: string; 29 | 30 | @Field({ nullable: true }) 31 | bio?: string 32 | 33 | @Field((type) => Role, { nullable: true }) 34 | role: Role 35 | 36 | password?: string 37 | } 38 | -------------------------------------------------------------------------------- /libs/api/feature-auth/src/lib/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common' 2 | import { PassportStrategy } from '@nestjs/passport' 3 | 4 | import { ExtractJwt, Strategy } from 'passport-jwt' 5 | import { ApiFeatureAuthService } from '../api-feature-auth.service' 6 | import { JwtDto } from '../dto/jwt.dto' 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor(private readonly auth: ApiFeatureAuthService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | secretOrKey: 'PANNG_STACK_SECRET', 14 | }) 15 | } 16 | 17 | async validate(payload: JwtDto) { 18 | const user = await this.auth.validateUser(payload.userId) 19 | if (!user) { 20 | throw new UnauthorizedException() 21 | } 22 | return user 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/api/feature-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/feature-auth/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-auth/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/feature-auth/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /libs/api/feature-comment/README.md: -------------------------------------------------------------------------------- 1 | # api-feature-comment 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-feature-comment` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/feature-comment/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-feature-comment', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/feature-comment', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-feature-comment.module' 2 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/lib/api-feature-comment.module.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessModule } from '@angular-graphql/api/data-access' 2 | import { Module } from '@nestjs/common' 3 | 4 | import { ApiFeatureCommentResolver } from './api-feature-comment.resolver' 5 | import { ApiFeatureCommentService } from './api-feature-comment.service' 6 | 7 | @Module({ 8 | imports: [ApiDataAccessModule], 9 | providers: [ApiFeatureCommentResolver, ApiFeatureCommentService], 10 | }) 11 | export class ApiFeatureCommentModule {} 12 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/lib/api-feature-comment.resolver.ts: -------------------------------------------------------------------------------- 1 | import { CtxUser, GqlAuthGuard, User } from '@angular-graphql/api/feature-auth' 2 | import { UseGuards } from '@nestjs/common' 3 | import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql' 4 | 5 | import { ApiFeatureCommentService } from './api-feature-comment.service' 6 | import { CreateCommentInput } from './dto/create-comment.input' 7 | import { Comment } from './models/comment' 8 | 9 | @Resolver(() => Comment) 10 | export class ApiFeatureCommentResolver { 11 | constructor(private readonly service: ApiFeatureCommentService) {} 12 | 13 | @Query(() => [Comment]) 14 | comments(@Args('postId') postId: string) { 15 | return this.service.comments(postId) 16 | } 17 | 18 | @Mutation(() => Comment) 19 | @UseGuards(GqlAuthGuard) 20 | async createComment(@CtxUser() user: User, @Args('data') data: CreateCommentInput) { 21 | return this.service.createComment(user.id, data) 22 | } 23 | 24 | @Mutation(() => Comment) 25 | @UseGuards(GqlAuthGuard) 26 | async deleteComment(@CtxUser() user: User, @Args('id') id: string): Promise { 27 | return this.service.deleteComment(user.id, id) 28 | } 29 | 30 | @ResolveField('author', () => User) 31 | author(@Parent() comment: Comment) { 32 | return this.service.commentAuthor(comment.id) 33 | } 34 | 35 | @ResolveField('post', () => [Comment]) 36 | post(@Parent() comment: Comment) { 37 | return this.service.commentPost(comment.id) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/lib/api-feature-comment.service.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessService } from '@angular-graphql/api/data-access' 2 | import { Injectable } from '@nestjs/common' 3 | import { CreateCommentInput } from './dto/create-comment.input' 4 | 5 | @Injectable() 6 | export class ApiFeatureCommentService { 7 | constructor(private readonly data: ApiDataAccessService) {} 8 | 9 | comments(postId) { 10 | return this.data.comment.findMany({ 11 | where: { post: { id: postId } }, 12 | orderBy: { createdAt: 'asc' }, 13 | take: 100, 14 | }) 15 | } 16 | 17 | comment({ id }) { 18 | return this.data.comment.findOne({ where: { id } }) 19 | } 20 | 21 | async createComment(userId: string, { postId, text }: CreateCommentInput) { 22 | const comment = await this.data.comment.create({ 23 | data: { 24 | author: { connect: { id: userId } }, 25 | post: { connect: { id: postId } }, 26 | text, 27 | }, 28 | }) 29 | const post = await this.data.post.findOne({ where: { id: postId } }) 30 | await this.data.post.update({ where: { id: postId }, data: { commentCount: post.commentCount + 1 } }) 31 | return comment 32 | } 33 | 34 | async deleteComment(userId: string, id: string) { 35 | const author = await this.data.comment.findOne({ where: { id } }).author() 36 | if (author.id !== userId) { 37 | throw new Error('You can only delete your own comments.') 38 | } 39 | return this.data.comment.delete({ where: { id } }) 40 | } 41 | 42 | commentAuthor(id: string) { 43 | return this.data.comment.findOne({ where: { id } }).author() 44 | } 45 | 46 | commentPost(id: string) { 47 | return this.data.comment.findOne({ where: { id } }).post() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/lib/dto/create-comment.input.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, MinLength } from 'class-validator' 2 | import { InputType, Field } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class CreateCommentInput { 6 | @Field() 7 | @IsNotEmpty() 8 | postId: string 9 | 10 | @Field() 11 | @IsNotEmpty() 12 | @MinLength(3) 13 | text: string 14 | } 15 | -------------------------------------------------------------------------------- /libs/api/feature-comment/src/lib/models/comment.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | import { Profile } from '@angular-graphql/api/feature-profile' 3 | 4 | @ObjectType() 5 | export class Comment { 6 | @Field({ nullable: true }) 7 | id: string 8 | 9 | @Field({ nullable: true }) 10 | created: Date 11 | 12 | @Field({ nullable: true }) 13 | text: string 14 | 15 | @Field(() => Profile, { nullable: true }) 16 | author: Profile 17 | } 18 | -------------------------------------------------------------------------------- /libs/api/feature-comment/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/feature-comment/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-comment/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/feature-comment/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../../tslint.json", "linterOptions": { "exclude": ["!**/*"] }, "rules": {} } 2 | -------------------------------------------------------------------------------- /libs/api/feature-core/README.md: -------------------------------------------------------------------------------- 1 | # api-feature-core 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-feature-core` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/feature-core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-feature-core', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/feature-core', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-feature-core.module' 2 | export * from './lib/api-feature-core.service' 3 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/api-feature-core.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common' 2 | import { ApiFeatureCoreService } from './api-feature-core.service' 3 | 4 | @Controller() 5 | export class ApiFeatureCoreController { 6 | constructor(private readonly service: ApiFeatureCoreService) {} 7 | 8 | @Get('uptime') 9 | uptime() { 10 | return this.service.uptime() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/api-feature-core.module.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLIntercomModule } from '@kikstart-playground/graphql-intercom' 2 | import { Module } from '@nestjs/common' 3 | import { ConfigModule } from '@nestjs/config' 4 | import { GraphQLModule } from '@nestjs/graphql' 5 | import { PubSub } from 'graphql-subscriptions' 6 | 7 | import { configuration } from './config/configuration' 8 | import { validationSchema } from './config/validation' 9 | import { ApiFeatureCoreController } from './api-feature-core.controller' 10 | import { ApiFeatureCoreResolver } from './api-feature-core.resolver' 11 | import { ApiFeatureCoreService } from './api-feature-core.service' 12 | 13 | @Module({ 14 | imports: [ 15 | ConfigModule.forRoot({ 16 | isGlobal: true, 17 | load: [configuration], 18 | validationSchema, 19 | }), 20 | GraphQLModule.forRoot({ 21 | autoSchemaFile: true, 22 | installSubscriptionHandlers: true, 23 | }), 24 | GraphQLIntercomModule.forRoot({ pubSub: new PubSub() }), 25 | ], 26 | controllers: [ApiFeatureCoreController], 27 | providers: [ApiFeatureCoreResolver, ApiFeatureCoreService], 28 | exports: [ApiFeatureCoreService], 29 | }) 30 | export class ApiFeatureCoreModule {} 31 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/api-feature-core.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Float, Query, Resolver } from '@nestjs/graphql' 2 | import { ApiFeatureCoreService } from './api-feature-core.service' 3 | 4 | @Resolver() 5 | export class ApiFeatureCoreResolver { 6 | constructor(private readonly service: ApiFeatureCoreService) {} 7 | 8 | @Query(() => Float) 9 | uptime() { 10 | return this.service.uptime() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/api-feature-core.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | import { ConfigService } from '@nestjs/config' 3 | import { join } from 'path' 4 | 5 | @Injectable() 6 | export class ApiFeatureCoreService { 7 | constructor(public readonly config: ConfigService) {} 8 | 9 | uptime(): number { 10 | return process.uptime() 11 | } 12 | 13 | get apiUrl(): string { 14 | return this.config.get('apiUrl') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/config/configuration.ts: -------------------------------------------------------------------------------- 1 | export const configuration = () => ({ 2 | prefix: 'api', 3 | environment: process.env.NODE_ENV, 4 | host: process.env.HOST, 5 | port: parseInt(process.env.PORT, 10), 6 | apiUrl: process.env.API_URL, 7 | }) 8 | -------------------------------------------------------------------------------- /libs/api/feature-core/src/lib/config/validation.ts: -------------------------------------------------------------------------------- 1 | import * as Joi from 'joi' 2 | 3 | export const validationSchema = Joi.object({ 4 | NODE_ENV: Joi.string().valid('development', 'production', 'test'), 5 | HOST: Joi.string().alphanum().default('localhost'), 6 | PORT: Joi.number().default(3000), 7 | API_URL: Joi.string().default(`http://${process.env.HOST || 'localhost'}:${process.env.PORT}/api`), 8 | }) 9 | -------------------------------------------------------------------------------- /libs/api/feature-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/feature-core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/feature-core/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /libs/api/feature-post/README.md: -------------------------------------------------------------------------------- 1 | # api-feature-post 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-feature-post` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/feature-post/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-feature-post', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/feature-post', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-feature-post.module' 2 | export * from './lib/models/post' 3 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/api-feature-post-profile.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Profile } from '@angular-graphql/api/feature-profile' 2 | import { Parent, ResolveField, Resolver } from '@nestjs/graphql' 3 | import { Post } from './models/post' 4 | 5 | @Resolver(() => Profile) 6 | export class ApiFeaturePostProfileResolver { 7 | @ResolveField(() => Post, { nullable: true }) 8 | posts(@Parent() profile) { 9 | return profile?.posts 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/api-feature-post.module.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessModule } from '@angular-graphql/api/data-access' 2 | import { Module } from '@nestjs/common' 3 | import { ApiFeaturePostProfileResolver } from './api-feature-post-profile.resolver' 4 | import { ApiFeaturePostResolver } from './api-feature-post.resolver' 5 | import { ApiFeaturePostService } from './api-feature-post.service' 6 | 7 | @Module({ 8 | imports: [ApiDataAccessModule], 9 | providers: [ApiFeaturePostResolver, ApiFeaturePostProfileResolver, ApiFeaturePostService], 10 | }) 11 | export class ApiFeaturePostModule {} 12 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/api-feature-post.resolver.ts: -------------------------------------------------------------------------------- 1 | import { CtxUser, GqlAuthGuard, User } from '@angular-graphql/api/feature-auth' 2 | import { UseGuards } from '@nestjs/common' 3 | import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql' 4 | 5 | import { ApiFeaturePostService } from './api-feature-post.service' 6 | import { CreatePostInput } from './dto/create-post.input' 7 | import { Comment } from '../../../feature-comment/src/lib/models/comment' 8 | import { Post } from './models/post' 9 | 10 | @Resolver(() => Post) 11 | export class ApiFeaturePostResolver { 12 | constructor(private readonly service: ApiFeaturePostService) {} 13 | 14 | @Query(() => [Post], { nullable: true }) 15 | posts() { 16 | return this.service.posts() 17 | } 18 | 19 | @Query(() => [Post], { nullable: true }) 20 | userPosts(@Args('userId') userId: string) { 21 | return this.service.userPosts(userId) 22 | } 23 | 24 | @Query(() => Post, { nullable: true }) 25 | post(@CtxUser() user: User, @Args('id') id: string) { 26 | return this.service.post({ id }) 27 | } 28 | 29 | @Mutation(() => Post, { nullable: true }) 30 | @UseGuards(GqlAuthGuard) 31 | async createPost(@CtxUser() user: User, @Args('data') data: CreatePostInput) { 32 | return this.service.createPost(user.id, data) 33 | } 34 | 35 | @Mutation(() => Post, { nullable: true }) 36 | @UseGuards(GqlAuthGuard) 37 | async deletePost(@CtxUser() user: User, @Args('id') id: string) { 38 | return this.service.deletePost(user.id, id) 39 | } 40 | 41 | @ResolveField('author', () => User, { nullable: true }) 42 | author(@Parent() post: Post) { 43 | return this.service.postAuthor(post.id) 44 | } 45 | 46 | @ResolveField('comments', () => [Comment], { nullable: true }) 47 | comments(@Parent() post: Post) { 48 | return this.service.postComments(post.id) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/api-feature-post.service.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessService } from '@angular-graphql/api/data-access' 2 | import { Injectable } from '@nestjs/common' 3 | 4 | @Injectable() 5 | export class ApiFeaturePostService { 6 | constructor(private readonly data: ApiDataAccessService) {} 7 | 8 | posts() { 9 | return this.data.post.findMany({ orderBy: { createdAt: 'desc' }, take: 25 }) 10 | } 11 | 12 | userPosts(userId: string) { 13 | return this.data.post.findMany({ where: { author: { id: userId } }, orderBy: { createdAt: 'desc' }, take: 25 }) 14 | } 15 | 16 | post({ id }) { 17 | return this.data.post.findOne({ where: { id } }) 18 | } 19 | 20 | createPost(userId, data) { 21 | return this.data.post.create({ 22 | data: { 23 | author: { connect: { id: userId } }, 24 | ...data, 25 | }, 26 | }) 27 | } 28 | 29 | async deletePost(userId: string, id: string) { 30 | const author = await this.data.post.findOne({ where: { id } }).author() 31 | if (author.id !== userId) { 32 | throw new Error('You can only delete your own posts.') 33 | } 34 | return this.data.post.delete({ where: { id } }) 35 | } 36 | 37 | postAuthor(id: string) { 38 | return this.data.post.findOne({ where: { id } }).author() 39 | } 40 | 41 | postComments(id: string) { 42 | return this.data.post.findOne({ where: { id } }).comments() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/dto/create-post.input.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, MinLength } from 'class-validator' 2 | import { Field, InputType } from '@nestjs/graphql' 3 | 4 | @InputType() 5 | export class CreatePostInput { 6 | @Field() 7 | @IsNotEmpty() 8 | @MinLength(3) 9 | text: string 10 | } 11 | -------------------------------------------------------------------------------- /libs/api/feature-post/src/lib/models/post.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | import { Profile } from '@angular-graphql/api/feature-profile' 3 | 4 | @ObjectType() 5 | export class Post { 6 | @Field({ nullable: true }) 7 | id: string 8 | 9 | @Field({ nullable: true }) 10 | created: Date 11 | 12 | @Field({ nullable: true }) 13 | text: string 14 | 15 | @Field((type) => Profile) 16 | author: Profile 17 | 18 | @Field({ nullable: true }) 19 | commentCount: number 20 | 21 | @Field((type) => [Profile], { nullable: true }) 22 | commentedBy: Profile[] 23 | } 24 | -------------------------------------------------------------------------------- /libs/api/feature-post/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/feature-post/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-post/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/feature-post/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../../tslint.json", "linterOptions": { "exclude": ["!**/*"] }, "rules": {} } 2 | -------------------------------------------------------------------------------- /libs/api/feature-profile/README.md: -------------------------------------------------------------------------------- 1 | # api-feature-profile 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-feature-profile` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api/feature-profile/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-feature-profile', 3 | preset: '../../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../../coverage/libs/api/feature-profile', 15 | } 16 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-feature-profile.module' 2 | export * from './lib/models/profile' 3 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/lib/api-feature-profile.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common' 2 | 3 | import { ApiFeatureProfileService } from './api-feature-profile.service' 4 | 5 | @Controller() 6 | export class ApiFeatureProfileController { 7 | constructor(private readonly auth: ApiFeatureProfileService) {} 8 | } 9 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/lib/api-feature-profile.module.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessModule } from '@angular-graphql/api/data-access' 2 | import { Module } from '@nestjs/common' 3 | import { ApiFeatureProfileResolver } from './api-feature-profile.resolver' 4 | import { ApiFeatureProfileService } from './api-feature-profile.service' 5 | 6 | @Module({ 7 | imports: [ApiDataAccessModule], 8 | providers: [ApiFeatureProfileResolver, ApiFeatureProfileService], 9 | }) 10 | export class ApiFeatureProfileModule {} 11 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/lib/api-feature-profile.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Args, Query, Resolver } from '@nestjs/graphql' 2 | 3 | import { ApiFeatureProfileService } from './api-feature-profile.service' 4 | import { Profile } from './models/profile' 5 | 6 | @Resolver(() => Profile) 7 | export class ApiFeatureProfileResolver { 8 | constructor(private readonly service: ApiFeatureProfileService) {} 9 | 10 | @Query(() => [Profile], { nullable: true }) 11 | profiles() { 12 | return this.service.profiles() 13 | } 14 | 15 | @Query(() => Profile, { nullable: true }) 16 | profile(@Args('username') username: string) { 17 | return this.service.profile(username) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/lib/api-feature-profile.service.ts: -------------------------------------------------------------------------------- 1 | import { ApiDataAccessService } from '@angular-graphql/api/data-access' 2 | import { Injectable } from '@nestjs/common' 3 | 4 | @Injectable() 5 | export class ApiFeatureProfileService { 6 | constructor(private readonly data: ApiDataAccessService) {} 7 | 8 | profiles() { 9 | return this.data.user.findMany({ 10 | orderBy: { createdAt: 'desc' }, 11 | }) 12 | } 13 | 14 | profile(username: string) { 15 | return this.data.findUserByUsername(username) 16 | } 17 | 18 | profilePosts(userId) { 19 | return this.data.findUserById(userId).posts() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/api/feature-profile/src/lib/models/profile.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql' 2 | 3 | @ObjectType() 4 | export class Profile { 5 | @Field({ nullable: true }) 6 | id: string 7 | 8 | @Field({ nullable: true }) 9 | created: Date 10 | 11 | @Field({ nullable: true }) 12 | username?: string 13 | 14 | @Field({ nullable: true }) 15 | name?: string 16 | 17 | @Field({ nullable: true }) 18 | avatarUrl?: string 19 | 20 | @Field({ nullable: true }) 21 | bio?: string 22 | 23 | @Field({ nullable: true }) 24 | location?: string 25 | } 26 | -------------------------------------------------------------------------------- /libs/api/feature-profile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/api/feature-profile/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/feature-profile/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api/feature-profile/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../../tslint.json", "linterOptions": { "exclude": ["!**/*"] }, "rules": {} } 2 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/README.md: -------------------------------------------------------------------------------- 1 | # web-data-access-auth 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-data-access-auth` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-data-access-auth', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/data-access-auth', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-data-access-auth.module' 2 | export * from './lib/web-data-access-auth.service' 3 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/src/lib/web-data-access-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessModule } from '@angular-graphql/web/data-access' 2 | import { NgModule } from '@angular/core' 3 | import { WebDataAccessAuthService } from './web-data-access-auth.service' 4 | 5 | @NgModule({ 6 | imports: [WebDataAccessModule], 7 | providers: [WebDataAccessAuthService], 8 | }) 9 | export class WebDataAccessAuthModule {} 10 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/src/lib/web-data-access-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { User, WebDataAccessService } from '@angular-graphql/web/data-access' 2 | import { Injectable } from '@angular/core' 3 | import { BehaviorSubject, Observable, of } from 'rxjs' 4 | import { catchError, concatMap, filter, map, tap } from 'rxjs/operators' 5 | 6 | export const WEB_AUTH_TOKEN = 'WEB_AUTH_TOKEN' 7 | export const WEB_AUTH_USER = 'WEB_AUTH_USER' 8 | 9 | @Injectable() 10 | export class WebDataAccessAuthService { 11 | private _token = new BehaviorSubject(null) 12 | private _user = new BehaviorSubject(null) 13 | 14 | public user$: Observable = this._user.asObservable() 15 | 16 | public get token(): string { 17 | return this._token.getValue() 18 | } 19 | 20 | public set token(token: string) { 21 | localStorage.setItem(WEB_AUTH_TOKEN, token) 22 | // console.log('Storing token') 23 | this._token.next(token) 24 | } 25 | 26 | public get user(): User { 27 | return this._user.getValue() 28 | } 29 | 30 | public set user(user: User) { 31 | localStorage.setItem(WEB_AUTH_USER, JSON.stringify(user)) 32 | // console.log('Storing user') 33 | this._user.next(user) 34 | } 35 | 36 | constructor(private readonly data: WebDataAccessService) { 37 | this.hydrate() 38 | } 39 | 40 | hydrate() { 41 | const token = localStorage.getItem(WEB_AUTH_TOKEN) 42 | if (token) { 43 | // console.log(`Hydrated token from localStorage token WEB_AUTH_TOKEN`) 44 | this._token.next(token) 45 | } 46 | const user = localStorage.getItem(WEB_AUTH_USER) 47 | if (user) { 48 | // console.log(`Hydrated user from localStorage token WEB_AUTH_USER`) 49 | this._user.next(JSON.parse(user)) 50 | } 51 | } 52 | 53 | login({ username, password }) { 54 | return this.data.sdk.login({ data: { username, password } }).pipe( 55 | map((result) => result.data.login), 56 | filter((data) => !!data.token), 57 | tap((data) => (this.token = data.token)), 58 | tap((data) => (this.user = data.user)), 59 | catchError((err) => { 60 | console.log('err', err) 61 | return err 62 | }), 63 | ) 64 | } 65 | 66 | register({ email, password, name, username }) { 67 | return this.data.sdk.register({ data: { email, password, name, username } }).pipe( 68 | map((result) => result?.data?.register?.token), 69 | filter((token) => !!token), 70 | tap((token: string) => (this.token = token)), 71 | catchError((err) => { 72 | console.log('err', err) 73 | return err 74 | }), 75 | concatMap(() => this.me()), 76 | ) 77 | } 78 | 79 | logout() { 80 | localStorage.removeItem(WEB_AUTH_TOKEN) 81 | localStorage.removeItem(WEB_AUTH_USER) 82 | this._token.next(null) 83 | this._user.next(null) 84 | return of(true) 85 | } 86 | 87 | me() { 88 | return this.data.sdk.me().pipe( 89 | map((result) => result.data.me), 90 | filter((me) => !!me), 91 | tap((me: User) => (this.user = me)), 92 | ) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/data-access-auth/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "auth", "camelCase"], 5 | "component-selector": [true, "element", "auth", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/data-access/README.md: -------------------------------------------------------------------------------- 1 | # web-data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-data-access` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-data-access', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/data-access', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/data-access/src/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'http://localhost:3000/graphql' 3 | documents: 4 | - 'libs/web/data-access/src/graphql/**/*.graphql' 5 | generates: 6 | libs/web/data-access/src/generated/graphql.ts: 7 | config: 8 | sdkClass: true 9 | plugins: 10 | - 'typescript' 11 | - 'typescript-operations' 12 | - 'typescript-apollo-angular' 13 | ./graphql.schema.json: 14 | plugins: 15 | - 'introspection' 16 | 17 | hooks: 18 | afterAllFileWrite: 19 | - prettier --write 20 | -------------------------------------------------------------------------------- /libs/web/data-access/src/graphql/auth-queries.graphql: -------------------------------------------------------------------------------- 1 | fragment userDetails on User { 2 | id 3 | name 4 | username 5 | avatarUrl 6 | email 7 | } 8 | 9 | query me { 10 | me { 11 | ...userDetails 12 | } 13 | } 14 | 15 | mutation Register($data: RegisterInput!) { 16 | register(data: $data) { 17 | token 18 | user { 19 | ...userDetails 20 | } 21 | } 22 | } 23 | 24 | mutation Login($data: LoginInput!) { 25 | login(data: $data) { 26 | token 27 | user { 28 | ...userDetails 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libs/web/data-access/src/graphql/core.graphql: -------------------------------------------------------------------------------- 1 | query Uptime { 2 | uptime 3 | } 4 | 5 | fragment IntercomDetails on IntercomMessage { 6 | type 7 | scope 8 | payload 9 | } 10 | 11 | mutation IntercomPub($type: String!, $scope: String, $payload: JSON) { 12 | intercomPub(type: $type, scope: $scope, payload: $payload) { 13 | ...IntercomDetails 14 | } 15 | } 16 | 17 | subscription IntercomSub { 18 | intercomSub { 19 | ...IntercomDetails 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/data-access/src/graphql/post-queries.graphql: -------------------------------------------------------------------------------- 1 | fragment authorDetails on Profile { 2 | id 3 | username 4 | avatarUrl 5 | name 6 | } 7 | 8 | fragment postDetails on Post { 9 | id 10 | text 11 | created 12 | commentCount 13 | author { 14 | ...authorDetails 15 | } 16 | } 17 | 18 | fragment commentDetails on Comment { 19 | id 20 | text 21 | created 22 | author { 23 | ...authorDetails 24 | } 25 | } 26 | 27 | query posts { 28 | posts { 29 | ...postDetails 30 | } 31 | } 32 | 33 | query userPosts($userId: String!) { 34 | userPosts(userId: $userId) { 35 | ...postDetails 36 | } 37 | } 38 | 39 | query post($id: String!) { 40 | post(id: $id) { 41 | ...postDetails 42 | comments { 43 | ...commentDetails 44 | } 45 | } 46 | } 47 | 48 | query comments($postId: String!) { 49 | comments(postId: $postId) { 50 | ...commentDetails 51 | } 52 | } 53 | 54 | mutation createPost($data: CreatePostInput!) { 55 | createPost(data: $data) { 56 | ...postDetails 57 | } 58 | } 59 | 60 | mutation createComment($data: CreateCommentInput!) { 61 | createComment(data: $data) { 62 | ...commentDetails 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /libs/web/data-access/src/graphql/profile-queries.graphql: -------------------------------------------------------------------------------- 1 | fragment profileDetails on Profile { 2 | id 3 | username 4 | avatarUrl 5 | name 6 | bio 7 | location 8 | } 9 | 10 | query profiles { 11 | profiles { 12 | ...profileDetails 13 | } 14 | } 15 | 16 | query profile($username: String!) { 17 | profile(username: $username) { 18 | ...profileDetails 19 | posts { 20 | ...postDetails 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/web/data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated/graphql' 2 | export * from './lib/web-data-access.module' 3 | export * from './lib/web-data-access.service' 4 | -------------------------------------------------------------------------------- /libs/web/data-access/src/lib/web-data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | 3 | import { WebDataAccessService } from './web-data-access.service' 4 | 5 | @NgModule({ 6 | providers: [WebDataAccessService], 7 | }) 8 | export class WebDataAccessModule {} 9 | -------------------------------------------------------------------------------- /libs/web/data-access/src/lib/web-data-access.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Observable } from 'rxjs' 3 | import { map } from 'rxjs/operators' 4 | import { ApolloAngularSDK, IntercomMessage } from '../generated/graphql' 5 | 6 | @Injectable() 7 | export class WebDataAccessService { 8 | constructor(public readonly sdk: ApolloAngularSDK) {} 9 | 10 | public uptime(): Observable { 11 | return this.sdk.uptime().pipe(map((res) => res?.data?.uptime)) 12 | } 13 | 14 | public intercomPub(type: string, scope: string, payload: any): Observable { 15 | return this.sdk.intercomPub({ type, scope, payload }).pipe(map((res) => res?.data?.intercomPub)) 16 | } 17 | 18 | public intercomSub(): Observable { 19 | return this.sdk.intercomSub().pipe(map((res) => res?.data?.intercomSub)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/data-access/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/data-access/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "dataAccess", "camelCase"], 5 | "component-selector": [true, "element", "data-access", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-auth/README.md: -------------------------------------------------------------------------------- 1 | # web-feature-auth 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-feature-auth` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/feature-auth/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-feature-auth', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/feature-auth', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-feature-auth.module' 2 | export * from './lib/web-feature-auth.service' 3 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/components/auth-page/auth-page.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |

{{ title }}

9 |
10 | 11 |
12 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/components/auth-page/auth-page.component.scss: -------------------------------------------------------------------------------- 1 | .avatar img { 2 | height: 128px; 3 | width: 128px; 4 | } 5 | .nav-link { 6 | color: #ebebeb; 7 | background-color: #2b3e50; 8 | border-color: #4e5d6c; 9 | } 10 | .nav-link.active { 11 | color: #ebebeb; 12 | background-color: #4e5d6c; 13 | border-color: #4e5d6c; 14 | } 15 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/components/auth-page/auth-page.component.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 2 | import { Component, EventEmitter, Input, Output } from '@angular/core' 3 | import { FormGroup } from '@angular/forms' 4 | import { FormHelper } from '@kikstart-ui/ui-form' 5 | 6 | @Component({ 7 | selector: 'auth-page', 8 | templateUrl: './auth-page.component.html', 9 | styleUrls: ['./auth-page.component.scss'], 10 | }) 11 | export class AuthPageComponent { 12 | links = [ 13 | { route: '/login', title: 'Log in' }, 14 | { route: '/register', title: 'Register' }, 15 | ] 16 | name: string 17 | 18 | @Input() title = 'GraphQL with Angular' 19 | @Input() message: string 20 | @Input() label: string 21 | @Output() action = new EventEmitter() 22 | 23 | @Input() form = new FormGroup({}) 24 | @Input() model = {} 25 | @Input() fields: FormHelper[] = [] 26 | @Input() navigation = true 27 | 28 | constructor(public service: WebDataAccessAuthService) {} 29 | } 30 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/containers/login.component.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 2 | import { Component, OnInit } from '@angular/core' 3 | import { FormGroup } from '@angular/forms' 4 | import { ActivatedRoute, Router } from '@angular/router' 5 | import { FormHelper } from '@kikstart-ui/ui-form' 6 | import { filter, map } from 'rxjs/operators' 7 | 8 | @Component({ 9 | template: ` `, 10 | }) 11 | export class LoginComponent implements OnInit { 12 | public brand = { 13 | logo: '/assets/logo.png', 14 | name: 'GraphQL', 15 | product: 'with Angular', 16 | } 17 | form = new FormGroup({}) 18 | fields: FormHelper[] = [ 19 | FormHelper.input('username', { 20 | label: 'Username', 21 | required: true, 22 | }), 23 | FormHelper.password('password', { 24 | label: 'Password', 25 | required: true, 26 | }), 27 | ] 28 | 29 | constructor(private route: ActivatedRoute, private router: Router, public auth: WebDataAccessAuthService) {} 30 | 31 | ngOnInit() { 32 | this.route.queryParams 33 | .pipe( 34 | map((params) => params.token), 35 | filter((token) => !!token), 36 | ) 37 | .subscribe(() => this.router.navigate(['/'])) 38 | } 39 | 40 | async handleAction({ payload }) { 41 | this.form.disable() 42 | return this.auth.login(payload).subscribe( 43 | (res) => this.router.navigate(['/']), 44 | () => this.form.enable(), 45 | () => this.form.enable(), 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/containers/logout.component.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 2 | import { Component, OnInit } from '@angular/core' 3 | import { Router } from '@angular/router' 4 | 5 | @Component({ 6 | template: ` `, 7 | }) 8 | export class LogoutComponent implements OnInit { 9 | constructor(private service: WebDataAccessAuthService, private router: Router) {} 10 | 11 | ngOnInit() { 12 | this.service.logout().subscribe(() => this.router.navigate(['/login'])) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/containers/profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'graphql-angular-profile', 5 | template: ` 6 |

7 | profile works! 8 |

9 | `, 10 | styles: [], 11 | }) 12 | export class ProfileComponent implements OnInit { 13 | constructor() {} 14 | 15 | ngOnInit() {} 16 | } 17 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/containers/register.component.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 2 | import { Component, OnInit } from '@angular/core' 3 | import { FormGroup } from '@angular/forms' 4 | import { Router } from '@angular/router' 5 | import { FormHelper } from '@kikstart-ui/ui-form' 6 | 7 | @Component({ 8 | template: ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | `, 22 | }) 23 | export class RegisterComponent implements OnInit { 24 | brand = { 25 | logo: '/assets/logo.png', 26 | name: 'GraphQL', 27 | product: 'with Angular', 28 | } 29 | form = new FormGroup({}) 30 | fields: FormHelper[] = [ 31 | FormHelper.input('username', { 32 | label: 'Username', 33 | required: true, 34 | }), 35 | FormHelper.email('email', { 36 | label: 'Email', 37 | required: true, 38 | }), 39 | FormHelper.password('password', { 40 | label: 'Password', 41 | required: true, 42 | }), 43 | FormHelper.input('name', { 44 | label: 'Name', 45 | required: true, 46 | }), 47 | ] 48 | 49 | constructor(private router: Router, public auth: WebDataAccessAuthService) {} 50 | 51 | ngOnInit() {} 52 | 53 | async handleAction({ type, payload }) { 54 | this.form.disable() 55 | return this.auth.register(payload).subscribe( 56 | (res) => { 57 | console.log('YAY USER REGISTERED', res) 58 | return this.router.navigate(['/']) 59 | }, 60 | (err) => { 61 | console.log('error submitting form', err) 62 | this.form.enable() 63 | }, 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 2 | import { Injectable } from '@angular/core' 3 | import { CanActivate, Router } from '@angular/router' 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class AuthGuard implements CanActivate { 7 | constructor(private service: WebDataAccessAuthService, private router: Router) {} 8 | 9 | canActivate() { 10 | if (this.service.user) { 11 | return true 12 | } 13 | console.log('You are not logged in!') 14 | this.router.navigate(['/login']) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/web-feature-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthModule } from '@angular-graphql/web/data-access-auth' 2 | import { CommonModule } from '@angular/common' 3 | import { NgModule } from '@angular/core' 4 | import { RouterModule, Routes } from '@angular/router' 5 | import { UiFormModule } from '@kikstart-ui/ui-form' 6 | 7 | import { AuthPageComponent } from './components/auth-page/auth-page.component' 8 | import { LoginComponent } from './containers/login.component' 9 | import { LogoutComponent } from './containers/logout.component' 10 | import { ProfileComponent } from './containers/profile.component' 11 | import { RegisterComponent } from './containers/register.component' 12 | 13 | import { AuthGuard } from './guards/auth.guard' 14 | 15 | const routes: Routes = [ 16 | { path: 'login', component: LoginComponent }, 17 | { path: 'logout', component: LogoutComponent }, 18 | { path: 'register', component: RegisterComponent }, 19 | { 20 | path: '', 21 | canActivate: [AuthGuard], 22 | children: [{ path: 'profile', component: ProfileComponent }], 23 | }, 24 | ] 25 | 26 | @NgModule({ 27 | imports: [CommonModule, RouterModule.forChild(routes), WebDataAccessAuthModule, UiFormModule], 28 | declarations: [AuthPageComponent, LoginComponent, RegisterComponent, ProfileComponent, LogoutComponent], 29 | }) 30 | export class WebFeatureAuthModule {} 31 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/lib/web-feature-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { ApolloAngularSDK, User } from '@angular-graphql/web/data-access' 2 | import { Injectable } from '@angular/core' 3 | import { BehaviorSubject, Observable, of } from 'rxjs' 4 | import { catchError, concatMap, filter, map, tap } from 'rxjs/operators' 5 | 6 | export const WEB_AUTH_TOKEN = 'WEB_AUTH_TOKEN' 7 | export const WEB_AUTH_USER = 'WEB_AUTH_USER' 8 | 9 | @Injectable({ 10 | providedIn: 'root', 11 | }) 12 | export class WebFeatureAuthService { 13 | private _token = new BehaviorSubject(null) 14 | private _user = new BehaviorSubject(null) 15 | 16 | public user$: Observable = this._user.asObservable() 17 | 18 | public get token(): string { 19 | return this._token.getValue() 20 | } 21 | 22 | public set token(token: string) { 23 | localStorage.setItem(WEB_AUTH_TOKEN, token) 24 | console.log('Storing token') 25 | this._token.next(token) 26 | } 27 | 28 | public get user(): User { 29 | return this._user.getValue() 30 | } 31 | 32 | public set user(user: User) { 33 | localStorage.setItem(WEB_AUTH_USER, JSON.stringify(user)) 34 | console.log('Storing user') 35 | this._user.next(user) 36 | } 37 | 38 | constructor(private sdk: ApolloAngularSDK) { 39 | this.hydrate() 40 | } 41 | 42 | hydrate() { 43 | const token = localStorage.getItem(WEB_AUTH_TOKEN) 44 | if (token) { 45 | console.log(`Hydrated token from localStorage token WEB_AUTH_TOKEN`) 46 | this._token.next(token) 47 | } 48 | const user = localStorage.getItem(WEB_AUTH_USER) 49 | if (user) { 50 | console.log(`Hydrated user from localStorage token WEB_AUTH_USER`) 51 | this._user.next(JSON.parse(user)) 52 | } 53 | } 54 | 55 | handleAction({ type, payload }: { payload: any; type: string }) { 56 | console.log({ type, payload }) 57 | switch (type) { 58 | case 'LOGIN': 59 | return of(null) 60 | default: 61 | return of(null) 62 | } 63 | } 64 | 65 | login({ username, password }) { 66 | return this.sdk.login({ data: { username, password } }).pipe( 67 | map((result) => result.data.login), 68 | filter((data) => !!data.token), 69 | tap((data) => (this.token = data.token)), 70 | tap((data) => (this.user = data.user)), 71 | catchError((err) => { 72 | console.log('err', err) 73 | return err 74 | }), 75 | ) 76 | } 77 | 78 | register({ email, password, name, username }) { 79 | return this.sdk.register({ data: { email, password, name, username } }).pipe( 80 | map((result) => result?.data?.register?.token), 81 | filter((token) => !!token), 82 | tap((token: string) => (this.token = token)), 83 | catchError((err) => { 84 | console.log('err', err) 85 | return err 86 | }), 87 | concatMap(() => this.me()), 88 | ) 89 | } 90 | 91 | logout() { 92 | localStorage.removeItem(WEB_AUTH_TOKEN) 93 | localStorage.removeItem(WEB_AUTH_USER) 94 | this._token.next(null) 95 | this._user.next(null) 96 | return of(true) 97 | } 98 | 99 | me() { 100 | return this.sdk.me().pipe( 101 | map((result) => result.data.me), 102 | filter((me) => !!me), 103 | tap((me: User) => (this.user = me)), 104 | ) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /libs/web/feature-auth/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/feature-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/feature-auth/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/feature-auth/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-auth/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "auth", "camelCase"], 5 | "component-selector": [true, "element", "auth", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-core/README.md: -------------------------------------------------------------------------------- 1 | # web-feature-core 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-feature-core` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/feature-core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-feature-core', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/feature-core', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | graphqlUri: 'http://localhost:3000/graphql', 4 | } 5 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | graphqlUri: 'http://localhost:3000/graphql', 8 | } 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-feature-core.module' 2 | export * from './environments/environment' 3 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/lib/graphql.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core' 3 | import { APOLLO_OPTIONS } from 'apollo-angular' 4 | import { HttpLink } from 'apollo-angular/http' 5 | import { environment } from '../environments/environment' 6 | 7 | export function createApollo(httpLink: HttpLink): ApolloClientOptions { 8 | // This should come from AuthService 9 | const token = localStorage.getItem('WEB_AUTH_TOKEN') 10 | const headers: any = { 11 | Authorization: token ? `Bearer ${token}` : null, 12 | } 13 | return { 14 | link: httpLink.create({ 15 | uri: environment.graphqlUri, 16 | headers, 17 | }), 18 | cache: new InMemoryCache(), 19 | } 20 | } 21 | 22 | @NgModule({ 23 | providers: [ 24 | { 25 | provide: APOLLO_OPTIONS, 26 | useFactory: createApollo, 27 | deps: [HttpLink], 28 | }, 29 | ], 30 | }) 31 | export class GraphQLModule {} 32 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/lib/web-feature-core.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common' 2 | import { NgModule } from '@angular/core' 3 | import { GraphQLModule } from './graphql.module' 4 | 5 | @NgModule({ 6 | imports: [CommonModule, GraphQLModule], 7 | }) 8 | export class WebFeatureCoreModule {} 9 | -------------------------------------------------------------------------------- /libs/web/feature-core/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/feature-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/feature-core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/feature-core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-core/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "core", "camelCase"], 5 | "component-selector": [true, "element", "core", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-post/README.md: -------------------------------------------------------------------------------- 1 | # web-feature-post 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-feature-post` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/feature-post/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-feature-post', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/feature-post', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-feature-post.module' 2 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/actions/post.actions.ts: -------------------------------------------------------------------------------- 1 | export enum PostActions { 2 | SHOW_COMMENTS = 'SHOW_COMMENTS', 3 | SUBMIT_POST = 'SUBMIT_POST', 4 | } 5 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/components/post-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Post, User } from '@angular-graphql/web/data-access' 2 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' 3 | 4 | import { BehaviorSubject, Observable } from 'rxjs' 5 | import { switchMap, tap } from 'rxjs/operators' 6 | 7 | @Component({ 8 | selector: 'app-post-comments', 9 | template: ` 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 |
Nobody commented yet...
24 |
25 | 26 |
27 | 32 |
33 |
34 |
35 | `, 36 | styles: [ 37 | ` 38 | .card-footer { 39 | background-color: inherit; 40 | } 41 | .avatar { 42 | height: 64px; 43 | width: 64px; 44 | border-radius: 50%; 45 | border: 1px solid #2b3e50; 46 | } 47 | `, 48 | ], 49 | }) 50 | export class PostModalComponent implements OnInit { 51 | public author$: Observable 52 | public post: Post 53 | public comments: Comment[] = [] 54 | @Output() action = new EventEmitter() 55 | loading = true 56 | handler: (id) => Observable 57 | fetcher: (id) => Observable 58 | public _refresh = new BehaviorSubject(true) 59 | public refresh$ = this._refresh.asObservable() 60 | 61 | ngOnInit() { 62 | this.refresh$ 63 | .pipe( 64 | tap(() => (this.loading = true)), 65 | switchMap(() => this.fetcher(this.post.id)), 66 | ) 67 | .subscribe((comments) => { 68 | this.loading = false 69 | this.comments = comments 70 | }) 71 | } 72 | 73 | refresh() { 74 | this._refresh.next(true) 75 | } 76 | 77 | handleAction({ payload: { text } }) { 78 | this.handler({ text }).subscribe(() => { 79 | this.refresh() 80 | this.action.emit({ type: 'REFRESH' }) 81 | }) 82 | console.log(text) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/containers/post-index.component.ts: -------------------------------------------------------------------------------- 1 | import { Post, User } from '@angular-graphql/web/data-access' 2 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 3 | import { Component, OnInit } from '@angular/core' 4 | import { BehaviorSubject, Observable } from 'rxjs' 5 | import { switchMap } from 'rxjs/operators' 6 | 7 | import { PostActions } from '../actions/post.actions' 8 | 9 | import { WebFeaturePostService } from '../web-feature-post.service' 10 | 11 | @Component({ 12 | template: ` 13 |
14 |
15 |
16 |
17 | 23 |
24 | 25 |
26 | 27 |
28 |
29 | 30 | 31 | 32 |
33 |
34 |
35 | `, 36 | }) 37 | export class PostIndexComponent implements OnInit { 38 | public author$: Observable 39 | public feed$: Observable 40 | public post: { text: string } = { text: null } 41 | public _refresh = new BehaviorSubject(true) 42 | public refresh$ = this._refresh.asObservable() 43 | 44 | constructor(public auth: WebDataAccessAuthService, public service: WebFeaturePostService) { 45 | this.author$ = this.auth.user$ 46 | } 47 | 48 | private refresh() { 49 | this._refresh.next(true) 50 | } 51 | 52 | ngOnInit() { 53 | this.feed$ = this.refresh$.pipe(switchMap(() => this.service.posts())) 54 | } 55 | 56 | createPost({ payload }) { 57 | return this.service.createPost(payload).subscribe( 58 | () => this.refresh(), 59 | (err) => console.log('Something went wrong!', err), 60 | ) 61 | } 62 | 63 | handleAction({ type, payload }: { type: string; payload?: any }) { 64 | if (type === PostActions.SHOW_COMMENTS) { 65 | const handler = (data) => this.service.createComment(payload.id, data) 66 | return this.service.openComments({ title: 'HE', handler, post: payload }) 67 | } 68 | console.log('Unhandled type', { type, payload }) 69 | return { type, payload } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/web-feature-post.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | 3 | @Component({ 4 | template: ` WebFeaturePost componnent `, 5 | }) 6 | export class WebFeaturePostComponent {} 7 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/web-feature-post.module.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthModule } from '@angular-graphql/web/data-access-auth' 2 | import { CommonModule } from '@angular/common' 3 | import { NgModule } from '@angular/core' 4 | import { RouterModule, Routes } from '@angular/router' 5 | import { UiCardModule } from '@kikstart-ui/ui-card' 6 | import { UiCommentModule } from '@kikstart-ui/ui-comment' 7 | import { UiLoadingModule } from '@kikstart-ui/ui-loading' 8 | 9 | import { BsModalService, ModalModule } from 'ngx-bootstrap/modal' 10 | import { PostModalComponent } from './components/post-modal.component' 11 | import { PostIndexComponent } from './containers/post-index.component' 12 | import { WebFeaturePostService } from './web-feature-post.service' 13 | 14 | const routes: Routes = [{ path: '', component: PostIndexComponent }] 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | RouterModule.forChild(routes), 20 | ModalModule.forRoot(), 21 | WebDataAccessAuthModule, 22 | UiCardModule, 23 | UiCommentModule, 24 | UiLoadingModule, 25 | ], 26 | providers: [BsModalService, WebFeaturePostService], 27 | declarations: [PostIndexComponent, PostModalComponent], 28 | entryComponents: [PostModalComponent], 29 | }) 30 | export class WebFeaturePostModule {} 31 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/lib/web-feature-post.service.ts: -------------------------------------------------------------------------------- 1 | import { Post, WebDataAccessService } from '@angular-graphql/web/data-access' 2 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 3 | import { Injectable } from '@angular/core' 4 | import { BsModalService } from 'ngx-bootstrap/modal' 5 | import { Observable } from 'rxjs' 6 | import { map } from 'rxjs/operators' 7 | 8 | import { PostActions } from './actions/post.actions' 9 | 10 | import { PostModalComponent } from './components/post-modal.component' 11 | 12 | const formatPost = (post: Post) => ({ 13 | ...post, 14 | author: { 15 | ...post.author, 16 | path: '/profiles/' + post.author.username, 17 | }, 18 | buttons: [ 19 | { 20 | icon: 'fa fa-fw fa-comment', 21 | label: `${post.commentCount} Comments`, 22 | payload: { id: post.id }, 23 | type: PostActions.SHOW_COMMENTS, 24 | }, 25 | ], 26 | }) 27 | 28 | const formatPosts = (posts: Post[]) => posts.map(formatPost) 29 | 30 | @Injectable() 31 | export class WebFeaturePostService { 32 | constructor( 33 | private auth: WebDataAccessAuthService, 34 | private data: WebDataAccessService, 35 | private modalService: BsModalService, 36 | ) {} 37 | 38 | posts() { 39 | return this.data.sdk.posts({}, { fetchPolicy: 'network-only' }).pipe( 40 | map((result) => result?.data?.posts), 41 | map(formatPosts), 42 | ) 43 | } 44 | 45 | post(id: string) { 46 | return this.data.sdk.post({ id }, { fetchPolicy: 'network-only' }).pipe( 47 | map((result) => result.data.post), 48 | map(formatPost), 49 | ) 50 | } 51 | 52 | createPost({ text }) { 53 | return this.data.sdk.createPost({ data: { text } }) 54 | } 55 | 56 | comments(postId: string) { 57 | return this.data.sdk 58 | .comments({ postId }, { fetchPolicy: 'network-only' }) 59 | .pipe(map((result) => result?.data?.comments)) 60 | } 61 | 62 | createComment(postId, { text }) { 63 | return this.data.sdk.createComment({ data: { postId, text } }, { fetchPolicy: 'no-cache' }) 64 | } 65 | 66 | openComments({ 67 | title, 68 | handler, 69 | post, 70 | comments, 71 | }: { 72 | handler?: (data) => Observable 73 | title?: string 74 | post?: Post 75 | comments?: Comment[] 76 | }) { 77 | const fetcher = (id) => this.comments(id) 78 | const showPost = { ...post } 79 | // @ts-ignore 80 | delete showPost.buttons 81 | this.modalService.show(PostModalComponent, { 82 | initialState: { 83 | title, 84 | handler, 85 | fetcher, 86 | author$: this.auth.user$, 87 | post: showPost, 88 | // comments: , 89 | }, 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /libs/web/feature-post/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/feature-post/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/feature-post/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/feature-post/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-post/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "post", "camelCase"], 5 | "component-selector": [true, "element", "post", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-profile/README.md: -------------------------------------------------------------------------------- 1 | # web-feature-profile 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-feature-profile` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/feature-profile/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-feature-profile', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/feature-profile', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-feature-profile.module' 2 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/lib/components/profile-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-profile-card', 5 | template: ` 6 |
7 |
8 |
9 | 10 |

{{ username }}

11 |
12 |

13 | {{ name }} 14 |

15 |

16 | {{ bio }} 17 |

18 |

19 | {{ location }} 20 |

21 |
22 |
23 | `, 24 | }) 25 | export class ProfileCardComponent { 26 | @Input() avatarUrl?: string 27 | @Input() username?: string 28 | @Input() name?: string 29 | @Input() bio?: string 30 | @Input() location?: string 31 | } 32 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/lib/containers/profile-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Profile } from '@angular-graphql/web/data-access' 2 | import { Component, OnInit } from '@angular/core' 3 | import { ActivatedRoute } from '@angular/router' 4 | import { Observable } from 'rxjs' 5 | import { map, switchMap } from 'rxjs/operators' 6 | import { WebFeatureProfileService } from '../web-feature-profile.service' 7 | 8 | @Component({ 9 | template: ` 10 |
11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | `, 26 | }) 27 | export class ProfileDetailComponent implements OnInit { 28 | public profile$: Observable 29 | 30 | constructor(private route: ActivatedRoute, private service: WebFeatureProfileService) {} 31 | 32 | ngOnInit() { 33 | this.profile$ = this.route.params.pipe( 34 | map((params) => params.username), 35 | switchMap((username) => this.service.profile(username)), 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/lib/web-feature-profile.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common' 2 | import { NgModule } from '@angular/core' 3 | import { RouterModule, Routes } from '@angular/router' 4 | import { UiAvatarModule } from '@kikstart-ui/ui-avatar' 5 | import { UiLoadingModule } from '@kikstart-ui/ui-loading' 6 | import { ProfileCardComponent } from './components/profile-card.component' 7 | import { ProfileDetailComponent } from './containers/profile-detail.component' 8 | import { WebFeatureProfileService } from './web-feature-profile.service' 9 | 10 | const routes: Routes = [{ path: ':username', component: ProfileDetailComponent }] 11 | 12 | @NgModule({ 13 | imports: [CommonModule, RouterModule.forChild(routes), UiAvatarModule, UiLoadingModule], 14 | providers: [WebFeatureProfileService], 15 | declarations: [ProfileCardComponent, ProfileDetailComponent], 16 | }) 17 | export class WebFeatureProfileModule {} 18 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/lib/web-feature-profile.service.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessService } from '@angular-graphql/web/data-access' 2 | import { Injectable } from '@angular/core' 3 | import { map } from 'rxjs/operators' 4 | 5 | @Injectable() 6 | export class WebFeatureProfileService { 7 | constructor(private readonly data: WebDataAccessService) {} 8 | 9 | profiles() { 10 | return this.data.sdk.profiles().pipe(map((result) => result.data.profiles)) 11 | } 12 | 13 | profile(username: string) { 14 | return this.data.sdk.profile({ username }).pipe(map((result) => result.data.profile)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/web/feature-profile/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/feature-profile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/feature-profile/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/feature-profile/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-profile/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "profile", "camelCase"], 5 | "component-selector": [true, "element", "profile", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-shell/README.md: -------------------------------------------------------------------------------- 1 | # web-feature-shell 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test web-feature-shell` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/web/feature-shell/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'web-feature-shell', 3 | preset: '../../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../../coverage/libs/web/feature-shell', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/web-feature-shell.module' 2 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/app.config.ts: -------------------------------------------------------------------------------- 1 | import { UiBrand } from '@kikstart-ui/ui-brand' 2 | 3 | export const brand: UiBrand = { 4 | logo: '/assets/logo.png', 5 | name: 'GraphQL', 6 | product: 'with Angular', 7 | } 8 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/components/app-header-dropdown.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core' 2 | import { UiLink } from '@kikstart-ui/ui-link' 3 | 4 | @Component({ 5 | selector: 'app-header-dropdown', 6 | template: ` 7 | 25 | `, 26 | }) 27 | export class AppHeaderDropdownComponent { 28 | @Input() public links: UiLink[] = [] 29 | } 30 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/components/app-header-links.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core' 2 | import { UiLink } from '@kikstart-ui/ui-link' 3 | 4 | @Component({ 5 | selector: 'app-header-link', 6 | template: ` 7 | 15 | `, 16 | }) 17 | export class AppHeaderLinksComponent { 18 | @Input() public link: UiLink 19 | } 20 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/components/app-header.component.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@angular-graphql/web/data-access' 2 | import { Component, Input } from '@angular/core' 3 | 4 | import { UiBrand } from '@kikstart-ui/ui-brand' 5 | import { UiLink } from '@kikstart-ui/ui-link' 6 | 7 | @Component({ 8 | selector: 'app-header', 9 | template: ` 10 |
11 | 35 |
36 | `, 37 | styles: [ 38 | ` 39 | .dense { 40 | padding-top: 0; 41 | padding-bottom: 2px !important; 42 | } 43 | `, 44 | ], 45 | }) 46 | export class AppHeaderComponent { 47 | @Input() public brand: UiBrand 48 | @Input() public dense = false 49 | @Input() public fluid = false 50 | @Input() public style: 'light' | 'dark' = 'dark' 51 | @Input() public user: User 52 | @Input() public showUsername = false 53 | @Input() public links: UiLink[] = [] 54 | @Input() public rightLinks: UiLink[] = [] 55 | @Input() public userLinks: UiLink[] = [] 56 | @Input() public adminLinks: UiLink[] = [] 57 | @Input() public devLinks: UiLink[] = [] 58 | 59 | public navBarOpen = false 60 | 61 | toggleNavBar() { 62 | this.navBarOpen = !this.navBarOpen 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/components/app-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@angular-graphql/web/data-access' 2 | import { WebDataAccessAuthService } from '@angular-graphql/web/data-access-auth' 3 | import { Component, OnInit } from '@angular/core' 4 | import { UiLink } from '@kikstart-ui/ui-link' 5 | import { Observable } from 'rxjs' 6 | import { tap } from 'rxjs/operators' 7 | import { brand } from '../app.config' 8 | 9 | @Component({ 10 | template: ` 11 | 12 |
13 | 14 |
15 | `, 16 | }) 17 | export class AppLayoutComponent implements OnInit { 18 | public brand = brand 19 | public topLinks = [] 20 | public loginLink = { divider: false, path: '/login', label: 'Log in', icon: 'fa fa-sign-in' } 21 | public userLinks: UiLink[] = [this.loginLink] 22 | public user$: Observable 23 | 24 | constructor(private auth: WebDataAccessAuthService) {} 25 | 26 | ngOnInit() { 27 | this.user$ = this.auth.user$.pipe( 28 | tap((res) => { 29 | if (res) { 30 | this.userLinks = [ 31 | { path: '/profiles/' + res.username, label: 'My Profile', icon: 'fa fa-user' }, 32 | { path: '/logout', label: 'Log out', icon: 'fa fa-sign-out' }, 33 | ] 34 | } else { 35 | this.userLinks = [this.loginLink] 36 | } 37 | }), 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/components/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core' 2 | 3 | @Component({ 4 | template: ` 5 |
6 |
7 |
8 |
Oh no!!
9 |
10 | The requested page could not be found! :( 11 |
12 |
13 |
14 |
15 | `, 16 | }) 17 | export class NotFoundComponent {} 18 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/lib/web-feature-shell.module.ts: -------------------------------------------------------------------------------- 1 | import { WebDataAccessAuthModule } from '@angular-graphql/web/data-access-auth' 2 | import { WebFeatureCoreModule } from '@angular-graphql/web/feature-core' 3 | import { CommonModule } from '@angular/common' 4 | import { HttpClientModule } from '@angular/common/http' 5 | import { NgModule } from '@angular/core' 6 | import { RouterModule } from '@angular/router' 7 | import { UiAvatarModule } from '@kikstart-ui/ui-avatar' 8 | import { UiBrandModule } from '@kikstart-ui/ui-brand' 9 | import { UiLabelModule } from '@kikstart-ui/ui-label' 10 | import { UiLinkModule } from '@kikstart-ui/ui-link' 11 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown' 12 | 13 | import { AppHeaderDropdownComponent } from './components/app-header-dropdown.component' 14 | import { AppHeaderLinksComponent } from './components/app-header-links.component' 15 | import { AppHeaderComponent } from './components/app-header.component' 16 | import { AppLayoutComponent } from './components/app-layout.component' 17 | import { NotFoundComponent } from './components/not-found.component' 18 | 19 | @NgModule({ 20 | declarations: [ 21 | AppHeaderComponent, 22 | AppHeaderDropdownComponent, 23 | AppHeaderLinksComponent, 24 | AppLayoutComponent, 25 | NotFoundComponent, 26 | ], 27 | imports: [ 28 | CommonModule, 29 | HttpClientModule, 30 | RouterModule.forRoot([ 31 | { 32 | path: '', 33 | component: AppLayoutComponent, 34 | children: [ 35 | { path: '', pathMatch: 'full', redirectTo: '/posts' }, 36 | { 37 | path: 'posts', 38 | loadChildren: () => import('@angular-graphql/web/feature-post').then((m) => m.WebFeaturePostModule), 39 | }, 40 | { 41 | path: 'profiles', 42 | loadChildren: () => import('@angular-graphql/web/feature-profile').then((m) => m.WebFeatureProfileModule), 43 | }, 44 | { path: '404', component: NotFoundComponent }, 45 | ], 46 | }, 47 | { 48 | path: '', 49 | loadChildren: () => import('@angular-graphql/web/feature-auth').then((m) => m.WebFeatureAuthModule), 50 | }, 51 | { path: '**', pathMatch: 'full', redirectTo: '/404' }, 52 | ]), 53 | WebFeatureCoreModule, 54 | WebDataAccessAuthModule, 55 | BsDropdownModule.forRoot(), 56 | UiAvatarModule, 57 | UiBrandModule, 58 | UiLinkModule, 59 | UiLabelModule, 60 | ], 61 | }) 62 | export class WebFeatureShellModule {} 63 | -------------------------------------------------------------------------------- /libs/web/feature-shell/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular' 2 | -------------------------------------------------------------------------------- /libs/web/feature-shell/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/web/feature-shell/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/web/feature-shell/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/web/feature-shell/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "shell", "camelCase"], 5 | "component-selector": [true, "element", "shell", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "angular-graphql", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "implicitDependencies": { 7 | "angular.json": "*", 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | "tsconfig.base.json": "*", 13 | "tslint.json": "*", 14 | "nx.json": "*" 15 | }, 16 | "tasksRunnerOptions": { 17 | "default": { 18 | "runner": "@nrwl/nx-cloud", 19 | "options": { 20 | "accessToken": "YTE1ODUwMzUtYmVkMi00OGY1LWFmYmMtYzBlM2E3Y2EyZGYwfHJlYWQtd3JpdGU=", 21 | "cacheableOperations": ["build", "test", "lint", "e2e"], 22 | "canTrackAnalytics": false, 23 | "showUsageWarnings": true 24 | } 25 | } 26 | }, 27 | "projects": { 28 | "api": { 29 | "tags": [] 30 | }, 31 | "api-data-access": { 32 | "tags": ["scope:api", "type:data-access"] 33 | }, 34 | "api-feature-core": { 35 | "tags": ["scope:api", "type:feature"] 36 | }, 37 | "api-feature-auth": { 38 | "tags": ["scope:api", "type:feature"] 39 | }, 40 | "web": { 41 | "tags": [] 42 | }, 43 | "web-e2e": { 44 | "tags": [], 45 | "implicitDependencies": ["web"] 46 | }, 47 | "web-data-access": { 48 | "tags": ["scope:web", "type:data-access"] 49 | }, 50 | "web-feature-shell": { 51 | "tags": ["scope:web", "type:feature"] 52 | }, 53 | "web-feature-auth": { 54 | "tags": ["scope:web", "type:feature"] 55 | }, 56 | "api-feature-post": { 57 | "tags": ["scope:api", "type:feature"] 58 | }, 59 | "api-feature-comment": { 60 | "tags": ["scope:api", "type:feature"] 61 | }, 62 | "api-feature-profile": { 63 | "tags": ["scope:api", "type:feature"] 64 | }, 65 | "web-feature-core": { 66 | "tags": ["scope:web", "type:feature"] 67 | }, 68 | "web-feature-post": { 69 | "tags": ["scope:web", "type:feature"] 70 | }, 71 | "web-feature-profile": { 72 | "tags": ["scope:web", "type:feature"] 73 | }, 74 | "web-data-access-auth": { 75 | "tags": ["scope:web", "type:data-access"] 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-graphql", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "nx", 7 | "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", 8 | "nx": "nx", 9 | "start": "node dist/apps/api/main.js", 10 | "build": "yarn build:web && yarn build:api", 11 | "test": "ng test", 12 | "lint": "nx workspace-lint && ng lint", 13 | "e2e": "ng e2e", 14 | "affected:apps": "nx affected:apps", 15 | "affected:libs": "nx affected:libs", 16 | "affected:build": "nx affected:build", 17 | "affected:e2e": "nx affected:e2e", 18 | "affected:test": "nx affected:test", 19 | "affected:lint": "nx affected:lint", 20 | "affected:dep-graph": "nx affected:dep-graph", 21 | "affected": "nx affected", 22 | "format": "nx format:write", 23 | "format:write": "nx format:write", 24 | "format:check": "nx format:check", 25 | "update": "ng update @nrwl/workspace", 26 | "workspace-schematic": "nx workspace-schematic", 27 | "dep-graph": "nx dep-graph", 28 | "help": "nx help", 29 | "docker:servers": "docker-compose up", 30 | "dev:api": "nx serve api", 31 | "build:api": "nx build api --prod", 32 | "dev:web": "nx serve web", 33 | "build:web": "nx build web --prod", 34 | "prisma:migrate": "yarn prisma migrate save --experimental && yarn prisma migrate up --experimental", 35 | "prisma:generate": "yarn prisma generate", 36 | "prisma:apply": "yarn prisma:migrate && yarn prisma:generate", 37 | "sdk": "yarn graphql-codegen --config ./libs/web/data-access/src/codegen.yml" 38 | }, 39 | "private": true, 40 | "dependencies": { 41 | "@angular/animations": "^10.0.0", 42 | "@angular/common": "^10.0.0", 43 | "@angular/compiler": "^10.0.0", 44 | "@angular/core": "^10.0.0", 45 | "@angular/forms": "^10.0.0", 46 | "@angular/platform-browser": "^10.0.0", 47 | "@angular/platform-browser-dynamic": "^10.0.0", 48 | "@angular/router": "^10.0.0", 49 | "@apollo/client": "^3.2.1", 50 | "@kikstart-playground/graphql-intercom": "1.4.1", 51 | "@kikstart-ui/layout-web": "^2.2.0", 52 | "@kikstart-ui/themes": "^2.2.0", 53 | "@nestjs/common": "^7.0.0", 54 | "@nestjs/config": "^0.5.0", 55 | "@nestjs/core": "^7.0.0", 56 | "@nestjs/graphql": "^7.7.0", 57 | "@nestjs/jwt": "^7.1.0", 58 | "@nestjs/passport": "^7.1.0", 59 | "@nestjs/platform-express": "^7.0.0", 60 | "apollo-angular": "^2.0.4", 61 | "apollo-server-express": "^2.18.0", 62 | "bcryptjs": "^2.4.3", 63 | "class-transformer": "^0.3.1", 64 | "class-validator": "^0.12.2", 65 | "graphql": "^15.3.0", 66 | "graphql-tools": "^6.2.3", 67 | "graphql-type-json": "^0.3.2", 68 | "joi": "^17.2.1", 69 | "ngx-bootstrap": "^6.1.0", 70 | "passport": "^0.4.1", 71 | "passport-jwt": "^4.0.0", 72 | "reflect-metadata": "^0.1.13", 73 | "rxjs": "~6.5.5", 74 | "zone.js": "^0.10.2" 75 | }, 76 | "devDependencies": { 77 | "@angular-devkit/build-angular": "~0.1000.0", 78 | "@angular/cli": "~10.0.0", 79 | "@angular/compiler-cli": "^10.0.0", 80 | "@angular/language-service": "^10.0.0", 81 | "@graphql-codegen/cli": "1.17.8", 82 | "@graphql-codegen/introspection": "1.17.8", 83 | "@graphql-codegen/typescript": "1.17.8", 84 | "@graphql-codegen/typescript-apollo-angular": "^2.0.1", 85 | "@graphql-codegen/typescript-operations": "1.17.8", 86 | "@nestjs/schematics": "^7.0.0", 87 | "@nestjs/testing": "^7.0.0", 88 | "@nrwl/angular": "^10.2.1", 89 | "@nrwl/cypress": "10.2.1", 90 | "@nrwl/jest": "10.2.1", 91 | "@nrwl/nest": "^10.2.1", 92 | "@nrwl/node": "10.2.1", 93 | "@nrwl/nx-cloud": "latest", 94 | "@nrwl/workspace": "10.2.1", 95 | "@nxpm/stack": "^1.1.0", 96 | "@prisma/cli": "^2.7.1", 97 | "@prisma/client": "^2.7.1", 98 | "@types/jest": "26.0.8", 99 | "@types/node": "~8.9.4", 100 | "codelyzer": "~5.0.1", 101 | "cypress": "^4.1.0", 102 | "dotenv": "6.2.0", 103 | "eslint": "6.8.0", 104 | "husky": "^4.3.0", 105 | "jest": "26.2.2", 106 | "jest-preset-angular": "8.3.1", 107 | "lint-staged": "^10.4.0", 108 | "prettier": "2.0.4", 109 | "ts-jest": "26.1.4", 110 | "ts-node": "~7.0.0", 111 | "tslint": "~6.0.0", 112 | "typescript": "~3.9.3" 113 | }, 114 | "husky": { 115 | "hooks": { 116 | "pre-commit": "lint-staged" 117 | } 118 | }, 119 | "lint-staged": { 120 | "*.{js,json,css,scss,md,ts,html,graphql}": [ 121 | "yarn run format --uncommitted" 122 | ] 123 | }, 124 | "prisma": { 125 | "schema": "libs/api/data-access/src/schema.prisma" 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tools/schematics/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeman/panng-stack/35aae3ea5358ff641e8e7c32b05541fae4e4261d/tools/schematics/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"] 9 | }, 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "typeRoots": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@angular-graphql/api/data-access": ["libs/api/data-access/src/index.ts"], 20 | "@angular-graphql/api/feature-core": ["libs/api/feature-core/src/index.ts"], 21 | "@angular-graphql/api/feature-auth": ["libs/api/feature-auth/src/index.ts"], 22 | "@angular-graphql/web/data-access": ["libs/web/data-access/src/index.ts"], 23 | "@angular-graphql/web/feature-shell": ["libs/web/feature-shell/src/index.ts"], 24 | "@angular-graphql/web/feature-auth": ["libs/web/feature-auth/src/index.ts"], 25 | "@angular-graphql/api/feature-post": ["libs/api/feature-post/src/index.ts"], 26 | "@angular-graphql/api/feature-comment": ["libs/api/feature-comment/src/index.ts"], 27 | "@angular-graphql/api/feature-profile": ["libs/api/feature-profile/src/index.ts"], 28 | "@angular-graphql/web/feature-core": ["libs/web/feature-core/src/index.ts"], 29 | "@angular-graphql/web/feature-post": ["libs/web/feature-post/src/index.ts"], 30 | "@angular-graphql/web/feature-profile": ["libs/web/feature-profile/src/index.ts"], 31 | "@angular-graphql/web/data-access-auth": ["libs/web/data-access-auth/src/index.ts"] 32 | } 33 | }, 34 | "exclude": ["node_modules", "tmp"] 35 | } 36 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/@nrwl/workspace/src/tslint", "node_modules/codelyzer"], 3 | "linterOptions": { 4 | "exclude": ["**/*"] 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": [true, "rxjs/Rx"], 15 | "interface-over-type-literal": true, 16 | "member-access": false, 17 | "member-ordering": [ 18 | true, 19 | { 20 | "order": ["static-field", "instance-field", "static-method", "instance-method"] 21 | } 22 | ], 23 | "no-arg": true, 24 | "no-bitwise": true, 25 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 26 | "no-construct": true, 27 | "no-debugger": true, 28 | "no-duplicate-super": true, 29 | "no-empty": false, 30 | "no-empty-interface": true, 31 | "no-eval": true, 32 | "no-inferrable-types": [true, "ignore-params"], 33 | "no-misused-new": true, 34 | "no-non-null-assertion": true, 35 | "no-shadowed-variable": true, 36 | "no-string-literal": false, 37 | "no-string-throw": true, 38 | "no-switch-case-fall-through": true, 39 | "no-unnecessary-initializer": true, 40 | "no-unused-expression": true, 41 | "no-var-keyword": true, 42 | "object-literal-sort-keys": false, 43 | "prefer-const": true, 44 | "radix": true, 45 | "triple-equals": [true, "allow-null-check"], 46 | "unified-signatures": true, 47 | "variable-name": false, 48 | "nx-enforce-module-boundaries": [ 49 | true, 50 | { 51 | "enforceBuildableLibDependency": true, 52 | "allow": [], 53 | "depConstraints": [ 54 | { 55 | "sourceTag": "*", 56 | "onlyDependOnLibsWithTags": ["*"] 57 | } 58 | ] 59 | } 60 | ], 61 | "directive-selector": [true, "attribute", "app", "camelCase"], 62 | "component-selector": [true, "element", "app", "kebab-case"], 63 | "no-conflicting-lifecycle": true, 64 | "no-host-metadata-property": true, 65 | "no-input-rename": true, 66 | "no-inputs-metadata-property": true, 67 | "no-output-native": true, 68 | "no-output-on-prefix": true, 69 | "no-output-rename": true, 70 | "no-outputs-metadata-property": true, 71 | "template-banana-in-box": true, 72 | "template-no-negated-async": true, 73 | "use-lifecycle-interface": true, 74 | "use-pipe-transform-interface": true 75 | } 76 | } 77 | --------------------------------------------------------------------------------