├── .dockerignore ├── .gitignore ├── .idea ├── .gitignore ├── dbnavigator.xml ├── inspectionProfiles │ └── Project_Default.xml ├── lesson-1.iml └── modules.xml ├── README.md ├── backend ├── .eslintrc.js ├── .prettierrc ├── Dockerfile ├── README.md ├── nest-cli.json ├── package.json ├── schema.gql ├── src │ ├── app.module.ts │ ├── main.ts │ └── users │ │ ├── entities │ │ └── user.entity.ts │ │ ├── inputs │ │ ├── create-user.input.ts │ │ └── update-user.input.ts │ │ ├── resolvers │ │ └── user │ │ │ ├── user.resolver.spec.ts │ │ │ └── user.resolver.ts │ │ ├── services │ │ └── user │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ │ └── users.module.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock ├── docker-compose.yml ├── frontend ├── .browserslistrc ├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── README.md ├── angular.json ├── karma.conf.js ├── nginx.conf ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── components │ │ │ ├── icon │ │ │ │ ├── icon.component.ts │ │ │ │ └── icon.module.ts │ │ │ ├── nav.component.ts │ │ │ └── user-form │ │ │ │ ├── user-form.component.ts │ │ │ │ └── user-form.module.ts │ │ ├── graphql.module.ts │ │ ├── interfaces.ts │ │ └── pages │ │ │ ├── main │ │ │ ├── main.component.html │ │ │ ├── main.component.scss │ │ │ ├── main.component.ts │ │ │ └── main.module.ts │ │ │ └── users │ │ │ ├── gql │ │ │ ├── create-user.ts │ │ │ ├── delete-user.ts │ │ │ ├── get-all-users.ts │ │ │ ├── get-one-user.ts │ │ │ └── update-user.ts │ │ │ ├── user │ │ │ ├── user.component.html │ │ │ ├── user.component.scss │ │ │ └── user.component.ts │ │ │ ├── users.component.html │ │ │ ├── users.component.scss │ │ │ ├── users.component.ts │ │ │ ├── users.module.ts │ │ │ └── users.service.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── yarn.lock ├── package.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | backend/dist 3 | backend/node_modules 4 | 5 | pgdata 6 | .env 7 | 8 | # Logs 9 | backend/logs 10 | backend/*.log 11 | backend/npm-debug.log* 12 | backend/pnpm-debug.log* 13 | backend/yarn-debug.log* 14 | backend/yarn-error.log* 15 | backend/lerna-debug.log* 16 | 17 | # OS 18 | backend/.DS_Store 19 | 20 | # Tests 21 | backend/coverage 22 | backend/.nyc_output 23 | 24 | # IDEs and editors 25 | backend/.idea 26 | backend/.project 27 | backend/.classpath 28 | backend/.c9/ 29 | backend/*.launch 30 | backend/.settings/ 31 | backend/*.sublime-workspace 32 | 33 | # IDE - VSCode 34 | backend/.vscode/* 35 | !backend/.vscode/settings.json 36 | !backend/.vscode/tasks.json 37 | !backend/.vscode/launch.json 38 | !backend/.vscode/extensions.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | backend/dist 3 | backend/node_modules 4 | 5 | pgdata 6 | .env 7 | 8 | # Logs 9 | backend/logs 10 | backend/*.log 11 | backend/npm-debug.log* 12 | backend/pnpm-debug.log* 13 | backend/yarn-debug.log* 14 | backend/yarn-error.log* 15 | backend/lerna-debug.log* 16 | 17 | # OS 18 | backend/.DS_Store 19 | 20 | # Tests 21 | backend/coverage 22 | backend/.nyc_output 23 | 24 | # IDEs and editors 25 | .idea 26 | .project 27 | .classpath 28 | .c9/ 29 | *.launch 30 | .settings/ 31 | *.sublime-workspace 32 | 33 | # IDE - VSCode 34 | backend/.vscode/* 35 | !backend/.vscode/settings.json 36 | !backend/.vscode/tasks.json 37 | !backend/.vscode/launch.json 38 | !backend/.vscode/extensions.json -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/lesson-1.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular NestJS PostgreSQL GraphQL Typeorm Docker 2 | 3 | Простое backend приложение на базе NestJS, где в качестве базы данных выступает PostgreSQL, для управления базой 4 | используется TypeORM, в качестве языка запроса у backend используется GraphQL. Все это упаковано в docker-compose 5 | 6 | 7 | 8 | 15 | 22 | 29 | 36 | 43 | 50 | 51 |
9 | 10 | Angular 11 | 12 |
13 | Angular 14 |
16 | 17 | NestJS 18 | 19 |
20 | NestJS 21 |
23 | 24 | PostgresSQL 25 | 26 |
27 | PostgresSQL 28 |
30 | 31 | GraphQL 32 | 33 |
34 | GraphQL 35 |
37 | 38 | TypeORM 39 | 40 |
41 | TypeORM 42 |
44 | 45 | Docker 46 | 47 |
48 | Docker 49 |
52 | 53 | ## Метод установки и запуска 54 | 55 | Скопируйте к себе репозиторий 56 | 57 | ```shell 58 | git clone https://github.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker.git 59 | ``` 60 | 61 | Создайте в корне репозитория .env файл, например: 62 | 63 | ```dotenv 64 | API_PORT=3001 65 | API_HOST=http://localhost: 66 | TYPEORM_CONNECTION=postgres 67 | TYPEORM_USERNAME=admin 68 | TYPEORM_PASSWORD=123456 69 | TYPEORM_DATABASE=lesson1 70 | TYPEORM_PORT=5432 71 | TYPEORM_HOST=localhost 72 | ``` 73 | 74 | ### С использованием Docker 75 | 76 | Убедитесь что у вас установлен Docker (Docker не поддерживается семейством операционных систем Windows, за исключением 77 | Windows-Professional или Корпоративная, т.к. для работы необходим Hyper-V, о чем сказано на сайте 78 | в [документации](https://docs.microsoft.com/ru-ru/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v#check-requirements) 79 | Microsoft) 80 | Выполните команду: 81 | 82 | ```shell 83 | docker-compose up 84 | # -d - для запуска в фоне 85 | # --build - для повторной пересборки контейнеров 86 | ``` 87 | 88 | ### Без Docker 89 | 90 | - Установите к себе PostgreSQL с официального [сайта](https://www.postgresql.org/) 91 | - Создайте экземпляр сервера и базу данных, добавьте пользователя и пароль как указано в .env файле 92 | - Убедитесь что postgreSQL запущен и работает 93 | - Выполните установку зависимостей 94 | 95 | ### Backend 96 | ```shell 97 | cd backend/ 98 | 99 | # yarn package manager 100 | yarn install 101 | yarn start 102 | 103 | # npm package manager 104 | npm install 105 | npm run start 106 | ``` 107 | 108 | ### Frontend 109 | ```shell 110 | cd frontend/ 111 | 112 | # yarn package manager 113 | yarn install 114 | yarn start 115 | 116 | # npm package manager 117 | npm install 118 | npm run start 119 | ``` -------------------------------------------------------------------------------- /backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [], 9 | root: true, 10 | env: { 11 | node: true, 12 | jest: true, 13 | }, 14 | ignorePatterns: ['.eslintrc.js'], 15 | rules: { 16 | '@typescript-eslint/interface-name-prefix': 'off', 17 | '@typescript-eslint/explicit-function-return-type': 'off', 18 | '@typescript-eslint/explicit-module-boundary-types': 'off', 19 | '@typescript-eslint/no-explicit-any': 'off', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine AS builder 2 | WORKDIR /app 3 | COPY /*.json ./ 4 | COPY . . 5 | RUN yarn run build 6 | 7 | FROM node:14-alpine 8 | WORKDIR /app 9 | COPY --from=builder /app ./ 10 | EXPOSE 3001 11 | CMD ["yarn", "run", "start:prod"] 12 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^8.0.0", 25 | "@nestjs/config": "^1.1.5", 26 | "@nestjs/core": "^8.0.0", 27 | "@nestjs/graphql": "^9.1.2", 28 | "@nestjs/platform-express": "^8.0.0", 29 | "@nestjs/typeorm": "^8.0.2", 30 | "apollo-server-express": "^3.5.0", 31 | "graphql": "^15", 32 | "pg": "^8.7.1", 33 | "reflect-metadata": "^0.1.13", 34 | "rimraf": "^3.0.2", 35 | "rxjs": "^7.2.0", 36 | "typeorm": "^0.2.41" 37 | }, 38 | "devDependencies": { 39 | "@nestjs/cli": "^8.0.0", 40 | "@nestjs/schematics": "^8.0.0", 41 | "@nestjs/testing": "^8.0.0", 42 | "@types/express": "^4.17.13", 43 | "@types/jest": "27.0.2", 44 | "@types/node": "^16.0.0", 45 | "@types/supertest": "^2.0.11", 46 | "@typescript-eslint/eslint-plugin": "^5.0.0", 47 | "@typescript-eslint/parser": "^5.0.0", 48 | "eslint": "^8.0.1", 49 | "eslint-config-prettier": "^8.3.0", 50 | "eslint-plugin-prettier": "^4.0.0", 51 | "jest": "^27.2.5", 52 | "prettier": "^2.3.2", 53 | "source-map-support": "^0.5.20", 54 | "supertest": "^6.1.3", 55 | "ts-jest": "^27.0.3", 56 | "ts-loader": "^9.2.3", 57 | "ts-node": "^10.0.0", 58 | "tsconfig-paths": "^3.10.1", 59 | "typescript": "^4.3.5" 60 | }, 61 | "jest": { 62 | "moduleFileExtensions": [ 63 | "js", 64 | "json", 65 | "ts" 66 | ], 67 | "rootDir": "src", 68 | "testRegex": ".*\\.spec\\.ts$", 69 | "transform": { 70 | "^.+\\.(t|j)s$": "ts-jest" 71 | }, 72 | "collectCoverageFrom": [ 73 | "**/*.(t|j)s" 74 | ], 75 | "coverageDirectory": "../coverage", 76 | "testEnvironment": "node" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /backend/schema.gql: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------ 2 | # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) 3 | # ------------------------------------------------------ 4 | 5 | input CreateUserInput { 6 | email: String! 7 | name: String 8 | } 9 | 10 | """ 11 | A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. 12 | """ 13 | scalar DateTime 14 | 15 | type Mutation { 16 | createUser(createUser: CreateUserInput!): UserEntity! 17 | removeUser(id: Float!): Float! 18 | updateUser(updateUser: UpdateUserInput!): UserEntity! 19 | } 20 | 21 | type Query { 22 | getAllUsers: [UserEntity!]! 23 | getOneUser(id: Float!): UserEntity! 24 | } 25 | 26 | input UpdateUserInput { 27 | email: String 28 | id: ID! 29 | name: String 30 | } 31 | 32 | type UserEntity { 33 | createdAt: DateTime! 34 | email: String! 35 | id: ID! 36 | name: String 37 | updatedAt: DateTime! 38 | } 39 | -------------------------------------------------------------------------------- /backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { ConfigModule, ConfigService } from '@nestjs/config' 3 | import { TypeOrmModule } from '@nestjs/typeorm' 4 | import { GraphQLModule } from '@nestjs/graphql' 5 | 6 | import { UsersModule } from './users/users.module' 7 | 8 | @Module({ 9 | imports: [ 10 | ConfigModule.forRoot({ isGlobal: true, envFilePath: '../.env' }), 11 | GraphQLModule.forRoot({ 12 | autoSchemaFile: 'schema.gql', 13 | sortSchema: true, 14 | playground: true, 15 | }), 16 | TypeOrmModule.forRootAsync({ 17 | imports: [ ConfigModule ], 18 | inject: [ ConfigService ], 19 | useFactory: async (config: ConfigService) => ({ 20 | type: config.get<'aurora-data-api'>('TYPEORM_CONNECTION'), 21 | host: config.get('TYPEORM_HOST'), 22 | username: config.get('TYPEORM_USERNAME'), 23 | password: config.get('TYPEORM_PASSWORD'), 24 | database: config.get('TYPEORM_DATABASE'), 25 | port: config.get('TYPEORM_PORT'), 26 | entities: [ __dirname + 'dist/**/*.entity{.ts,.js}' ], 27 | synchronize: true, 28 | autoLoadEntities: true, 29 | logging: true, 30 | }), 31 | }), 32 | UsersModule, 33 | ], 34 | providers: [], 35 | }) 36 | export class AppModule { 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ConfigService } from '@nestjs/config' 3 | 4 | import { AppModule } from './app.module' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule) 8 | const config = await app.get(ConfigService) 9 | const port = config.get('API_PORT') 10 | await app.listen(port || 3000, () => { 11 | console.log(`App started on port: ${port}`) 12 | }) 13 | } 14 | 15 | bootstrap() 16 | -------------------------------------------------------------------------------- /backend/src/users/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' 2 | import { Field, ID, ObjectType } from '@nestjs/graphql' 3 | 4 | @ObjectType() 5 | @Entity('users') 6 | export class UserEntity { 7 | @Field(() => ID) 8 | @PrimaryGeneratedColumn() 9 | id: number 10 | 11 | @Field() 12 | @CreateDateColumn() 13 | createdAt: Date 14 | 15 | @Field() 16 | @UpdateDateColumn() 17 | updatedAt: Date 18 | 19 | @Field() 20 | @Column() 21 | email: string 22 | 23 | @Field({ nullable: true }) 24 | @Column({ nullable: true }) 25 | name: string 26 | } -------------------------------------------------------------------------------- /backend/src/users/inputs/create-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql' 2 | 3 | @InputType() 4 | export class CreateUserInput { 5 | @Field() 6 | email: string 7 | 8 | @Field({ nullable: true }) 9 | name: string 10 | } -------------------------------------------------------------------------------- /backend/src/users/inputs/update-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, ID, InputType } from '@nestjs/graphql' 2 | 3 | @InputType() 4 | export class UpdateUserInput { 5 | @Field(() => ID) 6 | id: number 7 | 8 | @Field({ nullable: true }) 9 | email: string 10 | 11 | @Field({ nullable: true }) 12 | name: string 13 | } -------------------------------------------------------------------------------- /backend/src/users/resolvers/user/user.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserResolver } from './user.resolver'; 3 | 4 | describe('UserResolver', () => { 5 | let resolver: UserResolver; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserResolver], 10 | }).compile(); 11 | 12 | resolver = module.get(UserResolver); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(resolver).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/users/resolvers/user/user.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' 2 | 3 | import { UserService } from '../../services/user/user.service' 4 | import { UserEntity } from '../../entities/user.entity' 5 | import { CreateUserInput } from '../../inputs/create-user.input' 6 | import { UpdateUserInput } from '../../inputs/update-user.input' 7 | 8 | @Resolver('User') 9 | export class UserResolver { 10 | constructor( 11 | private readonly userService: UserService, 12 | ) { 13 | } 14 | 15 | @Mutation(() => UserEntity) 16 | async createUser(@Args('createUser') createUserInput: CreateUserInput): Promise { 17 | return await this.userService.createUser(createUserInput) 18 | } 19 | 20 | @Mutation(() => UserEntity) 21 | async updateUser(@Args('updateUser') updateUserInput: UpdateUserInput): Promise { 22 | return await this.userService.updateUser(updateUserInput) 23 | } 24 | 25 | @Mutation(() => Number) 26 | async removeUser(@Args('id') id: number): Promise { 27 | return await this.userService.removeUser(id) 28 | } 29 | 30 | @Query(() => UserEntity) 31 | async getOneUser(@Args('id') id: number): Promise { 32 | return await this.userService.getOneUser(id) 33 | } 34 | 35 | @Query(() => [ UserEntity ]) 36 | async getAllUsers(): Promise { 37 | return await this.userService.getAllUsers() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/users/services/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserService } from './user.service'; 3 | 4 | describe('UserService', () => { 5 | let service: UserService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserService], 10 | }).compile(); 11 | 12 | service = module.get(UserService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/users/services/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { InjectRepository } from '@nestjs/typeorm' 3 | import { UserEntity } from '../../entities/user.entity' 4 | import { Repository } from 'typeorm' 5 | import { CreateUserInput } from '../../inputs/create-user.input' 6 | import { UpdateUserInput } from '../../inputs/update-user.input' 7 | 8 | @Injectable() 9 | export class UserService { 10 | constructor( 11 | @InjectRepository(UserEntity) 12 | private readonly userRepository: Repository, 13 | ) { 14 | } 15 | 16 | async createUser(createUserInput: CreateUserInput): Promise { 17 | return await this.userRepository.save({ ...createUserInput }) 18 | } 19 | 20 | async getOneUser(id: number): Promise { 21 | return await this.userRepository.findOne({ id }) 22 | } 23 | 24 | async getAllUsers(): Promise { 25 | return await this.userRepository.find() 26 | } 27 | 28 | async removeUser(id: number): Promise { 29 | await this.userRepository.delete({ id }) 30 | return id 31 | } 32 | 33 | async updateUser(updateUserInput: UpdateUserInput): Promise { 34 | await this.userRepository.update({ id: updateUserInput.id }, { ...updateUserInput }) 35 | return await this.getOneUser(updateUserInput.id) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TypeOrmModule } from '@nestjs/typeorm' 3 | 4 | import { UserEntity } from './entities/user.entity' 5 | import { UserService } from './services/user/user.service' 6 | import { UserResolver } from './resolvers/user/user.resolver' 7 | 8 | @Module({ 9 | imports: [ TypeOrmModule.forFeature([ UserEntity ]) ], 10 | providers: [ UserService, UserResolver ], 11 | }) 12 | export class UsersModule { 13 | } 14 | -------------------------------------------------------------------------------- /backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | db: 5 | container_name: postgres-lesson-1 6 | image: postgres:14.0-alpine 7 | restart: always 8 | environment: 9 | - POSTGRES_USER=${TYPEORM_USERNAME} 10 | - POSTGRES_PASSWORD=${TYPEORM_PASSWORD} 11 | - POSTGRES_DB=${TYPEORM_DATABASE} 12 | volumes: 13 | - ./pgdata:/var/lib/postgresql/data 14 | ports: 15 | - ${TYPEORM_PORT}:${TYPEORM_PORT} 16 | backend: 17 | container_name: backend-nest-lesson-1 18 | build: 19 | context: ./backend 20 | depends_on: 21 | - db 22 | restart: unless-stopped 23 | ports: 24 | - '${API_PORT}:3001' 25 | environment: 26 | - API_PORT=${API_PORT} 27 | - API_HOST=${API_HOST} 28 | - TYPEORM_CONNECTION=${TYPEORM_CONNECTION} 29 | - TYPEORM_USERNAME=${TYPEORM_USERNAME} 30 | - TYPEORM_PASSWORD=${TYPEORM_PASSWORD} 31 | - TYPEORM_DATABASE=${TYPEORM_DATABASE} 32 | - TYPEORM_PORT=${TYPEORM_PORT} 33 | - TYPEORM_HOST=db 34 | frontend: 35 | container_name: frontend-angular-lesson-1 36 | build: 37 | context: ./frontend 38 | depends_on: 39 | - db 40 | - backend 41 | restart: unless-stopped 42 | ports: 43 | - '80:80' -------------------------------------------------------------------------------- /frontend/.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 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://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 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /frontend/.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 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | #STAGE 1 2 | FROM node:14-alpine AS build 3 | WORKDIR /usr/src/app 4 | COPY package*.json ./ 5 | RUN yarn install 6 | COPY . . 7 | RUN yarn build 8 | 9 | #STAGE 2 10 | FROM nginx:1.17.1-alpine 11 | COPY nginx.conf /etc/nginx/nginx.conf 12 | COPY --from=build /usr/src/app/dist/frontend /usr/share/nginx/html 13 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.16. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "cli": { 5 | "packageManager": "yarn" 6 | }, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "frontend": { 10 | "projectType": "application", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss", 14 | "skipTests": true, 15 | "changeDetection": "OnPush" 16 | }, 17 | "@schematics/angular:class": { 18 | "skipTests": true 19 | }, 20 | "@schematics/angular:directive": { 21 | "skipTests": true 22 | }, 23 | "@schematics/angular:guard": { 24 | "skipTests": true 25 | }, 26 | "@schematics/angular:interceptor": { 27 | "skipTests": true 28 | }, 29 | "@schematics/angular:pipe": { 30 | "skipTests": true 31 | }, 32 | "@schematics/angular:service": { 33 | "skipTests": true 34 | }, 35 | "@schematics/angular:application": { 36 | "strict": true 37 | } 38 | }, 39 | "root": "", 40 | "sourceRoot": "src", 41 | "prefix": "app", 42 | "architect": { 43 | "build": { 44 | "builder": "@angular-devkit/build-angular:browser", 45 | "options": { 46 | "outputPath": "dist/frontend", 47 | "index": "src/index.html", 48 | "main": "src/main.ts", 49 | "polyfills": "src/polyfills.ts", 50 | "tsConfig": "tsconfig.app.json", 51 | "inlineStyleLanguage": "scss", 52 | "assets": [ 53 | "src/favicon.ico", 54 | "src/assets" 55 | ], 56 | "styles": [ 57 | "src/styles.scss" 58 | ], 59 | "scripts": [] 60 | }, 61 | "configurations": { 62 | "production": { 63 | "budgets": [ 64 | { 65 | "type": "initial", 66 | "maximumWarning": "500kb", 67 | "maximumError": "1mb" 68 | }, 69 | { 70 | "type": "anyComponentStyle", 71 | "maximumWarning": "2kb", 72 | "maximumError": "4kb" 73 | } 74 | ], 75 | "fileReplacements": [ 76 | { 77 | "replace": "src/environments/environment.ts", 78 | "with": "src/environments/environment.prod.ts" 79 | } 80 | ], 81 | "outputHashing": "all" 82 | }, 83 | "development": { 84 | "buildOptimizer": false, 85 | "optimization": false, 86 | "vendorChunk": true, 87 | "extractLicenses": false, 88 | "sourceMap": true, 89 | "namedChunks": true 90 | } 91 | }, 92 | "defaultConfiguration": "production" 93 | }, 94 | "serve": { 95 | "builder": "@angular-devkit/build-angular:dev-server", 96 | "configurations": { 97 | "production": { 98 | "browserTarget": "frontend:build:production" 99 | }, 100 | "development": { 101 | "browserTarget": "frontend:build:development", 102 | "proxyConfig": "proxy.conf.json" 103 | } 104 | }, 105 | "defaultConfiguration": "development" 106 | }, 107 | "extract-i18n": { 108 | "builder": "@angular-devkit/build-angular:extract-i18n", 109 | "options": { 110 | "browserTarget": "frontend:build" 111 | } 112 | }, 113 | "test": { 114 | "builder": "@angular-devkit/build-angular:karma", 115 | "options": { 116 | "main": "src/test.ts", 117 | "polyfills": "src/polyfills.ts", 118 | "tsConfig": "tsconfig.spec.json", 119 | "karmaConfig": "karma.conf.js", 120 | "inlineStyleLanguage": "scss", 121 | "assets": [ 122 | "src/favicon.ico", 123 | "src/assets" 124 | ], 125 | "styles": [ 126 | "src/styles.scss" 127 | ], 128 | "scripts": [] 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | "defaultProject": "frontend" 135 | } 136 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/frontend'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | events{} 2 | http { 3 | include /etc/nginx/mime.types; 4 | server { 5 | listen 80; 6 | server_name localhost; 7 | root /usr/share/nginx/html; 8 | index index.html; 9 | location / { 10 | try_files $uri $uri/ /index.html; 11 | } 12 | location /graphql { 13 | proxy_pass http://backend:3001/graphql; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "~12.2.0", 14 | "@angular/common": "~12.2.0", 15 | "@angular/compiler": "~12.2.0", 16 | "@angular/core": "~12.2.0", 17 | "@angular/forms": "~12.2.0", 18 | "@angular/platform-browser": "~12.2.0", 19 | "@angular/platform-browser-dynamic": "~12.2.0", 20 | "@angular/router": "~12.2.0", 21 | "@apollo/client": "^3.0.0", 22 | "apollo-angular": "2.6.0", 23 | "graphql": "^15.0.0", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~12.2.16", 30 | "@angular/cli": "~12.2.16", 31 | "@angular/compiler-cli": "~12.2.0", 32 | "@types/jasmine": "~3.8.0", 33 | "@types/node": "^12.11.1", 34 | "jasmine-core": "~3.8.0", 35 | "karma": "~6.3.0", 36 | "karma-chrome-launcher": "~3.1.0", 37 | "karma-coverage": "~2.0.3", 38 | "karma-jasmine": "~4.0.0", 39 | "karma-jasmine-html-reporter": "~1.7.0", 40 | "typescript": "~4.3.5" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/graphql": { 3 | "target": "http://localhost:3001/graphql", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { PreloadAllModules, RouterModule, Routes } from '@angular/router' 3 | 4 | const routes: Routes = [ 5 | { path: '', pathMatch: 'full', loadChildren: () => import('./pages/main/main.module').then(m => m.MainModule) }, 6 | { path: 'users', loadChildren: () => import('./pages/users/users.module').then(m => m.UsersModule) }, 7 | { path: '**', redirectTo: '/' }, 8 | ] 9 | 10 | @NgModule({ 11 | imports: [ RouterModule.forRoot(routes, { 12 | preloadingStrategy: PreloadAllModules, 13 | }) ], 14 | exports: [ RouterModule ], 15 | }) 16 | export class AppRoutingModule { 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker/e6d00904eb34884ff574fe74150e9217350c7b5c/frontend/src/app/app.component.scss -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'frontend'; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { BrowserModule } from '@angular/platform-browser' 3 | 4 | import { AppRoutingModule } from './app-routing.module' 5 | import { AppComponent } from './app.component' 6 | import { NavComponent } from './components/nav.component' 7 | import { IconModule } from './components/icon/icon.module'; 8 | import { GraphQLModule } from './graphql.module'; 9 | import { HttpClientModule } from '@angular/common/http' 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | NavComponent 15 | ], 16 | imports: [ 17 | BrowserModule, 18 | AppRoutingModule, 19 | IconModule, 20 | GraphQLModule, 21 | HttpClientModule, 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /frontend/src/app/components/icon/icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core' 2 | 3 | @Component({ 4 | selector: 'app-icon', 5 | template: ` 6 | 7 | 8 | 9 | `, 10 | }) 11 | export class IconComponent { 12 | @Input('icon') icon?: string 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/components/icon/icon.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { CommonModule } from '@angular/common' 3 | import { IconComponent } from './icon.component' 4 | 5 | 6 | @NgModule({ 7 | declarations: [ IconComponent ], 8 | imports: [ 9 | CommonModule, 10 | ], 11 | exports: [ IconComponent ], 12 | }) 13 | export class IconModule { 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/components/nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core' 2 | import { IMenu } from '../interfaces' 3 | 4 | @Component({ 5 | selector: 'app-nav', 6 | template: ` 7 | 20 | `, 21 | }) 22 | export class NavComponent { 23 | menu?: IMenu[] 24 | 25 | constructor() { 26 | this.menu = [ 27 | { id: 1, title: 'Главная', href: '/' }, 28 | { id: 2, title: 'Пользователи', href: '/users', icon: 'bi bi-people' }, 29 | ] 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-form/user-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 2 | import { FormControl, FormGroup, Validators } from '@angular/forms' 3 | import { IUser } from '../../interfaces' 4 | 5 | @Component({ 6 | selector: 'app-user-form', 7 | template: ` 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 | 25 |
26 | `, 27 | }) 28 | export class UserFormComponent implements OnInit { 29 | @Input('user') user: IUser | null = null 30 | @Output() onSubmit: EventEmitter = new EventEmitter() 31 | form: FormGroup = new FormGroup({}) 32 | btnTitle?: string 33 | 34 | ngOnInit() { 35 | this.btnTitle = this.user ? 'Редактировать' : 'Создать' 36 | this.form = new FormGroup({ 37 | name: new FormControl(this.user?.name ?? '', [ Validators.required ]), 38 | email: new FormControl(this.user?.email ?? '', [ Validators.required, Validators.email ]), 39 | }) 40 | } 41 | 42 | submit() { 43 | this.onSubmit.emit({ 44 | ...this.form.value, 45 | id: this.user?.id, 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-form/user-form.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { CommonModule } from '@angular/common' 3 | import { UserFormComponent } from './user-form.component' 4 | import { ReactiveFormsModule } from '@angular/forms' 5 | 6 | 7 | @NgModule({ 8 | declarations: [ UserFormComponent ], 9 | imports: [ 10 | CommonModule, 11 | ReactiveFormsModule, 12 | ], 13 | exports: [ UserFormComponent ], 14 | }) 15 | export class UserFormModule { 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/graphql.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { APOLLO_OPTIONS } from 'apollo-angular' 3 | import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core' 4 | import { HttpLink } from 'apollo-angular/http' 5 | import { environment } from '../environments/environment' 6 | 7 | const uri = environment.graphql_uri // <-- add the URL of the GraphQL server here 8 | export function createApollo(httpLink: HttpLink): ApolloClientOptions { 9 | return { 10 | link: httpLink.create({ uri }), 11 | cache: new InMemoryCache(), 12 | } 13 | } 14 | 15 | @NgModule({ 16 | providers: [ 17 | { 18 | provide: APOLLO_OPTIONS, 19 | useFactory: createApollo, 20 | deps: [ HttpLink ], 21 | }, 22 | ], 23 | }) 24 | export class GraphQLModule { 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IMenu { 2 | id: number 3 | title: string 4 | href: string 5 | icon?: string 6 | } 7 | 8 | export interface IUser { 9 | id: number 10 | name: string 11 | email: string 12 | createdAt: Date 13 | updatedAt: Date 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/pages/main/main.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Главная страница

3 |
4 | -------------------------------------------------------------------------------- /frontend/src/app/pages/main/main.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker/e6d00904eb34884ff574fe74150e9217350c7b5c/frontend/src/app/pages/main/main.component.scss -------------------------------------------------------------------------------- /frontend/src/app/pages/main/main.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-main', 5 | templateUrl: './main.component.html', 6 | styleUrls: ['./main.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class MainComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit(): void { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/pages/main/main.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { CommonModule } from '@angular/common' 3 | import { MainComponent } from './main.component' 4 | import { RouterModule } from '@angular/router' 5 | 6 | 7 | @NgModule({ 8 | declarations: [ MainComponent ], 9 | imports: [ 10 | CommonModule, 11 | RouterModule.forChild([ 12 | { path: '', component: MainComponent }, 13 | ]), 14 | ], 15 | }) 16 | export class MainModule { 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/gql/create-user.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-angular' 2 | import { IUser } from '../../../interfaces' 3 | 4 | export interface ICREATE_USER { 5 | createUser: IUser 6 | } 7 | 8 | export const CREATE_USER = gql` 9 | mutation createUser($createUser: CreateUserInput!) { 10 | createUser(createUser: $createUser) { 11 | id 12 | } 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/gql/delete-user.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-angular' 2 | 3 | export interface IDELETE_USER { 4 | removeUser: number 5 | } 6 | 7 | export const DELETE_USER = gql` 8 | mutation deleteUser($id: Float!) { 9 | removeUser(id: $id) 10 | } 11 | ` 12 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/gql/get-all-users.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-angular' 2 | import { IUser } from '../../../interfaces' 3 | 4 | export interface IGET_ALL_USERS { 5 | getAllUsers: IUser[] 6 | } 7 | 8 | export const GET_ALL_USERS = gql` 9 | query getAllUsers { 10 | getAllUsers { 11 | id 12 | name 13 | } 14 | } 15 | ` 16 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/gql/get-one-user.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-angular' 2 | import { IUser } from '../../../interfaces' 3 | 4 | export interface IGET_ONE_USER { 5 | getOneUser: IUser 6 | } 7 | 8 | export const GET_ONE_USER = gql` 9 | query getOneUser($id: Float!) { 10 | getOneUser(id: $id) { 11 | id 12 | name 13 | email 14 | createdAt 15 | updatedAt 16 | } 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/gql/update-user.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-angular' 2 | import { IUser } from '../../../interfaces' 3 | 4 | export interface IUPDATE_USER { 5 | updateUser: IUser 6 | } 7 | 8 | export const UPDATE_USER = gql` 9 | mutation updateUser($updateUser: UpdateUserInput!) { 10 | updateUser(updateUser: $updateUser) { 11 | id 12 | } 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/user/user.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
{{ user.user.name }}
6 |

{{ user.user.email }}

7 |
8 | Создан: {{ user.user.createdAt | date: 'dd.MM.yyyy HH:mm' }}
9 | Обновлено: {{ user.user.updatedAt | date: 'dd.MM.yyyy HH:mm' }} 10 |
11 |
12 |
13 | 17 |
18 |
19 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 | Загрузка... 31 |
32 |
33 |
34 | 35 | 39 | 40 | 41 |
42 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/user/user.component.scss: -------------------------------------------------------------------------------- 1 | .card-width { 2 | width: 20rem; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/user/user.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core' 2 | import { Observable } from 'rxjs' 3 | import { IUser } from '../../../interfaces' 4 | import { UsersService } from '../users.service' 5 | import { ActivatedRoute, Router } from '@angular/router' 6 | 7 | @Component({ 8 | selector: 'app-user', 9 | templateUrl: './user.component.html', 10 | styleUrls: [ './user.component.scss' ], 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | }) 13 | export class UserComponent implements OnInit { 14 | imgSrc: string = 'https://png.pngtree.com/png-vector/20190710/ourmid/pngtree-user-vector-avatar-png-image_1541962.jpg' 15 | user$?: Observable<{ user: IUser, loading: boolean }> 16 | isChange: boolean = false 17 | 18 | constructor( 19 | private readonly usersService: UsersService, 20 | private readonly route: ActivatedRoute, 21 | private readonly cdr: ChangeDetectorRef, 22 | private readonly router: Router 23 | ) { 24 | } 25 | 26 | ngOnInit(): void { 27 | this.route.params.subscribe(({ id }) => { 28 | if (id) { 29 | this.user$ = this.usersService.getOneUser(id) 30 | this.cdr.detectChanges() 31 | } 32 | }) 33 | } 34 | 35 | change() { 36 | this.isChange = !this.isChange 37 | } 38 | 39 | delete(id: number) { 40 | if (confirm('Удалить пользователя?')) { 41 | this.usersService.deleteUser(id).subscribe(_ => { 42 | this.router.navigate(['/users']) 43 | }) 44 | } 45 | } 46 | 47 | onSubmit(user: IUser) { 48 | const {name, email, id} = user 49 | this.usersService.updateUser(id, name, email).subscribe(user => { 50 | if (user) { 51 | this.change() 52 | // this.router.navigate(['/users', user.id]) 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/users.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 9 |
10 | 11 | 21 | 22 | 23 |
24 |
Нет пользователей
25 |
26 |
27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/users.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker/e6d00904eb34884ff574fe74150e9217350c7b5c/frontend/src/app/pages/users/users.component.scss -------------------------------------------------------------------------------- /frontend/src/app/pages/users/users.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core' 2 | import { Observable } from 'rxjs' 3 | import { IUser } from '../../interfaces' 4 | import { UsersService } from './users.service' 5 | import { Router } from '@angular/router' 6 | 7 | @Component({ 8 | selector: 'app-users', 9 | templateUrl: './users.component.html', 10 | styleUrls: [ './users.component.scss' ], 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | }) 13 | export class UsersComponent implements OnInit { 14 | users$?: Observable 15 | isNew: boolean = false 16 | 17 | constructor( 18 | private readonly usersService: UsersService, 19 | private readonly router: Router, 20 | ) { 21 | } 22 | 23 | ngOnInit(): void { 24 | this.users$ = this.usersService.getAllUsers() 25 | } 26 | 27 | isNewTrigger(): void { 28 | this.isNew = !this.isNew 29 | } 30 | 31 | onSubmit(user: IUser) { 32 | const { email, name } = user 33 | this.usersService.createUser(name, email).subscribe(user => { 34 | if (user) { 35 | this.isNewTrigger() 36 | this.router.navigate(['/users', user.id]) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core' 2 | import { CommonModule } from '@angular/common' 3 | import { RouterModule } from '@angular/router' 4 | 5 | import { IconModule } from '../../components/icon/icon.module' 6 | import { UserComponent } from './user/user.component' 7 | import { UsersComponent } from './users.component' 8 | import { UserFormModule } from '../../components/user-form/user-form.module' 9 | 10 | 11 | @NgModule({ 12 | declarations: [ UsersComponent, UserComponent ], 13 | imports: [ 14 | CommonModule, 15 | RouterModule.forChild([ 16 | { 17 | path: '', component: UsersComponent, children: [ 18 | { path: ':id', component: UserComponent }, 19 | ], 20 | }, 21 | ]), 22 | IconModule, 23 | UserFormModule, 24 | ], 25 | }) 26 | export class UsersModule { 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/app/pages/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Apollo } from 'apollo-angular' 3 | import { Observable } from 'rxjs' 4 | import { map } from 'rxjs/operators' 5 | 6 | import { IUser } from '../../interfaces' 7 | import { GET_ALL_USERS, IGET_ALL_USERS } from './gql/get-all-users' 8 | import { GET_ONE_USER, IGET_ONE_USER } from './gql/get-one-user' 9 | import { DELETE_USER, IDELETE_USER } from './gql/delete-user' 10 | import { CREATE_USER, ICREATE_USER } from './gql/create-user' 11 | import { IUPDATE_USER, UPDATE_USER } from './gql/update-user' 12 | 13 | @Injectable({ 14 | providedIn: 'root', 15 | }) 16 | export class UsersService { 17 | 18 | constructor( 19 | private readonly apollo: Apollo, 20 | ) { 21 | } 22 | 23 | getAllUsers(): Observable { 24 | return this.apollo.watchQuery({ 25 | query: GET_ALL_USERS, 26 | }) 27 | .valueChanges 28 | .pipe( 29 | map(({ data }) => data?.getAllUsers), 30 | ) 31 | } 32 | 33 | getOneUser(id: string): Observable<{ user: IUser, loading: boolean }> { 34 | return this.apollo.watchQuery({ 35 | query: GET_ONE_USER, 36 | variables: { 37 | id: +id, 38 | }, 39 | }) 40 | .valueChanges 41 | .pipe( 42 | map(({ data, loading }) => ({ 43 | user: data?.getOneUser, 44 | loading, 45 | }), 46 | ), 47 | ) 48 | } 49 | 50 | deleteUser(id: number): Observable { 51 | return this.apollo.mutate({ 52 | mutation: DELETE_USER, 53 | variables: { 54 | id: +id, 55 | }, 56 | refetchQueries: [ 'getAllUsers' ], 57 | }).pipe(map(({ data }) => data?.removeUser)) 58 | } 59 | 60 | createUser(name: string, email: string): Observable { 61 | return this.apollo.mutate({ 62 | mutation: CREATE_USER, 63 | variables: { 64 | createUser: { 65 | name, email, 66 | }, 67 | }, 68 | refetchQueries: [ 'getAllUsers' ], 69 | }).pipe(map(({ data }) => data?.createUser)) 70 | } 71 | 72 | updateUser(id: number, name: string, email: string): Observable { 73 | return this.apollo.mutate({ 74 | mutation: UPDATE_USER, 75 | variables: { 76 | updateUser: { 77 | id, email, name, 78 | }, 79 | }, 80 | refetchQueries: [ 'getOneUser' ], 81 | }).pipe(map(({ data }) => data?.updateUser)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker/e6d00904eb34884ff574fe74150e9217350c7b5c/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | graphql_uri: '/graphql', 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` 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 | graphql_uri: '/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/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mogilevtsevdmitry/angular-nestjs-postgresql-typeorm-graphql-docker/e6d00904eb34884ff574fe74150e9217350c7b5c/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Frontend 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /frontend/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 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | //@import "bootstrap/dist/css/bootstrap.min.css"; 3 | //@import "bootstrap-icons/font/bootstrap-icons.css"; 4 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | { teardown: { destroyAfterEach: true }}, 22 | ); 23 | 24 | // Then we find all the tests. 25 | const context = require.context('./', true, /\.spec\.ts$/); 26 | // And load the modules. 27 | context.keys().map(context); 28 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "sourceMap": true, 11 | "declaration": false, 12 | "downlevelIteration": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "node", 15 | "importHelpers": true, 16 | "target": "es2017", 17 | "module": "es2020", 18 | "lib": [ 19 | "es2018", 20 | "dom", 21 | "esnext.asynciterable" 22 | ], 23 | "allowSyntheticDefaultImports": true 24 | }, 25 | "angularCompilerOptions": { 26 | "enableI18nLegacyMessageIdFormat": false, 27 | "strictInjectionParameters": true, 28 | "strictInputAccessModifiers": true, 29 | "strictTemplates": true 30 | } 31 | } -------------------------------------------------------------------------------- /frontend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bootstrap": "^5.1.3", 4 | "bootstrap-icons": "^1.8.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | bootstrap-icons@^1.8.1: 6 | version "1.8.1" 7 | resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.8.1.tgz#773c1625bcbf3e86090ea9da4386c2c6459c5582" 8 | integrity sha512-IXUqislddPJfwq6H+2nTkHyr9epO9h6u1AG0OZCx616w+TgzeoCjfmI3qJMQqt1J586gN2IxzB4M99Ip4sTZ1w== 9 | 10 | bootstrap@^5.1.3: 11 | version "5.1.3" 12 | resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34" 13 | integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q== 14 | --------------------------------------------------------------------------------