Compare commits
No commits in common. "4112645f5e61c9f42479f678cffa0eb1cb486531" and "fd4bf757fad6268670059466be5a4ccb2faf9df7" have entirely different histories.
4112645f5e
...
fd4bf757fa
|
|
@ -1,56 +1,56 @@
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
/build
|
/build
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
/coverage
|
/coverage
|
||||||
/.nyc_output
|
/.nyc_output
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
/.idea
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
# dotenv environment variable files
|
# dotenv environment variable files
|
||||||
.env
|
.env
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
.env.local
|
||||||
|
|
||||||
# temp directory
|
# temp directory
|
||||||
.temp
|
.temp
|
||||||
.tmp
|
.tmp
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
*.pid
|
*.pid
|
||||||
*.seed
|
*.seed
|
||||||
*.pid.lock
|
*.pid.lock
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
stages:
|
stages:
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
COMPOSE_PROJECT_NAME: travelmarine-backend
|
COMPOSE_PROJECT_NAME: travelmarine-backend
|
||||||
DOCKER_HOST: unix:///var/run/docker.sock
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
script:
|
script:
|
||||||
- docker compose -p "$COMPOSE_PROJECT_NAME" up -d --build
|
- docker compose -p "$COMPOSE_PROJECT_NAME" up -d --build
|
||||||
environment:
|
environment:
|
||||||
name: production
|
name: production
|
||||||
url: http://89.169.188.2
|
url: http://89.169.188.2
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
196
README.md
|
|
@ -1,98 +1,98 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||||
</p>
|
</p>
|
||||||
<!--[](https://opencollective.com/nest#backer)
|
<!--[](https://opencollective.com/nest#backer)
|
||||||
[](https://opencollective.com/nest#sponsor)-->
|
[](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compile and run the project
|
## Compile and run the project
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# development
|
# development
|
||||||
$ npm run start
|
$ npm run start
|
||||||
|
|
||||||
# watch mode
|
# watch mode
|
||||||
$ npm run start:dev
|
$ npm run start:dev
|
||||||
|
|
||||||
# production mode
|
# production mode
|
||||||
$ npm run start:prod
|
$ npm run start:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run tests
|
## Run tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# unit tests
|
# unit tests
|
||||||
$ npm run test
|
$ npm run test
|
||||||
|
|
||||||
# e2e tests
|
# e2e tests
|
||||||
$ npm run test:e2e
|
$ npm run test:e2e
|
||||||
|
|
||||||
# test coverage
|
# test coverage
|
||||||
$ npm run test:cov
|
$ npm run test:cov
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||||
|
|
||||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install -g @nestjs/mau
|
$ npm install -g @nestjs/mau
|
||||||
$ mau deploy
|
$ mau deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
Check out a few resources that may come in handy when working with NestJS:
|
Check out a few resources that may come in handy when working with NestJS:
|
||||||
|
|
||||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||||
|
|
||||||
## Support
|
## 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).
|
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
|
## Stay in touch
|
||||||
|
|
||||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,35 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import eslint from '@eslint/js';
|
import eslint from '@eslint/js';
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ['eslint.config.mjs'],
|
ignores: ['eslint.config.mjs'],
|
||||||
},
|
},
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
eslintPluginPrettierRecommended,
|
eslintPluginPrettierRecommended,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.node,
|
...globals.node,
|
||||||
...globals.jest,
|
...globals.jest,
|
||||||
},
|
},
|
||||||
sourceType: 'commonjs',
|
sourceType: 'commonjs',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
projectService: true,
|
projectService: true,
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'@typescript-eslint/no-floating-promises': 'warn',
|
'@typescript-eslint/no-floating-promises': 'warn',
|
||||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/nest-cli",
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"deleteOutDir": true
|
"deleteOutDir": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
172
package.json
|
|
@ -1,86 +1,86 @@
|
||||||
{
|
{
|
||||||
"name": "nest-app",
|
"name": "nest-app",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"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": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/jwt": "^11.0.2",
|
"@nestjs/jwt": "^11.0.2",
|
||||||
"@nestjs/mapped-types": "^2.1.0",
|
"@nestjs/mapped-types": "^2.1.0",
|
||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.1.9",
|
"@nestjs/platform-express": "^11.1.9",
|
||||||
"@nestjs/swagger": "^11.2.3",
|
"@nestjs/swagger": "^11.2.3",
|
||||||
"@types/multer": "^2.0.0",
|
"@types/multer": "^2.0.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.3",
|
"class-validator": "^0.14.3",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.18.0",
|
||||||
"@nestjs/cli": "^11.0.0",
|
"@nestjs/cli": "^11.0.0",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"jest": "^30.0.0",
|
"jest": "^30.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.20.0"
|
"typescript-eslint": "^8.20.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"js",
|
"js",
|
||||||
"json",
|
"json",
|
||||||
"ts"
|
"ts"
|
||||||
],
|
],
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
},
|
},
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
|
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
|
||||||
import { ApiBody } from '@nestjs/swagger';
|
import { ApiBody } from '@nestjs/swagger';
|
||||||
import { LocalAuthGuard } from './auth/local-auth.guard';
|
import { LocalAuthGuard } from './auth/local-auth.guard';
|
||||||
import { AuthService } from './auth/auth.service';
|
import { AuthService } from './auth/auth.service';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private authService: AuthService) {}
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
@UseGuards(LocalAuthGuard)
|
@UseGuards(LocalAuthGuard)
|
||||||
@Post('auth/login')
|
@Post('auth/login')
|
||||||
@ApiBody({
|
@ApiBody({
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
email: { type: 'string', example: 'emai1@email.com' },
|
email: { type: 'string', example: 'emai1@email.com' },
|
||||||
password: { type: 'string', example: 'admin' },
|
password: { type: 'string', example: 'admin' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
async login(@Request() req) {
|
async login(@Request() req) {
|
||||||
return this.authService.login(req.user);
|
return this.authService.login(req.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(LocalAuthGuard)
|
@UseGuards(LocalAuthGuard)
|
||||||
@Post('auth/logout')
|
@Post('auth/logout')
|
||||||
async logout(@Request() req) {
|
async logout(@Request() req) {
|
||||||
return req.logout();
|
return req.logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { YachtsModule } from './yachts/yachts.module';
|
import { YachtsModule } from './yachts/yachts.module';
|
||||||
import { CatalogModule } from './catalog/catalog.module';
|
import { CatalogModule } from './catalog/catalog.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { UsersModule } from './users/users.module';
|
import { UsersModule } from './users/users.module';
|
||||||
import { FilesModule } from './files/files.module';
|
import { FilesModule } from './files/files.module';
|
||||||
import { ReviewsModule } from './reviews/reviews.module';
|
|
||||||
import { ReservationsModule } from './reservations/reservations.module';
|
@Module({
|
||||||
|
imports: [YachtsModule, CatalogModule, AuthModule, UsersModule, FilesModule],
|
||||||
@Module({
|
controllers: [AppController],
|
||||||
imports: [YachtsModule, CatalogModule, AuthModule, UsersModule, FilesModule, ReviewsModule, ReservationsModule],
|
providers: [AppService],
|
||||||
controllers: [AppController],
|
})
|
||||||
providers: [AppService],
|
export class AppModule {}
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
return 'Hello World!';
|
return 'Hello World!';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { LocalStrategy } from './local.strategy';
|
import { LocalStrategy } from './local.strategy';
|
||||||
import { UsersModule } from '../users/users.module';
|
import { UsersModule } from '../users/users.module';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { jwtConstants } from './constants';
|
import { jwtConstants } from './constants';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
UsersModule,
|
UsersModule,
|
||||||
PassportModule,
|
PassportModule,
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: jwtConstants.secret,
|
secret: jwtConstants.secret,
|
||||||
signOptions: { expiresIn: '90d' },
|
signOptions: { expiresIn: '90d' },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
providers: [AuthService, LocalStrategy],
|
providers: [AuthService, LocalStrategy],
|
||||||
exports: [AuthService],
|
exports: [AuthService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
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>(AuthService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,38 +1,34 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { UsersService } from '../users/users.service';
|
import { UsersService } from '../users/users.service';
|
||||||
import { User } from 'src/users/user.entity';
|
import { User } from 'src/users/user.entity';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly logger = new Logger(AuthService.name);
|
private readonly logger = new Logger(AuthService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
private jwtService: JwtService,
|
private jwtService: JwtService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async validateUser(
|
async validateUser(
|
||||||
email: string,
|
email: string,
|
||||||
pass: string,
|
pass: string,
|
||||||
): Promise<Omit<User, 'password'> | null> {
|
): Promise<Omit<User, 'password'> | null> {
|
||||||
const user = await this.usersService.findOne(email);
|
const user = await this.usersService.findOne(email);
|
||||||
if (user && user.password === pass) {
|
if (user && user.password === pass) {
|
||||||
const { password, ...result } = user;
|
const { password, ...result } = user;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
login(user: Omit<User, 'password'>) {
|
async login(user: { email: string; password: string }) {
|
||||||
this.logger.log('LOG');
|
this.logger.log('LOG');
|
||||||
const payload = {
|
const payload = { username: user.email, sub: user.password };
|
||||||
username: user.email,
|
return {
|
||||||
sub: user.userId,
|
access_token: this.jwtService.sign(payload),
|
||||||
userId: user.userId,
|
};
|
||||||
};
|
}
|
||||||
return {
|
}
|
||||||
access_token: this.jwtService.sign(payload),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export const jwtConstants = {
|
export const jwtConstants = {
|
||||||
secret: '6by876hiuGHiugiuG8t78t87tGUYUYg8u7g87',
|
secret: '6by876hiuGHiugiuG8t78t87tGUYUYg8u7g87',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocalAuthGuard extends AuthGuard('local') {}
|
export class LocalAuthGuard extends AuthGuard('local') {}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
import { Strategy } from 'passport-local';
|
import { Strategy } from 'passport-local';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import type { User } from 'src/users/user.entity';
|
import type { User } from 'src/users/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||||
constructor(private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
super({
|
super({
|
||||||
usernameField: 'email',
|
usernameField: 'email',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(
|
async validate(
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<Omit<User, 'password'>> {
|
): Promise<Omit<User, 'password'>> {
|
||||||
const user = await this.authService.validateUser(email, password);
|
const user = await this.authService.validateUser(email, password);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { CatalogController } from './catalog.controller';
|
||||||
|
|
||||||
|
describe('CatalogController', () => {
|
||||||
|
let controller: CatalogController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [CatalogController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<CatalogController>(CatalogController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,113 +1,72 @@
|
||||||
import { Controller, Get, Query, Param, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
import { CatalogService } from './catalog.service';
|
import { CatalogService } from './catalog.service';
|
||||||
import { CatalogResponseDto } from './dto/catalog-response.dto';
|
import { CatalogResponseDto } from './dto/catalog-response.dto';
|
||||||
import { CatalogFiltersDto } from './dto/catalog-filters.dto';
|
import { CatalogItemDto } from './dto/catalog-item.dto';
|
||||||
import {
|
import { CatalogParamsDto } from './dto/catalog-params.dto';
|
||||||
CatalogItemShortDto,
|
import {
|
||||||
CatalogItemLongDto,
|
ApiQuery,
|
||||||
} from './dto/catalog-item.dto';
|
ApiResponse,
|
||||||
import {
|
ApiOperation,
|
||||||
ApiQuery,
|
ApiProperty,
|
||||||
ApiResponse,
|
} from '@nestjs/swagger';
|
||||||
ApiOperation,
|
import { MainPageCatalogResponseDto } from './dto/main-page-catalog-response.dto';
|
||||||
ApiProperty,
|
|
||||||
ApiBody,
|
@Controller('catalog')
|
||||||
ApiTags,
|
export class CatalogController {
|
||||||
} from '@nestjs/swagger';
|
constructor(private readonly catalogService: CatalogService) {}
|
||||||
import { MainPageCatalogResponseDto } from './dto/main-page-catalog-response.dto';
|
|
||||||
import { CreateYachtDto } from './dto/create-yacht.dto';
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get catalog with filters, search and sorting' })
|
||||||
@ApiTags('catalog')
|
@ApiResponse({
|
||||||
@Controller('catalog')
|
status: 200,
|
||||||
export class CatalogController {
|
description: 'Catalog items with filters applied',
|
||||||
constructor(private readonly catalogService: CatalogService) {}
|
type: CatalogResponseDto,
|
||||||
|
})
|
||||||
@Get('filter')
|
@ApiQuery({
|
||||||
@ApiOperation({ summary: 'Filter catalog items with query parameters' })
|
name: 'params',
|
||||||
@ApiResponse({
|
required: false,
|
||||||
status: 200,
|
description: 'JSON string of filter parameters',
|
||||||
description: 'Filtered catalog items',
|
type: String,
|
||||||
type: CatalogResponseDto,
|
})
|
||||||
})
|
async getCatalog(
|
||||||
@ApiQuery({ name: 'search', required: false, type: String })
|
@Query('params') paramsString?: string,
|
||||||
@ApiQuery({ name: 'minLength', required: false, type: Number })
|
): Promise<CatalogResponseDto> {
|
||||||
@ApiQuery({ name: 'maxLength', required: false, type: Number })
|
let params: CatalogParamsDto = {};
|
||||||
@ApiQuery({ name: 'minPrice', required: false, type: Number })
|
|
||||||
@ApiQuery({ name: 'maxPrice', required: false, type: Number })
|
if (paramsString) {
|
||||||
@ApiQuery({ name: 'minYear', required: false, type: Number })
|
try {
|
||||||
@ApiQuery({ name: 'maxYear', required: false, type: Number })
|
params = JSON.parse(decodeURIComponent(paramsString));
|
||||||
@ApiQuery({ name: 'guests', required: false, type: Number })
|
} catch (error) {
|
||||||
@ApiQuery({ name: 'sortByPrice', required: false, type: String })
|
params = {};
|
||||||
@ApiQuery({ name: 'paymentType', required: false, type: String })
|
}
|
||||||
@ApiQuery({ name: 'quickBooking', required: false, type: Boolean })
|
}
|
||||||
@ApiQuery({ name: 'hasToilet', required: false, type: Boolean })
|
|
||||||
@ApiQuery({ name: 'date', required: false, type: Date })
|
return this.catalogService.getCatalog(params);
|
||||||
@ApiQuery({ name: 'departureTime', required: false, type: String })
|
}
|
||||||
@ApiQuery({ name: 'arrivalTime', required: false, type: String })
|
|
||||||
async getFilteredCatalog(
|
@Get('main-page')
|
||||||
@Query() filters: CatalogFiltersDto,
|
@ApiOperation({ summary: 'Get catalog for main page' })
|
||||||
): Promise<CatalogResponseDto> {
|
@ApiProperty({ type: MainPageCatalogResponseDto })
|
||||||
return this.catalogService.getCatalog(filters);
|
async getMainPageCatalog(): Promise<MainPageCatalogResponseDto | null> {
|
||||||
}
|
return this.catalogService.getMainPageCatalog();
|
||||||
|
}
|
||||||
@Get()
|
|
||||||
@ApiOperation({ summary: 'Get all catalog items (deprecated, use /filter)' })
|
@Get('all')
|
||||||
@ApiResponse({
|
@ApiOperation({ summary: 'Get all catalog items without filters' })
|
||||||
status: 200,
|
@ApiResponse({
|
||||||
description: 'All catalog items',
|
status: 200,
|
||||||
type: [CatalogItemShortDto],
|
description: 'All catalog items',
|
||||||
})
|
type: [CatalogItemDto],
|
||||||
async getAllCatalogItems(): Promise<CatalogItemShortDto[]> {
|
})
|
||||||
return this.catalogService.getAllCatalogItems();
|
async getAllCatalogItems(): Promise<CatalogItemDto[]> {
|
||||||
}
|
return this.catalogService.getAllCatalogItems();
|
||||||
|
}
|
||||||
@Get('main-page')
|
|
||||||
@ApiOperation({ summary: 'Get catalog for main page' })
|
@Get('filtered')
|
||||||
@ApiProperty({ type: MainPageCatalogResponseDto })
|
@ApiOperation({ summary: 'Get catalog with explicit query parameters' })
|
||||||
async getMainPageCatalog(): Promise<MainPageCatalogResponseDto | null> {
|
async getFilteredCatalog(
|
||||||
return this.catalogService.getMainPageCatalog();
|
@Query() params: CatalogParamsDto,
|
||||||
}
|
): Promise<CatalogResponseDto> {
|
||||||
|
return this.catalogService.getCatalog(params);
|
||||||
@Get('user/:userId')
|
}
|
||||||
@ApiOperation({ summary: 'Get catalog items by user ID' })
|
}
|
||||||
@ApiResponse({
|
|
||||||
status: 200,
|
|
||||||
description: 'Catalog items for the specified user',
|
|
||||||
type: [CatalogItemShortDto],
|
|
||||||
})
|
|
||||||
async getCatalogByUserId(
|
|
||||||
@Param('userId') userId: string,
|
|
||||||
): Promise<CatalogItemShortDto[]> {
|
|
||||||
return this.catalogService.getCatalogByUserId(Number(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
|
||||||
@ApiOperation({ summary: 'Get catalog item by ID with full details' })
|
|
||||||
@ApiResponse({
|
|
||||||
status: 200,
|
|
||||||
description: 'Catalog item with reviews, reservations and owner',
|
|
||||||
type: CatalogItemLongDto,
|
|
||||||
})
|
|
||||||
async getCatalogItemById(
|
|
||||||
@Param('id') id: string,
|
|
||||||
): Promise<CatalogItemLongDto | null> {
|
|
||||||
return this.catalogService.getCatalogItemById(Number(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@HttpCode(HttpStatus.CREATED)
|
|
||||||
@ApiOperation({ summary: 'Create a new yacht in catalog' })
|
|
||||||
@ApiBody({ type: CreateYachtDto })
|
|
||||||
@ApiResponse({
|
|
||||||
status: 201,
|
|
||||||
description: 'Yacht successfully created',
|
|
||||||
type: CatalogItemLongDto,
|
|
||||||
})
|
|
||||||
@ApiResponse({
|
|
||||||
status: 400,
|
|
||||||
description: 'Bad request',
|
|
||||||
})
|
|
||||||
async createYacht(@Body() createYachtDto: CreateYachtDto): Promise<CatalogItemLongDto> {
|
|
||||||
return this.catalogService.createYacht(createYachtDto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
import { Module, forwardRef } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { CatalogService } from './catalog.service';
|
import { CatalogController } from './catalog.controller';
|
||||||
import { CatalogController } from './catalog.controller';
|
import { CatalogService } from './catalog.service';
|
||||||
import { UsersModule } from '../users/users.module';
|
|
||||||
import { ReservationsModule } from '../reservations/reservations.module';
|
@Module({
|
||||||
import { ReviewsModule } from '../reviews/reviews.module';
|
controllers: [CatalogController],
|
||||||
|
providers: [CatalogService],
|
||||||
@Module({
|
exports: [CatalogService],
|
||||||
imports: [
|
})
|
||||||
UsersModule, // This provides UsersService
|
export class CatalogModule {}
|
||||||
forwardRef(() => ReservationsModule), // This provides ReservationsService
|
|
||||||
ReviewsModule, // This provides ReviewsService
|
|
||||||
],
|
|
||||||
controllers: [CatalogController],
|
|
||||||
providers: [CatalogService],
|
|
||||||
exports: [CatalogService], // Export for other modules to use
|
|
||||||
})
|
|
||||||
export class CatalogModule {}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { CatalogService } from './catalog.service';
|
||||||
|
|
||||||
|
describe('CatalogService', () => {
|
||||||
|
let service: CatalogService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [CatalogService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<CatalogService>(CatalogService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
import {
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsNumber,
|
|
||||||
IsBoolean,
|
|
||||||
IsDate,
|
|
||||||
Min,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class CatalogFiltersDto {
|
|
||||||
@ApiProperty({ required: false, description: 'Search by yacht name' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
search?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Minimum length in meters',
|
|
||||||
example: 10,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Type(() => Number)
|
|
||||||
minLength?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Maximum length in meters',
|
|
||||||
example: 20,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Type(() => Number)
|
|
||||||
maxLength?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Minimum price',
|
|
||||||
example: 30000,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Type(() => Number)
|
|
||||||
minPrice?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Maximum price',
|
|
||||||
example: 100000,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Type(() => Number)
|
|
||||||
maxPrice?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Sort by price',
|
|
||||||
example: 100000,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Type(() => Number)
|
|
||||||
sortByPrice?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, description: 'Minimum year', example: 2019 })
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1900)
|
|
||||||
@Type(() => Number)
|
|
||||||
minYear?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, description: 'Maximum year', example: 2023 })
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1900)
|
|
||||||
@Type(() => Number)
|
|
||||||
maxYear?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, description: 'Number of guests', example: 4 })
|
|
||||||
@IsOptional()
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
@Type(() => Number)
|
|
||||||
guests?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Payment type',
|
|
||||||
example: 'card',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
paymentType?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Quick booking available',
|
|
||||||
example: true,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
@Type(() => Boolean)
|
|
||||||
quickBooking?: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, description: 'Has toilet', example: true })
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
@Type(() => Boolean)
|
|
||||||
hasToilet?: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Departure date',
|
|
||||||
example: '2025-12-20',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsDate()
|
|
||||||
@Type(() => Date)
|
|
||||||
date?: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Departure time',
|
|
||||||
example: '08:00',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
departureTime?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
description: 'Arrival time',
|
|
||||||
example: '20:00',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
arrivalTime?: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +1,12 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
export class CatalogItemDto {
|
||||||
export class UserDto {
|
id?: number;
|
||||||
@ApiProperty({ example: 1 })
|
name: string;
|
||||||
userId: number;
|
length: number;
|
||||||
|
speed: number;
|
||||||
@ApiProperty({ example: 'Иван' })
|
minCost: number;
|
||||||
firstName: string;
|
mainImageUrl: string;
|
||||||
|
galleryUrls: string[];
|
||||||
@ApiProperty({ example: 'Андреев' })
|
hasQuickRent: boolean;
|
||||||
lastName: string;
|
isFeatured: boolean;
|
||||||
|
topText?: string;
|
||||||
@ApiProperty({ example: '+79009009090' })
|
}
|
||||||
phone: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'ivan@yachting.ru' })
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Северный Флот', required: false })
|
|
||||||
companyName?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1234567890, required: false })
|
|
||||||
inn?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1122334455667, required: false })
|
|
||||||
ogrn?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReviewDto {
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
reviewerId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
yachtId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 5 })
|
|
||||||
starsCount: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Excellent yacht!' })
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReservationDto {
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
yachtId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
reservatorId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1733097600 })
|
|
||||||
startUtc: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1733133600 })
|
|
||||||
endUtc: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CatalogItemShortDto {
|
|
||||||
@ApiProperty({ example: 1 })
|
|
||||||
id?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Азимут 55' })
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 16.7 })
|
|
||||||
length: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 32 })
|
|
||||||
speed: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 85000 })
|
|
||||||
minCost: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'api/uploads/1765727362318-238005198.jpg' })
|
|
||||||
mainImageUrl: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: [String],
|
|
||||||
example: ['api/uploads/1765727362318-238005198.jpg'],
|
|
||||||
})
|
|
||||||
galleryUrls: string[];
|
|
||||||
|
|
||||||
@ApiProperty({ example: true })
|
|
||||||
hasQuickRent: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: true })
|
|
||||||
isFeatured: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, example: '🔥 Лучшее предложение' })
|
|
||||||
topText?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, example: true })
|
|
||||||
isBestOffer?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CatalogItemLongDto extends CatalogItemShortDto {
|
|
||||||
@ApiProperty({ example: 2022 })
|
|
||||||
year: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 8 })
|
|
||||||
comfortCapacity: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 12 })
|
|
||||||
maxCapacity: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 4.8 })
|
|
||||||
width: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 3 })
|
|
||||||
cabinsCount: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Стеклопластик' })
|
|
||||||
matherial: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1200 })
|
|
||||||
power: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Роскошная моторная яхта...' })
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
@ApiProperty({ type: UserDto })
|
|
||||||
owner: UserDto;
|
|
||||||
|
|
||||||
@ApiProperty({ type: [ReviewDto] })
|
|
||||||
reviews: ReviewDto[];
|
|
||||||
|
|
||||||
@ApiProperty({ type: [ReservationDto] })
|
|
||||||
reservations: ReservationDto[];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,65 @@
|
||||||
import {
|
import {
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export enum SortDirection {
|
export enum SortDirection {
|
||||||
ASC = 'asc',
|
ASC = 'asc',
|
||||||
DESC = 'desc',
|
DESC = 'desc',
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SortParams {
|
export class SortParams {
|
||||||
@ApiProperty({ required: false, enum: SortDirection })
|
@ApiProperty({ required: false, enum: SortDirection })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(SortDirection)
|
@IsEnum(SortDirection)
|
||||||
direction?: SortDirection;
|
direction?: SortDirection;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
field?: string;
|
field?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PriceFilter {
|
export class PriceFilter {
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
minvalue?: number;
|
minvalue?: number;
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
maxvalue?: number;
|
maxvalue?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FilterParams {
|
export class FilterParams {
|
||||||
@ApiProperty({ required: false, type: PriceFilter })
|
@ApiProperty({ required: false, type: PriceFilter })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => PriceFilter)
|
@Type(() => PriceFilter)
|
||||||
price?: PriceFilter;
|
price?: PriceFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CatalogParamsDto {
|
export class CatalogParamsDto {
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
search?: string;
|
search?: string;
|
||||||
|
|
||||||
@ApiProperty({ required: false, type: SortParams })
|
@ApiProperty({ required: false, type: SortParams })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => SortParams)
|
@Type(() => SortParams)
|
||||||
sort?: SortParams;
|
sort?: SortParams;
|
||||||
|
|
||||||
@ApiProperty({ required: false, type: FilterParams })
|
@ApiProperty({ required: false, type: FilterParams })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => FilterParams)
|
@Type(() => FilterParams)
|
||||||
filter?: FilterParams;
|
filter?: FilterParams;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,11 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { CatalogItemDto } from './catalog-item.dto';
|
||||||
import { CatalogItemShortDto } from './catalog-item.dto';
|
|
||||||
import { CatalogFiltersDto } from './catalog-filters.dto';
|
export class CatalogResponseDto {
|
||||||
|
items: CatalogItemDto[];
|
||||||
export class CatalogResponseDto {
|
total: number;
|
||||||
@ApiProperty({ type: [CatalogItemShortDto] })
|
page?: number;
|
||||||
items: CatalogItemShortDto[];
|
limit?: number;
|
||||||
|
filters?: any;
|
||||||
@ApiProperty({ example: 42 })
|
sort?: any;
|
||||||
total: number;
|
search?: string;
|
||||||
|
}
|
||||||
@ApiProperty({ required: false, example: 1 })
|
|
||||||
page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, example: 10 })
|
|
||||||
limit?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ type: CatalogFiltersDto, required: false })
|
|
||||||
filters?: CatalogFiltersDto;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
example: { field: 'name', direction: 'asc' },
|
|
||||||
})
|
|
||||||
sort?: any;
|
|
||||||
|
|
||||||
@ApiProperty({ required: false, example: 'yacht' })
|
|
||||||
search?: string;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class CreateYachtDto {
|
|
||||||
@ApiProperty({ example: 'Азимут 55', description: 'Название яхты' })
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 16.7, description: 'Длина яхты в метрах' })
|
|
||||||
length: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 32, description: 'Скорость в узлах' })
|
|
||||||
speed: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 85000, description: 'Минимальная стоимость аренды' })
|
|
||||||
minCost: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 'api/uploads/1765727362318-238005198.jpg',
|
|
||||||
description: 'URL главного изображения',
|
|
||||||
})
|
|
||||||
mainImageUrl: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: [String],
|
|
||||||
example: ['api/uploads/1765727362318-238005198.jpg'],
|
|
||||||
description: 'URL галереи изображений',
|
|
||||||
})
|
|
||||||
galleryUrls: string[];
|
|
||||||
|
|
||||||
@ApiProperty({ example: true, description: 'Доступна быстрая аренда' })
|
|
||||||
hasQuickRent: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: false, description: 'Рекомендуемая яхта' })
|
|
||||||
isFeatured: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 2022, description: 'Год выпуска' })
|
|
||||||
year: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 8, description: 'Комфортная вместимость' })
|
|
||||||
comfortCapacity: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 12, description: 'Максимальная вместимость' })
|
|
||||||
maxCapacity: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 4.8, description: 'Ширина в метрах' })
|
|
||||||
width: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 3, description: 'Количество кают' })
|
|
||||||
cabinsCount: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Стеклопластик', description: 'Материал корпуса' })
|
|
||||||
matherial: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1200, description: 'Мощность двигателя' })
|
|
||||||
power: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 'Роскошная моторная яхта...',
|
|
||||||
description: 'Описание яхты',
|
|
||||||
})
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1, description: 'ID владельца яхты' })
|
|
||||||
userId: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
required: false,
|
|
||||||
example: '🔥 Лучшее предложение',
|
|
||||||
description: 'Текст для отображения сверху',
|
|
||||||
})
|
|
||||||
topText?: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CatalogItemShortDto } from './catalog-item.dto';
|
import { CatalogItemDto } from './catalog-item.dto';
|
||||||
|
|
||||||
export class MainPageCatalogResponseDto {
|
export class MainPageCatalogResponseDto {
|
||||||
featuredYacht: CatalogItemShortDto;
|
featuredYacht: CatalogItemDto;
|
||||||
restYachts: CatalogItemShortDto[];
|
restYachts: CatalogItemDto[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { FilesController } from './files.controller';
|
import { FilesController } from './files.controller';
|
||||||
|
|
||||||
describe('FilesController', () => {
|
describe('FilesController', () => {
|
||||||
let controller: FilesController;
|
let controller: FilesController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [FilesController],
|
controllers: [FilesController],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<FilesController>(FilesController);
|
controller = module.get<FilesController>(FilesController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
|
|
||||||
@Controller('files')
|
@Controller('files')
|
||||||
export class FilesController {
|
export class FilesController {
|
||||||
constructor(private readonly filesService: FilesService) {}
|
constructor(private readonly filesService: FilesService) {}
|
||||||
|
|
||||||
@Post('upload')
|
@Post('upload')
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
uploadFile(@UploadedFile() file: Express.Multer.File) {
|
uploadFile(@UploadedFile() file: Express.Multer.File) {
|
||||||
return this.filesService.saveFileInfo(file);
|
return this.filesService.saveFileInfo(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('upload/multiple')
|
@Post('upload/multiple')
|
||||||
@UseInterceptors(FilesInterceptor('files', 10))
|
@UseInterceptors(FilesInterceptor('files', 10))
|
||||||
uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
|
uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
|
||||||
return this.filesService.saveMultipleFilesInfo(files);
|
return this.filesService.saveMultipleFilesInfo(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
import { FilesController } from './files.controller';
|
import { FilesController } from './files.controller';
|
||||||
import { MulterModule } from '@nestjs/platform-express';
|
import { MulterModule } from '@nestjs/platform-express';
|
||||||
import { diskStorage } from 'multer';
|
import { diskStorage } from 'multer';
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
MulterModule.register({
|
MulterModule.register({
|
||||||
storage: diskStorage({
|
storage: diskStorage({
|
||||||
destination: './uploads',
|
destination: './uploads',
|
||||||
filename: (req, file, callback) => {
|
filename: (req, file, callback) => {
|
||||||
const uniqueSuffix =
|
const uniqueSuffix =
|
||||||
Date.now() + '-' + Math.round(Math.random() * 1e9);
|
Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
const ext = extname(file.originalname);
|
const ext = extname(file.originalname);
|
||||||
const filename = `${uniqueSuffix}${ext}`;
|
const filename = `${uniqueSuffix}${ext}`;
|
||||||
callback(null, filename);
|
callback(null, filename);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 30 * 1024 * 1024,
|
fileSize: 30 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
],
|
],
|
||||||
controllers: [FilesController],
|
controllers: [FilesController],
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
exports: [FilesService],
|
exports: [FilesService],
|
||||||
})
|
})
|
||||||
export class FilesModule {}
|
export class FilesModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
|
|
||||||
describe('FilesService', () => {
|
describe('FilesService', () => {
|
||||||
let service: FilesService;
|
let service: FilesService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [FilesService],
|
providers: [FilesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<FilesService>(FilesService);
|
service = module.get<FilesService>(FilesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilesService {
|
export class FilesService {
|
||||||
constructor(private configService: ConfigService) {}
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
async saveFileInfo(file: Express.Multer.File) {
|
async saveFileInfo(file: Express.Multer.File) {
|
||||||
return {
|
return {
|
||||||
filename: file.filename,
|
filename: file.filename,
|
||||||
originalname: file.originalname,
|
originalname: file.originalname,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
mimetype: file.mimetype,
|
mimetype: file.mimetype,
|
||||||
url: `/api/uploads/${file.filename}`,
|
url: `/api/uploads/${file.filename}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveMultipleFilesInfo(files: Express.Multer.File[]) {
|
async saveMultipleFilesInfo(files: Express.Multer.File[]) {
|
||||||
return Promise.all(files.map((file) => this.saveFileInfo(file)));
|
return Promise.all(files.map((file) => this.saveFileInfo(file)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
src/main.ts
|
|
@ -1,36 +1,36 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
app.use('/uploads', express.static(join(__dirname, '..', 'uploads')));
|
app.use('/uploads', express.static(join(__dirname, '..', 'uploads')));
|
||||||
|
|
||||||
app.setGlobalPrefix('');
|
app.setGlobalPrefix('');
|
||||||
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: ['http://localhost:3000', "http://travelmarine.ru", "https://travelmarine.ru"],
|
origin: ['http://localhost:3000'],
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||||
credentials: true,
|
credentials: true,
|
||||||
allowedHeaders: 'Content-Type, Authorization, X-Requested-With',
|
allowedHeaders: 'Content-Type, Authorization, X-Requested-With',
|
||||||
exposedHeaders: 'Authorization',
|
exposedHeaders: 'Authorization',
|
||||||
maxAge: 86400,
|
maxAge: 86400,
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Travelmarine backend')
|
.setTitle('Travelmarine backend')
|
||||||
.setDescription('Backend API for Travelmarine service')
|
.setDescription('Backend API for Travelmarine service')
|
||||||
.setVersion('0.1')
|
.setVersion('0.1')
|
||||||
.addServer('http://localhost:4000', 'Local server')
|
.addServer('http://localhost:4000', 'Local server')
|
||||||
.addServer('http://89.169.188.2/api', 'Production server')
|
.addServer('http://89.169.188.2/api', 'Production server')
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('/', app, documentFactory);
|
SwaggerModule.setup('/', app, documentFactory);
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 4000);
|
await app.listen(process.env.PORT ?? 4000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { CatalogItemLongDto } from '../catalog/dto/catalog-item.dto';
|
|
||||||
|
|
||||||
export class ReservationItemDto {
|
|
||||||
@ApiProperty({ example: 1, description: 'ID яхты' })
|
|
||||||
yachtId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1, description: 'ID резерватора' })
|
|
||||||
reservatorId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1733097600, description: 'Начало резервации (Unix timestamp в UTC)' })
|
|
||||||
startUtc: number;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 1733133600, description: 'Конец резервации (Unix timestamp в UTC)' })
|
|
||||||
endUtc: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReservationWithYachtDto extends ReservationItemDto {
|
|
||||||
@ApiProperty({ example: 1, description: 'ID резервации' })
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: CatalogItemLongDto,
|
|
||||||
required: false,
|
|
||||||
description: 'Данные о яхте',
|
|
||||||
})
|
|
||||||
yacht?: CatalogItemLongDto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
|
|
||||||
import { ApiOperation, ApiResponse, ApiBody, ApiParam } from '@nestjs/swagger';
|
|
||||||
import { ReservationsService } from './reservations.service';
|
|
||||||
import { ReservationItemDto, ReservationWithYachtDto } from './reservation-item.dto';
|
|
||||||
|
|
||||||
@Controller('reservations')
|
|
||||||
export class ReservationsController {
|
|
||||||
constructor(private readonly reservationsService: ReservationsService) {}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@ApiOperation({ summary: 'Создать новую резервацию' })
|
|
||||||
@ApiBody({ type: ReservationItemDto })
|
|
||||||
@ApiResponse({ status: 201, description: 'Резервация успешно создана' })
|
|
||||||
create(@Body() dto: ReservationItemDto) {
|
|
||||||
return this.reservationsService.createReservation(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('user/:userId')
|
|
||||||
@ApiOperation({ summary: 'Получить резервации по ID пользователя' })
|
|
||||||
@ApiParam({ name: 'userId', description: 'ID пользователя', type: Number })
|
|
||||||
@ApiResponse({
|
|
||||||
status: 200,
|
|
||||||
description: 'Список резерваций пользователя с данными о яхтах',
|
|
||||||
type: [ReservationWithYachtDto],
|
|
||||||
})
|
|
||||||
async findByUserId(@Param('userId') userId: string) {
|
|
||||||
return this.reservationsService.getReservationsByUserId(Number(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('yacht/:yachtId')
|
|
||||||
findByYachtId(@Param('yachtId') yachtId: string) {
|
|
||||||
return this.reservationsService.getReservationsByYachtId(Number(yachtId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
findAll() {
|
|
||||||
return this.reservationsService.getAllReservations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { Module, forwardRef } from '@nestjs/common';
|
|
||||||
import { ReservationsService } from './reservations.service';
|
|
||||||
import { ReservationsController } from './reservations.controller';
|
|
||||||
import { CatalogModule } from '../catalog/catalog.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [forwardRef(() => CatalogModule)],
|
|
||||||
controllers: [ReservationsController],
|
|
||||||
providers: [ReservationsService],
|
|
||||||
exports: [ReservationsService], // Export for other modules to use
|
|
||||||
})
|
|
||||||
export class ReservationsModule {}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import { Injectable, Inject, forwardRef } from '@nestjs/common';
|
|
||||||
import { ReservationItemDto } from './reservation-item.dto';
|
|
||||||
import { CatalogService } from '../catalog/catalog.service';
|
|
||||||
import { CatalogItemLongDto } from '../catalog/dto/catalog-item.dto';
|
|
||||||
|
|
||||||
export interface Reservation extends ReservationItemDto {
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReservationWithYacht extends Reservation {
|
|
||||||
yacht?: CatalogItemLongDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ReservationsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(forwardRef(() => CatalogService))
|
|
||||||
private readonly catalogService: CatalogService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private reservations: Reservation[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
yachtId: 1,
|
|
||||||
reservatorId: 1,
|
|
||||||
// Corrected: Jan 1, 2026 20:00 UTC to Jan 2, 2026 08:00 UTC
|
|
||||||
startUtc: 1767369600, // Jan 1, 2026 20:00:00 UTC
|
|
||||||
endUtc: 1767412800, // Jan 2, 2026 08:00:00 UTC
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
yachtId: 3,
|
|
||||||
reservatorId: 2,
|
|
||||||
// Jan 3, 2026 08:00 UTC to Jan 5, 2026 20:00 UTC
|
|
||||||
startUtc: 1767484800, // Jan 3, 2026 08:00:00 UTC
|
|
||||||
endUtc: 1767715200, // Jan 5, 2026 20:00:00 UTC
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
yachtId: 5,
|
|
||||||
reservatorId: 1,
|
|
||||||
// Jan 10, 2026 20:00 UTC to Jan 12, 2026 08:00 UTC
|
|
||||||
startUtc: 1768070400, // Jan 10, 2026 20:00:00 UTC
|
|
||||||
endUtc: 1768176000, // Jan 12, 2026 08:00:00 UTC
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
yachtId: 7,
|
|
||||||
reservatorId: 2,
|
|
||||||
// Jan 15, 2026 08:00 UTC to Jan 17, 2026 20:00 UTC
|
|
||||||
startUtc: 1768435200, // Jan 15, 2026 08:00:00 UTC
|
|
||||||
endUtc: 1768684800, // Jan 17, 2026 20:00:00 UTC
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
yachtId: 9,
|
|
||||||
reservatorId: 1,
|
|
||||||
// Jan 20, 2026 20:00 UTC to Jan 22, 2026 08:00 UTC
|
|
||||||
startUtc: 1768944000, // Jan 20, 2026 20:00:00 UTC
|
|
||||||
endUtc: 1769049600, // Jan 22, 2026 08:00:00 UTC
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
yachtId: 11,
|
|
||||||
reservatorId: 2,
|
|
||||||
// Jan 25, 2026 08:00 UTC to Jan 27, 2026 20:00 UTC
|
|
||||||
startUtc: 1769385600, // Jan 25, 2026 08:00:00 UTC
|
|
||||||
endUtc: 1769635200, // Jan 27, 2026 20:00:00 UTC
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
private idCounter = 7;
|
|
||||||
|
|
||||||
createReservation(dto: ReservationItemDto): Reservation {
|
|
||||||
const reservation = {
|
|
||||||
id: this.idCounter++,
|
|
||||||
...dto,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.reservations.push(reservation);
|
|
||||||
return reservation;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getReservationsByUserId(userId: number): Promise<ReservationWithYacht[]> {
|
|
||||||
const reservations = this.reservations.filter((r) => r.reservatorId === userId);
|
|
||||||
|
|
||||||
// Populate данные по яхте для каждой резервации
|
|
||||||
const reservationsWithYacht = await Promise.all(
|
|
||||||
reservations.map(async (reservation) => {
|
|
||||||
const yacht = await this.catalogService.getCatalogItemById(reservation.yachtId);
|
|
||||||
return {
|
|
||||||
...reservation,
|
|
||||||
yacht: yacht || undefined,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reservationsWithYacht;
|
|
||||||
}
|
|
||||||
|
|
||||||
getReservationsByYachtId(yachtId: number): Reservation[] {
|
|
||||||
return this.reservations.filter((r) => r.yachtId === yachtId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllReservations(): Reservation[] {
|
|
||||||
return this.reservations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export class ReviewItemDto {
|
|
||||||
reviewerId: number;
|
|
||||||
yachtId: number;
|
|
||||||
starsCount: number;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
|
|
||||||
import { ReviewsService } from './reviews.service';
|
|
||||||
import { ReviewItemDto } from './review-item.dto';
|
|
||||||
|
|
||||||
@Controller('reviews')
|
|
||||||
export class ReviewsController {
|
|
||||||
constructor(private readonly reviewsService: ReviewsService) {}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
create(@Body() dto: ReviewItemDto) {
|
|
||||||
return this.reviewsService.createReview(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('user/:userId')
|
|
||||||
findByUserId(@Param('userId') userId: string) {
|
|
||||||
return this.reviewsService.getReviewsByUserId(Number(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('yacht/:yachtId')
|
|
||||||
findByYachtId(@Param('yachtId') yachtId: string) {
|
|
||||||
return this.reviewsService.getReviewsByYachtId(Number(yachtId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
findAll() {
|
|
||||||
return this.reviewsService.getAllReviews();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { ReviewsService } from './reviews.service';
|
|
||||||
import { ReviewsController } from './reviews.controller';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [ReviewsController],
|
|
||||||
providers: [ReviewsService],
|
|
||||||
exports: [ReviewsService], // Export for other modules to use
|
|
||||||
})
|
|
||||||
export class ReviewsModule {}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ReviewItemDto } from './review-item.dto';
|
|
||||||
|
|
||||||
export interface Review extends ReviewItemDto {
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ReviewsService {
|
|
||||||
private reviews: Review[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
reviewerId: 1,
|
|
||||||
yachtId: 1,
|
|
||||||
starsCount: 5,
|
|
||||||
description: 'Excellent yacht!',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
reviewerId: 2,
|
|
||||||
yachtId: 1,
|
|
||||||
starsCount: 4,
|
|
||||||
description: 'Very good experience',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
reviewerId: 1,
|
|
||||||
yachtId: 3,
|
|
||||||
starsCount: 3,
|
|
||||||
description: 'Average condition',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
reviewerId: 2,
|
|
||||||
yachtId: 5,
|
|
||||||
starsCount: 5,
|
|
||||||
description: 'Perfect for sailing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
reviewerId: 1,
|
|
||||||
yachtId: 7,
|
|
||||||
starsCount: 4,
|
|
||||||
description: 'Comfortable and fast',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
reviewerId: 2,
|
|
||||||
yachtId: 9,
|
|
||||||
starsCount: 2,
|
|
||||||
description: 'Needs maintenance',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
reviewerId: 1,
|
|
||||||
yachtId: 11,
|
|
||||||
starsCount: 5,
|
|
||||||
description: 'Luxury experience',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
reviewerId: 2,
|
|
||||||
yachtId: 12,
|
|
||||||
starsCount: 4,
|
|
||||||
description: 'Great value for money',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
private idCounter = 9;
|
|
||||||
|
|
||||||
createReview(dto: ReviewItemDto): Review {
|
|
||||||
const review = {
|
|
||||||
id: this.idCounter++,
|
|
||||||
...dto,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.reviews.push(review);
|
|
||||||
return review;
|
|
||||||
}
|
|
||||||
|
|
||||||
getReviewsByUserId(userId: number): Review[] {
|
|
||||||
return this.reviews.filter((r) => r.reviewerId === userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getReviewsByYachtId(yachtId: number): Review[] {
|
|
||||||
return this.reviews.filter((r) => r.yachtId === yachtId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllReviews(): Review[] {
|
|
||||||
return this.reviews;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import { Yacht } from '../yachts/yacht.entity';
|
import { Yacht } from '../yachts/yacht.entity';
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
userId: number;
|
userId: number;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
yachts?: Yacht[];
|
yachts?: Yacht[];
|
||||||
companyName?: string;
|
};
|
||||||
inn?: number;
|
|
||||||
ogrn?: number;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
exports: [UsersService], // This is important!
|
exports: [UsersService],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
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>(UsersService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,96 +1,70 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { YachtsService } from '../yachts/yachts.service';
|
import { YachtsService } from '../yachts/yachts.service';
|
||||||
import { User } from './user.entity';
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
private readonly logger = new Logger(UsersService.name);
|
private readonly logger = new Logger(UsersService.name);
|
||||||
|
|
||||||
private readonly users: User[] = [
|
private readonly users: User[] = [
|
||||||
{
|
{
|
||||||
userId: 1,
|
userId: 1,
|
||||||
firstName: 'Иван',
|
firstName: 'Ivan',
|
||||||
lastName: 'Андреев',
|
lastName: 'Andreev',
|
||||||
phone: '+79009009090',
|
phone: '+79009009090',
|
||||||
email: 'ivan@yachting.ru',
|
email: 'email@email.com',
|
||||||
password: 'admin',
|
password: 'admin',
|
||||||
companyName: 'Северный Флот',
|
},
|
||||||
inn: 1234567890,
|
{
|
||||||
ogrn: 1122334455667,
|
userId: 2,
|
||||||
},
|
firstName: 'Sergey',
|
||||||
{
|
lastName: 'Bolshakov',
|
||||||
userId: 2,
|
phone: '+79009009090',
|
||||||
firstName: 'Сергей',
|
email: 'email1@email.com',
|
||||||
lastName: 'Большаков',
|
password: 'admin',
|
||||||
phone: '+79119119191',
|
},
|
||||||
email: 'sergey@yachting.ru',
|
];
|
||||||
password: 'admin',
|
|
||||||
companyName: 'Балтийские Просторы',
|
async findOne(
|
||||||
inn: 9876543210,
|
email: string,
|
||||||
ogrn: 9988776655443,
|
includeYachts: boolean = false,
|
||||||
},
|
): Promise<User | undefined> {
|
||||||
{
|
const user = this.users.find((user) => user.email === email);
|
||||||
userId: 3,
|
|
||||||
firstName: 'Анна',
|
if (user && includeYachts) {
|
||||||
lastName: 'Петрова',
|
// Fetch yachts for this user
|
||||||
phone: '+79229229292',
|
user.yachts = await [];
|
||||||
email: 'anna@yachting.ru',
|
}
|
||||||
password: 'admin',
|
|
||||||
companyName: 'Ладожские Ветры',
|
return user;
|
||||||
inn: 5555555555,
|
}
|
||||||
ogrn: 3333444455556,
|
|
||||||
},
|
async findById(
|
||||||
{
|
userId: number,
|
||||||
userId: 4,
|
includeYachts: boolean = false,
|
||||||
firstName: 'Дмитрий',
|
): Promise<User | undefined> {
|
||||||
lastName: 'Соколов',
|
const user = this.users.find((user) => user.userId === userId);
|
||||||
phone: '+79339339393',
|
|
||||||
email: 'dmitry@yachting.ru',
|
if (user && includeYachts) {
|
||||||
password: 'admin',
|
user.yachts = [];
|
||||||
companyName: 'Финский Залив',
|
}
|
||||||
inn: 1111222233,
|
|
||||||
ogrn: 7777888899990,
|
return user;
|
||||||
},
|
}
|
||||||
];
|
|
||||||
|
async findAll(includeYachts: boolean = false): Promise<User[]> {
|
||||||
async findOne(
|
if (!includeYachts) {
|
||||||
email: string,
|
return this.users;
|
||||||
includeYachts: boolean = false,
|
}
|
||||||
): Promise<User | undefined> {
|
|
||||||
const user = this.users.find((user) => user.email === email);
|
// Fetch all users with their yachts
|
||||||
|
const usersWithYachts = await Promise.all(
|
||||||
if (user && includeYachts) {
|
this.users.map(async (user) => {
|
||||||
user.yachts = [];
|
const yachts = [];
|
||||||
}
|
return { ...user, yachts };
|
||||||
|
}),
|
||||||
return user;
|
);
|
||||||
}
|
|
||||||
|
return usersWithYachts;
|
||||||
async findById(
|
}
|
||||||
userId: number,
|
}
|
||||||
includeYachts: boolean = false,
|
|
||||||
): Promise<User | undefined> {
|
|
||||||
const user = this.users.find((user) => user.userId === userId);
|
|
||||||
|
|
||||||
if (user && includeYachts) {
|
|
||||||
user.yachts = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(includeYachts: boolean = false): Promise<User[]> {
|
|
||||||
if (!includeYachts) {
|
|
||||||
return this.users;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersWithYachts = await Promise.all(
|
|
||||||
this.users.map(async (user) => {
|
|
||||||
const yachts = [];
|
|
||||||
return { ...user, yachts };
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return usersWithYachts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export class CreateYachtDto {
|
export class CreateYachtDto {
|
||||||
name: string;
|
name: string;
|
||||||
model: string;
|
model: string;
|
||||||
year: number;
|
year: number;
|
||||||
length: number;
|
length: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PartialType } from '@nestjs/mapped-types';
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
import { CreateYachtDto } from './create-yacht.dto';
|
import { CreateYachtDto } from './create-yacht.dto';
|
||||||
|
|
||||||
export class UpdateYachtDto extends PartialType(CreateYachtDto) {}
|
export class UpdateYachtDto extends PartialType(CreateYachtDto) {}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
export type Yacht = {
|
export type Yacht = {
|
||||||
yachtId: number;
|
yachtId: number;
|
||||||
name: string;
|
name: string;
|
||||||
model: string;
|
model: string;
|
||||||
year: number;
|
year: number;
|
||||||
length: number;
|
length: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { YachtsController } from './yacht.controller';
|
||||||
|
|
||||||
|
describe('YachtController', () => {
|
||||||
|
let controller: YachtsController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [YachtsController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<YachtsController>(YachtsController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,54 +1,54 @@
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
Delete,
|
Delete,
|
||||||
Body,
|
Body,
|
||||||
Param,
|
Param,
|
||||||
Query,
|
Query,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { YachtsService } from './yachts.service';
|
import { YachtsService } from './yachts.service';
|
||||||
import { CreateYachtDto } from './dto/create-yacht.dto';
|
import { CreateYachtDto } from './dto/create-yacht.dto';
|
||||||
import { UpdateYachtDto } from './dto/update-yacht.dto';
|
import { UpdateYachtDto } from './dto/update-yacht.dto';
|
||||||
|
|
||||||
@Controller('yachts')
|
@Controller('yachts')
|
||||||
export class YachtsController {
|
export class YachtsController {
|
||||||
constructor(private readonly yachtsService: YachtsService) {}
|
constructor(private readonly yachtsService: YachtsService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async findAll(@Query('userId') userId?: string) {
|
async findAll(@Query('userId') userId?: string) {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return this.yachtsService.findByUserId(parseInt(userId));
|
return this.yachtsService.findByUserId(parseInt(userId));
|
||||||
}
|
}
|
||||||
return this.yachtsService.findAll();
|
return this.yachtsService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async findOne(@Param('id', ParseIntPipe) id: number) {
|
async findOne(@Param('id', ParseIntPipe) id: number) {
|
||||||
return this.yachtsService.findById(id);
|
return this.yachtsService.findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
async create(@Body() createYachtDto: CreateYachtDto) {
|
async create(@Body() createYachtDto: CreateYachtDto) {
|
||||||
return this.yachtsService.create(createYachtDto);
|
return this.yachtsService.create(createYachtDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async update(
|
async update(
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body() updateYachtDto: UpdateYachtDto,
|
@Body() updateYachtDto: UpdateYachtDto,
|
||||||
) {
|
) {
|
||||||
return this.yachtsService.update(id, updateYachtDto);
|
return this.yachtsService.update(id, updateYachtDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
async delete(@Param('id', ParseIntPipe) id: number) {
|
async delete(@Param('id', ParseIntPipe) id: number) {
|
||||||
return this.yachtsService.delete(id);
|
return this.yachtsService.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { YachtsService } from './yachts.service';
|
import { YachtsService } from './yachts.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [YachtsService],
|
providers: [YachtsService],
|
||||||
exports: [YachtsService],
|
exports: [YachtsService],
|
||||||
})
|
})
|
||||||
export class YachtsModule {}
|
export class YachtsModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { YachtsService } from './yachts.service';
|
||||||
|
|
||||||
|
describe('YachtsService', () => {
|
||||||
|
let service: YachtsService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [YachtsService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<YachtsService>(YachtsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,95 +1,95 @@
|
||||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||||
import { Yacht } from './yacht.entity';
|
import { Yacht } from './yacht.entity';
|
||||||
import { CreateYachtDto } from './dto/create-yacht.dto';
|
import { CreateYachtDto } from './dto/create-yacht.dto';
|
||||||
import { UpdateYachtDto } from './dto/update-yacht.dto';
|
import { UpdateYachtDto } from './dto/update-yacht.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class YachtsService {
|
export class YachtsService {
|
||||||
private readonly logger = new Logger(YachtsService.name);
|
private readonly logger = new Logger(YachtsService.name);
|
||||||
|
|
||||||
private yachts: Yacht[] = [
|
private yachts: Yacht[] = [
|
||||||
{
|
{
|
||||||
yachtId: 1,
|
yachtId: 1,
|
||||||
name: 'Sea Dream',
|
name: 'Sea Dream',
|
||||||
model: 'Sunseeker 76',
|
model: 'Sunseeker 76',
|
||||||
year: 2020,
|
year: 2020,
|
||||||
length: 76,
|
length: 76,
|
||||||
userId: 1,
|
userId: 1,
|
||||||
createdAt: new Date('2023-01-15'),
|
createdAt: new Date('2023-01-15'),
|
||||||
updatedAt: new Date('2023-01-15'),
|
updatedAt: new Date('2023-01-15'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
yachtId: 2,
|
yachtId: 2,
|
||||||
name: 'Ocean Breeze',
|
name: 'Ocean Breeze',
|
||||||
model: 'Princess 68',
|
model: 'Princess 68',
|
||||||
year: 2021,
|
year: 2021,
|
||||||
length: 68,
|
length: 68,
|
||||||
userId: 1,
|
userId: 1,
|
||||||
createdAt: new Date('2023-02-20'),
|
createdAt: new Date('2023-02-20'),
|
||||||
updatedAt: new Date('2023-02-20'),
|
updatedAt: new Date('2023-02-20'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
yachtId: 3,
|
yachtId: 3,
|
||||||
name: 'Wave Rider',
|
name: 'Wave Rider',
|
||||||
model: 'Ferretti 70',
|
model: 'Ferretti 70',
|
||||||
year: 2019,
|
year: 2019,
|
||||||
length: 70,
|
length: 70,
|
||||||
userId: 2,
|
userId: 2,
|
||||||
createdAt: new Date('2023-03-10'),
|
createdAt: new Date('2023-03-10'),
|
||||||
updatedAt: new Date('2023-03-10'),
|
updatedAt: new Date('2023-03-10'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async findAll(): Promise<Yacht[]> {
|
async findAll(): Promise<Yacht[]> {
|
||||||
return this.yachts;
|
return this.yachts;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(yachtId: number): Promise<Yacht> {
|
async findById(yachtId: number): Promise<Yacht> {
|
||||||
const yacht = this.yachts.find((y) => y.yachtId === yachtId);
|
const yacht = this.yachts.find((y) => y.yachtId === yachtId);
|
||||||
if (!yacht) {
|
if (!yacht) {
|
||||||
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
||||||
}
|
}
|
||||||
return yacht;
|
return yacht;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByUserId(userId: number): Promise<Yacht[]> {
|
async findByUserId(userId: number): Promise<Yacht[]> {
|
||||||
return this.yachts.filter((y) => y.userId === userId);
|
return this.yachts.filter((y) => y.userId === userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(createYachtDto: CreateYachtDto): Promise<Yacht> {
|
async create(createYachtDto: CreateYachtDto): Promise<Yacht> {
|
||||||
const newYacht: Yacht = {
|
const newYacht: Yacht = {
|
||||||
yachtId: this.yachts.length + 1,
|
yachtId: this.yachts.length + 1,
|
||||||
...createYachtDto,
|
...createYachtDto,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
this.yachts.push(newYacht);
|
this.yachts.push(newYacht);
|
||||||
return newYacht;
|
return newYacht;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
yachtId: number,
|
yachtId: number,
|
||||||
updateYachtDto: UpdateYachtDto,
|
updateYachtDto: UpdateYachtDto,
|
||||||
): Promise<Yacht> {
|
): Promise<Yacht> {
|
||||||
const index = this.yachts.findIndex((y) => y.yachtId === yachtId);
|
const index = this.yachts.findIndex((y) => y.yachtId === yachtId);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.yachts[index] = {
|
this.yachts[index] = {
|
||||||
...this.yachts[index],
|
...this.yachts[index],
|
||||||
...updateYachtDto,
|
...updateYachtDto,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.yachts[index];
|
return this.yachts[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(yachtId: number): Promise<void> {
|
async delete(yachtId: number): Promise<void> {
|
||||||
const index = this.yachts.findIndex((y) => y.yachtId === yachtId);
|
const index = this.yachts.findIndex((y) => y.yachtId === yachtId);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
throw new NotFoundException(`Yacht with ID ${yachtId} not found`);
|
||||||
}
|
}
|
||||||
this.yachts.splice(index, 1);
|
this.yachts.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"resolvePackageJsonExports": true,
|
"resolvePackageJsonExports": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"noFallthroughCasesInSwitch": false
|
"noFallthroughCasesInSwitch": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 206 KiB |
BIN
uploads/1st.jpg
|
Before Width: | Height: | Size: 94 KiB |
BIN
uploads/2nd.jpg
|
Before Width: | Height: | Size: 125 KiB |
BIN
uploads/3rd.jpg
|
Before Width: | Height: | Size: 176 KiB |
BIN
uploads/4th.jpg
|
Before Width: | Height: | Size: 35 KiB |
BIN
uploads/6th.jpg
|
Before Width: | Height: | Size: 177 KiB |
BIN
uploads/gal1.jpg
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 134 KiB |
BIN
uploads/gal2.jpg
|
Before Width: | Height: | Size: 133 KiB |
BIN
uploads/gal3.jpg
|
Before Width: | Height: | Size: 167 KiB |
BIN
uploads/gal4.jpg
|
Before Width: | Height: | Size: 176 KiB |
BIN
uploads/gal5.jpg
|
Before Width: | Height: | Size: 166 KiB |
BIN
uploads/gal6.jpg
|
Before Width: | Height: | Size: 134 KiB |
BIN
uploads/gal7.jpg
|
Before Width: | Height: | Size: 167 KiB |
BIN
uploads/gal8.jpg
|
Before Width: | Height: | Size: 176 KiB |
BIN
uploads/gal9.jpg
|
Before Width: | Height: | Size: 166 KiB |