Repository: HaiderMalik12/nestjs-fundamentals Branch: main Commit: 0c095be3c70d Files: 2870 Total size: 2.0 MB Directory structure: gitextract_9ikg04a7/ ├── module-02-Creating-REST-APIS/ │ ├── 01-Lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ └── songs.module.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── 02-Lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ └── songs.module.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── 03-Lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ └── songs/ │ │ ├── songs.controller.spec.ts │ │ ├── songs.controller.ts │ │ ├── songs.module.ts │ │ ├── songs.service.spec.ts │ │ └── songs.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-03-middlewares-exception-filters-pipes/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ └── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ └── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ └── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-04/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── common/ │ │ │ └── middleware/ │ │ │ └── logger.middleware.ts │ │ ├── main.ts │ │ └── songs/ │ │ ├── dto/ │ │ │ └── create-song-dto.ts │ │ ├── songs.controller.spec.ts │ │ ├── songs.controller.ts │ │ ├── songs.module.ts │ │ ├── songs.service.spec.ts │ │ └── songs.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-04-dependency-injection/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-02/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── main.ts │ │ └── songs/ │ │ ├── dto/ │ │ │ └── create-song-dto.ts │ │ ├── songs.controller.spec.ts │ │ ├── songs.controller.ts │ │ ├── songs.module.ts │ │ ├── songs.service.spec.ts │ │ └── songs.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-05-connect-nestjs-to-postgress/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-04/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── main.ts │ │ └── songs/ │ │ ├── dto/ │ │ │ ├── create-song-dto.ts │ │ │ └── update-song-dto.ts │ │ ├── song.entity.ts │ │ ├── songs.controller.spec.ts │ │ ├── songs.controller.ts │ │ ├── songs.module.ts │ │ ├── songs.service.spec.ts │ │ └── songs.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-06-relations/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ └── user.entity.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-02-and-lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ └── artist.entity.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-07-authetication-and-authorization/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ └── auth.service.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ └── dto/ │ │ │ │ └── login.dto.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── login.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ └── jwt-strategy.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-04/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── login.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-05/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-06/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ ├── artist.entity.ts │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ └── artists.service.ts │ │ ├── auth/ │ │ │ ├── api-key-strategy.ts │ │ │ ├── artists-jwt-guard.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ ├── login.dto.ts │ │ │ │ └── validate-token.dto.ts │ │ │ ├── jwt-guard.ts │ │ │ ├── jwt-strategy.ts │ │ │ └── types.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-08-migrations-seeds-debugging/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ └── migrations/ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ └── 1685010456982-removed-phone.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ ├── db/ │ │ ├── data-source.ts │ │ ├── migrations/ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ └── 1685010456982-removed-phone.ts │ │ └── seeds/ │ │ └── seed-data.ts │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ ├── artist.entity.ts │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ └── artists.service.ts │ │ ├── auth/ │ │ │ ├── api-key-strategy.ts │ │ │ ├── artists-jwt-guard.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ ├── login.dto.ts │ │ │ │ └── validate-token.dto.ts │ │ │ ├── jwt-guard.ts │ │ │ ├── jwt-strategy.ts │ │ │ └── types.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── seed/ │ │ │ ├── seed.module.ts │ │ │ └── seed.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-09-application-configurations/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ │ └── 1685010456982-removed-phone.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ │ └── 1685010456982-removed-phone.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── env.validation.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ ├── db/ │ │ ├── data-source.ts │ │ ├── migrations/ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ └── 1685010456982-removed-phone.ts │ │ └── seeds/ │ │ └── seed-data.ts │ ├── env.validation.ts │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ ├── artist.entity.ts │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ └── artists.service.ts │ │ ├── auth/ │ │ │ ├── api-key-strategy.ts │ │ │ ├── artists-jwt-guard.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ ├── login.dto.ts │ │ │ │ └── validate-token.dto.ts │ │ │ ├── jwt-guard.ts │ │ │ ├── jwt-strategy.ts │ │ │ └── types.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── config/ │ │ │ └── configuration.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── seed/ │ │ │ ├── seed.module.ts │ │ │ └── seed.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── webpack-hmr.config.js ├── module-10-api-documentation-with-swagger/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ │ └── 1685010456982-removed-phone.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── env.validation.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── webpack-hmr.config.js │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ │ └── 1685010456982-removed-phone.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── env.validation.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── webpack-hmr.config.js │ ├── lesson-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ │ └── 1685010456982-removed-phone.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── env.validation.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── webpack-hmr.config.js │ └── lesson-04/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ ├── db/ │ │ ├── data-source.ts │ │ ├── migrations/ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ └── 1685010456982-removed-phone.ts │ │ └── seeds/ │ │ └── seed-data.ts │ ├── env.validation.ts │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ ├── artist.entity.ts │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ └── artists.service.ts │ │ ├── auth/ │ │ │ ├── api-key-strategy.ts │ │ │ ├── artists-jwt-guard.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ ├── login.dto.ts │ │ │ │ └── validate-token.dto.ts │ │ │ ├── jwt-guard.ts │ │ │ ├── jwt-strategy.ts │ │ │ └── types.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── config/ │ │ │ └── configuration.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── seed/ │ │ │ ├── seed.module.ts │ │ │ └── seed.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── webpack-hmr.config.js ├── module-11-mongodb/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ └── main.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02-and-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ └── schemas/ │ │ │ └── song.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-04/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── api.http │ │ ├── docker-compose.yml │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── schemas/ │ │ │ │ └── song.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-05/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── api.http │ │ ├── docker-compose.yml │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── schemas/ │ │ │ │ └── song.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-06/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── api.http │ ├── docker-compose.yml │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── albums/ │ │ │ ├── albums.controller.ts │ │ │ ├── albums.module.ts │ │ │ ├── albums.service.ts │ │ │ ├── dto/ │ │ │ │ └── create-album-dto.ts │ │ │ └── schemas/ │ │ │ └── album.schema.ts │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ └── songs/ │ │ ├── dto/ │ │ │ └── create-song-dto.ts │ │ ├── schemas/ │ │ │ └── song.ts │ │ ├── songs.controller.spec.ts │ │ ├── songs.controller.ts │ │ ├── songs.module.ts │ │ ├── songs.service.spec.ts │ │ └── songs.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-12-deploy-nestjs/ │ ├── deployment-finish/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── db/ │ │ │ ├── data-source.ts │ │ │ ├── migrations/ │ │ │ │ └── 1686309549613-init.ts │ │ │ └── seeds/ │ │ │ └── seed-data.ts │ │ ├── env.validation.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artist.entity.ts │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ └── artists.service.ts │ │ │ ├── auth/ │ │ │ │ ├── api-key-strategy.ts │ │ │ │ ├── artists-jwt-guard.ts │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── validate-token.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ ├── jwt-strategy.ts │ │ │ │ └── types.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── config/ │ │ │ │ └── configuration.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── seed/ │ │ │ │ ├── seed.module.ts │ │ │ │ └── seed.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── webpack-hmr.config.js │ └── deployment-starter/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ ├── db/ │ │ ├── data-source.ts │ │ ├── migrations/ │ │ │ ├── 1685010320827-my-migrations.ts │ │ │ └── 1685010456982-removed-phone.ts │ │ └── seeds/ │ │ └── seed-data.ts │ ├── env.validation.ts │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ ├── artist.entity.ts │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ └── artists.service.ts │ │ ├── auth/ │ │ │ ├── api-key-strategy.ts │ │ │ ├── artists-jwt-guard.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ ├── login.dto.ts │ │ │ │ └── validate-token.dto.ts │ │ │ ├── jwt-guard.ts │ │ │ ├── jwt-strategy.ts │ │ │ └── types.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── config/ │ │ │ └── configuration.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── seed/ │ │ │ ├── seed.module.ts │ │ │ └── seed.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── webpack-hmr.config.js ├── module-13-testing/ │ ├── end-to-end-testing/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.module.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── songs/ │ │ │ └── songs.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── jest-basics/ │ │ ├── .prettierrc │ │ ├── mock-function.spec.js │ │ ├── package.json │ │ ├── spyon-demon.spec.js │ │ ├── sum.js │ │ └── sum.test.js │ ├── unit-test-controller-and-service/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.module.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── unit-test-setup/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ └── song/ │ │ ├── dto/ │ │ │ ├── create-song-dto.ts │ │ │ └── update-song-dto.ts │ │ ├── song.controller.spec.ts │ │ ├── song.controller.ts │ │ ├── song.entity.ts │ │ ├── song.module.ts │ │ ├── song.service.spec.ts │ │ └── song.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-14-websocket/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ └── main.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── lesson-01/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── README.md │ │ │ ├── nest-cli.json │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── app.controller.spec.ts │ │ │ │ ├── app.controller.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── app.service.ts │ │ │ │ └── main.ts │ │ │ ├── test/ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ └── jest-e2e.json │ │ │ ├── tsconfig.build.json │ │ │ └── tsconfig.json │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── events/ │ │ │ │ ├── events.gateway.spec.ts │ │ │ │ ├── events.gateway.ts │ │ │ │ └── events.module.ts │ │ │ └── main.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── settings.json │ ├── README.md │ ├── client/ │ │ └── index.html │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── events/ │ │ │ ├── events.gateway.spec.ts │ │ │ ├── events.gateway.ts │ │ │ └── events.module.ts │ │ └── main.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-15-build-graphql-apis/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-03-and-04/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.resolver.spec.ts │ │ │ ├── song.resolver.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-05/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── generate-typings.ts │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── graphql.ts │ │ ├── main.ts │ │ └── song/ │ │ ├── dto/ │ │ │ ├── create-song-dto.ts │ │ │ └── update-song-dto.ts │ │ ├── song.controller.spec.ts │ │ ├── song.controller.ts │ │ ├── song.entity.ts │ │ ├── song.graphql │ │ ├── song.module.ts │ │ ├── song.resolver.spec.ts │ │ ├── song.resolver.ts │ │ ├── song.service.spec.ts │ │ └── song.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-16-authenticate-graphql-apis/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.graphql │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── login.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ └── jwt-strategy.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── lesson-01/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── .vscode/ │ │ │ │ └── launch.json │ │ │ ├── README.md │ │ │ ├── generate-typings.ts │ │ │ ├── nest-cli.json │ │ │ ├── package.json │ │ │ ├── rest-client.http │ │ │ ├── src/ │ │ │ │ ├── app.controller.spec.ts │ │ │ │ ├── app.controller.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── app.service.ts │ │ │ │ ├── artists/ │ │ │ │ │ └── artist.entity.ts │ │ │ │ ├── auth/ │ │ │ │ │ ├── auth.constants.ts │ │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ │ ├── auth.controller.ts │ │ │ │ │ ├── auth.graphql │ │ │ │ │ ├── auth.module.ts │ │ │ │ │ ├── auth.service.spec.ts │ │ │ │ │ ├── auth.service.ts │ │ │ │ │ ├── dto/ │ │ │ │ │ │ └── login.dto.ts │ │ │ │ │ ├── jwt-guard.ts │ │ │ │ │ └── jwt-strategy.ts │ │ │ │ ├── common/ │ │ │ │ │ ├── constatnts/ │ │ │ │ │ │ └── connection.ts │ │ │ │ │ ├── middleware/ │ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ │ └── providers/ │ │ │ │ │ └── DevConfigService.ts │ │ │ │ ├── graphql.ts │ │ │ │ ├── main.ts │ │ │ │ ├── playlists/ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ │ ├── playlist.entity.ts │ │ │ │ │ ├── playlists.controller.ts │ │ │ │ │ ├── playlists.module.ts │ │ │ │ │ └── playlists.service.ts │ │ │ │ ├── songs/ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ │ └── update-song-dto.ts │ │ │ │ │ ├── song.entity.ts │ │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ │ ├── songs.controller.ts │ │ │ │ │ ├── songs.module.ts │ │ │ │ │ ├── songs.service.spec.ts │ │ │ │ │ └── songs.service.ts │ │ │ │ └── users/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-user.dto.ts │ │ │ │ ├── user.entity.ts │ │ │ │ ├── users.module.ts │ │ │ │ ├── users.service.spec.ts │ │ │ │ └── users.service.ts │ │ │ ├── test/ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ └── jest-e2e.json │ │ │ ├── tsconfig.build.json │ │ │ └── tsconfig.json │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.graphql │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.resolver.spec.ts │ │ │ │ ├── auth.resolver.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── login.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ └── jwt-strategy.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-03/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ ├── generate-typings.ts │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .vscode/ │ │ │ └── launch.json │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── lesson-01/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── .vscode/ │ │ │ │ └── launch.json │ │ │ ├── README.md │ │ │ ├── generate-typings.ts │ │ │ ├── nest-cli.json │ │ │ ├── package.json │ │ │ ├── rest-client.http │ │ │ ├── src/ │ │ │ │ ├── app.controller.spec.ts │ │ │ │ ├── app.controller.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── app.service.ts │ │ │ │ ├── artists/ │ │ │ │ │ └── artist.entity.ts │ │ │ │ ├── auth/ │ │ │ │ │ ├── auth.constants.ts │ │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ │ ├── auth.controller.ts │ │ │ │ │ ├── auth.graphql │ │ │ │ │ ├── auth.module.ts │ │ │ │ │ ├── auth.service.spec.ts │ │ │ │ │ ├── auth.service.ts │ │ │ │ │ ├── dto/ │ │ │ │ │ │ └── login.dto.ts │ │ │ │ │ ├── jwt-guard.ts │ │ │ │ │ └── jwt-strategy.ts │ │ │ │ ├── common/ │ │ │ │ │ ├── constatnts/ │ │ │ │ │ │ └── connection.ts │ │ │ │ │ ├── middleware/ │ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ │ └── providers/ │ │ │ │ │ └── DevConfigService.ts │ │ │ │ ├── graphql.ts │ │ │ │ ├── main.ts │ │ │ │ ├── playlists/ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ │ ├── playlist.entity.ts │ │ │ │ │ ├── playlists.controller.ts │ │ │ │ │ ├── playlists.module.ts │ │ │ │ │ └── playlists.service.ts │ │ │ │ ├── songs/ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ │ └── update-song-dto.ts │ │ │ │ │ ├── song.entity.ts │ │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ │ ├── songs.controller.ts │ │ │ │ │ ├── songs.module.ts │ │ │ │ │ ├── songs.service.spec.ts │ │ │ │ │ └── songs.service.ts │ │ │ │ └── users/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-user.dto.ts │ │ │ │ ├── user.entity.ts │ │ │ │ ├── users.module.ts │ │ │ │ ├── users.service.spec.ts │ │ │ │ └── users.service.ts │ │ │ ├── test/ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ └── jest-e2e.json │ │ │ ├── tsconfig.build.json │ │ │ └── tsconfig.json │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── rest-client.http │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ └── artist.entity.ts │ │ │ ├── auth/ │ │ │ │ ├── auth.constants.ts │ │ │ │ ├── auth.controller.spec.ts │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.graphql │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.resolver.spec.ts │ │ │ │ ├── auth.resolver.ts │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── login.dto.ts │ │ │ │ ├── jwt-guard.ts │ │ │ │ └── jwt-strategy.ts │ │ │ ├── common/ │ │ │ │ ├── constatnts/ │ │ │ │ │ └── connection.ts │ │ │ │ ├── middleware/ │ │ │ │ │ └── logger.middleware.ts │ │ │ │ └── providers/ │ │ │ │ └── DevConfigService.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ ├── playlists/ │ │ │ │ ├── dto/ │ │ │ │ │ └── create-playlist.dto.ts │ │ │ │ ├── playlist.entity.ts │ │ │ │ ├── playlists.controller.ts │ │ │ │ ├── playlists.module.ts │ │ │ │ └── playlists.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song-dto.ts │ │ │ │ │ └── update-song-dto.ts │ │ │ │ ├── song.entity.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ └── create-user.dto.ts │ │ │ ├── user.entity.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── nest-cli.json │ ├── package.json │ ├── rest-client.http │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── artists/ │ │ │ └── artist.entity.ts │ │ ├── auth/ │ │ │ ├── auth.constants.ts │ │ │ ├── auth.controller.spec.ts │ │ │ ├── auth.controller.ts │ │ │ ├── auth.graphql │ │ │ ├── auth.module.ts │ │ │ ├── auth.resolver.spec.ts │ │ │ ├── auth.resolver.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto/ │ │ │ │ └── login.dto.ts │ │ │ ├── gql-auth-guard.ts │ │ │ ├── jwt-guard.ts │ │ │ └── jwt-strategy.ts │ │ ├── common/ │ │ │ ├── constatnts/ │ │ │ │ └── connection.ts │ │ │ ├── middleware/ │ │ │ │ └── logger.middleware.ts │ │ │ └── providers/ │ │ │ └── DevConfigService.ts │ │ ├── graphql.ts │ │ ├── main.ts │ │ ├── playlists/ │ │ │ ├── dto/ │ │ │ │ └── create-playlist.dto.ts │ │ │ ├── playlist.entity.ts │ │ │ ├── playlists.controller.ts │ │ │ ├── playlists.module.ts │ │ │ └── playlists.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-17-subscription/ │ ├── subscription-finish/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.resolver.spec.ts │ │ │ ├── song.resolver.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── subscription-starter/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── generate-typings.ts │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── graphql.ts │ │ ├── main.ts │ │ └── song/ │ │ ├── dto/ │ │ │ ├── create-song-dto.ts │ │ │ └── update-song-dto.ts │ │ ├── song.controller.spec.ts │ │ ├── song.controller.ts │ │ ├── song.entity.ts │ │ ├── song.graphql │ │ ├── song.module.ts │ │ ├── song.resolver.spec.ts │ │ ├── song.resolver.ts │ │ ├── song.service.spec.ts │ │ └── song.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-18-testing-graphql-apis/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.resolver.spec.ts │ │ │ ├── song.resolver.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-02/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── generate-typings.ts │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── graphql.ts │ │ ├── main.ts │ │ └── song/ │ │ ├── dto/ │ │ │ ├── create-song-dto.ts │ │ │ └── update-song-dto.ts │ │ ├── song.controller.spec.ts │ │ ├── song.controller.ts │ │ ├── song.entity.ts │ │ ├── song.graphql │ │ ├── song.module.ts │ │ ├── song.resolver.spec.ts │ │ ├── song.resolver.ts │ │ ├── song.service.spec.ts │ │ └── song.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ ├── jest-e2e.json │ │ └── song/ │ │ └── song.e2e-spec.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-19-graphql-advanced-concepts/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── generate-typings.ts │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── graphql.ts │ │ │ ├── main.ts │ │ │ └── song/ │ │ │ ├── dto/ │ │ │ │ ├── create-song-dto.ts │ │ │ │ └── update-song-dto.ts │ │ │ ├── song.controller.spec.ts │ │ │ ├── song.controller.ts │ │ │ ├── song.entity.ts │ │ │ ├── song.graphql │ │ │ ├── song.module.ts │ │ │ ├── song.resolver.spec.ts │ │ │ ├── song.resolver.ts │ │ │ ├── song.service.spec.ts │ │ │ └── song.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02-dataloaders/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.module.ts │ │ │ ├── main.ts │ │ │ ├── posts/ │ │ │ │ ├── post.entity.ts │ │ │ │ ├── posts.module.ts │ │ │ │ ├── posts.resolver.ts │ │ │ │ └── posts.service.ts │ │ │ ├── schema.gql │ │ │ ├── users/ │ │ │ │ ├── user.entity.ts │ │ │ │ ├── users.loader.ts │ │ │ │ ├── users.module.ts │ │ │ │ ├── users.resolver.ts │ │ │ │ └── users.service.ts │ │ │ └── util.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-03-fetching-external-api/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── api.http │ ├── docker-compose.yml │ ├── generate-typings.ts │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── album/ │ │ │ ├── album.controller.ts │ │ │ ├── album.module.ts │ │ │ ├── album.service.ts │ │ │ ├── dto/ │ │ │ │ └── create-album-dto.ts │ │ │ └── schemas/ │ │ │ └── album.schema.ts │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── graphql.ts │ │ ├── main.ts │ │ ├── product/ │ │ │ ├── product-owner.resolver.ts │ │ │ ├── product.graphql │ │ │ ├── product.module.ts │ │ │ ├── product.resolver.spec.ts │ │ │ ├── product.resolver.ts │ │ │ ├── product.service.spec.ts │ │ │ ├── product.service.ts │ │ │ └── schemas/ │ │ │ └── product.schema.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ └── create-song-dto.ts │ │ │ ├── schemas/ │ │ │ │ └── song.schema.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── todo/ │ │ │ ├── todo.graphql │ │ │ ├── todo.module.ts │ │ │ ├── todo.resolver.spec.ts │ │ │ ├── todo.resolver.ts │ │ │ ├── todo.service.spec.ts │ │ │ └── todo.service.ts │ │ └── user/ │ │ ├── schemas/ │ │ │ └── user.schema.ts │ │ ├── user.module.ts │ │ └── user.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── module-20-prisma-integration/ │ ├── lesson-01/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ └── main.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-02/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ └── main.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-03/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ └── prisma.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-04-and-05/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── http-client.http │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── main.ts │ │ │ ├── prisma.service.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song.dto.ts │ │ │ │ └── update-song.dto.ts │ │ │ ├── entities/ │ │ │ │ └── song.entity.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-06/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── http-client.http │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801082432_add_artists/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ ├── artists.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-artist.dto.ts │ │ │ │ └── update-artist.dto.ts │ │ │ ├── main.ts │ │ │ ├── prisma.service.ts │ │ │ └── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song.dto.ts │ │ │ │ └── update-song.dto.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-07/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── http-client.http │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801082432_add_artists/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801091013_one_to_one/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── artists/ │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ ├── artists.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-artist.dto.ts │ │ │ │ └── update-artist.dto.ts │ │ │ ├── main.ts │ │ │ ├── prisma.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song.dto.ts │ │ │ │ │ └── update-song.dto.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ ├── create-user.dto.ts │ │ │ │ └── update-user.dto.ts │ │ │ ├── users.controller.spec.ts │ │ │ ├── users.controller.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-08/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── http-client.http │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801082432_add_artists/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801091013_one_to_one/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802074328_many_to_many/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802081727_nested_queries/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802083400_default_value_for_type/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── applications/ │ │ │ │ ├── applications.controller.spec.ts │ │ │ │ ├── applications.controller.ts │ │ │ │ ├── applications.module.ts │ │ │ │ ├── applications.service.spec.ts │ │ │ │ ├── applications.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-application.dto.ts │ │ │ │ └── update-application.dto.ts │ │ │ ├── artists/ │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ ├── artists.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-artist.dto.ts │ │ │ │ └── update-artist.dto.ts │ │ │ ├── main.ts │ │ │ ├── posts/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-post.dto.ts │ │ │ │ │ └── update-post.dto.ts │ │ │ │ ├── posts.controller.spec.ts │ │ │ │ ├── posts.controller.ts │ │ │ │ ├── posts.module.ts │ │ │ │ ├── posts.service.spec.ts │ │ │ │ └── posts.service.ts │ │ │ ├── prisma.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song.dto.ts │ │ │ │ │ └── update-song.dto.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ ├── create-user.dto.ts │ │ │ │ └── update-user.dto.ts │ │ │ ├── users.controller.spec.ts │ │ │ ├── users.controller.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── lesson-09/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── http-client.http │ │ ├── nest-cli.json │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230730081110_init/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801082432_add_artists/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230801091013_one_to_one/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802074328_many_to_many/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802081727_nested_queries/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230802083400_default_value_for_type/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── src/ │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── applications/ │ │ │ │ ├── applications.controller.spec.ts │ │ │ │ ├── applications.controller.ts │ │ │ │ ├── applications.module.ts │ │ │ │ ├── applications.service.spec.ts │ │ │ │ ├── applications.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-application.dto.ts │ │ │ │ └── update-application.dto.ts │ │ │ ├── artists/ │ │ │ │ ├── artists.controller.spec.ts │ │ │ │ ├── artists.controller.ts │ │ │ │ ├── artists.module.ts │ │ │ │ ├── artists.service.spec.ts │ │ │ │ ├── artists.service.ts │ │ │ │ └── dto/ │ │ │ │ ├── create-artist.dto.ts │ │ │ │ └── update-artist.dto.ts │ │ │ ├── main.ts │ │ │ ├── posts/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-post.dto.ts │ │ │ │ │ └── update-post.dto.ts │ │ │ │ ├── posts.controller.spec.ts │ │ │ │ ├── posts.controller.ts │ │ │ │ ├── posts.module.ts │ │ │ │ ├── posts.service.spec.ts │ │ │ │ └── posts.service.ts │ │ │ ├── prisma.service.ts │ │ │ ├── songs/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── create-song.dto.ts │ │ │ │ │ └── update-song.dto.ts │ │ │ │ ├── songs.controller.spec.ts │ │ │ │ ├── songs.controller.ts │ │ │ │ ├── songs.module.ts │ │ │ │ ├── songs.service.spec.ts │ │ │ │ └── songs.service.ts │ │ │ └── users/ │ │ │ ├── dto/ │ │ │ │ ├── create-user.dto.ts │ │ │ │ └── update-user.dto.ts │ │ │ ├── users.controller.spec.ts │ │ │ ├── users.controller.ts │ │ │ ├── users.module.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ ├── jest-e2e.json │ │ │ └── song/ │ │ │ └── song.e2e-spec.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── lesson-11-interactive-transactions/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── prisma/ │ │ ├── migrations/ │ │ │ ├── 20230730081110_init/ │ │ │ │ └── migration.sql │ │ │ ├── 20230801082432_add_artists/ │ │ │ │ └── migration.sql │ │ │ ├── 20230801091013_one_to_one/ │ │ │ │ └── migration.sql │ │ │ ├── 20230802074328_many_to_many/ │ │ │ │ └── migration.sql │ │ │ ├── 20230802081727_nested_queries/ │ │ │ │ └── migration.sql │ │ │ ├── 20230802083400_default_value_for_type/ │ │ │ │ └── migration.sql │ │ │ ├── 20230804090722_add_account/ │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ ├── src/ │ │ ├── accounts/ │ │ │ ├── accounts.controller.spec.ts │ │ │ ├── accounts.controller.ts │ │ │ ├── accounts.module.ts │ │ │ ├── accounts.service.spec.ts │ │ │ ├── accounts.service.ts │ │ │ └── dto/ │ │ │ ├── create-account.dto.ts │ │ │ ├── transfer-account.dto.ts │ │ │ └── update-account.dto.ts │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── applications/ │ │ │ ├── applications.controller.spec.ts │ │ │ ├── applications.controller.ts │ │ │ ├── applications.module.ts │ │ │ ├── applications.service.spec.ts │ │ │ ├── applications.service.ts │ │ │ └── dto/ │ │ │ ├── create-application.dto.ts │ │ │ └── update-application.dto.ts │ │ ├── artists/ │ │ │ ├── artists.controller.spec.ts │ │ │ ├── artists.controller.ts │ │ │ ├── artists.module.ts │ │ │ ├── artists.service.spec.ts │ │ │ ├── artists.service.ts │ │ │ └── dto/ │ │ │ ├── create-artist.dto.ts │ │ │ └── update-artist.dto.ts │ │ ├── main.ts │ │ ├── posts/ │ │ │ ├── dto/ │ │ │ │ ├── create-post.dto.ts │ │ │ │ └── update-post.dto.ts │ │ │ ├── posts.controller.spec.ts │ │ │ ├── posts.controller.ts │ │ │ ├── posts.module.ts │ │ │ ├── posts.service.spec.ts │ │ │ └── posts.service.ts │ │ ├── prisma.service.ts │ │ ├── songs/ │ │ │ ├── dto/ │ │ │ │ ├── create-song.dto.ts │ │ │ │ └── update-song.dto.ts │ │ │ ├── songs.controller.spec.ts │ │ │ ├── songs.controller.ts │ │ │ ├── songs.module.ts │ │ │ ├── songs.service.spec.ts │ │ │ └── songs.service.ts │ │ └── users/ │ │ ├── dto/ │ │ │ ├── create-user.dto.ts │ │ │ └── update-user.dto.ts │ │ ├── users.controller.spec.ts │ │ ├── users.controller.ts │ │ ├── users.module.ts │ │ ├── users.service.spec.ts │ │ └── users.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ ├── jest-e2e.json │ │ └── song/ │ │ └── song.e2e-spec.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── module-21-nestjs-advanced-concepts/ ├── 0-starter/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ └── main.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-01-file-upload/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ └── main.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-02-custom-decorator/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-03-Task-scheduling/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── task/ │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-04-cookies/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── task/ │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-05-queues/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ ├── task/ │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-06-event-emitter/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── docker-compose.yml │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── audio/ │ │ │ ├── audio-converted-listener.ts │ │ │ ├── audio.controller.spec.ts │ │ │ ├── audio.controller.ts │ │ │ ├── audio.module.ts │ │ │ ├── audio.processor.ts │ │ │ └── events/ │ │ │ └── audio-converted-event.ts │ │ ├── main.ts │ │ ├── task/ │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── lesson-07-streaming/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── docker-compose.yml │ ├── http-client.http │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── audio/ │ │ │ ├── audio-converted-listener.ts │ │ │ ├── audio.controller.spec.ts │ │ │ ├── audio.controller.ts │ │ │ ├── audio.module.ts │ │ │ ├── audio.processor.ts │ │ │ └── events/ │ │ │ └── audio-converted-event.ts │ │ ├── file/ │ │ │ ├── file.controller.spec.ts │ │ │ └── file.controller.ts │ │ ├── main.ts │ │ ├── task/ │ │ │ ├── task.service.spec.ts │ │ │ └── task.service.ts │ │ ├── user.decorator.ts │ │ └── user.entity.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json └── lesson-08-session/ ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── docker-compose.yml ├── http-client.http ├── nest-cli.json ├── package.json ├── src/ │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── audio/ │ │ ├── audio-converted-listener.ts │ │ ├── audio.controller.spec.ts │ │ ├── audio.controller.ts │ │ ├── audio.module.ts │ │ ├── audio.processor.ts │ │ └── events/ │ │ └── audio-converted-event.ts │ ├── file/ │ │ ├── file.controller.spec.ts │ │ └── file.controller.ts │ ├── main.ts │ ├── task/ │ │ ├── task.service.spec.ts │ │ └── task.service.ts │ ├── user.decorator.ts │ └── user.entity.ts ├── test/ │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; @Module({}) export class SongsModule {} ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-02-Creating-REST-APIS/01-Lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post } from '@nestjs/common'; @Controller('songs') export class SongsController { @Post() create() { return 'create a new song endpoint'; } @Get() findAll() { return 'find all songs endpoint'; } @Get(':id') findOne() { return 'fetch song on the based on id'; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; @Module({ controllers: [SongsController] }) export class SongsModule {} ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-02-Creating-REST-APIS/02-Lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post } from '@nestjs/common'; import { SongsService } from './songs.service'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create() { return this.songsService.create('Animals by Martin Garrix'); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne() { return 'fetch song on the based on id'; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService] }) export class SongsModule {} ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db return this.songs; } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-02-Creating-REST-APIS/03-Lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post } from '@nestjs/common'; import { SongsService } from './songs.service'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create() { return this.songsService.create('Animals by Martin Garrix'); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne() { return 'fetch song on the based on id'; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService] }) export class SongsModule {} ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db return this.songs; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, } from '@nestjs/common'; import { SongsService } from './songs.service'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create() { return this.songsService.create('Animals by Martin Garrix'); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne() { return 'fetch song on the based on id'; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService] }) export class SongsModule {} ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create() { return this.songsService.create('Animals by Martin Garrix'); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService] }) export class SongsModule {} ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3001/songs Content-Type: application/json { "title": "lasting lover", "artists": ["sigla"], "releasedDate" : "2022-09-29", "duration" :"02:34" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title: string; @IsNotEmpty() @IsArray() @IsString() readonly artists: string[]; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO) { return this.songsService.create(createSongDTO); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService] }) export class SongsModule {} ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-03-middlewares-exception-filters-pipes/lesson-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-04-dependency-injection/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-04-dependency-injection/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-04-dependency-injection/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-04-dependency-injection/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-04-dependency-injection/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-04-dependency-injection/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-04-dependency-injection/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "lasting lover", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2022-09-29", "duration" :"02:34" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-04-dependency-injection/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-04-dependency-injection/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { DevConfigService } from './common/providers/DevConfigService'; const devConfig = { port: 3000 }; const proConfig = { port: 4000 }; @Module({ imports: [SongsModule], controllers: [AppController], providers: [ AppService, { provide: DevConfigService, useClass: DevConfigService, }, { provide: 'CONFIG', useFactory: () => { return process.env.NODE_ENV === 'development' ? devConfig : proConfig; }, }, ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { constructor( private devConfigService: DevConfigService, @Inject('CONFIG') private config: { port: string }, ) {} getHello(): string { return `Hello I am learning Nest.js Fundamentals ${this.devConfigService.getDBHOST()} PORT = ${ this.config.port }`; } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-04-dependency-injection/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; } ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Connection } from 'src/common/constatnts/connection'; @Controller('songs') export class SongsController { constructor( private songsService: SongsService, @Inject('CONNECTION') private connection: Connection, ) { console.log( `THIS IS CONNECTION STRING ${this.connection.CONNECTION_STRING}`, ); } @Post() create(@Body() createSongDTO: CreateSongDTO) { return this.songsService.create(createSongDTO); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { connection } from 'src/common/constatnts/connection'; const mockSongsService = { findAll() { return [{ id: 1, title: 'Lasting lover', artists: ['Siagla'] }]; }, }; @Module({ controllers: [SongsController], providers: [ SongsService, // { // provide: SongsService, // useClass: SongsService, // }, // { // provide: SongsService, // useValue: mockSongsService, // }, { provide: 'CONNECTION', useValue: connection, }, ], }) export class SongsModule {} ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-04-dependency-injection/lesson-01/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB // throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-04-dependency-injection/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-04-dependency-injection/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-04-dependency-injection/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-04-dependency-injection/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-04-dependency-injection/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-04-dependency-injection/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-04-dependency-injection/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-04-dependency-injection/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-04-dependency-injection/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-04-dependency-injection/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-04-dependency-injection/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "lasting lover", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2022-09-29", "duration" :"02:34" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-04-dependency-injection/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-04-dependency-injection/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { DevConfigService } from './common/providers/DevConfigService'; const devConfig = { port: 3000 }; const proConfig = { port: 4000 }; @Module({ imports: [SongsModule], controllers: [AppController], providers: [ AppService, { provide: DevConfigService, useClass: DevConfigService, }, { provide: 'CONFIG', useFactory: () => { return process.env.NODE_ENV === 'development' ? devConfig : proConfig; }, }, ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { constructor( private devConfigService: DevConfigService, @Inject('CONFIG') private config: { port: string }, ) {} getHello(): string { return `Hello I am learning Nest.js Fundamentals ${this.devConfigService.getDBHOST()} PORT = ${ this.config.port }`; } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-04-dependency-injection/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; } ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Connection } from 'src/common/constatnts/connection'; @Controller({ path: 'songs', scope: Scope.REQUEST }) export class SongsController { constructor( private songsService: SongsService, @Inject('CONNECTION') private connection: Connection, ) { console.log( `THIS IS CONNECTION STRING ${this.connection.CONNECTION_STRING}`, ); } @Post() create(@Body() createSongDTO: CreateSongDTO) { return this.songsService.create(createSongDTO); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { connection } from 'src/common/constatnts/connection'; const mockSongsService = { findAll() { return [{ id: 1, title: 'Lasting lover', artists: ['Siagla'] }]; }, }; @Module({ controllers: [SongsController], providers: [ SongsService, // { // provide: SongsService, // useClass: SongsService, // }, // { // provide: SongsService, // useValue: mockSongsService, // }, { provide: 'CONNECTION', useValue: connection, }, ], }) export class SongsModule {} ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-04-dependency-injection/lesson-02/src/songs/songs.service.ts ================================================ import { Injectable, Scope } from '@nestjs/common'; @Injectable({ scope: Scope.TRANSIENT, }) export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB // throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-04-dependency-injection/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-04-dependency-injection/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-04-dependency-injection/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-04-dependency-injection/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "lasting lover", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2022-09-29", "duration" :"02:34" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [], synchronize: true, }), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(private dataSource: DataSource) { console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO) { return this.songsService.create(createSongDTO); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB // throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "lasting lover", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2022-09-29", "duration" :"02:34" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/1 ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; // import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song], synchronize: true, }), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column('varchar', { array: true }) artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO) { return this.songsService.create(createSongDTO); } @Get() findAll() { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ) { return `fetch song on the based on id ${typeof id}`; } @Put(':id') update() { return 'update song on the based on id'; } @Delete(':id') delete() { return 'delete song on the based on id'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class SongsService { // local db // local array private readonly songs = []; create(song) { // Save the song in the database this.songs.push(song); return this.songs; } findAll() { // fetch the songs from the db // Errors comes while fetching the data from DB // throw new Error('Error in Db whil fetching record'); return this.songs; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "lasting lover 2", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2022-09-29", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; // import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song], synchronize: true, }), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsString({ each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column('varchar', { array: true }) artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll(): Promise { try { return this.songsService.findAll(); } catch (e) { throw new HttpException( 'server error', HttpStatus.INTERNAL_SERVER_ERROR, { cause: e, }, ); } } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, ) {} create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "New Song 5", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; // import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song], synchronize: true, }), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsString({ each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column('varchar', { array: true }) artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, ) {} create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-05-connect-nestjs-to-postgress/lesson-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-06-relations/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-06-relations/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-06-relations/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-06-relations/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-06-relations/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-06-relations/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-06-relations/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "New Song 5", "artists": [ "Siagla", "Martin", "John" ], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ================================================ FILE: module-06-relations/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-06-relations/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-06-relations/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; // import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User], synchronize: true, }), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-06-relations/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-06-relations/lesson-01/src/artists/artist.entity.ts ================================================ import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; } ================================================ FILE: module-06-relations/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-06-relations/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-06-relations/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-06-relations/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-06-relations/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsString({ each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-06-relations/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsString({ each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-06-relations/lesson-01/src/songs/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column('varchar', { array: true }) artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; } ================================================ FILE: module-06-relations/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-06-relations/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-06-relations/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-06-relations/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-06-relations/lesson-01/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, ) {} create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-06-relations/lesson-01/src/users/user.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column() email: string; @Column() password: string; } ================================================ FILE: module-06-relations/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-06-relations/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-06-relations/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-06-relations/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/src/users/user.entity.ts ================================================ import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column() email: string; @Column() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-06-relations/lesson-02-and-lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3", "bcryptjs": "^2.4.3" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "@types/bcryptjs": "^2.4.2" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-01', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; @Controller('auth') export class AuthController { constructor(private userService: UsersService) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; @Module({ imports: [UsersModule], providers: [AuthService], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AuthService {} ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3", "bcryptjs": "^2.4.3", "@nestjs/passport": "^9.0.3", "passport": "^0.6.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "@types/bcryptjs": "^2.4.2" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-01', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; @Module({ imports: [UsersModule], providers: [AuthService], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; @Injectable() export class AuthService { constructor(private userService: UsersService) {} async login(loginDTO: LoginDTO): Promise { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); // 2. if (passwordMatched) { //3 delete user.password; // 4. return user; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3", "bcryptjs": "^2.4.3", "@nestjs/passport": "^9.0.3", "passport": "^0.6.0", "@nestjs/jwt": "^10.0.3", "passport-jwt": "^4.0.1" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "@types/bcryptjs": "^2.4.2", "@types/passport-jwt": "^3.0.8" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-01', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3", "bcryptjs": "^2.4.3", "@nestjs/passport": "^9.0.3", "passport": "^0.6.0", "@nestjs/jwt": "^10.0.3", "passport-jwt": "^4.0.1" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "@types/bcryptjs": "^2.4.2", "@types/passport-jwt": "^3.0.8" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-01', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { PayloadType } from './types'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-05/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-05/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-05/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.10.0", "typeorm": "^0.3.15", "nestjs-typeorm-paginate": "^4.0.3", "bcryptjs": "^2.4.3", "@nestjs/passport": "^9.0.3", "passport": "^0.6.0", "@nestjs/jwt": "^10.0.3", "passport-jwt": "^4.0.1", "speakeasy": "^2.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "@types/bcryptjs": "^2.4.2", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-01', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-05/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-05/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-07-authetication-and-authorization/lesson-06/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-07-authetication-and-authorization/lesson-06/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-07-authetication-and-authorization/lesson-06/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-02', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-07-authetication-and-authorization/lesson-06/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-07-authetication-and-authorization/lesson-06/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'spotify-clone-02', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/db/data-source.ts ================================================ import { DataSource, DataSourceOptions } from 'typeorm'; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: 'localhost', port: 5432, username: 'postgres', password: 'root', database: 'spotify-clone', entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { dataSourceOptions } from 'db/data-source'; @Module({ imports: [ TypeOrmModule.forRoot(dataSourceOptions), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/db/data-source.ts ================================================ import { DataSource, DataSourceOptions } from 'typeorm'; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: 'localhost', port: 5432, username: 'postgres', password: 'root', database: 'spotify-clone', entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { dataSourceOptions } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; @Module({ imports: [ TypeOrmModule.forRoot(dataSourceOptions), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); await app.listen(3000); } bootstrap(); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-08-migrations-seeds-debugging/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-09-application-configurations/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-09-application-configurations/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-09-application-configurations/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-09-application-configurations/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-09-application-configurations/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-09-application-configurations/lesson-01/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: ['dist/**/*.entity.js'], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-09-application-configurations/lesson-01/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-09-application-configurations/lesson-01/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-09-application-configurations/lesson-01/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-09-application-configurations/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-09-application-configurations/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0", "@nestjs/config": "^2.3.2" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-09-application-configurations/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkwNzIsImV4cCI6MTY4NDk5NTQ3Mn0.wYEhyDMor-bs2_Ghmcno0mEJqkqkP9XwOrKUDf0YAZc ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-09-application-configurations/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-09-application-configurations/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-09-application-configurations/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-09-application-configurations/lesson-01/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-09-application-configurations/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-09-application-configurations/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-09-application-configurations/lesson-01/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-09-application-configurations/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const configService = app.get(ConfigService); await app.listen(configService.get('port')); } bootstrap(); ================================================ FILE: module-09-application-configurations/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-09-application-configurations/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-09-application-configurations/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-09-application-configurations/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-09-application-configurations/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-09-application-configurations/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-09-application-configurations/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-09-application-configurations/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-09-application-configurations/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-09-application-configurations/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-09-application-configurations/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-09-application-configurations/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-09-application-configurations/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-09-application-configurations/lesson-02/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-09-application-configurations/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-09-application-configurations/lesson-02/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: ['dist/**/*.entity.js'], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-09-application-configurations/lesson-02/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-09-application-configurations/lesson-02/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-09-application-configurations/lesson-02/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-09-application-configurations/lesson-02/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-09-application-configurations/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-09-application-configurations/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0", "@nestjs/config": "^2.3.2" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-09-application-configurations/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-09-application-configurations/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-09-application-configurations/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-09-application-configurations/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-09-application-configurations/lesson-02/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-09-application-configurations/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-09-application-configurations/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-09-application-configurations/lesson-02/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-09-application-configurations/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const configService = app.get(ConfigService); await app.listen(configService.get('port')); } bootstrap(); ================================================ FILE: module-09-application-configurations/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-09-application-configurations/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-09-application-configurations/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-09-application-configurations/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-09-application-configurations/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-09-application-configurations/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-09-application-configurations/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-09-application-configurations/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-09-application-configurations/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-09-application-configurations/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-09-application-configurations/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-09-application-configurations/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-09-application-configurations/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-09-application-configurations/lesson-03/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-09-application-configurations/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-09-application-configurations/lesson-03/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: ['dist/**/*.entity.js'], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-09-application-configurations/lesson-03/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-09-application-configurations/lesson-03/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-09-application-configurations/lesson-03/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-09-application-configurations/lesson-03/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-09-application-configurations/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-09-application-configurations/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-09-application-configurations/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-09-application-configurations/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-09-application-configurations/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-09-application-configurations/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-09-application-configurations/lesson-03/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-09-application-configurations/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-09-application-configurations/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-09-application-configurations/lesson-03/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-09-application-configurations/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-09-application-configurations/lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-09-application-configurations/lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-09-application-configurations/lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-09-application-configurations/lesson-03/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-09-application-configurations/lesson-03/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-09-application-configurations/lesson-03/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-09-application-configurations/lesson-03/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-09-application-configurations/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-09-application-configurations/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-09-application-configurations/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-09-application-configurations/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-09-application-configurations/lesson-03/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: ['dist/**/*.entity.js'], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-01/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: [User, Playlist, Artist, Song], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('auth') @ApiTags('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'It will return the user in the response', }) signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-02/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: [User, Playlist, Artist, Song], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "plugins": [ { "name": "@nestjs/swagger", "options": { "introspectComments": true } } ] } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('auth') @ApiTags('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'It will return the user in the response', }) signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/users/user.entity.ts ================================================ import { ApiProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @ApiProperty({ example: 'Jane', description: 'provide the firstName of the user', }) @Column() firstName: string; @ApiProperty({ example: 'Doe', description: 'provide the lastName of the user', }) @Column() lastName: string; @ApiProperty({ example: 'jane_doe@gmail.com', description: 'provide the email of the user', }) @Column({ unique: true }) email: string; @ApiProperty({ description: 'provide the password of the user', }) @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-03/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: [User, Playlist, Artist, Song], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "plugins": [ { "name": "@nestjs/swagger", "options": { "introspectComments": true } } ] } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; import { ApiBearerAuth } from '@nestjs/swagger'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) @ApiBearerAuth('JWT-auth') //1 getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('auth') @ApiTags('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'It will return the user in the response', }) signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') @ApiOperation({ summary: 'Login user' }) @ApiResponse({ status: 200, description: 'It will give you the access_token in the response', }) login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: process.env.SECRET, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .addBearerAuth( // Enable Bearer Auth here { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', name: 'JWT', description: 'Enter JWT token', in: 'header', }, 'JWT-auth', // We will use this Bearer Auth with JWT-auth name on the controller function ) .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; import { ApiTags } from '@nestjs/swagger'; @Controller('playlists') @ApiTags('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; import { ApiTags } from '@nestjs/swagger'; @Controller('songs') @ApiTags('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/users/user.entity.ts ================================================ import { ApiProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @ApiProperty({ example: 'Jane', description: 'provide the firstName of the user', }) @Column() firstName: string; @ApiProperty({ example: 'Doe', description: 'provide the lastName of the user', }) @Column() lastName: string; @ApiProperty({ example: 'jane_doe@gmail.com', description: 'provide the email of the user', }) @Column({ unique: true }) email: string; @ApiProperty({ description: 'provide the password of the user', }) @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-10-api-documentation-with-swagger/lesson-04/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-11-mongodb/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-11-mongodb/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-11-mongodb/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-11-mongodb/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-11-mongodb/lesson-01/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="spotify-clone" ports: - 27017:27017 ================================================ FILE: module-11-mongodb/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-11-mongodb/lesson-01/package.json ================================================ { "name": "n-mongo-production", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-11-mongodb/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-11-mongodb/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-11-mongodb/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [MongooseModule.forRoot('mongodb://localhost:27017/spotify-clone')], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-11-mongodb/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-11-mongodb/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-11-mongodb/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-11-mongodb/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-11-mongodb/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-11-mongodb/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-11-mongodb/lesson-02-and-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-11-mongodb/lesson-02-and-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-11-mongodb/lesson-02-and-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-11-mongodb/lesson-02-and-03/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="spotify-clone" ports: - 27017:27017 ================================================ FILE: module-11-mongodb/lesson-02-and-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/package.json ================================================ { "name": "n-mongo-production", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [MongooseModule.forRoot('mongodb://localhost:27017/spotify-clone')], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-11-mongodb/lesson-02-and-03/src/songs/schemas/song.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; export type SongDocument = HydratedDocument; //1. @Schema() //2. export class Song { @Prop({ // 3. required: true, }) title: string; @Prop({ required: true, }) releasedDate: Date; @Prop({ required: true, }) duration: string; lyrics: string; } export const SongSchema = SchemaFactory.createForClass(Song); ================================================ FILE: module-11-mongodb/lesson-02-and-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-11-mongodb/lesson-02-and-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-11-mongodb/lesson-02-and-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-11-mongodb/lesson-02-and-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-11-mongodb/lesson-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-11-mongodb/lesson-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-11-mongodb/lesson-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-11-mongodb/lesson-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-11-mongodb/lesson-04/api.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title": "Love me", "releasedDate" : "2023-05-11", "duration" :"02:33", "lyrics": "1adas adsdasd asdasdasd qeqew" } ================================================ FILE: module-11-mongodb/lesson-04/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="spotify-clone" ports: - 27017:27017 ================================================ FILE: module-11-mongodb/lesson-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-11-mongodb/lesson-04/package.json ================================================ { "name": "n-mongo-production", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-11-mongodb/lesson-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-11-mongodb/lesson-04/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-11-mongodb/lesson-04/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost:27017/spotify-clone'), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-11-mongodb/lesson-04/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-11-mongodb/lesson-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-11-mongodb/lesson-04/src/songs/dto/create-song-dto.ts ================================================ export class CreateSongDTO { title: string; releasedDate: Date; duration: Date; lyrics: string; } ================================================ FILE: module-11-mongodb/lesson-04/src/songs/schemas/song.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; export type SongDocument = HydratedDocument; //1. @Schema() //2. export class Song { @Prop({ // 3. required: true, }) title: string; @Prop({ required: true, }) releasedDate: Date; @Prop({ required: true, }) duration: string; lyrics: string; } export const SongSchema = SchemaFactory.createForClass(Song); ================================================ FILE: module-11-mongodb/lesson-04/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-04/src/songs/songs.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateSongDTO } from './dto/create-song-dto'; import { SongsService } from './songs.service'; @Controller('songs') export class SongsController { constructor(private songService: SongsService) {} @Post() create( @Body() createSongDTO: CreateSongDTO, ) { return this.songService.create(createSongDTO); } } ================================================ FILE: module-11-mongodb/lesson-04/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { Song, SongSchema } from './schemas/song'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [ MongooseModule.forFeature([{ name: Song.name, schema: SongSchema }]), ], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-11-mongodb/lesson-04/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-04/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song, SongDocument } from './schemas/song'; @Injectable() export class SongsService { constructor( @InjectModel(Song.name) //1 private readonly songModel: Model, //2 ) {} async create(createSongDTO: CreateSongDTO): Promise { const song = await this.songModel.create(createSongDTO); //3. return song; } } ================================================ FILE: module-11-mongodb/lesson-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-11-mongodb/lesson-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-11-mongodb/lesson-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-11-mongodb/lesson-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-11-mongodb/lesson-05/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-11-mongodb/lesson-05/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-11-mongodb/lesson-05/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-11-mongodb/lesson-05/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-11-mongodb/lesson-05/api.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title": "New song", "releasedDate" : "2023-05-11", "duration" :"02:33", "lyrics": "1adas adsdasd asdasdasd qeqew" } ### Find all songs GET http://localhost:3000/songs ### Find one song GET http://localhost:3000/songs/6481786f31a6103e10489ced ### Delete Song DELETE http://localhost:3000/songs/6481793569b739d7236e73b3 ================================================ FILE: module-11-mongodb/lesson-05/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="spotify-clone" ports: - 27017:27017 ================================================ FILE: module-11-mongodb/lesson-05/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-11-mongodb/lesson-05/package.json ================================================ { "name": "n-mongo-production", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-11-mongodb/lesson-05/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-11-mongodb/lesson-05/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-11-mongodb/lesson-05/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost:27017/spotify-clone'), SongsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-11-mongodb/lesson-05/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-11-mongodb/lesson-05/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-11-mongodb/lesson-05/src/songs/dto/create-song-dto.ts ================================================ export class CreateSongDTO { title: string; releasedDate: Date; duration: Date; lyrics: string; } ================================================ FILE: module-11-mongodb/lesson-05/src/songs/schemas/song.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; export type SongDocument = HydratedDocument; //1. @Schema() //2. export class Song { @Prop({ // 3. required: true, }) title: string; @Prop({ required: true, }) releasedDate: Date; @Prop({ required: true, }) duration: string; lyrics: string; } export const SongSchema = SchemaFactory.createForClass(Song); ================================================ FILE: module-11-mongodb/lesson-05/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-05/src/songs/songs.controller.ts ================================================ import { Body, Controller, Post, Get, Param, Delete } from '@nestjs/common'; import { CreateSongDTO } from './dto/create-song-dto'; import { SongsService } from './songs.service'; import { Song } from './schemas/song'; @Controller('songs') export class SongsController { constructor(private songService: SongsService) {} @Post() create( @Body() createSongDTO: CreateSongDTO, ) { return this.songService.create(createSongDTO); } @Get() find(): Promise { return this.songService.find(); } @Get(':id') findOne( @Param('id') id: string, ): Promise { return this.songService.findById(id); } @Delete(':id') delete( @Param('id') id: string, ) { return this.songService.delete(id); } } ================================================ FILE: module-11-mongodb/lesson-05/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { Song, SongSchema } from './schemas/song'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [ MongooseModule.forFeature([{ name: Song.name, schema: SongSchema }]), ], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-11-mongodb/lesson-05/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-05/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song, SongDocument } from './schemas/song'; @Injectable() export class SongsService { constructor( @InjectModel(Song.name) //1 private readonly songModel: Model, //2 ) {} async create(createSongDTO: CreateSongDTO): Promise { const song = await this.songModel.create(createSongDTO); //3. return song; } async find(): Promise { return this.songModel.find(); } async findById(id: string): Promise { return this.songModel.findById(id); } async delete(id: string) { return this.songModel.deleteOne({ _id: id }); } } ================================================ FILE: module-11-mongodb/lesson-05/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-11-mongodb/lesson-05/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-11-mongodb/lesson-05/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-11-mongodb/lesson-05/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-11-mongodb/lesson-06/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-11-mongodb/lesson-06/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-11-mongodb/lesson-06/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-11-mongodb/lesson-06/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-11-mongodb/lesson-06/api.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title": "New song", "releasedDate" : "2023-05-11", "duration" :"02:33", "lyrics": "1adas adsdasd asdasdasd qeqew" } ### Find all songs GET http://localhost:3000/songs ### Find one song GET http://localhost:3000/songs/6481786f31a6103e10489ced ### Delete Song DELETE http://localhost:3000/songs/6481793569b739d7236e73b3 ### Create Album POST http://localhost:3000/albums Content-Type: application/json { "title": "Dance", "songs": ["6481786f31a6103e10489ced","6481787831a6103e10489cef"] } ### Find all albums with songs GET http://localhost:3000/albums ================================================ FILE: module-11-mongodb/lesson-06/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="spotify-clone" ports: - 27017:27017 ================================================ FILE: module-11-mongodb/lesson-06/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-11-mongodb/lesson-06/package.json ================================================ { "name": "n-mongo-production", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-11-mongodb/lesson-06/src/albums/albums.controller.ts ================================================ import { Body, Controller, Get, Post } from '@nestjs/common'; import { Album } from './schemas/album.schema'; import { AlbumsService } from './albums.service'; import { CreateAlbumDTO } from './dto/create-album-dto'; @Controller('albums') export class AlbumsController { constructor(private albumService: AlbumsService) {} @Post() create( @Body() createAlbumDTO: CreateAlbumDTO, ): Promise { return this.albumService.createAlbum(createAlbumDTO); } @Get() find(): Promise { return this.albumService.findAlbums(); } } ================================================ FILE: module-11-mongodb/lesson-06/src/albums/albums.module.ts ================================================ import { Module } from '@nestjs/common'; import { AlbumsController } from './albums.controller'; import { AlbumsService } from './albums.service'; import { MongooseModule } from '@nestjs/mongoose'; import { Album, AlbumSchema } from './schemas/album.schema'; @Module({ imports: [ MongooseModule.forFeature([{ name: Album.name, schema: AlbumSchema }]), ], controllers: [AlbumsController], providers: [AlbumsService], }) export class AlbumsModule {} ================================================ FILE: module-11-mongodb/lesson-06/src/albums/albums.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Album, AlbumDocument } from './schemas/album.schema'; import { Model } from 'mongoose'; import { InjectModel } from '@nestjs/mongoose'; import { CreateAlbumDTO } from './dto/create-album-dto'; import { Song } from 'src/songs/schemas/song'; @Injectable() export class AlbumsService { constructor( @InjectModel(Album.name) private readonly albumModel: Model, ) {} async createAlbum(createAlbumDTO: CreateAlbumDTO): Promise { return this.albumModel.create(createAlbumDTO); } async findAlbums() { return this.albumModel.find().populate('songs', null, Song.name); //1 } } ================================================ FILE: module-11-mongodb/lesson-06/src/albums/dto/create-album-dto.ts ================================================ export class CreateAlbumDTO { title: string; songs: string[]; } ================================================ FILE: module-11-mongodb/lesson-06/src/albums/schemas/album.schema.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose'; import { Song } from 'src/songs/schemas/song'; export type AlbumDocument = HydratedDocument; @Schema() export class Album { @Prop({ required: true, }) title: string; @Prop({ type: [Types.ObjectId], ref: 'songs' }) //1 songs: Song[]; } export const AlbumSchema = SchemaFactory.createForClass(Album); ================================================ FILE: module-11-mongodb/lesson-06/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-11-mongodb/lesson-06/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-11-mongodb/lesson-06/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; import { SongsModule } from './songs/songs.module'; import { AlbumsModule } from './albums/albums.module'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost:27017/spotify-clone'), SongsModule, AlbumsModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-11-mongodb/lesson-06/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-11-mongodb/lesson-06/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-11-mongodb/lesson-06/src/songs/dto/create-song-dto.ts ================================================ export class CreateSongDTO { title: string; releasedDate: Date; duration: Date; lyrics: string; album: string; } ================================================ FILE: module-11-mongodb/lesson-06/src/songs/schemas/song.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Types } from 'mongoose'; import { Album } from 'src/albums/schemas/album.schema'; export type SongDocument = HydratedDocument; //1. @Schema() //2. export class Song { @Prop({ // 3. required: true, }) title: string; @Prop({ required: true, }) releasedDate: Date; @Prop({ required: true, }) duration: string; @Prop() lyrics: string; // song.schema.ts @Prop({ type: Types.ObjectId, ref: Album.name, }) album: Album; } export const SongSchema = SchemaFactory.createForClass(Song); ================================================ FILE: module-11-mongodb/lesson-06/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-06/src/songs/songs.controller.ts ================================================ import { Body, Controller, Post, Get, Param, Delete } from '@nestjs/common'; import { CreateSongDTO } from './dto/create-song-dto'; import { SongsService } from './songs.service'; import { Song } from './schemas/song'; @Controller('songs') export class SongsController { constructor(private songService: SongsService) {} @Post() create( @Body() createSongDTO: CreateSongDTO, ) { return this.songService.create(createSongDTO); } @Get() find(): Promise { return this.songService.find(); } @Get(':id') findOne( @Param('id') id: string, ): Promise { return this.songService.findById(id); } @Delete(':id') delete( @Param('id') id: string, ) { return this.songService.delete(id); } } ================================================ FILE: module-11-mongodb/lesson-06/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { Song, SongSchema } from './schemas/song'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [ MongooseModule.forFeature([{ name: Song.name, schema: SongSchema }]), ], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-11-mongodb/lesson-06/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-11-mongodb/lesson-06/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song, SongDocument } from './schemas/song'; @Injectable() export class SongsService { constructor( @InjectModel(Song.name) //1 private readonly songModel: Model, //2 ) {} async create(createSongDTO: CreateSongDTO): Promise { const song = await this.songModel.create(createSongDTO); //3. return song; } async find(): Promise { return this.songModel.find(); } async findById(id: string): Promise { return this.songModel.findById(id); } async delete(id: string) { return this.songModel.deleteOne({ _id: id }); } } ================================================ FILE: module-11-mongodb/lesson-06/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-11-mongodb/lesson-06/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-11-mongodb/lesson-06/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-11-mongodb/lesson-06/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-12-deploy-nestjs/deployment-finish/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-12-deploy-nestjs/deployment-finish/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; //LOAD Environment Variables require('dotenv').config(); export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: [User, Playlist, Artist, Song], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; // console.log(process.env.NODE_ENV); // console.log(process.env.DB_HOST); // these variables are undefined // console.log(process.env.PASSWORD); export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/db/migrations/1686309549613-init.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class Init1686309549613 implements MigrationInterface { name = 'Init1686309549613' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "plugins": [ { "name": "@nestjs/swagger", "options": { "introspectComments": true } } ] } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "NODE_ENV=development nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "NODE_ENV=production node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.1.4", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; import { ApiBearerAuth } from '@nestjs/swagger'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) @ApiBearerAuth('JWT-auth') //1 getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: [`${process.cwd()}/.env.${process.env.NODE_ENV}`], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('auth') @ApiTags('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'It will return the user in the response', }) signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') @ApiOperation({ summary: 'Login user' }) @ApiResponse({ status: 200, description: 'It will give you the access_token in the response', }) login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: process.env.SECRET, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/config/configuration.ts ================================================ export default () => ({ NODE_ENV: process.env.NODE_ENV, port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .addBearerAuth( // Enable Bearer Auth here { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', name: 'JWT', description: 'Enter JWT token', in: 'header', }, 'JWT-auth', // We will use this Bearer Auth with JWT-auth name on the controller function ) .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); console.log(configService.get('NODE_ENV')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; import { ApiTags } from '@nestjs/swagger'; @Controller('playlists') @ApiTags('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; import { ApiTags } from '@nestjs/swagger'; @Controller('songs') @ApiTags('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/users/user.entity.ts ================================================ import { ApiProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @ApiProperty({ example: 'Jane', description: 'provide the firstName of the user', }) @Column() firstName: string; @ApiProperty({ example: 'Doe', description: 'provide the lastName of the user', }) @Column() lastName: string; @ApiProperty({ example: 'jane_doe@gmail.com', description: 'provide the email of the user', }) @Column({ unique: true }) email: string; @ApiProperty({ description: 'provide the password of the user', }) @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-finish/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-12-deploy-nestjs/deployment-finish/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-12-deploy-nestjs/deployment-starter/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-12-deploy-nestjs/deployment-starter/db/data-source.ts ================================================ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from '@nestjs/typeorm'; import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = { imports: [ConfigModule], inject: [ConfigService], useFactory: async ( configService: ConfigService, ): Promise => { return { type: 'postgres', host: configService.get('dbHost'), port: configService.get('dbPort'), username: configService.get('username'), database: configService.get('dbName'), password: configService.get('password'), entities: [User, Playlist, Artist, Song], synchronize: false, migrations: ['dist/db/migrations/*.js'], }; }, }; export const dataSourceOptions: DataSourceOptions = { type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.USERNAME, database: process.env.DB_NAME, password: process.env.DB_PASSWORD, entities: ['dist/**/*.entity.js'], //1 synchronize: false, // 2 migrations: ['dist/db/migrations/*.js'], // 3 }; const dataSource = new DataSource(dataSourceOptions); //4 export default dataSource; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/db/migrations/1685010320827-my-migrations.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class MyMigrations1685010320827 implements MigrationInterface { name = 'MyMigrations1685010320827' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`CREATE TABLE "users" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "twoFASecret" text, "enable2FA" boolean NOT NULL DEFAULT false, "apiKey" character varying NOT NULL, "phone" character varying NOT NULL, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "playlists" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "userId" integer, CONSTRAINT "PK_a4597f4189a75d20507f3f7ef0d" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "releasedDate" date NOT NULL, "duration" TIME NOT NULL, "lyrics" text NOT NULL, "playListId" integer, CONSTRAINT "PK_e504ce8ad2e291d3a1d8f1ea2f4" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "artists" ("id" SERIAL NOT NULL, "userId" integer, CONSTRAINT "REL_f7bd9114dc2849a90d39512911" UNIQUE ("userId"), CONSTRAINT "PK_09b823d4607d2675dc4ffa82261" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "songs_artists" ("songsId" integer NOT NULL, "artistsId" integer NOT NULL, CONSTRAINT "PK_78eb64551964b78d544c2ac019b" PRIMARY KEY ("songsId", "artistsId"))`); await queryRunner.query(`CREATE INDEX "IDX_971d95bf6df45f2b07c317b6b3" ON "songs_artists" ("songsId") `); await queryRunner.query(`CREATE INDEX "IDX_3f43a7e4032521e4edd2e7ecd2" ON "songs_artists" ("artistsId") `); await queryRunner.query(`ALTER TABLE "playlists" ADD CONSTRAINT "FK_708a919e9aa49019000d9e9b68e" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs" ADD CONSTRAINT "FK_54cf41bc33d524b206b93581950" FOREIGN KEY ("playListId") REFERENCES "playlists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "artists" ADD CONSTRAINT "FK_f7bd9114dc2849a90d39512911b" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34" FOREIGN KEY ("songsId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE`); await queryRunner.query(`ALTER TABLE "songs_artists" ADD CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29" FOREIGN KEY ("artistsId") REFERENCES "artists"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_3f43a7e4032521e4edd2e7ecd29"`); await queryRunner.query(`ALTER TABLE "songs_artists" DROP CONSTRAINT "FK_971d95bf6df45f2b07c317b6b34"`); await queryRunner.query(`ALTER TABLE "artists" DROP CONSTRAINT "FK_f7bd9114dc2849a90d39512911b"`); await queryRunner.query(`ALTER TABLE "songs" DROP CONSTRAINT "FK_54cf41bc33d524b206b93581950"`); await queryRunner.query(`ALTER TABLE "playlists" DROP CONSTRAINT "FK_708a919e9aa49019000d9e9b68e"`); await queryRunner.query(`DROP INDEX "public"."IDX_3f43a7e4032521e4edd2e7ecd2"`); await queryRunner.query(`DROP INDEX "public"."IDX_971d95bf6df45f2b07c317b6b3"`); await queryRunner.query(`DROP TABLE "songs_artists"`); await queryRunner.query(`DROP TABLE "artists"`); await queryRunner.query(`DROP TABLE "songs"`); await queryRunner.query(`DROP TABLE "playlists"`); await queryRunner.query(`DROP TABLE "users"`); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/db/migrations/1685010456982-removed-phone.ts ================================================ import { MigrationInterface, QueryRunner } from "typeorm"; export class RemovedPhone1685010456982 implements MigrationInterface { name = 'RemovedPhone1685010456982' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "phone"`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "phone" character varying NOT NULL`); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/db/seeds/seed-data.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { User } from 'src/users/user.entity'; import { EntityManager } from 'typeorm'; import { faker } from '@faker-js/faker'; import { v4 as uuid4 } from 'uuid'; import * as bcrypt from 'bcryptjs'; import { Playlist } from '../../src/playlists/playlist.entity'; export const seedData = async (manager: EntityManager): Promise => { //1 // Add your seeding logic here using the manager // For example: await seedUser(); await seedArtist(); await seedPlayLists(); async function seedUser() { //2 const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); await manager.getRepository(User).save(user); } async function seedArtist() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const artist = new Artist(); artist.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Artist).save(artist); } async function seedPlayLists() { const salt = await bcrypt.genSalt(); const encryptedPassword = await bcrypt.hash('123456', salt); const user = new User(); user.firstName = faker.person.firstName(); user.lastName = faker.person.lastName(); user.email = faker.internet.email(); user.password = encryptedPassword; user.apiKey = uuid4(); const playList = new Playlist(); playList.name = faker.music.genre(); playList.user = user; await manager.getRepository(User).save(user); await manager.getRepository(Playlist).save(playList); } }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/env.validation.ts ================================================ import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator'; enum Environment { Development = 'development', Production = 'production', Test = 'test', Provision = 'provision', } class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment; @IsNumber() PORT: number; @IsNumber() DB_PORT: number; @IsString() DB_HOST: string; @IsString() USERNAME: string; @IsString() PASSWORD: string; @IsString() DB_NAME: string; @IsString() SECRET: string; } export function validate(config: Record) { // console.log('config ', config); const validatedConfig = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true, }); // console.log(validatedConfig); const errors = validateSync(validatedConfig, { skipMissingProperties: false, }); if (errors.length > 0) { throw new Error(errors.toString()); } return validatedConfig; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "plugins": [ { "name": "@nestjs/swagger", "options": { "introspectComments": true } } ] } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typeorm": "npm run build && npx typeorm -d dist/db/data-source.js", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "@nestjs/swagger": "^6.3.0" }, "dependencies": { "@faker-js/faker": "^8.0.1", "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.3.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "speakeasy": "^2.0.0", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/speakeasy": "^2.0.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc { "title": "Love again", "artists": [1], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john13@gmail.com", "password": "123456" } ### API KEY JOHN13 TEMP : 17838da8-99a7-443f-89fa-ba7338581ee0 ### Signup Artist POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "Martin", "lastName": "Garrix", "email": "martingarrix@gmail.com", "password": "123456" } ### Login Artist POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix1@gmail.com", "password": "123456" } ### Artist Token Temp: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjIsImFydGlzdElkIjoxLCJpYXQiOjE2ODQ5MDkxMTMsImV4cCI6MTY4NDk5NTUxM30.u7vwcccTXkbMIZvg1k0ZOA_dD1TvzZRDbO6xm8w23Bc ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "martingarrix@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Enable 2FA GET http://localhost:3000/auth/enable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA ### Validate 2FA Token POST http://localhost:3000/auth/validate-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hcnRpbmdhcnJpeEBnbWFpbC5jb20iLCJ1c2VySWQiOjQ2LCJpYXQiOjE2ODU3ODYzODksImV4cCI6MTY4NTg3Mjc4OX0.dxUxLCYS8YFLGkVXMu85DMJy5ev1CJGj_vP7Qx8v8hA Content-Type: application/json { "token": "993913" } ### Disable 2FA GET http://localhost:3000/auth/disable-2fa Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTY4NDkxMTk3OCwiZXhwIjoxNjg0OTk4Mzc4fQ.qbBHZfu0VL_tY_bC2ccl1I_Xoc0IqG6wAk-D2-tZDa8 ### Access Profile GET http://localhost:3000/auth/profile Authorization: Bearer 17838da8-99a7-443f-89fa-ba7338581ee0 ### Test Env GET http://localhost:3000/auth/test ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGuard } from './auth/jwt-guard'; import { ApiBearerAuth } from '@nestjs/swagger'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGuard) @ApiBearerAuth('JWT-auth') //1 getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/app.module.ts ================================================ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { SongsController } from './songs/songs.controller'; import { SongsModule } from './songs/songs.module'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ArtistsModule } from './artists/artists.module'; import { typeOrmAsyncConfig } from 'db/data-source'; import { SeedModule } from './seed/seed.module'; import { ConfigModule } from '@nestjs/config'; import configuration from './config/configuration'; import { validate } from 'env.validation'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: ['.env.development', '.env.production'], isGlobal: true, load: [configuration], validate: validate, }), TypeOrmModule.forRootAsync(typeOrmAsyncConfig), SongsModule, PlayListModule, AuthModule, UsersModule, ArtistsModule, SeedModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { constructor(/*private dataSource: DataSource*/) { // console.log('dbName ', dataSource.driver.database); } configure(consumer: MiddlewareConsumer) { // consumer.apply(LoggerMiddleware).forRoutes('songs'); // option no 1 // consumer // .apply(LoggerMiddleware) // .forRoutes({ path: 'songs', method: RequestMethod.POST }); //option no 2 consumer.apply(LoggerMiddleware).forRoutes(SongsController); //option no 3 } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artists.controller.ts ================================================ import { Controller } from '@nestjs/common'; @Controller('artists') export class ArtistsController {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Artist } from './artist.entity'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; @Module({ imports: [TypeOrmModule.forFeature([Artist])], providers: [ArtistsService], controllers: [ArtistsController], exports: [ArtistsService], }) export class ArtistsModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Artist } from './artist.entity'; @Injectable() export class ArtistsService { constructor( @InjectRepository(Artist) private artistRepo: Repository, ) {} findArtist(userId: number): Promise { return this.artistRepo.findOneBy({ user: { id: userId } }); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/api-key-strategy.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-http-bearer'; import { AuthService } from './auth.service'; @Injectable() export class ApiKeyStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(apiKey: string) { const user = await this.authService.validateUserByApiKey(apiKey); if (!user) { throw new UnauthorizedException(); } else { return user; } } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/artists-jwt-guard.ts ================================================ import { ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class ArtistJwtGuard extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { return super.canActivate(context); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw err || new UnauthorizedException(); } console.log(user); if (user.artistId) { return user; } throw err || new UnauthorizedException(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.controller.ts ================================================ import { Body, Controller, Get, Post, Request, UseGuards, } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; import { JwtAuthGuard } from './jwt-guard'; import { Enable2FAType } from './types'; import { ValidateTokenDTO } from './dto/validate-token.dto'; import { UpdateResult } from 'typeorm'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('auth') @ApiTags('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'It will return the user in the response', }) signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') @ApiOperation({ summary: 'Login user' }) @ApiResponse({ status: 200, description: 'It will give you the access_token in the response', }) login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } @Get('enable-2fa') @UseGuards(JwtAuthGuard) enable2FA( @Request() req, ): Promise { console.log(req.user); return this.authService.enable2FA(req.user.userId); } @Post('validate-2fa') @UseGuards(JwtAuthGuard) validate2FA( @Request() req, @Body() ValidateTokenDTO: ValidateTokenDTO, ): Promise<{ verified: boolean }> { return this.authService.validate2FAToken( req.user.userId, ValidateTokenDTO.token, ); } @Get('disable-2fa') @UseGuards(JwtAuthGuard) disable2FA( @Request() req, ): Promise { return this.authService.disable2FA(req.user.userId); } @Get('profile') @UseGuards(AuthGuard('bearer')) getProfile( @Request() req, ) { delete req.user.password; return { msg: 'authenticated with api key', user: req.user, }; } @Get('test') testEnvVariable() { return this.authService.getEnvVariable(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt-strategy'; import { ArtistsModule } from 'src/artists/artists.module'; import { ApiKeyStrategy } from './api-key-strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ secret: configService.get('secret'), signOptions: { expiresIn: '1d', }, }), inject: [ConfigService], }), ArtistsModule, ], providers: [AuthService, JwtStrategy, ApiKeyStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; import { ArtistsService } from 'src/artists/artists.service'; import { Enable2FAType, PayloadType } from './types'; import * as speakeasy from 'speakeasy'; import { UpdateResult } from 'typeorm'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, private artistsService: ArtistsService, private configService: ConfigService, ) {} async login( loginDTO: LoginDTO, ): Promise< { accessToken: string } | { validate2FA: string; message: string } > { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload: PayloadType = { email: user.email, userId: user.id }; const artist = await this.artistsService.findArtist(user.id); // 2 if (artist) { payload.artistId = artist.id; } if (user.enable2FA && user.twoFASecret) { //1. // sends the validateToken request link // else otherwise sends the json web token in the response return { //2. validate2FA: 'http://localhost:3000/auth/validate-2fa', message: 'Please sends the one time password/token from your Google Authenticator App', }; } return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } async enable2FA(userId: number): Promise { const user = await this.userService.findById(userId); //1 if (user.enable2FA) { //2 return { secret: user.twoFASecret }; } const secret = speakeasy.generateSecret(); //3 console.log(secret); user.twoFASecret = secret.base32; //4 await this.userService.updateSecretKey(user.id, user.twoFASecret); //5 return { secret: user.twoFASecret }; //6 } async validate2FAToken( userId: number, token: string, ): Promise<{ verified: boolean }> { try { // find the user on the based on id const user = await this.userService.findById(userId); // extract his 2FA secret // verify the secret with token by calling the speakeasy verify method const verified = speakeasy.totp.verify({ secret: user.twoFASecret, token: token, encoding: 'base32', }); // if validated then sends the json web token in the response if (verified) { return { verified: true }; } else { return { verified: false }; } } catch (err) { throw new UnauthorizedException('Error verifying token'); } } async disable2FA(userId: number): Promise { return this.userService.disable2FA(userId); } async validateUserByApiKey(apiKey: string): Promise { return this.userService.findByApiKey(apiKey); } getEnvVariable() { return this.configService.get('port'); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/dto/validate-token.dto.ts ================================================ import { IsNotEmpty, IsString } from 'class-validator'; export class ValidateTokenDTO { @IsNotEmpty() @IsString() token: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { PayloadType } from './types'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: process.env.SECRET, }); } async validate(payload: PayloadType) { return { userId: payload.userId, email: payload.email, artistId: payload.artistId, }; } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/auth/types.ts ================================================ export interface PayloadType { email: string; userId: number; artistId?: number; } export type Enable2FAType = { secret: string; }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/config/configuration.ts ================================================ export default () => ({ port: parseInt(process.env.PORT), secret: process.env.SECRET, dbHost: process.env.DB_HOST, dbPort: parseInt(process.env.DB_PORT), username: process.env.USERNAME, password: process.env.PASSWORD, dbName: process.env.DB_NAME, }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { SeedService } from './seed/seed.service'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); /** * You can enable the seeding here */ // const seedService = app.get(SeedService); // await seedService.seed(); const config = new DocumentBuilder() //1 .setTitle('Spotify Clone') .setDescription('The Spotify Clone Api documentation') .setVersion('1.0') .addBearerAuth( // Enable Bearer Auth here { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', name: 'JWT', description: 'Enter JWT token', in: 'header', }, 'JWT-auth', // We will use this Bearer Auth with JWT-auth name on the controller function ) .build(); const document = SwaggerModule.createDocument(app, config); //2 SwaggerModule.setup('api', app, document); //3 const configService = app.get(ConfigService); await app.listen(configService.get('port')); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; import { ApiTags } from '@nestjs/swagger'; @Controller('playlists') @ApiTags('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/seed/seed.module.ts ================================================ import { Module } from '@nestjs/common'; import { SeedService } from './seed.service'; @Module({ providers: [SeedService], }) export class SeedModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/seed/seed.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { seedData } from '../../db/seeds/seed-data'; @Injectable() export class SeedService { constructor(private readonly connection: DataSource) {} async seed(): Promise { const queryRunner = this.connection.createQueryRunner(); //1 await queryRunner.connect(); //2 await queryRunner.startTransaction(); //3 try { const manager = queryRunner.manager; await seedData(manager); await queryRunner.commitTransaction(); //4 } catch (err) { console.log('Error during database seeding:', err); await queryRunner.rollbackTransaction(); // 5 } finally { await queryRunner.release(); //6 } } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, UseGuards, Request, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; import { ArtistJwtGuard } from 'src/auth/artists-jwt-guard'; import { ApiTags } from '@nestjs/swagger'; @Controller('songs') @ApiTags('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() @UseGuards(ArtistJwtGuard) create( @Body() createSongDTO: CreateSongDTO, @Request() request, ): Promise { console.log('request.user: ', request.user); return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/users/user.entity.ts ================================================ import { ApiProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @ApiProperty({ example: 'Jane', description: 'provide the firstName of the user', }) @Column() firstName: string; @ApiProperty({ example: 'Doe', description: 'provide the lastName of the user', }) @Column() lastName: string; @ApiProperty({ example: 'jane_doe@gmail.com', description: 'provide the email of the user', }) @Column({ unique: true }) email: string; @ApiProperty({ description: 'provide the password of the user', }) @Column() @Exclude() password: string; @Column({ nullable: true, type: 'text' }) twoFASecret: string; @Column({ default: false, type: 'boolean' }) enable2FA: boolean; @Column() apiKey: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, UpdateResult } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; import { v4 as uuid4 } from 'uuid'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const user = new User(); user.firstName = userDTO.firstName; user.lastName = userDTO.lastName; user.email = userDTO.email; user.apiKey = uuid4(); const salt = await bcrypt.genSalt(); // 2. user.password = await bcrypt.hash(userDTO.password, salt); // 3. const savedUser = await this.userRepository.save(user); delete savedUser.password; return savedUser; } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } async findById(id: number): Promise { return this.userRepository.findOneBy({ id: id }); } async updateSecretKey(userId, secret: string): Promise { return this.userRepository.update( { id: userId }, { twoFASecret: secret, enable2FA: true, }, ); } async disable2FA(userId: number): Promise { return this.userRepository.update( { id: userId }, { enable2FA: false, twoFASecret: null, }, ); } async findByApiKey(apiKey: string): Promise { return this.userRepository.findOneBy({ apiKey }); } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-12-deploy-nestjs/deployment-starter/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-12-deploy-nestjs/deployment-starter/webpack-hmr.config.js ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const nodeExternals = require('webpack-node-externals'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false, }), ], }; }; ================================================ FILE: module-13-testing/end-to-end-testing/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-13-testing/end-to-end-testing/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-13-testing/end-to-end-testing/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-13-testing/end-to-end-testing/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-13-testing/end-to-end-testing/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-13-testing/end-to-end-testing/package.json ================================================ { "name": "testing-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.16" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-13-testing/end-to-end-testing/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-13-testing/end-to-end-testing/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-13-testing/end-to-end-testing/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { SongModule } from './song/song.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), SongModule, ], }) export class AppModule {} ================================================ FILE: module-13-testing/end-to-end-testing/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-13-testing/end-to-end-testing/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-13-testing/end-to-end-testing/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-13-testing/end-to-end-testing/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService], }) export class SongModule {} ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-13-testing/end-to-end-testing/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-13-testing/end-to-end-testing/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-13-testing/end-to-end-testing/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-13-testing/end-to-end-testing/test/songs/songs.e2e-spec.ts ================================================ import { INestApplication } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../../src/song/song.entity'; import { SongModule } from '../../src/song/song.module'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; import { CreateSongDTO } from 'src/song/dto/create-song-dto'; import { UpdateSongDTO } from 'src/song/dto/update-song-dto'; describe('Songs - /songs', () => { let app: INestApplication; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it(`/GET songs`, async () => { const newSong = await createSong({ title: 'Animals' }); const results = await request(app.getHttpServer()).get('/songs'); expect(results.statusCode).toBe(200); expect(results.body).toHaveLength(1); expect(results.body).toEqual([newSong]); }); it('/GET songs/:id', async () => { const newSong = await createSong({ title: 'Animals' }); const results = await request(app.getHttpServer()).get( `/songs/${newSong.id}`, ); expect(results.statusCode).toBe(200); expect(results.body).toEqual(newSong); }); it('/PUT songs/:id', async () => { const newSong = await createSong({ title: 'Animals' }); const updateSongDTO: UpdateSongDTO = { title: 'Wonderful' }; const results = await request(app.getHttpServer()) .put(`/songs/${newSong.id}`) .send(updateSongDTO as UpdateSongDTO); expect(results.statusCode).toBe(200); expect(results.body.affected).toEqual(1); }); it('/POST songs', async () => { const createSongDTO = { title: 'Animals' }; const results = await request(app.getHttpServer()) .post(`/songs`) .send(createSongDTO as CreateSongDTO); expect(results.status).toBe(201); expect(results.body.title).toBe('Animals'); }); it('/DELETE songs', async () => { const createSongDTO: CreateSongDTO = { title: 'Animals' }; const newSong = await createSong(createSongDTO); const results = await request(app.getHttpServer()).delete( `/songs/${newSong.id}`, ); expect(results.statusCode).toBe(200); expect(results.body.affected).toBe(1); }); }); ================================================ FILE: module-13-testing/end-to-end-testing/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-13-testing/end-to-end-testing/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-13-testing/jest-basics/.prettierrc ================================================ {"singleQuote": true} ================================================ FILE: module-13-testing/jest-basics/mock-function.spec.js ================================================ describe('Mock Function Examples', () => { it('should create a basic mock function', () => { const mockFun = jest.fn(); mockFun.mockReturnValue(4); expect(mockFun()).toBe(4); expect(mockFun()).toBe(4); expect(mockFun.mock.calls.length).toBe(2); expect(mockFun).toHaveBeenCalled(); }); it('should create a mock function with an argument', () => { const mockCreateSong = jest.fn((createSongDTO) => ({ ...createSongDTO, id: 1, })); expect(mockCreateSong({ title: 'Lover' })).toEqual({ title: 'Lover', id: 1, }); }); it('should create a mock function with an argument with mock implementation', () => { const mockCreateSong = jest.fn(); mockCreateSong.mockImplementation((createSongDTO) => ({ ...createSongDTO, id: 1, })); expect(mockCreateSong({ title: 'Lover' })).toEqual({ title: 'Lover', id: 1, }); }); it('should create a mock function with promise', () => { const fetchSongs = jest.fn(); fetchSongs.mockResolvedValue([{ id: 1, title: 'Lover' }]); expect(fetchSongs()).resolves.toEqual([{ id: 1, title: 'Lover' }]); expect(fetchSongs()).resolves.toHaveLength(1); }); }); ================================================ FILE: module-13-testing/jest-basics/package.json ================================================ { "scripts": { "test": "jest", "test:watch": "jest --watchAll" }, "devDependencies": { "@types/jest": "^29.5.2", "jest": "^29.5.0" } } ================================================ FILE: module-13-testing/jest-basics/spyon-demon.spec.js ================================================ const songRepository = { create: (createSongDTO) => { // Original method implementation }, find: () => {}, findOne: (id) => {}, }; class ArtistRepository { save(createArtistDTO) { // Original method implementation } } describe('spyOn Demo', () => { it('should spyon the existing object method', () => { const spy = jest.spyOn(songRepository, 'create'); //1 songRepository.create({ title: 'Lover' }); console.log(spy.mock.calls.length); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ title: 'Lover' }); expect(spy).toHaveBeenCalledTimes(1); }); it('should spy on the class method', () => { const artist = new ArtistRepository(); const spy = jest .spyOn(artist, 'save') .mockImplementation((createArtistDTO) => ({ ...createArtistDTO, id: 1 })); // Call the method artist.save({ name: 'Martin Garrix' }); //1 console.log(spy({ name: 'Martin Garrix' })); // Assertions expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith({ name: 'Martin Garrix' }); expect(spy.mock.calls.length).toBe(2); }); afterEach(() => jest.resetAllMocks()); }); ================================================ FILE: module-13-testing/jest-basics/sum.js ================================================ function sum(a, b) { return a + b; } module.exports = sum; ================================================ FILE: module-13-testing/jest-basics/sum.test.js ================================================ const sum = require('./sum'); test('it should give me the result 1+2 = 3', () => { expect(sum(1, 2)).toBe(3) expect(sum(1,4)).toBe(5) }) ================================================ FILE: module-13-testing/unit-test-controller-and-service/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-13-testing/unit-test-controller-and-service/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-13-testing/unit-test-controller-and-service/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-13-testing/unit-test-controller-and-service/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-13-testing/unit-test-controller-and-service/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/package.json ================================================ { "name": "testing-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.16" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { SongModule } from './song/song.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), SongModule, ], }) export class AppModule {} ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService], }) export class SongModule {} ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-13-testing/unit-test-controller-and-service/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-13-testing/unit-test-controller-and-service/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-13-testing/unit-test-controller-and-service/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-13-testing/unit-test-controller-and-service/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-13-testing/unit-test-setup/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-13-testing/unit-test-setup/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-13-testing/unit-test-setup/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-13-testing/unit-test-setup/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-13-testing/unit-test-setup/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-13-testing/unit-test-setup/package.json ================================================ { "name": "testing-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "pg": "^8.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.16" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-13-testing/unit-test-setup/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-13-testing/unit-test-setup/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-13-testing/unit-test-setup/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { SongModule } from './song/song.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), SongModule, ], }) export class AppModule {} ================================================ FILE: module-13-testing/unit-test-setup/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-13-testing/unit-test-setup/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-13-testing/unit-test-setup/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-13-testing/unit-test-setup/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService], }) export class SongModule {} ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; describe('SongService', () => { let service: SongService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongService], }).compile(); service = module.get(SongService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-13-testing/unit-test-setup/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-13-testing/unit-test-setup/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-13-testing/unit-test-setup/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-13-testing/unit-test-setup/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-13-testing/unit-test-setup/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-14-websocket/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-14-websocket/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-14-websocket/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-14-websocket/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-14-websocket/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-14-websocket/lesson-01/package.json ================================================ { "name": "websocket-impl", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.0.3", "@nestjs/websockets": "^10.0.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-14-websocket/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-14-websocket/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-14-websocket/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-14-websocket/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World from nestjs version 10'; } } ================================================ FILE: module-14-websocket/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-14-websocket/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-14-websocket/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-14-websocket/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-14-websocket/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-14-websocket/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-14-websocket/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-14-websocket/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-14-websocket/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-14-websocket/lesson-02/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-14-websocket/lesson-02/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-14-websocket/lesson-02/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-14-websocket/lesson-02/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/package.json ================================================ { "name": "websocket-impl", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.0.3", "@nestjs/websockets": "^10.0.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-14-websocket/lesson-02/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-14-websocket/lesson-02/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World from nestjs version 10'; } } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-14-websocket/lesson-02/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-14-websocket/lesson-02/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-14-websocket/lesson-02/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-14-websocket/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-14-websocket/lesson-02/package.json ================================================ { "name": "websocket-impl", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.0.3", "@nestjs/websockets": "^10.0.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-14-websocket/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-14-websocket/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-14-websocket/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; @Module({ controllers: [AppController], providers: [AppService], imports: [EventsModule], }) export class AppModule {} ================================================ FILE: module-14-websocket/lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World from nestjs version 10'; } } ================================================ FILE: module-14-websocket/lesson-02/src/events/events.gateway.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { EventsGateway } from './events.gateway'; describe('EventsGateway', () => { let gateway: EventsGateway; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [EventsGateway], }).compile(); gateway = module.get(EventsGateway); }); it('should be defined', () => { expect(gateway).toBeDefined(); }); }); ================================================ FILE: module-14-websocket/lesson-02/src/events/events.gateway.ts ================================================ import { OnModuleInit } from '@nestjs/common'; import { MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer, } from '@nestjs/websockets'; import { Server } from 'socket.io'; @WebSocketGateway({ //1 cors: { origin: '*', }, }) export class EventsGateway implements OnModuleInit { @WebSocketServer() //2 server: Server; onModuleInit() { //3 this.server.on('connection', (socket) => { console.log(socket.id); console.log(socket.connected); }); } @SubscribeMessage('message') handleMessage( @MessageBody() data: any, ) { console.log('Message receieved from the client '); console.log(data); } } ================================================ FILE: module-14-websocket/lesson-02/src/events/events.module.ts ================================================ import { Module } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; @Module({ providers: [EventsGateway] }) export class EventsModule {} ================================================ FILE: module-14-websocket/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-14-websocket/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-14-websocket/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-14-websocket/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-14-websocket/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-14-websocket/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-14-websocket/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-14-websocket/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-14-websocket/lesson-03/.vscode/settings.json ================================================ { "liveServer.settings.port": 5501 } ================================================ FILE: module-14-websocket/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-14-websocket/lesson-03/client/index.html ================================================

Please check the console for the message reply

================================================ FILE: module-14-websocket/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-14-websocket/lesson-03/package.json ================================================ { "name": "websocket-impl", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.0.3", "@nestjs/websockets": "^10.0.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-14-websocket/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-14-websocket/lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-14-websocket/lesson-03/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { EventsModule } from './events/events.module'; @Module({ controllers: [AppController], providers: [AppService], imports: [EventsModule], }) export class AppModule {} ================================================ FILE: module-14-websocket/lesson-03/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World from nestjs version 10'; } } ================================================ FILE: module-14-websocket/lesson-03/src/events/events.gateway.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { EventsGateway } from './events.gateway'; describe('EventsGateway', () => { let gateway: EventsGateway; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [EventsGateway], }).compile(); gateway = module.get(EventsGateway); }); it('should be defined', () => { expect(gateway).toBeDefined(); }); }); ================================================ FILE: module-14-websocket/lesson-03/src/events/events.gateway.ts ================================================ import { OnModuleInit } from '@nestjs/common'; import { MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer, WsResponse, } from '@nestjs/websockets'; import { Observable, of } from 'rxjs'; import { Server } from 'socket.io'; @WebSocketGateway({ //1 cors: { origin: '*', }, }) export class EventsGateway implements OnModuleInit { @WebSocketServer() //2 server: Server; onModuleInit() { //3 this.server.on('connection', (socket) => { console.log(socket.id); console.log(socket.connected); }); } @SubscribeMessage('message') handleMessage( @MessageBody() data: any, ): Observable> { console.log('Message receieved from the client '); console.log(data); return of({ event: 'message', data: 'MESSAGE RETURNED FROM SERVER: HELLo', }); } } ================================================ FILE: module-14-websocket/lesson-03/src/events/events.module.ts ================================================ import { Module } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; @Module({ providers: [EventsGateway] }) export class EventsModule {} ================================================ FILE: module-14-websocket/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-14-websocket/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-14-websocket/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-14-websocket/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-14-websocket/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-15-build-graphql-apis/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-15-build-graphql-apis/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-15-build-graphql-apis/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-15-build-graphql-apis/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-15-build-graphql-apis/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; } type Nullable = T | null; ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! } ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService], }) export class SongModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-01/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-15-build-graphql-apis/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-15-build-graphql-apis/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-15-build-graphql-apis/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-15-build-graphql-apis/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-15-build-graphql-apis/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-15-build-graphql-apis/lesson-02/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-15-build-graphql-apis/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService], }) export class SongModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-02/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-15-build-graphql-apis/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-15-build-graphql-apis/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongResolver], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { return this.songService.getSongs(); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { return this.songService.createSong(args); } @Mutation('updateSong') async updateSong( @Args('updateSongInput') args: UpdateSongDTO, @Args('id') id: string, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-15-build-graphql-apis/lesson-03-and-04/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-15-build-graphql-apis/lesson-05/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-15-build-graphql-apis/lesson-05/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-15-build-graphql-apis/lesson-05/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-15-build-graphql-apis/lesson-05/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongResolver], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { // return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); throw new GraphQLError('Unable to fetch the songs', { extensions: { code: 'INTERNAL_SERVER_ERROR', }, }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { return this.songService.createSong(args); } @Mutation('updateSong') async updateSong( @Args('updateSongInput') args: UpdateSongDTO, @Args('id') id: string, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-15-build-graphql-apis/lesson-05/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-15-build-graphql-apis/lesson-05/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-15-build-graphql-apis/lesson-05/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! } type Mutation { singup(singupInput: SingupInput!): SignupResponse! } input SingupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { acessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SingupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; } export abstract class IMutation { abstract singup(singupInput: SingupInput): SignupResponse | Promise; } export class SignupResponse { email: string; } export class LoginResponse { acessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! } type Mutation { singup(singupInput: SingupInput!): SignupResponse! } input SingupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { acessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SingupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; } export abstract class IMutation { abstract singup(singupInput: SingupInput): SignupResponse | Promise; } export class SignupResponse { email: string; } export class LoginResponse { acessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! } type Mutation { signup(signupInput: SignupInput!): SignupResponse! } input SignupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { accessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { AuthResolver } from './auth.resolver'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy, AuthResolver], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthResolver } from './auth.resolver'; describe('AuthResolver', () => { let resolver: AuthResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthResolver], }).compile(); resolver = module.get(AuthResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.resolver.ts ================================================ import { Args, Mutation, Resolver, Query } from '@nestjs/graphql'; import { SignupResponse, SignupInput, LoginInput, LoginResponse, } from 'src/graphql'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; @Resolver() export class AuthResolver { constructor( private userService: UsersService, private authService: AuthService, ) {} @Mutation('signup') singupUser( @Args('signupInput') signupInput: SignupInput, ): Promise { return this.userService.create(signupInput); } @Query('login') loginUser( @Args('loginInput') loginInput: LoginInput, ): Promise { return this.authService.login(loginInput); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SignupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; } export abstract class IMutation { abstract signup(signupInput: SignupInput): SignupResponse | Promise; } export class SignupResponse { email: string; } export class LoginResponse { accessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/.vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Attach", "port": 9229, "request": "attach", "skipFiles": ["/**"], "type": "node" } ] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! } type Mutation { singup(singupInput: SingupInput!): SignupResponse! } input SingupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { acessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SingupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; } export abstract class IMutation { abstract singup(singupInput: SingupInput): SignupResponse | Promise; } export class SignupResponse { email: string; } export class LoginResponse { acessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! } type Mutation { signup(signupInput: SignupInput!): SignupResponse! } input SignupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { accessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { AuthResolver } from './auth.resolver'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy, AuthResolver], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthResolver } from './auth.resolver'; describe('AuthResolver', () => { let resolver: AuthResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthResolver], }).compile(); resolver = module.get(AuthResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.resolver.ts ================================================ import { Args, Mutation, Resolver, Query } from '@nestjs/graphql'; import { SignupResponse, SignupInput, LoginInput, LoginResponse, } from 'src/graphql'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; @Resolver() export class AuthResolver { constructor( private userService: UsersService, private authService: AuthService, ) {} @Mutation('signup') singupUser( @Args('signupInput') signupInput: SignupInput, ): Promise { return this.userService.create(signupInput); } @Query('login') loginUser( @Args('loginInput') loginInput: LoginInput, ): Promise { return this.authService.login(loginInput); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SignupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; } export abstract class IMutation { abstract signup(signupInput: SignupInput): SignupResponse | Promise; } export class SignupResponse { email: string; } export class LoginResponse { accessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/package.json ================================================ { "name": "n-fundamentals-pro", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/jwt": "^10.0.3", "@nestjs/passport": "^9.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "graphql": "^16.7.1", "nestjs-typeorm-paginate": "^4.0.3", "passport": "^0.6.0", "passport-jwt": "^4.0.1", "pg": "^8.10.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "typeorm": "^0.3.15" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^25.0.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-morph": "^19.0.0", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/rest-client.http ================================================ GET http://localhost:3000 ### SEND FETCH SONGS REQUEST GET http://localhost:3000/songs/?page=1&limit=2 ### Find SONGS REQUEST GET http://localhost:3000/songs/1 ### Create New SONGS REQUEST POST http://localhost:3000/songs Content-Type: application/json { "title": "You for me 3", "artists": [1,2], "releasedDate" : "2023-05-11", "duration" :"02:34", "lyrics": "Sby, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST PUT http://localhost:3000/songs/2 Content-Type: application/json { "title": "Animals", "artists": [ "Martin" ], "releasedDate" : "2023-02-02", "duration" :"03:43", "lyrics": "ANIM, you're my adrenaline. Brought out this other side of me You don't even know Controlling my whole anatomy, oh Fingers are holding you right at the edge You're slipping out of my hands Keeping my secrets all up in my head I'm scared that you won't want me back, oh I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya I wish that I was honest when I had you I shoulda told you that I wanted you for me I dance to every song like it's about ya I drink 'til I kiss someone who looks like ya" } ### Update SONGS REQUEST DELETE http://localhost:3000/songs/1 ### Create new PlayList POST http://localhost:3000/playlists Content-Type: application/json { "name": "Feel Good Now", "songs": [ 6 ], "user": 2 } ### Signup User POST http://localhost:3000/auth/signup Content-Type: application/json { "firstName": "john", "lastName": "doe", "email": "john12@gmail.com", "password": "123456" } ### Login User POST http://localhost:3000/auth/login Content-Type: application/json { "email": "john12@gmail.com", "password": "123456" } ## Access TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ### Profile GET http://localhost:3000/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4xMkBnbWFpbC5jb20iLCJzdWIiOjEsImlhdCI6MTY4NDg1NTYyMSwiZXhwIjoxNjg0OTQyMDIxfQ.4FAABSVzS_6NUAjldhn7-EZ0UbAUUfKgGZ0Qv4tma7M ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/app.controller.ts ================================================ import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { JwtAuthGaurd } from './auth/jwt-guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('profile') @UseGuards(JwtAuthGaurd) getProfile( @Req() request, ) { return request.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { Song } from './songs/song.entity'; import { Artist } from './artists/artist.entity'; import { User } from './users/user.entity'; import { Playlist } from './playlists/playlist.entity'; import { PlayListModule } from './playlists/playlists.module'; // import { DataSource } from 'typeorm'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', database: 'graphql-auth', host: 'localhost', port: 5432, username: 'postgres', password: 'root', entities: [Song, Artist, User, Playlist], synchronize: true, }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: ({ req }) => ({ req }), }), SongsModule, PlayListModule, AuthModule, UsersModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/app.service.ts ================================================ import { Inject, Injectable } from '@nestjs/common'; import { DevConfigService } from './common/providers/DevConfigService'; @Injectable() export class AppService { getHello(): string { return 'Hello I am learning Nest.js Fundamentals'; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/artists/artist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Entity, JoinColumn, ManyToMany, OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('artists') export class Artist { @PrimaryGeneratedColumn() id: number; @OneToOne(() => User) @JoinColumn() user: User; @ManyToMany(() => Song, (song) => song.artists) songs: Song[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.constants.ts ================================================ export const authConstants = { secret: 'HAD_12X#@', }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; describe('AuthController', () => { let controller: AuthController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], }).compile(); controller = module.get(AuthController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { CreateUserDTO } from 'src/users/dto/create-user.dto'; import { User } from 'src/users/user.entity'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { LoginDTO } from './dto/login.dto'; @Controller('auth') export class AuthController { constructor( private userService: UsersService, private authService: AuthService, ) {} @Post('signup') signup( @Body() userDTO: CreateUserDTO, ): Promise { return this.userService.create(userDTO); } @Post('login') login( @Body() loginDTO: LoginDTO, ) { return this.authService.login(loginDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.graphql ================================================ type User { id: ID! firstName: String! lastName: String! email: String! password: String! } type Query { login(loginInput: LoginInput!): LoginResponse! profile: Profile! } type Mutation { signup(signupInput: SignupInput!): SignupResponse! } type Profile { email: String! userId: String! } input SignupInput { firstName: String! lastName: String! email: String! password: String! } input LoginInput { email: String! password: String! } type SignupResponse { email: String! } type LoginResponse { accessToken: String! } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.module.ts ================================================ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from 'src/users/users.module'; import { JwtModule } from '@nestjs/jwt'; import { authConstants } from './auth.constants'; import { JwtStrategy } from './jwt-strategy'; import { AuthResolver } from './auth.resolver'; @Module({ imports: [ UsersModule, JwtModule.register({ secret: authConstants.secret, signOptions: { expiresIn: '1d', }, }), ], providers: [AuthService, JwtStrategy, AuthResolver], controllers: [AuthController], exports: [AuthService], }) export class AuthModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthResolver } from './auth.resolver'; describe('AuthResolver', () => { let resolver: AuthResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthResolver], }).compile(); resolver = module.get(AuthResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.resolver.ts ================================================ import { Args, Mutation, Resolver, Query } from '@nestjs/graphql'; import { SignupResponse, SignupInput, LoginInput, LoginResponse, Profile, } from 'src/graphql'; import { UsersService } from 'src/users/users.service'; import { AuthService } from './auth.service'; import { UseGuards } from '@nestjs/common'; import { GraphQLAuthGaurd } from './gql-auth-guard'; @Resolver() export class AuthResolver { constructor( private userService: UsersService, private authService: AuthService, ) {} @Mutation('signup') singupUser( @Args('signupInput') signupInput: SignupInput, ): Promise { return this.userService.create(signupInput); } @Query('login') loginUser( @Args('loginInput') loginInput: LoginInput, ): Promise { return this.authService.login(loginInput); } @Query('profile') @UseGuards(GraphQLAuthGaurd) getProfile(parent, args, contextValue, info): Profile { console.log(parent); console.log(args); console.log(contextValue); console.log(info); return contextValue.req.user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AuthService], }).compile(); service = module.get(AuthService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/auth.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { LoginDTO } from './dto/login.dto'; import { User } from 'src/users/user.entity'; import * as bcrypt from 'bcryptjs'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( private userService: UsersService, private jwtService: JwtService, ) {} async login(loginDTO: LoginDTO): Promise<{ accessToken: string }> { const user = await this.userService.findOne(loginDTO); // 1. const passwordMatched = await bcrypt.compare( loginDTO.password, user.password, ); if (passwordMatched) { delete user.password; const payload = { email: user.email, sub: user.id }; return { accessToken: this.jwtService.sign(payload), }; } else { throw new UnauthorizedException('Password does not match'); // 5. } } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/dto/login.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class LoginDTO { @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/gql-auth-guard.ts ================================================ import { AuthenticationError } from '@nestjs/apollo'; import { ExecutionContext, Injectable } from '@nestjs/common'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; import { GqlExecutionContext } from '@nestjs/graphql'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; @Injectable() export class GraphQLAuthGaurd extends AuthGuard('jwt') { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { const ctx = GqlExecutionContext.create(context); const { req } = ctx.getContext(); return super.canActivate(new ExecutionContextHost([req])); } handleRequest(err: any, user: any): TUser { if (err || !user) { throw new AuthenticationError('GqlAuthguard'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/jwt-guard.ts ================================================ import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGaurd extends AuthGuard('jwt') {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/auth/jwt-strategy.ts ================================================ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { authConstants } from './auth.constants'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: authConstants.secret, }); } async validate(payload: any) { return { userId: payload.sub, email: payload.email }; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/common/constatnts/connection.ts ================================================ export const connection: Connection = { CONNECTION_STRING: 'MYSQL://12324/sad', DB: 'MYSQL', DBNAME: 'TEST', }; export type Connection = { CONNECTION_STRING: string; DB: string; DBNAME: string; }; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/common/middleware/logger.middleware.ts ================================================ import { Injectable, NestMiddleware } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { console.log('Request ....', new Date().toDateString()); next(); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/common/providers/DevConfigService.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class DevConfigService { DBHOST = 'localhost'; getDBHOST() { return this.DBHOST; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class SignupInput { firstName: string; lastName: string; email: string; password: string; } export class LoginInput { email: string; password: string; } export class User { id: string; firstName: string; lastName: string; email: string; password: string; } export abstract class IQuery { abstract login(loginInput: LoginInput): LoginResponse | Promise; abstract profile(): Profile | Promise; } export abstract class IMutation { abstract signup(signupInput: SignupInput): SignupResponse | Promise; } export class Profile { email: string; userId: string; } export class SignupResponse { email: string; } export class LoginResponse { accessToken: string; } type Nullable = T | null; ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/playlists/dto/create-playlist.dto.ts ================================================ import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreatePlayListDto { @IsString() @IsNotEmpty() readonly name; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly songs; @IsNumber() @IsNotEmpty() readonly user: number; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/playlists/playlist.entity.ts ================================================ import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('playlists') export class Playlist { @PrimaryGeneratedColumn() id: number; @Column() name: string; /** * Each Playlist will have multiple songs */ @OneToMany(() => Song, (song) => song.playList) songs: Song[]; /** * Many Playlist can belong to a single unique user */ @ManyToOne(() => User, (user) => user.playLists) user: User; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/playlists/playlists.controller.ts ================================================ import { Body, Controller, Post } from '@nestjs/common'; import { Playlist } from './playlist.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; import { PlayListsService } from './playlists.service'; @Controller('playlists') export class PlayListsController { constructor(private playListService: PlayListsService) {} @Post() create( @Body() playlistDTO: CreatePlayListDto, ): Promise { return this.playListService.create(playlistDTO); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/playlists/playlists.module.ts ================================================ import { Module } from '@nestjs/common'; import { PlayListsController } from './playlists.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { PlayListsService } from './playlists.service'; import { Song } from 'src/songs/song.entity'; import { User } from 'src/users/user.entity'; @Module({ imports: [TypeOrmModule.forFeature([Playlist, Song, User])], controllers: [PlayListsController], providers: [PlayListsService], }) export class PlayListModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/playlists/playlists.service.ts ================================================ import { InjectRepository } from '@nestjs/typeorm'; import { Playlist } from './playlist.entity'; import { Song } from 'src/songs/song.entity'; import { Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { User } from 'src/users/user.entity'; import { CreatePlayListDto } from './dto/create-playlist.dto'; @Injectable() export class PlayListsService { constructor( @InjectRepository(Playlist) private playListRepo: Repository, @InjectRepository(Song) private songsRepo: Repository, @InjectRepository(User) private userRepo: Repository, ) {} async create(playListDTO: CreatePlayListDto): Promise { const playList = new Playlist(); playList.name = playListDTO.name; // songs will be the array of ids that we are getting from the DTO object const songs = await this.songsRepo.findByIds(playListDTO.songs); // set the relation for the songs with playlist entity playList.songs = songs; // A user will be the id of the user we are getting from the request // when we implemented the user authentication this id will become the loggedIn user id const user = await this.userRepo.findOneBy({ id: playListDTO.user }); playList.user = user; return this.playListRepo.save(playList); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/dto/create-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateSongDTO { @IsString() @IsNotEmpty() readonly title; @IsNotEmpty() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsNotEmpty() @IsDateString() readonly releasedDate: Date; @IsMilitaryTime() @IsNotEmpty() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/dto/update-song-dto.ts ================================================ import { IsArray, IsDateString, IsMilitaryTime, IsNumber, IsOptional, IsString, } from 'class-validator'; export class UpdateSongDto { @IsString() @IsOptional() readonly title; @IsOptional() @IsArray() @IsNumber({}, { each: true }) readonly artists; @IsDateString() @IsOptional() readonly releasedDate: Date; @IsMilitaryTime() @IsOptional() readonly duration: Date; @IsString() @IsOptional() readonly lyrics: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/song.entity.ts ================================================ import { Artist } from 'src/artists/artist.entity'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn() id: number; @Column() title: string; // @Column('varchar', { array: true }) // artists: string[]; @Column('date') releasedDate: Date; @Column('time') duration: Date; @Column('text') lyrics: string; @ManyToMany(() => Artist, (artist) => artist.songs, { cascade: true }) @JoinTable({ name: 'songs_artists' }) artists: Artist[]; /** * Many songs can belong to playlist for each unique user */ @ManyToOne(() => Playlist, (playList) => playList.songs) playList: Playlist; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/songs.controller.ts ================================================ import { Controller, Get, Put, Delete, Post, HttpException, HttpStatus, Param, ParseIntPipe, Body, Inject, Scope, Query, DefaultValuePipe, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { Song } from './song.entity'; import { DeleteResult, UpdateResult } from 'typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Pagination } from 'nestjs-typeorm-paginate'; @Controller('songs') export class SongsController { constructor(private songsService: SongsService) {} @Post() create(@Body() createSongDTO: CreateSongDTO): Promise { return this.songsService.create(createSongDTO); } @Get() findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit = 10, ): Promise> { limit = limit > 100 ? 100 : limit; return this.songsService.paginate({ page, limit, }); } @Get(':id') findOne( @Param( 'id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }), ) id: number, ): Promise { return this.songsService.findOne(id); } @Put(':id') update( @Param('id', ParseIntPipe) id: number, @Body() updateSongDTO: UpdateSongDto, ): Promise { return this.songsService.update(id, updateSongDTO); } @Delete(':id') delete(@Param('id', ParseIntPipe) id: number): Promise { return this.songsService.remove(id); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { Artist } from 'src/artists/artist.entity'; @Module({ imports: [TypeOrmModule.forFeature([Song, Artist])], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/songs/songs.service.ts ================================================ import { ConsoleLogger, Injectable } from '@nestjs/common'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { paginate, Pagination, IPaginationOptions, } from 'nestjs-typeorm-paginate'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { InjectRepository } from '@nestjs/typeorm'; import { UpdateSongDto } from './dto/update-song-dto'; import { Artist } from 'src/artists/artist.entity'; @Injectable() export class SongsService { constructor( @InjectRepository(Song) private songsRepository: Repository, @InjectRepository(Artist) private artistsRepository: Repository, ) {} async create(songDTO: CreateSongDTO): Promise { const song = new Song(); song.title = songDTO.title; song.artists = songDTO.artists; song.duration = songDTO.duration; song.lyrics = songDTO.lyrics; song.releasedDate = songDTO.releasedDate; console.log(songDTO.artists); // find all the artits on the based on ids const artists = await this.artistsRepository.findByIds(songDTO.artists); console.log(artists); //set the relation with artist and songs song.artists = artists; return this.songsRepository.save(song); } findAll(): Promise { return this.songsRepository.find(); } findOne(id: number): Promise { return this.songsRepository.findOneBy({ id }); } remove(id: number): Promise { return this.songsRepository.delete(id); } update(id: number, recordToUpdate: UpdateSongDto): Promise { return this.songsRepository.update(id, recordToUpdate); } async paginate(options: IPaginationOptions): Promise> { const queryBuilder = this.songsRepository.createQueryBuilder('c'); queryBuilder.orderBy('c.releasedDate', 'DESC'); return paginate(queryBuilder, options); } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/users/dto/create-user.dto.ts ================================================ import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; export class CreateUserDTO { @IsString() @IsNotEmpty() firstName: string; @IsString() @IsNotEmpty() lastName: string; @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/users/user.entity.ts ================================================ import { Exclude } from 'class-transformer'; import { Playlist } from 'src/playlists/playlist.entity'; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column() firstName: string; @Column() lastName: string; @Column({ unique: true }) email: string; @Column() @Exclude() password: string; /** * A user can create many playLists */ @OneToMany(() => Playlist, (playList) => playList.user) playLists: Playlist[]; } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { AuthService } from 'src/auth/auth.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/src/users/users.service.ts ================================================ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { User } from './user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDTO } from './dto/create-user.dto'; import * as bcrypt from 'bcryptjs'; import { LoginDTO } from 'src/auth/dto/login.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private userRepository: Repository, // 1. ) {} async create(userDTO: CreateUserDTO): Promise { const salt = await bcrypt.genSalt(); // 2. userDTO.password = await bcrypt.hash(userDTO.password, salt); // 3. const user = await this.userRepository.save(userDTO); // 4. delete user.password; // 5. return user; // 6. } async findOne(data: LoginDTO): Promise { const user = await this.userRepository.findOneBy({ email: data.email }); if (!user) { throw new UnauthorizedException('Could not find user'); } return user; } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-16-authenticate-graphql-apis/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-17-subscription/subscription-finish/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-17-subscription/subscription-finish/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-17-subscription/subscription-finish/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-17-subscription/subscription-finish/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-17-subscription/subscription-finish/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-17-subscription/subscription-finish/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-17-subscription/subscription-finish/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "graphql-subscriptions": "^2.0.0", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-17-subscription/subscription-finish/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-17-subscription/subscription-finish/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-17-subscription/subscription-finish/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, installSubscriptionHandlers: true, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-17-subscription/subscription-finish/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-17-subscription/subscription-finish/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export abstract class ISubscription { abstract songCreated(): Song | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-17-subscription/subscription-finish/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-17-subscription/subscription-finish/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-17-subscription/subscription-finish/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } type Subscription { songCreated: Song! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongResolver], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver, Subscription } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; const pubSub = new PubSub(); @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { // return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); throw new GraphQLError('Unable to fetch the songs', { extensions: { code: 'INTERNAL_SERVER_ERROR', }, }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { const newSong = this.songService.createSong(args); pubSub.publish('songCreated', { songCreated: newSong }); return newSong; } @Mutation('updateSong') async updateSong( @Args('updateSongInput') args: UpdateSongDTO, @Args('id') id: string, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } @Subscription('songCreated') songCreated() { return pubSub.asyncIterator('songCreated'); //1 } } ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-17-subscription/subscription-finish/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-17-subscription/subscription-finish/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-17-subscription/subscription-finish/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-17-subscription/subscription-finish/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-17-subscription/subscription-finish/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-17-subscription/subscription-starter/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-17-subscription/subscription-starter/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-17-subscription/subscription-starter/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-17-subscription/subscription-starter/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-17-subscription/subscription-starter/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-17-subscription/subscription-starter/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-17-subscription/subscription-starter/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-17-subscription/subscription-starter/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-17-subscription/subscription-starter/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-17-subscription/subscription-starter/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-17-subscription/subscription-starter/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-17-subscription/subscription-starter/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-17-subscription/subscription-starter/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-17-subscription/subscription-starter/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-17-subscription/subscription-starter/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongResolver], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { // return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); throw new GraphQLError('Unable to fetch the songs', { extensions: { code: 'INTERNAL_SERVER_ERROR', }, }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { return this.songService.createSong(args); } @Mutation('updateSong') async updateSong( @Args('updateSongInput') args: UpdateSongDTO, @Args('id') id: string, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-17-subscription/subscription-starter/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-17-subscription/subscription-starter/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-17-subscription/subscription-starter/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-17-subscription/subscription-starter/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-17-subscription/subscription-starter/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-18-testing-graphql-apis/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-18-testing-graphql-apis/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-18-testing-graphql-apis/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "graphql-subscriptions": "^2.0.0", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, installSubscriptionHandlers: true, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export abstract class ISubscription { abstract songCreated(): Song | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } type Subscription { songCreated: Song! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; import { SongService } from '../../src/song/song.service'; import { CreateSongInput, UpdateSongInput } from '../../src/graphql'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongResolver, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 'a uuid', title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongInput: CreateSongInput) => { return Promise.resolve({ id: 'a uuid', ...createSongInput }); }), updateSong: jest .fn() .mockImplementation( (id, string, updateSongInput: UpdateSongInput) => { return Promise.resolve({ affected: 1 }); }, ), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); it('should fetch the songs', async () => { const songs = await resolver.getSongs(); expect(songs).toEqual([{ id: 'a uuid', title: 'Dancing Feat' }]); expect(songs.length).toBe(1); }); it('should create new song', async () => { const song = await resolver.createSong({ title: 'Animals' }); expect(song).toEqual({ id: 'a uuid', title: 'Animals' }); }); it('should update the song', async () => { const song = await resolver.updateSong('a uuid', { title: 'DANCING FEAT' }); expect(song.affected).toBe(1); }); it('should delete the song', async () => { const song = await resolver.deleteSong('a uuid'); expect(song.affected).toBe(1); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver, Subscription } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; const pubSub = new PubSub(); @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); // throw new GraphQLError('Unable to fetch the songs', { // extensions: { // code: 'INTERNAL_SERVER_ERROR', // }, // }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { const newSong = this.songService.createSong(args); pubSub.publish('songCreated', { songCreated: newSong }); return newSong; } @Mutation('updateSong') async updateSong( @Args('id') id: string, @Args('updateSongInput') args: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } @Subscription('songCreated') songCreated() { return pubSub.asyncIterator('songCreated'); //1 } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-18-testing-graphql-apis/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-18-testing-graphql-apis/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-18-testing-graphql-apis/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-18-testing-graphql-apis/lesson-02/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@apollo/server": "^4.7.5", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "graphql-subscriptions": "^2.0.0", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, installSubscriptionHandlers: true, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export abstract class ISubscription { abstract songCreated(): Song | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.graphql ================================================ type Song { id: ID! title: String } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } type Subscription { songCreated: Song! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; import { SongService } from '../../src/song/song.service'; import { CreateSongInput, UpdateSongInput } from '../../src/graphql'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongResolver, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 'a uuid', title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongInput: CreateSongInput) => { return Promise.resolve({ id: 'a uuid', ...createSongInput }); }), updateSong: jest .fn() .mockImplementation( (id, string, updateSongInput: UpdateSongInput) => { return Promise.resolve({ affected: 1 }); }, ), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); it('should fetch the songs', async () => { const songs = await resolver.getSongs(); expect(songs).toEqual([{ id: 'a uuid', title: 'Dancing Feat' }]); expect(songs.length).toBe(1); }); it('should create new song', async () => { const song = await resolver.createSong({ title: 'Animals' }); expect(song).toEqual({ id: 'a uuid', title: 'Animals' }); }); it('should update the song', async () => { const song = await resolver.updateSong('a uuid', { title: 'DANCING FEAT' }); expect(song.affected).toBe(1); }); it('should delete the song', async () => { const song = await resolver.deleteSong('a uuid'); expect(song.affected).toBe(1); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver, Subscription } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; const pubSub = new PubSub(); @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); // throw new GraphQLError('Unable to fetch the songs', { // extensions: { // code: 'INTERNAL_SERVER_ERROR', // }, // }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { const newSong = this.songService.createSong(args); pubSub.publish('songCreated', { songCreated: newSong }); return newSong; } @Mutation('updateSong') async updateSong( @Args('id') id: string, @Args('updateSongInput') args: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } @Subscription('songCreated') songCreated() { return pubSub.asyncIterator('songCreated'); //1 } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); }); ================================================ FILE: module-18-testing-graphql-apis/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-18-testing-graphql-apis/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "generate:typings": "ts-node generate-typings", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@apollo/server": "^4.7.5", "@apollo/server-plugin-response-cache": "^4.1.3", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.7", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.0", "graphql": "^16.7.1", "graphql-subscriptions": "^2.0.0", "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0", "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongModule } from './song/song.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; import responseCachePlugin from '@apollo/server-plugin-response-cache'; import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [__dirname + '/**/*.entity.{ts,js}'], }), GraphQLModule.forRoot({ driver: ApolloDriver, plugins: [ ApolloServerPluginCacheControl({ defaultMaxAge: 5 }), responseCachePlugin(), ], typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, installSubscriptionHandlers: true, }), SongModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export enum CacheControlScope { PUBLIC = "PUBLIC", PRIVATE = "PRIVATE" } export class CreateSongInput { title: string; } export class UpdateSongInput { title?: Nullable; } export class Song { id: string; title?: Nullable; } export abstract class IQuery { abstract songs(): Song[] | Promise; abstract song(id: string): Song | Promise; } export abstract class IMutation { abstract createSong(createSongInput: CreateSongInput): Song | Promise; abstract updateSong(id: string, updateSongInput: UpdateSongInput): UpdateResult | Promise; abstract deleteSong(id: string): DeleteResult | Promise; } export abstract class ISubscription { abstract songCreated(): Song | Promise; } export class UpdateResult { affected: number; } export class DeleteResult { affected: number; } type Nullable = T | null; ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/dto/create-song-dto.ts ================================================ export interface CreateSongDTO { title: string; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/dto/update-song-dto.ts ================================================ export interface UpdateSongDTO { title?: string; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongController', () => { let controller: SongController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongController], providers: [ SongService, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 1, title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve({ id: 'a uuid', ...createSongDTO }); }), updateSong: jest .fn() .mockImplementation((updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); controller = module.get(SongController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); describe('getSongs', () => { it('should fetch all the songs', async () => { const songs = await controller.getSongs(); expect(songs).toEqual([{ id: 1, title: 'Dancing Feat' }]); }); }); describe('getSong by id', () => { it('should give me the song by id', async () => { const song = await controller.getSong('a uuid'); expect(song.id).toBe('a uuid'); }); }); describe('createSong', () => { it('should create a new song', async () => { const newSongDTO: CreateSongDTO = { title: 'Runaway', }; const song = await controller.createSong(newSongDTO); expect(song.title).toBe('Runaway'); expect(song).toEqual({ id: 'a uuid', title: 'Runaway' }); }); }); describe('updateSong', () => { it('should update the song DTO', async () => { const updatesongDTO: UpdateSongDTO = { title: 'Animals', }; const updateResults = await controller.updateSong( 'a uuid', updatesongDTO, ); expect(updateResults).toBeDefined(); expect(updateResults.affected).toBe(1); }); }); describe('deleteSong', () => { it('should delete the song', async () => { const deleteResult = await controller.deleteSong('a uuid'); expect(deleteResult.affected).toBe(1); }); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.controller.ts ================================================ import { Body, Controller, Delete, Get, Param, Post, Put, } from '@nestjs/common'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; @Controller('songs') export class SongController { constructor(private songService: SongService) {} @Get() getSongs(): Promise { return this.songService.getSongs(); } @Get(':id') getSong( @Param('id') id: string, ): Promise { return this.songService.getSong(id); } @Post() createSong( @Body() createSongDTO: CreateSongDTO, ): Promise { return this.songService.createSong(createSongDTO); } @Put(':id') updateSong( @Param('id') id: string, @Body() updateSongDTO: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, updateSongDTO); } @Delete(':id') deleteSong( @Param('id') id: string, ): Promise { return this.songService.deleteSong(id); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.entity.ts ================================================ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity('songs') export class Song { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.graphql ================================================ enum CacheControlScope { PUBLIC PRIVATE } directive @cacheControl( maxAge: Int scope: CacheControlScope inheritMaxAge: Boolean ) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION type Song @cacheControl(maxAge: 240) { id: ID! title: String @cacheControl(maxAge: 30) } type Query { songs: [Song!]! song(id: ID!): Song! } type Mutation { createSong(createSongInput: CreateSongInput!): Song! updateSong(id: ID!, updateSongInput: UpdateSongInput!): UpdateResult! deleteSong(id: ID!): DeleteResult! } type Subscription { songCreated: Song! } input CreateSongInput { title: String! } input UpdateSongInput { title: String } type UpdateResult { affected: Int! } type DeleteResult { affected: Int! } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongController } from './song.controller'; import { SongService } from './song.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from './song.entity'; import { SongResolver } from './song.resolver'; @Module({ imports: [TypeOrmModule.forFeature([Song])], controllers: [SongController], providers: [SongService, SongResolver], }) export class SongModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongResolver } from './song.resolver'; import { SongService } from '../../src/song/song.service'; import { CreateSongInput, UpdateSongInput } from '../../src/graphql'; describe('SongResolver', () => { let resolver: SongResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongResolver, { provide: SongService, useValue: { getSongs: jest .fn() .mockResolvedValue([{ id: 'a uuid', title: 'Dancing Feat' }]), getSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ id: id, title: 'Dancing' }); }), createSong: jest .fn() .mockImplementation((createSongInput: CreateSongInput) => { return Promise.resolve({ id: 'a uuid', ...createSongInput }); }), updateSong: jest .fn() .mockImplementation( (id, string, updateSongInput: UpdateSongInput) => { return Promise.resolve({ affected: 1 }); }, ), deleteSong: jest.fn().mockImplementation((id: string) => { return Promise.resolve({ affected: 1 }); }), }, }, ], }).compile(); resolver = module.get(SongResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); it('should fetch the songs', async () => { const songs = await resolver.getSongs(); expect(songs).toEqual([{ id: 'a uuid', title: 'Dancing Feat' }]); expect(songs.length).toBe(1); }); it('should create new song', async () => { const song = await resolver.createSong({ title: 'Animals' }); expect(song).toEqual({ id: 'a uuid', title: 'Animals' }); }); it('should update the song', async () => { const song = await resolver.updateSong('a uuid', { title: 'DANCING FEAT' }); expect(song.affected).toBe(1); }); it('should delete the song', async () => { const song = await resolver.deleteSong('a uuid'); expect(song.affected).toBe(1); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.resolver.ts ================================================ import { Args, Mutation, Resolver, Subscription } from '@nestjs/graphql'; import { SongService } from './song.service'; import { Query } from '@nestjs/graphql'; import { CreateSongInput, Song } from '../graphql'; import { UpdateSongDTO } from './dto/update-song-dto'; import { DeleteResult, UpdateResult } from 'typeorm'; import { GraphQLError } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; const pubSub = new PubSub(); @Resolver() export class SongResolver { constructor(private songService: SongService) {} @Query('songs') async getSongs(): Promise { return this.songService.getSongs(); // throw new Error('Unable to fetch songs!'); // throw new GraphQLError('Unable to fetch the songs', { // extensions: { // code: 'INTERNAL_SERVER_ERROR', // }, // }); } @Query('song') async getSong( @Args('id') id: string, ): Promise { return this.songService.getSong(id); } @Mutation('createSong') async createSong( @Args('createSongInput') args: CreateSongInput, ): Promise { const newSong = this.songService.createSong(args); pubSub.publish('songCreated', { songCreated: newSong }); return newSong; } @Mutation('updateSong') async updateSong( @Args('id') id: string, @Args('updateSongInput') args: UpdateSongDTO, ): Promise { return this.songService.updateSong(id, args); } @Mutation('deleteSong') async deleteSong( @Args('id') id: string, ): Promise { return this.songService.deleteSong(id); } @Subscription('songCreated') songCreated() { return pubSub.asyncIterator('songCreated'); //1 } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongService } from './song.service'; import { Song } from './song.entity'; import { FindOneOptions, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; describe('SongService', () => { let service: SongService; let repo: Repository; const oneSong = { id: 'a uuid', title: 'Lover' }; const songArray = [{ id: 'a uuid', title: 'Lover' }]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SongService, { provide: getRepositoryToken(Song), useValue: { find: jest .fn() .mockImplementation(() => Promise.resolve(songArray)), findOneOrFail: jest .fn() .mockImplementation((options: FindOneOptions) => { return Promise.resolve(oneSong); }), create: jest .fn() .mockImplementation((createSongDTO: CreateSongDTO) => { return Promise.resolve(oneSong); }), save: jest.fn(), update: jest .fn() .mockImplementation( (id: string, updateSongDTO: UpdateSongDTO) => { return Promise.resolve({ affected: 1 }); }, ), delete: jest .fn() .mockImplementation((id: string) => Promise.resolve({ affected: 1 }), ), }, }, ], }).compile(); service = module.get(SongService); repo = module.get>(getRepositoryToken(Song)); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('should give me the song by id', async () => { const song = await service.getSong('a uuid'); const repoSpy = jest.spyOn(repo, 'findOneOrFail'); expect(song).toEqual(oneSong); expect(repoSpy).toBeCalledWith({ where: { id: 'a uuid' } }); }); it('should create the song', async () => { const song = await service.createSong({ title: 'Lover' }); expect(song).toEqual(oneSong); expect(repo.create).toBeCalledTimes(1); expect(repo.create).toBeCalledWith({ title: 'Lover' }); }); it('should update the song', async () => { const result = await service.updateSong('a uuid', { title: 'Lover' }); expect(repo.update).toBeCalledTimes(1); expect(result.affected).toEqual(1); }); it('should delete the song', async () => { const song = await service.deleteSong('a uuid'); const repoSpyOn = jest.spyOn(repo, 'delete'); expect(repo.delete).toBeCalledTimes(1); expect(song.affected).toBe(1); expect(repoSpyOn).toBeCalledWith('a uuid'); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/src/song/song.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Song } from './song.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { DeleteResult, Repository, UpdateResult } from 'typeorm'; import { CreateSongDTO } from './dto/create-song-dto'; import { UpdateSongDTO } from './dto/update-song-dto'; @Injectable() export class SongService { constructor( @InjectRepository(Song) private readonly songRepo: Repository, ) {} async getSongs(): Promise { return this.songRepo.find(); } getSong(id: string) { return this.songRepo.findOneOrFail({ where: { id } }); } async createSong(createSongDTO: CreateSongDTO) { const newSong = this.songRepo.create(createSongDTO); await this.songRepo.save(newSong); return newSong; } async updateSong(id, updateSongDTO: UpdateSongDTO): Promise { return this.songRepo.update({ id }, updateSongDTO); } async deleteSong(id: string): Promise { return this.songRepo.delete(id); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all", "printWidth": 100 } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/nest-cli.json ================================================ { "collection": "@nestjs/schematics", "sourceRoot": "src" } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/package.json ================================================ { "name": "example-app", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^7.6.15", "@nestjs/core": "^7.6.15", "@nestjs/graphql": "^7.11.0", "@nestjs/platform-express": "^7.6.15", "apollo-server-express": "^2.25.2", "dataloader": "^2.0.0", "graphql": "^15.5.1", "graphql-tools": "^7.0.5", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.6.6" }, "devDependencies": { "@nestjs/cli": "^7.6.0", "@nestjs/schematics": "^7.3.0", "@nestjs/testing": "^7.6.15", "@types/express": "^4.17.11", "@types/jest": "^26.0.22", "@types/node": "^14.14.36", "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/parser": "^4.19.0", "eslint": "^7.22.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-prettier": "^3.3.1", "jest": "^26.6.3", "prettier": "^2.2.1", "supertest": "^6.1.3", "ts-jest": "^26.5.4", "ts-loader": "^8.0.18", "ts-node": "^9.1.1", "tsconfig-paths": "^3.9.0", "typescript": "^4.2.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { join } from 'path'; import { PostsModule } from './posts/posts.module'; import { createUsersLoader } from './users/users.loader'; import { UsersModule } from './users/users.module'; import { UsersService } from './users/users.service'; @Module({ imports: [ PostsModule, GraphQLModule.forRootAsync({ imports: [UsersModule], useFactory: (usersService: UsersService) => ({ autoSchemaFile: join(process.cwd(), 'src/schema.gql'), context: () => ({ randomValue: Math.random(), usersLoader: createUsersLoader(usersService), }), }), inject: [UsersService], }), ], controllers: [], providers: [], }) export class AppModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/posts/post.entity.ts ================================================ import { Field, ObjectType } from '@nestjs/graphql'; import { User } from '../users/user.entity'; @ObjectType() export class Post { @Field() id: string; @Field() title: string; @Field() body: string; userId: number; @Field(() => User) createdBy?: User; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/posts/posts.module.ts ================================================ import { Module } from '@nestjs/common'; import { PostsResolver } from './posts.resolver'; import { PostsService } from './posts.service'; import { UsersService } from 'src/users/users.service'; import { UsersModule } from 'src/users/users.module'; @Module({ imports: [UsersModule], providers: [PostsService, PostsResolver, UsersService], }) export class PostsModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/posts/posts.resolver.ts ================================================ import { Context, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import * as DataLoader from 'dataloader'; import { User } from '../users/user.entity'; import { Post } from './post.entity'; import { PostsService } from './posts.service'; import { UsersService } from 'src/users/users.service'; @Resolver(Post) export class PostsResolver { constructor(private readonly postsService: PostsService, private userService: UsersService) {} @Query(() => [Post], { name: 'posts' }) getPosts() { return this.postsService.getPosts(); } @ResolveField('createdBy', () => User) getCreatedBy( @Parent() post: Post, @Context('usersLoader') usersLoader: DataLoader, ) { const { userId } = post; return usersLoader.load(userId); // return this.userService.getUser(userId); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/posts/posts.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { delay } from '../util'; import { Post } from './post.entity'; @Injectable() export class PostsService { private posts: Post[] = [ { id: 'post-1', title: 'Post 1', body: 'Lorem 1', userId: 1 }, { id: 'post-2', title: 'Post 2', body: 'Lorem 2', userId: 1 }, { id: 'post-3', title: 'Post 3', body: 'Lorem 3', userId: 2 }, { id: 'post-4', title: 'Post 4', body: 'Somehting', userId: 4 }, ]; async getPosts() { console.log('Getting posts...'); await delay(3000); return this.posts; } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/schema.gql ================================================ # ------------------------------------------------------ # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) # ------------------------------------------------------ type User { id: Int! name: String! } type Post { id: String! title: String! body: String! createdBy: User! } type Query { posts: [Post!]! users: [User!]! } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/users/user.entity.ts ================================================ import { Field, Int, ObjectType } from '@nestjs/graphql'; @ObjectType() export class User { @Field(() => Int) id: number; @Field() name: string; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/users/users.loader.ts ================================================ import * as DataLoader from 'dataloader'; import { mapFromArray } from '../util'; import { User } from './user.entity'; import { UsersService } from './users.service'; export function createUsersLoader(usersService: UsersService) { return new DataLoader(async (ids) => { const users = await usersService.getUsersByIds(ids); const usersMap = mapFromArray(users, (user) => user.id); console.log('usersMap', usersMap); const results = ids.map((id) => usersMap[id]); console.log('results', results); return results; }); } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { UsersResolver } from './users.resolver'; import { UsersService } from './users.service'; @Module({ providers: [UsersService, UsersResolver], exports: [UsersService], }) export class UsersModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/users/users.resolver.ts ================================================ import { Query, Resolver } from '@nestjs/graphql'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Resolver(User) export class UsersResolver { constructor(private readonly usersService: UsersService) {} @Query(() => [User], { name: 'users' }) getUsers() { return this.usersService.getUsers(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { delay } from '../util'; import { User } from './user.entity'; @Injectable() export class UsersService { private users: User[] = [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Alex' }, { id: 4, name: 'Anna' }, ]; async getUsers() { console.log('Getting users...'); await delay(3000); return this.users; } async getUser(id: number) { console.log(`FETCHING DATA FROM DB: Getting user with id ${id}...`); await delay(1000); return this.users.find((user) => user.id === id); } async getUsersByIds(ids: readonly number[]) { console.log(`Getting users with ids (${ids.join(',')})`); await delay(1000); return this.users.filter((u) => ids.includes(u.id)); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/src/util.ts ================================================ export function delay(timeout: number) { return new Promise((resolve) => setTimeout(() => resolve(), timeout)); } export function mapFromArray( array: T[], keyStrategy: (v: T) => string | number, ) { const map: Record = {}; for (const item of array) { map[keyStrategy(item)] = item; } return map; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-02-dataloaders/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/api.http ================================================ ### Create Song Endpoint POST http://localhost:3001/songs Content-Type: application/json { "title": "Danza ", "releasedDate" : "2023-05-11", "duration" :"02:33", "lyrics": "1adas adsdasd asdasdasd qeqew", "album": "648097fd6535d2cff889633d" } ### Find Songs GET http://localhost:3001/songs ### Delete Song DELETE http://localhost:3001/songs/648047e39cd98439a21a741e ### Create PlayList POST http://localhost:3001/albums Content-Type: application/json { "title": "Dance Music", "songs": ["648097fd6535d2cff889633d"] } ### Find Albums GET http://localhost:3001/albums ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/docker-compose.yml ================================================ version: '3' services: mongodb: image: mongo:latest environment: - MONGODB_DATABASE="test" ports: - 27017:27017 ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/generate-typings.ts ================================================ import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; import { join } from 'path'; const definitionsFactory = new GraphQLDefinitionsFactory(); definitionsFactory.generate({ typePaths: ['./src/**/*.graphql'], path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/package.json ================================================ { "name": "n-mongo-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@apollo/datasource-rest": "^6.0.1", "@apollo/server": "^4.8.0", "@nestjs/apollo": "^12.0.7", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^12.0.8", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "^9.0.0", "graphql": "^16.7.1", "mongoose": "^7.2.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@types/express": "^4.17.13", "@types/jest": "29.2.4", "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "29.3.1", "prettier": "^2.3.2", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "29.0.3", "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", "typescript": "^4.7.4", "webpack": "^5.88.2" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/album/album.controller.ts ================================================ import { Body, Controller, Get, Post } from '@nestjs/common'; import { Album } from './schemas/album.schema'; import { AlbumService } from './album.service'; import { CreateAlbumDTO } from './dto/create-album-dto'; @Controller('albums') export class AlbumController { constructor(private albumService: AlbumService) {} @Post() create( @Body() createAlbumDTO: CreateAlbumDTO, ): Promise { return this.albumService.createAlbum(createAlbumDTO); } @Get() find(): Promise { return this.albumService.findAlbums(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/album/album.module.ts ================================================ import { Module } from '@nestjs/common'; import { AlbumController } from './album.controller'; import { AlbumService } from './album.service'; import { MongooseModule } from '@nestjs/mongoose'; import { Album, AlbumSchema } from './schemas/album.schema'; @Module({ imports: [ MongooseModule.forFeature([{ name: Album.name, schema: AlbumSchema }]), ], controllers: [AlbumController], providers: [AlbumService], }) export class AlbumModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/album/album.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { Album, AlbumDocument } from './schemas/album.schema'; import { Model } from 'mongoose'; import { InjectModel } from '@nestjs/mongoose'; import { CreateAlbumDTO } from './dto/create-album-dto'; import { Song } from 'src/songs/schemas/song.schema'; @Injectable() export class AlbumService { constructor( @InjectModel(Album.name) private readonly albumModel: Model, ) {} async createAlbum(createAlbumDTO: CreateAlbumDTO): Promise { return this.albumModel.create(createAlbumDTO); } async findAlbums() { return this.albumModel.find().populate('songs', null, Song.name); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/album/dto/create-album-dto.ts ================================================ export class CreateAlbumDTO { title: string; songs: string[]; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/album/schemas/album.schema.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose'; import { Song } from 'src/songs/schemas/song.schema'; export type AlbumDocument = HydratedDocument; @Schema() export class Album { @Prop({ required: true, }) title: string; @Prop({ type: [Types.ObjectId], ref: 'songs' }) songs: Song[]; } export const AlbumSchema = SchemaFactory.createForClass(Album); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; import { SongsModule } from './songs/songs.module'; import { AlbumModule } from './album/album.module'; import { ProductModule } from './product/product.module'; import { UserModule } from './user/user.module'; import { join } from 'path'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { GraphQLModule } from '@nestjs/graphql'; import { TodoModule } from './todo/todo.module'; import * as mongoose from 'mongoose'; import { TodoService } from './todo/todo.service'; mongoose.set('debug', true); const dataSources = () => ({ todoAPI: new TodoService(), }); @Module({ imports: [ GraphQLModule.forRoot({ driver: ApolloDriver, typePaths: ['./**/*.graphql'], definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class', }, context: async () => ({ dataSources, }), }), MongooseModule.forRoot('mongodb://localhost:27017/test'), SongsModule, AlbumModule, ProductModule, UserModule, TodoModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/graphql.ts ================================================ /* * ------------------------------------------------------- * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) * ------------------------------------------------------- */ /* tslint:disable */ /* eslint-disable */ export enum Status { NEW = "NEW", RELEASED = "RELEASED", FAILED = "FAILED" } export class ProductInput { name: string; qty?: Nullable; status?: Nullable; } export class Owner { _id: string; name: string; } export class Product { _id?: Nullable; name: string; qty?: Nullable; owner: Owner; status?: Nullable; } export abstract class IQuery { abstract product(_id?: Nullable): Product | Promise; abstract products(): Nullable[] | Promise[]>; abstract todos(): Todo[] | Promise; } export abstract class IMutation { abstract createProduct(input: ProductInput): Product | Promise; abstract updateProduct(_id: string, input: ProductInput): Product | Promise; abstract deleteProduct(_id: string): Product | Promise; } export class Todo { id: string; userId: number; title: string; completed?: Nullable; } type Nullable = T | null; ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product-owner.resolver.ts ================================================ import { Resolver, Query, ResolveField, Parent } from '@nestjs/graphql'; import { ProductService } from './product.service'; import { UserService } from '../user/user.service'; import { Product } from 'src/graphql'; @Resolver('Product') export class ProductOwnerResolver { constructor( private productService: ProductService, private ownerService: UserService, ) {} @ResolveField('owner') async getOwner( @Parent() product: Product, ) { console.log('DB CALLED'); return await this.ownerService.findOneById(product.owner); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.graphql ================================================ enum Status { NEW RELEASED FAILED } type Owner { _id: ID! name: String! } type Product { _id: ID name: String! qty: Int owner: Owner! status: Status } type Query { product(_id: ID): Product! products: [Product]! } input ProductInput { name: String! qty: Int status: Status } type Mutation { createProduct(input: ProductInput!): Product! updateProduct(_id: ID!, input: ProductInput!): Product! deleteProduct(_id: ID!): Product! } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.module.ts ================================================ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { Product, ProductSchema } from './schemas/product.schema'; import { ProductResolver } from './product.resolver'; import { ProductService } from './product.service'; import { ProductOwnerResolver } from './product-owner.resolver'; import { UserModule } from 'src/user/user.module'; import { UserService } from 'src/user/user.service'; import { User, UserSchema } from 'src/user/schemas/user.schema'; @Module({ imports: [ MongooseModule.forFeature([ { name: Product.name, schema: ProductSchema }, { name: User.name, schema: UserSchema }, ]), UserModule, ], providers: [ ProductResolver, ProductService, ProductOwnerResolver, UserService, ], }) export class ProductModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ProductResolver } from './product.resolver'; describe('ProductResolver', () => { let resolver: ProductResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ProductResolver], }).compile(); resolver = module.get(ProductResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.resolver.ts ================================================ import { Resolver, Query } from '@nestjs/graphql'; import { Product } from './schemas/product.schema'; import { ProductService } from './product.service'; @Resolver() export class ProductResolver { constructor(private productService: ProductService) {} @Query('products') async getProducts(): Promise { return this.productService.fetchProducts(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ProductService } from './product.service'; describe('ProductService', () => { let service: ProductService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ProductService], }).compile(); service = module.get(ProductService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/product.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { Product, ProductDocument } from './schemas/product.schema'; @Injectable() export class ProductService { constructor( @InjectModel(Product.name) private readonly productModel: Model, ) {} async fetchProducts(): Promise { const results = await this.productModel.find(); console.log(results); return results; } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/product/schemas/product.schema.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose'; import { User } from 'src/user/schemas/user.schema'; export type ProductDocument = HydratedDocument; @Schema() export class Product { @Prop({ required: true, }) name: string; @Prop({ type: Number, }) qty: number; @Prop({ type: String, enum: ['NEW', 'RELEASED', 'FAILED'], default: 'NEW', }) status: string; @Prop({ type: Types.ObjectId, ref: User.name, }) owner: User; } export const ProductSchema = SchemaFactory.createForClass(Product); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/dto/create-song-dto.ts ================================================ export class CreateSongDTO { title: string; releasedDate: Date; duration: Date; lyrics: string; album: string; } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/schemas/song.schema.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Types } from 'mongoose'; import { Album } from 'src/album/schemas/album.schema'; export type SongDocument = HydratedDocument; @Schema() export class Song { @Prop({ required: true, }) title: string; @Prop({ required: true, }) releasedDate: Date; @Prop({ required: true, }) duration: string; lyrics: string; @Prop({ type: Types.ObjectId, ref: Album.name, }) album: Album; } export const SongSchema = SchemaFactory.createForClass(Song); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/songs.controller.ts ================================================ import { Body, Controller, Post, Get, Param, Delete } from '@nestjs/common'; import { CreateSongDTO } from './dto/create-song-dto'; import { SongsService } from './songs.service'; import { Song } from './schemas/song.schema'; @Controller('songs') export class SongsController { constructor(private songService: SongsService) {} @Post() create( @Body() createSongDTO: CreateSongDTO, ) { return this.songService.create(createSongDTO); } @Get() find(): Promise { return this.songService.find(); } @Get(':id') findOne( @Param('id') id: string, ): Promise { return this.songService.findById(id); } @Delete(':id') delete( @Param('id') id: string, ) { return this.songService.delete(id); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { Song, SongSchema } from './schemas/song.schema'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; @Module({ imports: [ MongooseModule.forFeature([{ name: Song.name, schema: SongSchema }]), ], controllers: [SongsController], providers: [SongsService], }) export class SongsModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Song, SongDocument } from './schemas/song.schema'; import { Model } from 'mongoose'; import { CreateSongDTO } from './dto/create-song-dto'; @Injectable() export class SongsService { constructor( @InjectModel(Song.name) private readonly songModel: Model, ) {} async create(createSongDTO: CreateSongDTO): Promise { const song = await this.songModel.create(createSongDTO); return song; } async find(): Promise { return this.songModel.find(); } async findById(id: string): Promise { return this.songModel.findById(id); } async delete(id: string) { return this.songModel.deleteOne({ _id: id }); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.graphql ================================================ type Todo { id: ID! userId: Int! title: String! completed: Boolean } type Query { todos: [Todo!]! } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.module.ts ================================================ import { Module } from '@nestjs/common'; import { TodoService } from './todo.service'; import { TodoResolver } from './todo.resolver'; @Module({ providers: [TodoService, TodoResolver] }) export class TodoModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.resolver.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TodoResolver } from './todo.resolver'; describe('TodoResolver', () => { let resolver: TodoResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TodoResolver], }).compile(); resolver = module.get(TodoResolver); }); it('should be defined', () => { expect(resolver).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.resolver.ts ================================================ import { Context, Resolver } from '@nestjs/graphql'; import { Query } from '@nestjs/graphql'; @Resolver() export class TodoResolver { @Query('todos') async getTodods( @Context('dataSources') dataSources, ) { return dataSources().todoAPI.getTodos(); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TodoService } from './todo.service'; describe('TodoService', () => { let service: TodoService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TodoService], }).compile(); service = module.get(TodoService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/todo/todo.service.ts ================================================ import { RESTDataSource } from '@apollo/datasource-rest'; import { Injectable } from '@nestjs/common'; import { Todo } from 'src/graphql'; @Injectable() export class TodoService extends RESTDataSource { constructor() { super(); this.baseURL = 'https://jsonplaceholder.typicode.com'; } async getTodos(): Promise { return this.get('/todos'); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/user/schemas/user.schema.ts ================================================ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose'; import { Product } from 'src/product/schemas/product.schema'; export type UserDocument = HydratedDocument; @Schema() export class User { @Prop({ required: true, }) name: string; @Prop({ type: [Types.ObjectId], ref: 'products' }) products: Product[]; } export const UserSchema = SchemaFactory.createForClass(User); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/user/user.module.ts ================================================ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { User, UserSchema } from './schemas/user.schema'; import { UserService } from './user.service'; @Module({ imports: [ MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], providers: [UserService], exports: [UserService], }) export class UserModule {} ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/src/user/user.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import mongoose, { Model } from 'mongoose'; import { User, UserDocument } from 'src/user/schemas/user.schema'; @Injectable() export class UserService { constructor( @InjectModel(User.name) private readonly userModel: Model, ) {} async findOneById(ownerId: any) { return await this.userModel.findOne({ _id: ownerId }); } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-19-graphql-advanced-concepts/lesson-03-fetching-external-api/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-01/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-01/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-01/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-01/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-01/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-01/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-01/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } ================================================ FILE: module-20-prisma-integration/lesson-01/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-01/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-01/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-01/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-01/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-01/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-01/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-01/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-01/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-01/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-02/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-02/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-02/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-02/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-02/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-02/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-02/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-02/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-02/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String } ================================================ FILE: module-20-prisma-integration/lesson-02/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-02/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-02/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-02/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-02/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-02/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-02/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-02/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-02/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-02/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-03/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-03/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-03/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-03/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-03/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-03/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-03/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-03/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-03/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String } ================================================ FILE: module-20-prisma-integration/lesson-03/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-03/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-03/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-03/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-03/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-03/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-03/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-03/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-03/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-03/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-03/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "LOVER ON THE SUN" } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; @Module({ imports: [SongsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/entities/song.entity.ts ================================================ export class Song {} ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany(); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-04-and-05/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-06/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-06/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-06/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-06/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-06/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "Animals", "artistId":1 } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ### Create Artist POST http://localhost:3000/artists Content-Type: application/json { "name" : "Avicci" } ================================================ FILE: module-20-prisma-integration/lesson-06/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-06/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-06/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-06/prisma/migrations/20230801082432_add_artists/migration.sql ================================================ -- AlterTable ALTER TABLE "Song" ADD COLUMN "artistId" INTEGER; -- CreateTable CREATE TABLE "Artist" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Artist_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-06/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-06/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @id @default(autoincrement()) name String songs Song[] } ================================================ FILE: module-20-prisma-integration/lesson-06/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { ArtistsModule } from './artists/artists.module'; @Module({ imports: [SongsModule, ArtistsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], providers: [ArtistsService], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/artists.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Prisma } from '@prisma/client'; @Controller('artists') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Post() create(@Body() createArtistDto: Prisma.ArtistCreateInput) { return this.artistsService.create(createArtistDto); } @Get() findAll() { return this.artistsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.artistsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateArtistDto: UpdateArtistDto) { return this.artistsService.update(+id, updateArtistDto); } @Delete(':id') remove(@Param('id') id: string) { return this.artistsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ArtistsController], providers: [ArtistsService, PrismaService], }) export class ArtistsModule {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ArtistsService { constructor(private prisma: PrismaService) {} create(createArtistDto: Prisma.ArtistCreateInput) { return this.prisma.artist.create({ data: createArtistDto, }); } findAll() { return `This action returns all artists`; } findOne(id: number) { return `This action returns a #${id} artist`; } update(id: number, updateArtistDto: UpdateArtistDto) { return `This action updates a #${id} artist`; } remove(id: number) { return `This action removes a #${id} artist`; } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/dto/create-artist.dto.ts ================================================ export class CreateArtistDto {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/artists/dto/update-artist.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateArtistDto } from './create-artist.dto'; export class UpdateArtistDto extends PartialType(CreateArtistDto) {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-06/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongUncheckedCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany({ include: { artist: true } }); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-06/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-06/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-06/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-06/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-07/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-07/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-07/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-07/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-07/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "Animals", "artistId":1 } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ### Create Artist POST http://localhost:3000/artists Content-Type: application/json { "name" : "Avicci" } ### Create User POST http://localhost:3000/users Content-Type: application/json { "name" : "Jane Doe", "photo": "some-api.com/photos/1.jpg", "phone": "+9234344433" } ### FETCH Users GET http://localhost:3000/users ================================================ FILE: module-20-prisma-integration/lesson-07/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-07/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-07/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-07/prisma/migrations/20230801082432_add_artists/migration.sql ================================================ -- AlterTable ALTER TABLE "Song" ADD COLUMN "artistId" INTEGER; -- CreateTable CREATE TABLE "Artist" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Artist_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-07/prisma/migrations/20230801091013_one_to_one/migration.sql ================================================ -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Profile" ( "id" SERIAL NOT NULL, "userId" INTEGER NOT NULL, "photo" TEXT NOT NULL, "phone" TEXT NOT NULL, CONSTRAINT "Profile_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId"); -- AddForeignKey ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-07/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-07/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @id @default(autoincrement()) name String songs Song[] } model User { id Int @id @default(autoincrement()) name String profile Profile? } model Profile { id Int @id @default(autoincrement()) user User @relation(fields: [userId], references: [id]) userId Int @unique photo String phone String } ================================================ FILE: module-20-prisma-integration/lesson-07/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { ArtistsModule } from './artists/artists.module'; import { UsersModule } from './users/users.module'; @Module({ imports: [SongsModule, ArtistsModule, UsersModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], providers: [ArtistsService], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/artists.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Prisma } from '@prisma/client'; @Controller('artists') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Post() create(@Body() createArtistDto: Prisma.ArtistCreateInput) { return this.artistsService.create(createArtistDto); } @Get() findAll() { return this.artistsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.artistsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateArtistDto: UpdateArtistDto) { return this.artistsService.update(+id, updateArtistDto); } @Delete(':id') remove(@Param('id') id: string) { return this.artistsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ArtistsController], providers: [ArtistsService, PrismaService], }) export class ArtistsModule {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ArtistsService { constructor(private prisma: PrismaService) {} create(createArtistDto: Prisma.ArtistCreateInput) { return this.prisma.artist.create({ data: createArtistDto, }); } findAll() { return `This action returns all artists`; } findOne(id: number) { return `This action returns a #${id} artist`; } update(id: number, updateArtistDto: UpdateArtistDto) { return `This action updates a #${id} artist`; } remove(id: number) { return `This action removes a #${id} artist`; } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/dto/create-artist.dto.ts ================================================ export class CreateArtistDto {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/artists/dto/update-artist.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateArtistDto } from './create-artist.dto'; export class UpdateArtistDto extends PartialType(CreateArtistDto) {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-07/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongUncheckedCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany({ include: { artist: true } }); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/dto/create-user.dto.ts ================================================ export class CreateUserDto { name: string; photo: string; phone: string; } ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/dto/update-user.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/users.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; describe('UsersController', () => { let controller: UsersController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], providers: [UsersService], }).compile(); controller = module.get(UsersController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/users.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); } @Get() findAll() { return this.usersService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.usersService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.usersService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.usersService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [UsersController], providers: [UsersService, PrismaService], }) export class UsersModule {} ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from '../prisma.service'; @Injectable() export class UsersService { constructor(private prisma: PrismaService) {} create(createUserDto: CreateUserDto) { return this.prisma.user.create({ data: { name: createUserDto.name, profile: { create: { phone: createUserDto.phone, photo: createUserDto.photo, }, }, }, }); } findAll() { return this.prisma.user.findMany({ include: { profile: true } }); } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } } ================================================ FILE: module-20-prisma-integration/lesson-07/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-07/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-07/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-07/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-08/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-08/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-08/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-08/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-08/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "Animals", "artistId":1 } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ### Create Artist POST http://localhost:3000/artists Content-Type: application/json { "name" : "Avicci" } ### Create User POST http://localhost:3000/users Content-Type: application/json { "name" : "Jane Doe", "photo": "some-api.com/photos/1.jpg", "phone": "+9234344433" } ### FETCH Users GET http://localhost:3000/users ### Create POST POST http://localhost:3000/posts Content-Type: application/json { "title": "One to Many Relation", "categories": { "create": [ { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Prisma" } } }, { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Nest.js" } } } ] } } ### Create Post with Existing categories POST http://localhost:3000/posts Content-Type: application/json { "title": "Transactions in Prisma", "categories": { "create": [ { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 1 } } }, { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 2 } } } ] } } ### FETCH ALL THE POSTS WITH NEST.JS CATEGORY GET http://localhost:3000/posts Content-Type: application/json { "categories": { "some": { "category": { "name": "Nest.js" } } } } ### CREATE NEW APPLICATION POST http://localhost:3000/applications Content-Type: application/json { "email": "jane1@gmail.com", "name": "Jone Doe", "address": { "create": { "city": "New York", "country": "USA", "zip": "34443" } }, "applications": { "create": [ { "amount": 32224, "tenure": "5 Years", "type": "BUSINESS_FINANCING" } ] } } ================================================ FILE: module-20-prisma-integration/lesson-08/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-08/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230801082432_add_artists/migration.sql ================================================ -- AlterTable ALTER TABLE "Song" ADD COLUMN "artistId" INTEGER; -- CreateTable CREATE TABLE "Artist" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Artist_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230801091013_one_to_one/migration.sql ================================================ -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Profile" ( "id" SERIAL NOT NULL, "userId" INTEGER NOT NULL, "photo" TEXT NOT NULL, "phone" TEXT NOT NULL, CONSTRAINT "Profile_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId"); -- AddForeignKey ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230802074328_many_to_many/migration.sql ================================================ -- CreateTable CREATE TABLE "Post" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Post_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Category" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Category_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "CategoriesOnPosts" ( "postId" INTEGER NOT NULL, "categoryId" INTEGER NOT NULL, "asignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "assignedBy" TEXT NOT NULL, CONSTRAINT "CategoriesOnPosts_pkey" PRIMARY KEY ("postId","categoryId") ); -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230802081727_nested_queries/migration.sql ================================================ -- CreateEnum CREATE TYPE "APPLICATION_TYPE" AS ENUM ('LOAN', 'CAR_FINANCING', 'BUSINESS_FINANCING'); -- CreateTable CREATE TABLE "Customer" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, "email" TEXT NOT NULL, "addressId" INTEGER, CONSTRAINT "Customer_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Address" ( "id" SERIAL NOT NULL, "zip" TEXT, "city" TEXT NOT NULL, "country" TEXT NOT NULL, CONSTRAINT "Address_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Application" ( "id" SERIAL NOT NULL, "type" "APPLICATION_TYPE" NOT NULL, "tenure" TEXT NOT NULL, "amount" INTEGER NOT NULL, "customerId" INTEGER, CONSTRAINT "Application_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Customer_email_key" ON "Customer"("email"); -- CreateIndex CREATE UNIQUE INDEX "Customer_addressId_key" ON "Customer"("addressId"); -- AddForeignKey ALTER TABLE "Customer" ADD CONSTRAINT "Customer_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Application" ADD CONSTRAINT "Application_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/20230802083400_default_value_for_type/migration.sql ================================================ -- AlterTable ALTER TABLE "Application" ALTER COLUMN "type" SET DEFAULT 'LOAN'; ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-08/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @id @default(autoincrement()) name String songs Song[] } model User { id Int @id @default(autoincrement()) name String profile Profile? } model Profile { id Int @id @default(autoincrement()) user User @relation(fields: [userId], references: [id]) userId Int @unique photo String phone String } model Post { id Int @id @default(autoincrement()) title String categories CategoriesOnPosts[] } model Category { id Int @id @default(autoincrement()) name String posts CategoriesOnPosts[] } model CategoriesOnPosts { post Post @relation(fields: [postId], references: [id]) postId Int category Category @relation(fields: [categoryId], references: [id]) categoryId Int asignedAt DateTime @default(now()) assignedBy String @@id([postId, categoryId]) } // You have to create Customer, Address and the Application Model // One to one relation with Customer and Address // One to many relation with Customer and Application model Customer { id Int @id @default(autoincrement()) name String email String @unique address Address? @relation(fields: [addressId], references: [id]) applications Application[] addressId Int? @unique } model Address { id Int @id @default(autoincrement()) zip String? city String country String Customer Customer? } enum APPLICATION_TYPE { LOAN CAR_FINANCING BUSINESS_FINANCING } model Application { id Int @id @default(autoincrement()) type APPLICATION_TYPE @default(LOAN) tenure String amount Int Customer Customer? @relation(fields: [customerId], references: [id]) customerId Int? } ================================================ FILE: module-20-prisma-integration/lesson-08/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { ArtistsModule } from './artists/artists.module'; import { UsersModule } from './users/users.module'; import { PostsModule } from './posts/posts.module'; import { ApplicationsModule } from './applications/applications.module'; @Module({ imports: [SongsModule, ArtistsModule, UsersModule, PostsModule, ApplicationsModule], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/applications.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsController } from './applications.controller'; import { ApplicationsService } from './applications.service'; describe('ApplicationsController', () => { let controller: ApplicationsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ApplicationsController], providers: [ApplicationsService], }).compile(); controller = module.get(ApplicationsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/applications.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { Prisma } from '@prisma/client'; @Controller('applications') export class ApplicationsController { constructor(private readonly applicationsService: ApplicationsService) {} @Post() create(@Body() createApplicationDto: Prisma.CustomerCreateInput) { return this.applicationsService.create(createApplicationDto); } @Get() findAll() { return this.applicationsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.applicationsService.findOne(+id); } @Patch(':id') update( @Param('id') id: string, @Body() updateApplicationDto: UpdateApplicationDto, ) { return this.applicationsService.update(+id, updateApplicationDto); } @Delete(':id') remove(@Param('id') id: string) { return this.applicationsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/applications.module.ts ================================================ import { Module } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { ApplicationsController } from './applications.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ApplicationsController], providers: [ApplicationsService, PrismaService], }) export class ApplicationsModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/applications.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsService } from './applications.service'; describe('ApplicationsService', () => { let service: ApplicationsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ApplicationsService], }).compile(); service = module.get(ApplicationsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/applications.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ApplicationsService { constructor(private prisma: PrismaService) {} create(createApplicationDto: Prisma.CustomerCreateInput) { return this.prisma.customer.create({ data: createApplicationDto }); } findAll() { return `This action returns all applications`; } findOne(id: number) { return `This action returns a #${id} application`; } update(id: number, updateApplicationDto: UpdateApplicationDto) { return `This action updates a #${id} application`; } remove(id: number) { return `This action removes a #${id} application`; } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/dto/create-application.dto.ts ================================================ export class CreateApplicationDto {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/applications/dto/update-application.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateApplicationDto } from './create-application.dto'; export class UpdateApplicationDto extends PartialType(CreateApplicationDto) {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], providers: [ArtistsService], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/artists.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Prisma } from '@prisma/client'; @Controller('artists') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Post() create(@Body() createArtistDto: Prisma.ArtistCreateInput) { return this.artistsService.create(createArtistDto); } @Get() findAll() { return this.artistsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.artistsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateArtistDto: UpdateArtistDto) { return this.artistsService.update(+id, updateArtistDto); } @Delete(':id') remove(@Param('id') id: string) { return this.artistsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ArtistsController], providers: [ArtistsService, PrismaService], }) export class ArtistsModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ArtistsService { constructor(private prisma: PrismaService) {} create(createArtistDto: Prisma.ArtistCreateInput) { return this.prisma.artist.create({ data: createArtistDto, }); } findAll() { return `This action returns all artists`; } findOne(id: number) { return `This action returns a #${id} artist`; } update(id: number, updateArtistDto: UpdateArtistDto) { return `This action updates a #${id} artist`; } remove(id: number) { return `This action removes a #${id} artist`; } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/dto/create-artist.dto.ts ================================================ export class CreateArtistDto {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/artists/dto/update-artist.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateArtistDto } from './create-artist.dto'; export class UpdateArtistDto extends PartialType(CreateArtistDto) {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/dto/create-post.dto.ts ================================================ export class CreatePostDto {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/dto/update-post.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreatePostDto } from './create-post.dto'; export class UpdatePostDto extends PartialType(CreatePostDto) {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/posts.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; describe('PostsController', () => { let controller: PostsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [PostsController], providers: [PostsService], }).compile(); controller = module.get(PostsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/posts.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { PostsService } from './posts.service'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { Prisma } from '@prisma/client'; @Controller('posts') export class PostsController { constructor(private readonly postsService: PostsService) {} @Post() @Post() create(@Body() createPostDto: Prisma.PostCreateInput) { return this.postsService.create(createPostDto); } @Get() findAll( @Body() where: Prisma.PostWhereUniqueInput, ) { return this.postsService.findAll(where); } @Get(':id') findOne(@Param('id') id: string) { return this.postsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) { return this.postsService.update(+id, updatePostDto); } @Delete(':id') remove(@Param('id') id: string) { return this.postsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/posts.module.ts ================================================ import { Module } from '@nestjs/common'; import { PostsService } from './posts.service'; import { PostsController } from './posts.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [PostsController], providers: [PostsService, PrismaService], }) export class PostsModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/posts.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { let service: PostsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [PostsService], }).compile(); service = module.get(PostsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/posts/posts.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class PostsService { constructor(private prisma: PrismaService) {} create(createPostDto: Prisma.PostCreateInput) { return this.prisma.post.create({ data: createPostDto }); } findAll(where: Prisma.PostWhereUniqueInput) { return this.prisma.post.findMany({ where, }); } findOne(id: number) { return `This action returns a #${id} post`; } update(id: number, updatePostDto: UpdatePostDto) { return `This action updates a #${id} post`; } remove(id: number) { return `This action removes a #${id} post`; } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongUncheckedCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany({ include: { artist: true } }); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/dto/create-user.dto.ts ================================================ export class CreateUserDto { name: string; photo: string; phone: string; } ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/dto/update-user.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/users.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; describe('UsersController', () => { let controller: UsersController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], providers: [UsersService], }).compile(); controller = module.get(UsersController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/users.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); } @Get() findAll() { return this.usersService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.usersService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.usersService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.usersService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [UsersController], providers: [UsersService, PrismaService], }) export class UsersModule {} ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from '../prisma.service'; @Injectable() export class UsersService { constructor(private prisma: PrismaService) {} create(createUserDto: CreateUserDto) { return this.prisma.user.create({ data: { name: createUserDto.name, profile: { create: { phone: createUserDto.phone, photo: createUserDto.photo, }, }, }, }); } findAll() { return this.prisma.user.findMany({ include: { profile: true } }); } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } } ================================================ FILE: module-20-prisma-integration/lesson-08/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-08/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-08/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-08/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-09/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-09/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-09/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-09/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-09/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "Animals", "artistId":1 } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ### Create Artist POST http://localhost:3000/artists Content-Type: application/json { "name" : "Avicci" } ### Create User POST http://localhost:3000/users Content-Type: application/json { "name" : "Jane Doe", "photo": "some-api.com/photos/1.jpg", "phone": "+9234344433" } ### FETCH Users GET http://localhost:3000/users ### Create POST POST http://localhost:3000/posts Content-Type: application/json { "title": "One to Many Relation", "categories": { "create": [ { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Prisma" } } }, { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Nest.js" } } } ] } } ### Create Post with Existing categories POST http://localhost:3000/posts Content-Type: application/json { "title": "Transactions in Prisma", "categories": { "create": [ { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 1 } } }, { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 2 } } } ] } } ### FETCH ALL THE POSTS WITH NEST.JS CATEGORY GET http://localhost:3000/posts Content-Type: application/json { "categories": { "some": { "category": { "name": "Nest.js" } } } } ### CREATE NEW APPLICATION POST http://localhost:3000/applications Content-Type: application/json { "email": "jane1@gmail.com", "name": "Jone Doe", "address": { "create": { "city": "New York", "country": "USA", "zip": "34443" } }, "applications": { "create": [ { "amount": 32224, "tenure": "5 Years", "type": "BUSINESS_FINANCING" } ] } } ### SEQUENTIAL QUERY GET http://localhost:3000/sequential ================================================ FILE: module-20-prisma-integration/lesson-09/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-09/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230801082432_add_artists/migration.sql ================================================ -- AlterTable ALTER TABLE "Song" ADD COLUMN "artistId" INTEGER; -- CreateTable CREATE TABLE "Artist" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Artist_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230801091013_one_to_one/migration.sql ================================================ -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Profile" ( "id" SERIAL NOT NULL, "userId" INTEGER NOT NULL, "photo" TEXT NOT NULL, "phone" TEXT NOT NULL, CONSTRAINT "Profile_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId"); -- AddForeignKey ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230802074328_many_to_many/migration.sql ================================================ -- CreateTable CREATE TABLE "Post" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Post_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Category" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Category_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "CategoriesOnPosts" ( "postId" INTEGER NOT NULL, "categoryId" INTEGER NOT NULL, "asignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "assignedBy" TEXT NOT NULL, CONSTRAINT "CategoriesOnPosts_pkey" PRIMARY KEY ("postId","categoryId") ); -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230802081727_nested_queries/migration.sql ================================================ -- CreateEnum CREATE TYPE "APPLICATION_TYPE" AS ENUM ('LOAN', 'CAR_FINANCING', 'BUSINESS_FINANCING'); -- CreateTable CREATE TABLE "Customer" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, "email" TEXT NOT NULL, "addressId" INTEGER, CONSTRAINT "Customer_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Address" ( "id" SERIAL NOT NULL, "zip" TEXT, "city" TEXT NOT NULL, "country" TEXT NOT NULL, CONSTRAINT "Address_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Application" ( "id" SERIAL NOT NULL, "type" "APPLICATION_TYPE" NOT NULL, "tenure" TEXT NOT NULL, "amount" INTEGER NOT NULL, "customerId" INTEGER, CONSTRAINT "Application_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Customer_email_key" ON "Customer"("email"); -- CreateIndex CREATE UNIQUE INDEX "Customer_addressId_key" ON "Customer"("addressId"); -- AddForeignKey ALTER TABLE "Customer" ADD CONSTRAINT "Customer_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Application" ADD CONSTRAINT "Application_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/20230802083400_default_value_for_type/migration.sql ================================================ -- AlterTable ALTER TABLE "Application" ALTER COLUMN "type" SET DEFAULT 'LOAN'; ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-09/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @id @default(autoincrement()) name String songs Song[] } model User { id Int @id @default(autoincrement()) name String profile Profile? } model Profile { id Int @id @default(autoincrement()) user User @relation(fields: [userId], references: [id]) userId Int @unique photo String phone String } model Post { id Int @id @default(autoincrement()) title String categories CategoriesOnPosts[] } model Category { id Int @id @default(autoincrement()) name String posts CategoriesOnPosts[] } model CategoriesOnPosts { post Post @relation(fields: [postId], references: [id]) postId Int category Category @relation(fields: [categoryId], references: [id]) categoryId Int asignedAt DateTime @default(now()) assignedBy String @@id([postId, categoryId]) } // You have to create Customer, Address and the Application Model // One to one relation with Customer and Address // One to many relation with Customer and Application model Customer { id Int @id @default(autoincrement()) name String email String @unique address Address? @relation(fields: [addressId], references: [id]) applications Application[] addressId Int? @unique } model Address { id Int @id @default(autoincrement()) zip String? city String country String Customer Customer? } enum APPLICATION_TYPE { LOAN CAR_FINANCING BUSINESS_FINANCING } model Application { id Int @id @default(autoincrement()) type APPLICATION_TYPE @default(LOAN) tenure String amount Int Customer Customer? @relation(fields: [customerId], references: [id]) customerId Int? } ================================================ FILE: module-20-prisma-integration/lesson-09/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; import { PrismaService } from './prisma.service'; @Controller() export class AppController { constructor( private readonly appService: AppService, private prisma: PrismaService, ) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('sequential') getSequentialResults() { return this.prisma.$transaction([ this.prisma.post.findMany(), this.prisma.artist.findMany(), this.prisma.song.findMany(), this.prisma.application.findMany(), ]); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { ArtistsModule } from './artists/artists.module'; import { UsersModule } from './users/users.module'; import { PostsModule } from './posts/posts.module'; import { ApplicationsModule } from './applications/applications.module'; import { PrismaService } from './prisma.service'; @Module({ imports: [ SongsModule, ArtistsModule, UsersModule, PostsModule, ApplicationsModule, ], controllers: [AppController], providers: [AppService, PrismaService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/applications.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsController } from './applications.controller'; import { ApplicationsService } from './applications.service'; describe('ApplicationsController', () => { let controller: ApplicationsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ApplicationsController], providers: [ApplicationsService], }).compile(); controller = module.get(ApplicationsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/applications.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { Prisma } from '@prisma/client'; @Controller('applications') export class ApplicationsController { constructor(private readonly applicationsService: ApplicationsService) {} @Post() create(@Body() createApplicationDto: Prisma.CustomerCreateInput) { return this.applicationsService.create(createApplicationDto); } @Get() findAll() { return this.applicationsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.applicationsService.findOne(+id); } @Patch(':id') update( @Param('id') id: string, @Body() updateApplicationDto: UpdateApplicationDto, ) { return this.applicationsService.update(+id, updateApplicationDto); } @Delete(':id') remove(@Param('id') id: string) { return this.applicationsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/applications.module.ts ================================================ import { Module } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { ApplicationsController } from './applications.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ApplicationsController], providers: [ApplicationsService, PrismaService], }) export class ApplicationsModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/applications.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsService } from './applications.service'; describe('ApplicationsService', () => { let service: ApplicationsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ApplicationsService], }).compile(); service = module.get(ApplicationsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/applications.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ApplicationsService { constructor(private prisma: PrismaService) {} create(createApplicationDto: Prisma.CustomerCreateInput) { return this.prisma.customer.create({ data: createApplicationDto }); } findAll() { return `This action returns all applications`; } findOne(id: number) { return `This action returns a #${id} application`; } update(id: number, updateApplicationDto: UpdateApplicationDto) { return `This action updates a #${id} application`; } remove(id: number) { return `This action removes a #${id} application`; } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/dto/create-application.dto.ts ================================================ export class CreateApplicationDto {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/applications/dto/update-application.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateApplicationDto } from './create-application.dto'; export class UpdateApplicationDto extends PartialType(CreateApplicationDto) {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], providers: [ArtistsService], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/artists.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Prisma } from '@prisma/client'; @Controller('artists') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Post() create(@Body() createArtistDto: Prisma.ArtistCreateInput) { return this.artistsService.create(createArtistDto); } @Get() findAll() { return this.artistsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.artistsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateArtistDto: UpdateArtistDto) { return this.artistsService.update(+id, updateArtistDto); } @Delete(':id') remove(@Param('id') id: string) { return this.artistsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ArtistsController], providers: [ArtistsService, PrismaService], }) export class ArtistsModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ArtistsService { constructor(private prisma: PrismaService) {} create(createArtistDto: Prisma.ArtistCreateInput) { return this.prisma.artist.create({ data: createArtistDto, }); } findAll() { return `This action returns all artists`; } findOne(id: number) { return `This action returns a #${id} artist`; } update(id: number, updateArtistDto: UpdateArtistDto) { return `This action updates a #${id} artist`; } remove(id: number) { return `This action removes a #${id} artist`; } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/dto/create-artist.dto.ts ================================================ export class CreateArtistDto {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/artists/dto/update-artist.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateArtistDto } from './create-artist.dto'; export class UpdateArtistDto extends PartialType(CreateArtistDto) {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/dto/create-post.dto.ts ================================================ export class CreatePostDto {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/dto/update-post.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreatePostDto } from './create-post.dto'; export class UpdatePostDto extends PartialType(CreatePostDto) {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/posts.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; describe('PostsController', () => { let controller: PostsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [PostsController], providers: [PostsService], }).compile(); controller = module.get(PostsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/posts.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { PostsService } from './posts.service'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { Prisma } from '@prisma/client'; @Controller('posts') export class PostsController { constructor(private readonly postsService: PostsService) {} @Post() @Post() create(@Body() createPostDto: Prisma.PostCreateInput) { return this.postsService.create(createPostDto); } @Get() findAll( @Body() where: Prisma.PostWhereUniqueInput, ) { return this.postsService.findAll(where); } @Get(':id') findOne(@Param('id') id: string) { return this.postsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) { return this.postsService.update(+id, updatePostDto); } @Delete(':id') remove(@Param('id') id: string) { return this.postsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/posts.module.ts ================================================ import { Module } from '@nestjs/common'; import { PostsService } from './posts.service'; import { PostsController } from './posts.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [PostsController], providers: [PostsService, PrismaService], }) export class PostsModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/posts.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { let service: PostsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [PostsService], }).compile(); service = module.get(PostsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/posts/posts.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class PostsService { constructor(private prisma: PrismaService) {} create(createPostDto: Prisma.PostCreateInput) { return this.prisma.post.create({ data: createPostDto }); } findAll(where: Prisma.PostWhereUniqueInput) { return this.prisma.post.findMany({ where, }); } findOne(id: number) { return `This action returns a #${id} post`; } update(id: number, updatePostDto: UpdatePostDto) { return `This action updates a #${id} post`; } remove(id: number) { return `This action removes a #${id} post`; } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongUncheckedCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany({ include: { artist: true } }); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/dto/create-user.dto.ts ================================================ export class CreateUserDto { name: string; photo: string; phone: string; } ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/dto/update-user.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/users.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; describe('UsersController', () => { let controller: UsersController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], providers: [UsersService], }).compile(); controller = module.get(UsersController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/users.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); } @Get() findAll() { return this.usersService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.usersService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.usersService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.usersService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [UsersController], providers: [UsersService, PrismaService], }) export class UsersModule {} ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from '../prisma.service'; @Injectable() export class UsersService { constructor(private prisma: PrismaService) {} create(createUserDto: CreateUserDto) { return this.prisma.user.create({ data: { name: createUserDto.name, profile: { create: { phone: createUserDto.phone, photo: createUserDto.photo, }, }, }, }); } findAll() { return this.prisma.user.findMany({ include: { profile: true } }); } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } } ================================================ FILE: module-20-prisma-integration/lesson-09/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-09/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-09/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-09/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .env ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/http-client.http ================================================ ### Create Song POST http://localhost:3000/songs Content-Type: application/json { "title" : "Animals", "artistId":1 } ### FETCH ALL SONGS GET http://localhost:3000/songs ### FETCH SONG BY ID GET http://localhost:3000/songs/2 ### UPDATE SONG BY ID PATCH http://localhost:3000/songs/3 Content-Type: application/json { "title" : "LOVING ME" } ### DELETE Song by id DELETE http://localhost:3000/songs/3 ### Create Artist POST http://localhost:3000/artists Content-Type: application/json { "name" : "Avicci" } ### Create User POST http://localhost:3000/users Content-Type: application/json { "name" : "Jane Doe", "photo": "some-api.com/photos/1.jpg", "phone": "+9234344433" } ### FETCH Users GET http://localhost:3000/users ### Create POST POST http://localhost:3000/posts Content-Type: application/json { "title": "One to Many Relation", "categories": { "create": [ { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Prisma" } } }, { "assignedBy": "Jane", "asignedAt": "2023-08-01T10:03:38.016Z", "category": { "create": { "name": "Nest.js" } } } ] } } ### Create Post with Existing categories POST http://localhost:3000/posts Content-Type: application/json { "title": "Transactions in Prisma", "categories": { "create": [ { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 1 } } }, { "assignedBy": "Bob", "asignedAt": "2023-08-01T10:07:00.918Z", "category": { "connect": { "id": 2 } } } ] } } ### FETCH ALL THE POSTS WITH NEST.JS CATEGORY GET http://localhost:3000/posts Content-Type: application/json { "categories": { "some": { "category": { "name": "Nest.js" } } } } ### CREATE NEW APPLICATION POST http://localhost:3000/applications Content-Type: application/json { "email": "jane1@gmail.com", "name": "Jone Doe", "address": { "create": { "city": "New York", "country": "USA", "zip": "34443" } }, "applications": { "create": [ { "amount": 32224, "tenure": "5 Years", "type": "BUSINESS_FINANCING" } ] } } ### SEQUENTIAL QUERY GET http://localhost:3000/sequential ### Create Account POST http://localhost:3000/accounts Content-Type: application/json { "title" : "Sam", "balance" : 100 } ### Trasnfer Amount POST http://localhost:3000/accounts/transfer Content-Type: application/json { "sender" :1, "receiver" :2, "amount": 20 } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "builder": "swc", "typeCheck": true } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/package.json ================================================ { "name": "graphql-dev", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "generate:typings": "ts-node generate-typings.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e:watch": "jest --watch --detectOpenHandles --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.0.0", "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-morph": "^19.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "prisma": "^5.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230730081110_init/migration.sql ================================================ -- CreateTable CREATE TABLE "Song" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Song_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230801082432_add_artists/migration.sql ================================================ -- AlterTable ALTER TABLE "Song" ADD COLUMN "artistId" INTEGER; -- CreateTable CREATE TABLE "Artist" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Artist_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230801091013_one_to_one/migration.sql ================================================ -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Profile" ( "id" SERIAL NOT NULL, "userId" INTEGER NOT NULL, "photo" TEXT NOT NULL, "phone" TEXT NOT NULL, CONSTRAINT "Profile_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId"); -- AddForeignKey ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230802074328_many_to_many/migration.sql ================================================ -- CreateTable CREATE TABLE "Post" ( "id" SERIAL NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Post_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Category" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, CONSTRAINT "Category_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "CategoriesOnPosts" ( "postId" INTEGER NOT NULL, "categoryId" INTEGER NOT NULL, "asignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "assignedBy" TEXT NOT NULL, CONSTRAINT "CategoriesOnPosts_pkey" PRIMARY KEY ("postId","categoryId") ); -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230802081727_nested_queries/migration.sql ================================================ -- CreateEnum CREATE TYPE "APPLICATION_TYPE" AS ENUM ('LOAN', 'CAR_FINANCING', 'BUSINESS_FINANCING'); -- CreateTable CREATE TABLE "Customer" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, "email" TEXT NOT NULL, "addressId" INTEGER, CONSTRAINT "Customer_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Address" ( "id" SERIAL NOT NULL, "zip" TEXT, "city" TEXT NOT NULL, "country" TEXT NOT NULL, CONSTRAINT "Address_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Application" ( "id" SERIAL NOT NULL, "type" "APPLICATION_TYPE" NOT NULL, "tenure" TEXT NOT NULL, "amount" INTEGER NOT NULL, "customerId" INTEGER, CONSTRAINT "Application_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "Customer_email_key" ON "Customer"("email"); -- CreateIndex CREATE UNIQUE INDEX "Customer_addressId_key" ON "Customer"("addressId"); -- AddForeignKey ALTER TABLE "Customer" ADD CONSTRAINT "Customer_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Application" ADD CONSTRAINT "Application_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230802083400_default_value_for_type/migration.sql ================================================ -- AlterTable ALTER TABLE "Application" ALTER COLUMN "type" SET DEFAULT 'LOAN'; ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/20230804090722_add_account/migration.sql ================================================ -- CreateTable CREATE TABLE "Account" ( "id" SERIAL NOT NULL, "balance" DOUBLE PRECISION NOT NULL, "title" TEXT NOT NULL, CONSTRAINT "Account_pkey" PRIMARY KEY ("id") ); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Song { id Int @id @default(autoincrement()) title String artist Artist? @relation(fields: [artistId], references: [id]) artistId Int? } model Artist { id Int @id @default(autoincrement()) name String songs Song[] } model User { id Int @id @default(autoincrement()) name String profile Profile? } model Profile { id Int @id @default(autoincrement()) user User @relation(fields: [userId], references: [id]) userId Int @unique photo String phone String } model Post { id Int @id @default(autoincrement()) title String categories CategoriesOnPosts[] } model Category { id Int @id @default(autoincrement()) name String posts CategoriesOnPosts[] } model CategoriesOnPosts { post Post @relation(fields: [postId], references: [id]) postId Int category Category @relation(fields: [categoryId], references: [id]) categoryId Int asignedAt DateTime @default(now()) assignedBy String @@id([postId, categoryId]) } // You have to create Customer, Address and the Application Model // One to one relation with Customer and Address // One to many relation with Customer and Application model Customer { id Int @id @default(autoincrement()) name String email String @unique address Address? @relation(fields: [addressId], references: [id]) applications Application[] addressId Int? @unique } model Address { id Int @id @default(autoincrement()) zip String? city String country String Customer Customer? } enum APPLICATION_TYPE { LOAN CAR_FINANCING BUSINESS_FINANCING } model Application { id Int @id @default(autoincrement()) type APPLICATION_TYPE @default(LOAN) tenure String amount Int Customer Customer? @relation(fields: [customerId], references: [id]) customerId Int? } model Account { id Int @id @default(autoincrement()) balance Float title String } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/accounts.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AccountsController } from './accounts.controller'; import { AccountsService } from './accounts.service'; describe('AccountsController', () => { let controller: AccountsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AccountsController], providers: [AccountsService], }).compile(); controller = module.get(AccountsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/accounts.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { AccountsService } from './accounts.service'; import { UpdateAccountDto } from './dto/update-account.dto'; import { Prisma } from '@prisma/client'; import { TransferAccountDTO } from './dto/transfer-account.dto'; @Controller('accounts') export class AccountsController { constructor(private readonly accountsService: AccountsService) {} @Post() create(@Body() createAccountDto: Prisma.AccountCreateInput) { return this.accountsService.create(createAccountDto); } @Post('transfer') transfer(@Body() transferAccountDTO: TransferAccountDTO) { return this.accountsService.transfer(transferAccountDTO); } @Get() findAll() { return this.accountsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.accountsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateAccountDto: UpdateAccountDto) { return this.accountsService.update(+id, updateAccountDto); } @Delete(':id') remove(@Param('id') id: string) { return this.accountsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/accounts.module.ts ================================================ import { Module } from '@nestjs/common'; import { AccountsService } from './accounts.service'; import { AccountsController } from './accounts.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [AccountsController], providers: [AccountsService, PrismaService], }) export class AccountsModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/accounts.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AccountsService } from './accounts.service'; describe('AccountsService', () => { let service: AccountsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [AccountsService], }).compile(); service = module.get(AccountsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/accounts.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateAccountDto } from './dto/create-account.dto'; import { UpdateAccountDto } from './dto/update-account.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; import { TransferAccountDTO } from './dto/transfer-account.dto'; @Injectable() export class AccountsService { constructor(private prisma: PrismaService) {} transfer(transferAccountDTO: TransferAccountDTO) { const { sender: from, receiver: to, amount } = transferAccountDTO; return this.prisma.$transaction(async (tx) => { // John Account // 1. Decrement amount from the sender. const sender = await tx.account.update({ data: { balance: { decrement: amount, }, }, where: { id: from, }, }); // 2. Verify that the sender's balance didn't go below zero. if (sender.balance < 0) { throw new Error(`${from} doesn't have enough to send ${amount}`); } // 3. Increment the recipient's balance by amount // SAM Account const recipient = await tx.account.update({ data: { balance: { increment: amount, }, }, where: { id: to, }, }); return recipient; }); } create(createAccountDto: Prisma.AccountCreateInput) { return this.prisma.account.create({ data: createAccountDto }); } findAll() { return `This action returns all accounts`; } findOne(id: number) { return `This action returns a #${id} account`; } update(id: number, updateAccountDto: UpdateAccountDto) { return `This action updates a #${id} account`; } remove(id: number) { return `This action removes a #${id} account`; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/dto/create-account.dto.ts ================================================ export class CreateAccountDto {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/dto/transfer-account.dto.ts ================================================ export class TransferAccountDTO { sender: number; receiver: number; amount: number; } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/accounts/dto/update-account.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateAccountDto } from './create-account.dto'; export class UpdateAccountDto extends PartialType(CreateAccountDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; import { PrismaService } from './prisma.service'; @Controller() export class AppController { constructor( private readonly appService: AppService, private prisma: PrismaService, ) {} @Get() getHello(): string { return this.appService.getHello(); } @Get('sequential') getSequentialResults() { return this.prisma.$transaction([ this.prisma.post.findMany(), this.prisma.artist.findMany(), this.prisma.song.findMany(), this.prisma.application.findMany(), ]); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SongsModule } from './songs/songs.module'; import { ArtistsModule } from './artists/artists.module'; import { UsersModule } from './users/users.module'; import { PostsModule } from './posts/posts.module'; import { ApplicationsModule } from './applications/applications.module'; import { PrismaService } from './prisma.service'; import { AccountsModule } from './accounts/accounts.module'; @Module({ imports: [ SongsModule, ArtistsModule, UsersModule, PostsModule, ApplicationsModule, AccountsModule, ], controllers: [AppController], providers: [AppService, PrismaService], }) export class AppModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/applications.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsController } from './applications.controller'; import { ApplicationsService } from './applications.service'; describe('ApplicationsController', () => { let controller: ApplicationsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ApplicationsController], providers: [ApplicationsService], }).compile(); controller = module.get(ApplicationsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/applications.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { Prisma } from '@prisma/client'; @Controller('applications') export class ApplicationsController { constructor(private readonly applicationsService: ApplicationsService) {} @Post() create(@Body() createApplicationDto: Prisma.CustomerCreateInput) { return this.applicationsService.create(createApplicationDto); } @Get() findAll() { return this.applicationsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.applicationsService.findOne(+id); } @Patch(':id') update( @Param('id') id: string, @Body() updateApplicationDto: UpdateApplicationDto, ) { return this.applicationsService.update(+id, updateApplicationDto); } @Delete(':id') remove(@Param('id') id: string) { return this.applicationsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/applications.module.ts ================================================ import { Module } from '@nestjs/common'; import { ApplicationsService } from './applications.service'; import { ApplicationsController } from './applications.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ApplicationsController], providers: [ApplicationsService, PrismaService], }) export class ApplicationsModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/applications.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ApplicationsService } from './applications.service'; describe('ApplicationsService', () => { let service: ApplicationsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ApplicationsService], }).compile(); service = module.get(ApplicationsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/applications.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateApplicationDto } from './dto/create-application.dto'; import { UpdateApplicationDto } from './dto/update-application.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ApplicationsService { constructor(private prisma: PrismaService) {} create(createApplicationDto: Prisma.CustomerCreateInput) { return this.prisma.customer.create({ data: createApplicationDto }); } findAll() { return `This action returns all applications`; } findOne(id: number) { return `This action returns a #${id} application`; } update(id: number, updateApplicationDto: UpdateApplicationDto) { return `This action updates a #${id} application`; } remove(id: number) { return `This action removes a #${id} application`; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/dto/create-application.dto.ts ================================================ export class CreateApplicationDto {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/applications/dto/update-application.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateApplicationDto } from './create-application.dto'; export class UpdateApplicationDto extends PartialType(CreateApplicationDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/artists.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsController } from './artists.controller'; import { ArtistsService } from './artists.service'; describe('ArtistsController', () => { let controller: ArtistsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ArtistsController], providers: [ArtistsService], }).compile(); controller = module.get(ArtistsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/artists.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { Prisma } from '@prisma/client'; @Controller('artists') export class ArtistsController { constructor(private readonly artistsService: ArtistsService) {} @Post() create(@Body() createArtistDto: Prisma.ArtistCreateInput) { return this.artistsService.create(createArtistDto); } @Get() findAll() { return this.artistsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.artistsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateArtistDto: UpdateArtistDto) { return this.artistsService.update(+id, updateArtistDto); } @Delete(':id') remove(@Param('id') id: string) { return this.artistsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/artists.module.ts ================================================ import { Module } from '@nestjs/common'; import { ArtistsService } from './artists.service'; import { ArtistsController } from './artists.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [ArtistsController], providers: [ArtistsService, PrismaService], }) export class ArtistsModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/artists.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { ArtistsService } from './artists.service'; describe('ArtistsService', () => { let service: ArtistsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ArtistsService], }).compile(); service = module.get(ArtistsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/artists.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateArtistDto } from './dto/create-artist.dto'; import { UpdateArtistDto } from './dto/update-artist.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class ArtistsService { constructor(private prisma: PrismaService) {} create(createArtistDto: Prisma.ArtistCreateInput) { return this.prisma.artist.create({ data: createArtistDto, }); } findAll() { return `This action returns all artists`; } findOne(id: number) { return `This action returns a #${id} artist`; } update(id: number, updateArtistDto: UpdateArtistDto) { return `This action updates a #${id} artist`; } remove(id: number) { return `This action removes a #${id} artist`; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/dto/create-artist.dto.ts ================================================ export class CreateArtistDto {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/artists/dto/update-artist.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateArtistDto } from './create-artist.dto'; export class UpdateArtistDto extends PartialType(CreateArtistDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/dto/create-post.dto.ts ================================================ export class CreatePostDto {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/dto/update-post.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreatePostDto } from './create-post.dto'; export class UpdatePostDto extends PartialType(CreatePostDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/posts.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; describe('PostsController', () => { let controller: PostsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [PostsController], providers: [PostsService], }).compile(); controller = module.get(PostsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/posts.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { PostsService } from './posts.service'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { Prisma } from '@prisma/client'; @Controller('posts') export class PostsController { constructor(private readonly postsService: PostsService) {} @Post() @Post() create(@Body() createPostDto: Prisma.PostCreateInput) { return this.postsService.create(createPostDto); } @Get() findAll( @Body() where: Prisma.PostWhereUniqueInput, ) { return this.postsService.findAll(where); } @Get(':id') findOne(@Param('id') id: string) { return this.postsService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) { return this.postsService.update(+id, updatePostDto); } @Delete(':id') remove(@Param('id') id: string) { return this.postsService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/posts.module.ts ================================================ import { Module } from '@nestjs/common'; import { PostsService } from './posts.service'; import { PostsController } from './posts.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [PostsController], providers: [PostsService, PrismaService], }) export class PostsModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/posts.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { let service: PostsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [PostsService], }).compile(); service = module.get(PostsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/posts/posts.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class PostsService { constructor(private prisma: PrismaService) {} create(createPostDto: Prisma.PostCreateInput) { return this.prisma.post.create({ data: createPostDto }); } findAll(where: Prisma.PostWhereUniqueInput) { return this.prisma.post.findMany({ where, }); } findOne(id: number) { return `This action returns a #${id} post`; } update(id: number, updatePostDto: UpdatePostDto) { return `This action updates a #${id} post`; } remove(id: number) { return `This action removes a #${id} post`; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/prisma.service.ts ================================================ import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { onModuleInit() { this.$connect(); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/dto/create-song.dto.ts ================================================ export class CreateSongDto {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/dto/update-song.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateSongDto } from './create-song.dto'; export class UpdateSongDto extends PartialType(CreateSongDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/songs.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsController } from './songs.controller'; import { SongsService } from './songs.service'; describe('SongsController', () => { let controller: SongsController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SongsController], providers: [SongsService], }).compile(); controller = module.get(SongsController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/songs.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, } from '@nestjs/common'; import { SongsService } from './songs.service'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { Prisma } from '@prisma/client'; @Controller('songs') export class SongsController { constructor(private readonly songsService: SongsService) {} @Post() create(@Body() createSongDto: Prisma.SongCreateInput) { return this.songsService.create(createSongDto); } @Get() findAll() { return this.songsService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.songsService.findOne({ id: +id }); } @Patch(':id') update( @Param('id') id: string, @Body() updateSongDto: Prisma.SongUpdateInput, ) { return this.songsService.update({ id: +id }, updateSongDto); } @Delete(':id') remove(@Param('id') id: string) { return this.songsService.remove({ id: +id }); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/songs.module.ts ================================================ import { Module } from '@nestjs/common'; import { SongsService } from './songs.service'; import { SongsController } from './songs.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [SongsController], providers: [SongsService, PrismaService], }) export class SongsModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/songs.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { SongsService } from './songs.service'; describe('SongsService', () => { let service: SongsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SongsService], }).compile(); service = module.get(SongsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/songs/songs.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateSongDto } from './dto/create-song.dto'; import { UpdateSongDto } from './dto/update-song.dto'; import { PrismaService } from '../prisma.service'; import { Prisma } from '@prisma/client'; @Injectable() export class SongsService { constructor(private prisma: PrismaService) {} create(createSongDto: Prisma.SongUncheckedCreateInput) { return this.prisma.song.create({ data: createSongDto, }); } findAll() { return this.prisma.song.findMany({ include: { artist: true } }); } findOne(songWhereUniqueInput: Prisma.SongWhereUniqueInput) { return this.prisma.song.findUnique({ where: songWhereUniqueInput }); } update( where: Prisma.SongWhereUniqueInput, updateSongDto: Prisma.SongUpdateInput, ) { return this.prisma.song.update({ where, data: updateSongDto, }); } remove(where: Prisma.SongWhereUniqueInput) { return this.prisma.song.delete({ where }); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/dto/create-user.dto.ts ================================================ export class CreateUserDto { name: string; photo: string; phone: string; } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/dto/update-user.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/users.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; describe('UsersController', () => { let controller: UsersController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], providers: [UsersService], }).compile(); controller = module.get(UsersController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/users.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); } @Get() findAll() { return this.usersService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.usersService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.usersService.update(+id, updateUserDto); } @Delete(':id') remove(@Param('id') id: string) { return this.usersService.remove(+id); } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/users.module.ts ================================================ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { PrismaService } from '../prisma.service'; @Module({ controllers: [UsersController], providers: [UsersService, PrismaService], }) export class UsersModule {} ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/users.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UsersService], }).compile(); service = module.get(UsersService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/src/users/users.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from '../prisma.service'; @Injectable() export class UsersService { constructor(private prisma: PrismaService) {} create(createUserDto: CreateUserDto) { return this.prisma.user.create({ data: { name: createUserDto.name, profile: { create: { phone: createUserDto.phone, photo: createUserDto.photo, }, }, }, }); } findAll() { return this.prisma.user.findMany({ include: { profile: true } }); } findOne(id: number) { return `This action returns a #${id} user`; } update(id: number, updateUserDto: UpdateUserDto) { return `This action updates a #${id} user`; } remove(id: number) { return `This action removes a #${id} user`; } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/test/song/song.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../song/../../src/app.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Song } from '../song/../../src/song/song.entity'; import { SongModule } from '../song/../../src/song/song.module'; import { CreateSongDTO } from '../song/../../src/song/dto/create-song-dto'; describe('Song Resolver (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', url: 'postgres://postgres:root@localhost:5432/test-dev', synchronize: true, entities: [Song], dropSchema: true, }), SongModule, ], }).compile(); app = moduleRef.createNestApplication(); await app.init(); }); afterEach(async () => { // Fetch all the entities const songRepository = app.get('SongRepository'); await songRepository.clear(); }); afterAll(async () => { await app.close(); }); const createSong = (createSongDTO: CreateSongDTO): Promise => { const song = new Song(); song.title = createSongDTO.title; const songRepo = app.get('SongRepository'); return songRepo.save(song); }; it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); it('(Query) it should get all songs with songs query', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query { songs { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData); expect(results.statusCode).toBe(200); expect(results.body).toEqual({ data: { songs: [newSong] } }); }); it('(Query) it should get a song by id', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `query GetSong($id: ID!){ song(id: $id){ title id } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body).toEqual({ data: { song: newSong } }); }); it('(Mutation) it should create a new song', async () => { const queryData = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.createSong.title).toBe('Animals'); }); it('(Mutation) it should update existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation UpdateSong($id: ID!, $updateSongInput: UpdateSongInput!){ updateSong(id: $id, updateSongInput: $updateSongInput){ affected } }`, variables: { id: newSong.id, updateSongInput: { title: 'Lover', }, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.updateSong.affected).toBe(1); }); it('(Mutation) it should delete existing song', async () => { const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `mutation DeleteSong($id: ID!){ deleteSong(id: $id){ affected } }`, variables: { id: newSong.id, }, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); expect(results.body.data.deleteSong.affected).toBe(1); }); it('(Subscription) it should test subscription', async () => { // const newSong = await createSong({ title: 'Animals' }); const queryData = { query: `subscription SongCreated{ songCreated { id title } }`, }; const results = await request(app.getHttpServer()) .post('/graphql') .send(queryData) .expect(200); // console.log(results); const queryData1 = { query: `mutation CreateSong($createSongInput: CreateSongInput!){ createSong(createSongInput: $createSongInput){ title id } }`, variables: { createSongInput: { title: 'Animals', }, }, }; const results1 = await request(app.getHttpServer()) .post('/graphql') .send(queryData1) .expect(200); console.log(results.body); }); }); ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-20-prisma-integration/lesson-11-interactive-transactions/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/0-starter/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@types/multer": "^1.4.7", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-01-file-upload/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@types/multer": "^1.4.7", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-02-custom-decorator/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; @Module({ imports: [ScheduleModule.forRoot()], controllers: [AppController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); @Cron('0 * * * * *') myCronTask() { this.logger.debug('Cron Task Called'); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-03-Task-scheduling/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ### TEST COOKIE PARSER GET http://localhost:3000/set-cookie ### GET COOKIE GET http://localhost:3000/get-cookie ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "cookie-parser": "^1.4.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, Req, Res, } from '@nestjs/common'; import { Request, Response } from 'express'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } @Get('get-cookie') finndAll(@Req() req: Request) { console.log(req.cookies); return req.cookies; } @Get('set-cookie') setCookie( @Res({ passthrough: true }) response: Response, ) { response.cookie( 'userId', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', ); response.send('Cookie Saved Successfully'); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; @Module({ imports: [ScheduleModule.forRoot()], controllers: [AppController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); // @Cron('0 * * * * *') // myCronTask() { // this.logger.debug('Cron Task Called'); // } // } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-04-cookies/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ### TEST COOKIE PARSER GET http://localhost:3000/set-cookie ### GET COOKIE GET http://localhost:3000/get-cookie ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "cookie-parser": "^1.4.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, Req, Res, } from '@nestjs/common'; import { Request, Response } from 'express'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } @Get('get-cookie') finndAll(@Req() req: Request) { console.log(req.cookies); return req.cookies; } @Get('set-cookie') setCookie( @Res({ passthrough: true }) response: Response, ) { response.cookie( 'userId', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', ); response.send('Cookie Saved Successfully'); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; @Module({ imports: [ScheduleModule.forRoot()], controllers: [AppController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); // @Cron('0 * * * * *') // myCronTask() { // this.logger.debug('Cron Task Called'); // } // } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-05-queues/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/docker-compose.yml ================================================ version: '3' services: redis: image: redis:alpine ports: - 6379:6379 ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ### TEST COOKIE PARSER GET http://localhost:3000/set-cookie ### GET COOKIE GET http://localhost:3000/get-cookie ### CONVERT .WAV FILE TO Mp3 POST http://localhost:3000/audio/convert ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/bull": "^10.0.1", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/event-emitter": "^2.0.1", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "bull": "^4.11.3", "cookie-parser": "^1.4.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, Req, Res, } from '@nestjs/common'; import { Request, Response } from 'express'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } @Get('get-cookie') finndAll(@Req() req: Request) { console.log(req.cookies); return req.cookies; } @Get('set-cookie') setCookie( @Res({ passthrough: true }) response: Response, ) { response.cookie( 'userId', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', ); response.send('Cookie Saved Successfully'); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; import { AudioModule } from './audio/audio.module'; import { BullModule } from '@nestjs/bull'; import { EventEmitterModule } from '@nestjs/event-emitter'; @Module({ imports: [ ScheduleModule.forRoot(), BullModule.forRoot({ redis: { host: 'localhost', port: 6379, }, }), EventEmitterModule.forRoot(), AudioModule, ], controllers: [AppController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/audio-converted-listener.ts ================================================ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { AudioConvertedEvent } from './events/audio-converted-event'; @Injectable() export class AudioConvertedListener { @OnEvent('audio.converted') handleAudioConvertedEvent(event: AudioConvertedEvent) { console.log(event); console.log( 'Notification has sent to user that file is converted successfully', ); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/audio.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AudioController } from './audio.controller'; describe('AudioController', () => { let controller: AudioController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AudioController], }).compile(); controller = module.get(AudioController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/audio.controller.ts ================================================ import { InjectQueue } from '@nestjs/bull'; import { Controller, Post } from '@nestjs/common'; import { Queue } from 'bull'; @Controller('audio') export class AudioController { constructor( @InjectQueue('audio-queue') private readonly audioQueue: Queue, ) {} /** * Let's imagine we would like to convert .wav file into .mp3 */ @Post('convert') async convert() { await this.audioQueue.add('convert', { file: 'sample.wav', id: 1, }); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/audio.module.ts ================================================ import { Module } from '@nestjs/common'; import { AudioController } from './audio.controller'; import { BullModule } from '@nestjs/bull'; import { AudioProcessor } from './audio.processor'; import { AudioConvertedListener } from './audio-converted-listener'; @Module({ imports: [ BullModule.registerQueue({ name: 'audio-queue', }), ], controllers: [AudioController], providers: [AudioProcessor, AudioConvertedListener], }) export class AudioModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/audio.processor.ts ================================================ import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { Job } from 'bull'; import { EventEmitter2 } from '@nestjs/event-emitter'; @Processor('audio-queue') export class AudioProcessor { constructor(private eventEmitter: EventEmitter2) {} private logger = new Logger(AudioProcessor.name); @Process('convert') handleConvert(job: Job) { this.logger.debug('start converting wav file to mp3'); this.logger.debug(job.data); this.logger.debug('file converted successfully'); this.eventEmitter.emit('audio.converted', job.data); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/audio/events/audio-converted-event.ts ================================================ export class AudioConvertedEvent { file: string; id: number; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); // @Cron('0 * * * * *') // myCronTask() { // this.logger.debug('Cron Task Called'); // } // } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-06-event-emitter/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/docker-compose.yml ================================================ version: '3' services: redis: image: redis:alpine ports: - 6379:6379 ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ### TEST COOKIE PARSER GET http://localhost:3000/set-cookie ### GET COOKIE GET http://localhost:3000/get-cookie ### CONVERT .WAV FILE TO Mp3 POST http://localhost:3000/audio/convert ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/bull": "^10.0.1", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/event-emitter": "^2.0.1", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "bull": "^4.11.3", "cookie-parser": "^1.4.6", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, Req, Res, } from '@nestjs/common'; import { Request, Response } from 'express'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } @Get('get-cookie') finndAll(@Req() req: Request) { console.log(req.cookies); return req.cookies; } @Get('set-cookie') setCookie( @Res({ passthrough: true }) response: Response, ) { response.cookie( 'userId', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', ); response.send('Cookie Saved Successfully'); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; import { AudioModule } from './audio/audio.module'; import { BullModule } from '@nestjs/bull'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { FileController } from './file/file.controller'; @Module({ imports: [ ScheduleModule.forRoot(), EventEmitterModule.forRoot(), BullModule.forRoot({ redis: { host: 'localhost', port: 6379, }, }), AudioModule, ], controllers: [AppController, FileController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/audio-converted-listener.ts ================================================ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { AudioConvertedEvent } from './events/audio-converted-event'; @Injectable() export class AudioConvertedListener { @OnEvent('audio.converted') // We have registered a new event lister with audio.converted name handleAudioConvertedEvent(event: AudioConvertedEvent) { //We have to create the type for the AudioConvertedEvent console.log(event); // Here you can have your EmailService method you can call here console.log( 'Notification has sent to user that file is converted successfully', ); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/audio.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AudioController } from './audio.controller'; describe('AudioController', () => { let controller: AudioController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AudioController], }).compile(); controller = module.get(AudioController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/audio.controller.ts ================================================ import { InjectQueue } from '@nestjs/bull'; import { Controller, Post } from '@nestjs/common'; import { Queue } from 'bull'; @Controller('audio') export class AudioController { constructor( @InjectQueue('audio-queue') private readonly audioQueue: Queue, ) {} /** * Let's imagine we would like to convert .wav file into .mp3 */ @Post('convert') async convert() { await this.audioQueue.add('convert', { file: 'sample.wav', id: 1, }); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/audio.module.ts ================================================ import { Module } from '@nestjs/common'; import { AudioController } from './audio.controller'; import { BullModule } from '@nestjs/bull'; import { AudioProcessor } from './audio.processor'; import { AudioConvertedListener } from './audio-converted-listener'; @Module({ imports: [ BullModule.registerQueue({ name: 'audio-queue', }), ], controllers: [AudioController], providers: [AudioProcessor, AudioConvertedListener], }) export class AudioModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/audio.processor.ts ================================================ import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { Job } from 'bull'; import { EventEmitter2 } from '@nestjs/event-emitter'; @Processor('audio-queue') export class AudioProcessor { constructor(private eventEmitter: EventEmitter2) {} private logger = new Logger(AudioProcessor.name); @Process('convert') handleConvert(job: Job) { this.logger.debug('start converting wav file to mp3'); this.logger.debug(job.data); this.logger.debug('file converted successfully'); this.eventEmitter.emit('audio.converted', job.data); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/audio/events/audio-converted-event.ts ================================================ export class AudioConvertedEvent { file: string; id: number; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/file/file.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { FileController } from './file.controller'; describe('FileController', () => { let controller: FileController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [FileController], }).compile(); controller = module.get(FileController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/file/file.controller.ts ================================================ import { Controller, Get, Header, Res, StreamableFile } from '@nestjs/common'; import { Response } from 'express'; import { createReadStream } from 'fs'; import { join } from 'path'; @Controller('file') export class FileController { @Get('stream-file') getFile1(): StreamableFile { const file = createReadStream(join(process.cwd(), 'package.json')); return new StreamableFile(file); } @Get('stream-file-customize') getFileCustomizedResponse(@Res({ passthrough: true }) res): StreamableFile { const file = createReadStream(join(process.cwd(), 'package.json')); res.set({ 'Content-Type': 'application/json', 'Content-Disposition': 'attachment; filename="package.json', }); return new StreamableFile(file); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); // @Cron('0 * * * * *') // myCronTask() { // this.logger.debug('Cron Task Called'); // } // } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-07-streaming/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/README.md ================================================

Nest Logo

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

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

NPM Version Package License NPM Downloads CircleCI Coverage Discord Backers on Open Collective Sponsors on Open Collective Support us

## Description [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. ## Installation ```bash $ npm install ``` ## Running the app ```bash # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod ``` ## Test ```bash # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov ``` ## Support 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). ## Stay in touch - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) - Website - [https://nestjs.com](https://nestjs.com/) - Twitter - [@nestframework](https://twitter.com/nestframework) ## License Nest is [MIT licensed](LICENSE). ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/docker-compose.yml ================================================ version: '3' services: redis: image: redis:alpine ports: - 6379:6379 ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/http-client.http ================================================ ### FIND USER ON THE BASED ON ID GET http://localhost:3000/user/1 ### TEST COOKIE PARSER GET http://localhost:3000/set-cookie ### GET COOKIE GET http://localhost:3000/get-cookie ### CONVERT .WAV FILE TO Mp3 POST http://localhost:3000/audio/convert ### TEST SESSION GET http://localhost:3000/login ### USER PROFILE GET http://localhost:3000/profile ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/nest-cli.json ================================================ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/package.json ================================================ { "name": "fie-upload", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/bull": "^10.0.1", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/event-emitter": "^2.0.1", "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^3.0.2", "@types/multer": "^1.4.7", "bull": "^4.11.3", "cookie-parser": "^1.4.6", "express-session": "^1.17.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.4.0", "@types/express": "^4.17.17", "@types/express-session": "^1.17.7", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/app.controller.ts ================================================ import { Controller, Get, Post, HttpStatus, ParseFilePipeBuilder, UploadedFile, UseInterceptors, Req, Res, Session, } from '@nestjs/common'; import { Request, Response } from 'express'; import { AppService } from './app.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { User } from './user.decorator'; import { UserEntity } from './user.entity'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } @Post('upload') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadFile(@UploadedFile() file: Express.Multer.File) { console.log(file); return { messge: 'file uploaded successfully!', }; } // only want to accept png file @Post('upload-png') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './upload/files', filename: (req, file, cb) => { cb(null, file.originalname); }, }), }), ) uploadPngFile( @UploadedFile( new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'png', }) // .addMaxSizeValidator({ // maxSize: 70706, // }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), ) file: Express.Multer.File, ) { console.log(file); return { messge: 'file uploaded successfully!', }; } @Get('/user/:id') findOne( @User() user: UserEntity, ) { console.log(user); return user; } @Get('get-cookie') finndAll(@Req() req: Request) { console.log(req.cookies); return req.cookies; } @Get('set-cookie') setCookie( @Res({ passthrough: true }) response: Response, ) { response.cookie( 'userId', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', ); response.send('Cookie Saved Successfully'); } @Get('login') loginUser(@Session() session: Record) { session.user = { id: 1, username: 'Jane' }; return 'LoggedIn'; } @Get('profile') profile(@Session() session: Record) { const user = session.user; if (user) { return `Hello, ${user.username}`; } else { return 'Not logged in'; } } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ScheduleModule } from '@nestjs/schedule'; import { TaskService } from './task/task.service'; import { AudioModule } from './audio/audio.module'; import { BullModule } from '@nestjs/bull'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { FileController } from './file/file.controller'; @Module({ imports: [ ScheduleModule.forRoot(), EventEmitterModule.forRoot(), BullModule.forRoot({ redis: { host: 'localhost', port: 6379, }, }), AudioModule, ], controllers: [AppController, FileController], providers: [AppService, TaskService], }) export class AppModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/audio-converted-listener.ts ================================================ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { AudioConvertedEvent } from './events/audio-converted-event'; @Injectable() export class AudioConvertedListener { @OnEvent('audio.converted') // We have registered a new event lister with audio.converted name handleAudioConvertedEvent(event: AudioConvertedEvent) { //We have to create the type for the AudioConvertedEvent console.log(event); // Here you can have your EmailService method you can call here console.log( 'Notification has sent to user that file is converted successfully', ); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/audio.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AudioController } from './audio.controller'; describe('AudioController', () => { let controller: AudioController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AudioController], }).compile(); controller = module.get(AudioController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/audio.controller.ts ================================================ import { InjectQueue } from '@nestjs/bull'; import { Controller, Post } from '@nestjs/common'; import { Queue } from 'bull'; @Controller('audio') export class AudioController { constructor( @InjectQueue('audio-queue') private readonly audioQueue: Queue, ) {} /** * Let's imagine we would like to convert .wav file into .mp3 */ @Post('convert') async convert() { await this.audioQueue.add('convert', { file: 'sample.wav', id: 1, }); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/audio.module.ts ================================================ import { Module } from '@nestjs/common'; import { AudioController } from './audio.controller'; import { BullModule } from '@nestjs/bull'; import { AudioProcessor } from './audio.processor'; import { AudioConvertedListener } from './audio-converted-listener'; @Module({ imports: [ BullModule.registerQueue({ name: 'audio-queue', }), ], controllers: [AudioController], providers: [AudioProcessor, AudioConvertedListener], }) export class AudioModule {} ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/audio.processor.ts ================================================ import { Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { Job } from 'bull'; import { EventEmitter2 } from '@nestjs/event-emitter'; @Processor('audio-queue') export class AudioProcessor { constructor(private eventEmitter: EventEmitter2) {} private logger = new Logger(AudioProcessor.name); @Process('convert') handleConvert(job: Job) { this.logger.debug('start converting wav file to mp3'); this.logger.debug(job.data); this.logger.debug('file converted successfully'); this.eventEmitter.emit('audio.converted', job.data); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/audio/events/audio-converted-event.ts ================================================ export class AudioConvertedEvent { file: string; id: number; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/file/file.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { FileController } from './file.controller'; describe('FileController', () => { let controller: FileController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [FileController], }).compile(); controller = module.get(FileController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/file/file.controller.ts ================================================ import { Controller, Get, Header, Res, StreamableFile } from '@nestjs/common'; import { Response } from 'express'; import { createReadStream } from 'fs'; import { join } from 'path'; @Controller('file') export class FileController { @Get('stream-file') getFile1(): StreamableFile { const file = createReadStream(join(process.cwd(), 'package.json')); return new StreamableFile(file); } @Get('stream-file-customize') getFileCustomizedResponse(@Res({ passthrough: true }) res): StreamableFile { const file = createReadStream(join(process.cwd(), 'package.json')); res.set({ 'Content-Type': 'application/json', 'Content-Disposition': 'attachment; filename="package.json', }); return new StreamableFile(file); } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; import * as session from 'express-session'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); app.use( session({ secret: 'my-secret', resave: false, saveUninitialized: false, }), ); await app.listen(3000); } bootstrap(); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/task/task.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { TaskService } from './task.service'; describe('TaskService', () => { let service: TaskService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TaskService], }).compile(); service = module.get(TaskService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/task/task.service.ts ================================================ import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); // @Cron('0 * * * * *') // myCronTask() { // this.logger.debug('Cron Task Called'); // } // } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/user.decorator.ts ================================================ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const User = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); request.user = { id: 1, name: 'Jane Done' }; return request.user; }, ); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/src/user.entity.ts ================================================ export class UserEntity { id: number; name: string; } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: module-21-nestjs-advanced-concepts/lesson-08-session/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } }