nestjs rewrite
This commit is contained in:
56
server/.gitignore
vendored
Normal file
56
server/.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# temp directory
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
4
server/.prettierrc
Normal file
4
server/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
98
server/README.md
Normal file
98
server/README.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<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>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[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 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/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://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#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://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>
|
||||||
|
</p>
|
||||||
|
<!--[](https://opencollective.com/nest#backer)
|
||||||
|
[](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compile and run the project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ npm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ npm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ npm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ npm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
$ npm install -g @nestjs/mau
|
||||||
|
$ 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.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
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.
|
||||||
|
- 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/).
|
||||||
|
- 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).
|
||||||
|
- 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).
|
||||||
|
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.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
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
BIN
server/ems.db
Normal file
BIN
server/ems.db
Normal file
Binary file not shown.
34
server/eslint.config.mjs
Normal file
34
server/eslint.config.mjs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// @ts-check
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: ['eslint.config.mjs'],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.jest,
|
||||||
|
},
|
||||||
|
sourceType: 'commonjs',
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
8
server/nest-cli.json
Normal file
8
server/nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
15089
server/package-lock.json
generated
Normal file
15089
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
86
server/package.json
Normal file
86
server/package.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/axios": "^4.0.0",
|
||||||
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.2",
|
||||||
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/swagger": "^11.2.0",
|
||||||
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.2",
|
||||||
|
"mssql": "^11.0.1",
|
||||||
|
"pg": "^8.16.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"sharp": "^0.34.2",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"typeorm": "^0.3.24"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@swc/cli": "^0.6.0",
|
||||||
|
"@swc/core": "^1.10.7",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/multer": "^1.4.13",
|
||||||
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/supertest": "^6.0.2",
|
||||||
|
"eslint": "^9.18.0",
|
||||||
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
|
"ts-jest": "^29.2.5",
|
||||||
|
"ts-loader": "^9.5.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
22
server/src/app.controller.spec.ts
Normal file
22
server/src/app.controller.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
describe('AppController', () => {
|
||||||
|
let appController: AppController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
appController = app.get<AppController>(AppController);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('root', () => {
|
||||||
|
it('should return "Hello World!"', () => {
|
||||||
|
expect(appController.getHello()).toBe('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
12
server/src/app.controller.ts
Normal file
12
server/src/app.controller.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AppController {
|
||||||
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getHello(): string {
|
||||||
|
return this.appService.getHello();
|
||||||
|
}
|
||||||
|
}
|
86
server/src/app.module.ts
Normal file
86
server/src/app.module.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { FuelModule } from './fuel/fuel.module';
|
||||||
|
import { GeneralModule } from './general/general.module';
|
||||||
|
import { EmsModule } from './ems/ems.module';
|
||||||
|
import { TilesModule } from './tiles/tiles.module';
|
||||||
|
import { GisModule } from './gis/gis.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot(),
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: 'ems.db',
|
||||||
|
synchronize: true,
|
||||||
|
name: 'sqliteConnection'
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'mssql',
|
||||||
|
host: process.env.FUEL_DB_HOST,
|
||||||
|
port: 1433,
|
||||||
|
username: process.env.FUEL_DB_USER,
|
||||||
|
password: process.env.FUEL_DB_PASSWORD,
|
||||||
|
database: 'isFuels',
|
||||||
|
options: {
|
||||||
|
encrypt: false,
|
||||||
|
trustServerCertificate: true
|
||||||
|
},
|
||||||
|
synchronize: false,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
name: 'fuelConnection'
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'mssql',
|
||||||
|
host: process.env.EMS_DB_HOST,
|
||||||
|
port: Number(process.env.EMS_DB_PORT) || 1433,
|
||||||
|
username: process.env.EMS_DB_USER,
|
||||||
|
password: process.env.EMS_DB_PASSWORD,
|
||||||
|
database: process.env.EMS_DB_NAME,
|
||||||
|
options: {
|
||||||
|
encrypt: false,
|
||||||
|
trustServerCertificate: true
|
||||||
|
},
|
||||||
|
synchronize: false,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
name: 'emsConnection'
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'mssql',
|
||||||
|
host: process.env.GENERAL_DB_HOST,
|
||||||
|
port: Number(process.env.GENERAL_DB_PORT) || 1433,
|
||||||
|
username: process.env.GENERAL_DB_USER,
|
||||||
|
password: process.env.GENERAL_DB_PASSWORD,
|
||||||
|
database: process.env.GENERAL_DB_NAME,
|
||||||
|
options: {
|
||||||
|
encrypt: false,
|
||||||
|
trustServerCertificate: true
|
||||||
|
},
|
||||||
|
synchronize: false,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
name: 'generalConnection'
|
||||||
|
}),
|
||||||
|
// TypeOrmModule.forRoot({
|
||||||
|
// type: 'postgres',
|
||||||
|
// host: process.env.PG_HOST,
|
||||||
|
// port: Number(process.env.PG_PORT) || 5432,
|
||||||
|
// username: process.env.PG_USER,
|
||||||
|
// password: process.env.PG_PASSWORD,
|
||||||
|
// database: process.env.PG_DB,
|
||||||
|
// synchronize: false,
|
||||||
|
// autoLoadEntities: true,
|
||||||
|
// name: 'pgConnection'
|
||||||
|
// }),
|
||||||
|
FuelModule,
|
||||||
|
GeneralModule,
|
||||||
|
EmsModule,
|
||||||
|
TilesModule,
|
||||||
|
GisModule,
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
8
server/src/app.service.ts
Normal file
8
server/src/app.service.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
getHello(): string {
|
||||||
|
return 'Hello World!';
|
||||||
|
}
|
||||||
|
}
|
12
server/src/constants/ems.ts
Normal file
12
server/src/constants/ems.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Extent } from "../interfaces/map"
|
||||||
|
|
||||||
|
const epsg3857extent = [
|
||||||
|
-20037508.342789244,
|
||||||
|
-20037508.342789244,
|
||||||
|
20037508.342789244,
|
||||||
|
20037508.342789244
|
||||||
|
] as Extent
|
||||||
|
|
||||||
|
export {
|
||||||
|
epsg3857extent
|
||||||
|
}
|
22
server/src/ems/dto/get-figures.ts
Normal file
22
server/src/ems/dto/get-figures.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"
|
||||||
|
import { IsNumberString, IsOptional } from "class-validator"
|
||||||
|
|
||||||
|
export class GetFiguresDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
city_id: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
offset?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
limit?: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
year: number
|
||||||
|
}
|
19
server/src/ems/dto/get-images.ts
Normal file
19
server/src/ems/dto/get-images.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger"
|
||||||
|
import { IsNumberString, IsOptional } from "class-validator"
|
||||||
|
|
||||||
|
export class GetImagesDTO {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
city_id?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
offset?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
limit?: number
|
||||||
|
}
|
18
server/src/ems/ems.controller.spec.ts
Normal file
18
server/src/ems/ems.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { EmsController } from './ems.controller';
|
||||||
|
|
||||||
|
describe('EmsController', () => {
|
||||||
|
let controller: EmsController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [EmsController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<EmsController>(EmsController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
26
server/src/ems/ems.controller.ts
Normal file
26
server/src/ems/ems.controller.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
|
import { EmsService } from './ems.service';
|
||||||
|
import { GetImagesDTO } from './dto/get-images';
|
||||||
|
import { GetFiguresDTO } from './dto/get-figures';
|
||||||
|
|
||||||
|
@Controller('ems')
|
||||||
|
export class EmsController {
|
||||||
|
constructor(private readonly emsService: EmsService) { }
|
||||||
|
|
||||||
|
@Get('/regions')//✅
|
||||||
|
async getRegions() {
|
||||||
|
return this.emsService.getTypeRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/images')//✅
|
||||||
|
async getImages(@Query() getImagesDTO: GetImagesDTO) {
|
||||||
|
const { city_id, limit, offset } = getImagesDTO
|
||||||
|
return this.emsService.getImages(city_id, offset, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/figures')
|
||||||
|
async getFigures(@Query() getFiguresDTO: GetFiguresDTO) {
|
||||||
|
const { offset, limit, year, city_id } = getFiguresDTO
|
||||||
|
return this.emsService.getFigures(year, city_id, offset, limit)
|
||||||
|
}
|
||||||
|
}
|
9
server/src/ems/ems.module.ts
Normal file
9
server/src/ems/ems.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { EmsController } from './ems.controller';
|
||||||
|
import { EmsService } from './ems.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [EmsController],
|
||||||
|
providers: [EmsService]
|
||||||
|
})
|
||||||
|
export class EmsModule {}
|
18
server/src/ems/ems.service.spec.ts
Normal file
18
server/src/ems/ems.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { EmsService } from './ems.service';
|
||||||
|
|
||||||
|
describe('EmsService', () => {
|
||||||
|
let service: EmsService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [EmsService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<EmsService>(EmsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
40
server/src/ems/ems.service.ts
Normal file
40
server/src/ems/ems.service.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmsService {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource('emsConnection')
|
||||||
|
private dataSource: DataSource
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getTypeRoles(): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM "TypeRoles";
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImages(city_id?: number, offset?: number, limit?: number): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM "images"
|
||||||
|
${city_id ? `WHERE city_id = ${city_id}` : ''}
|
||||||
|
ORDER BY city_id
|
||||||
|
OFFSET ${offset || 0} ROWS
|
||||||
|
FETCH NEXT ${limit || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFigures(year: number, city_id: number, offset?: number, limit?: number): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM figures f
|
||||||
|
JOIN vObjects o ON f.object_id = o.object_id WHERE o.id_city = ${city_id} AND f.year = ${year}
|
||||||
|
ORDER BY f.year
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
22
server/src/fuel/dto/create-expense.ts
Normal file
22
server/src/fuel/dto/create-expense.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsDate, IsNumber, IsUUID } from "class-validator"
|
||||||
|
|
||||||
|
export class CreateExpenseDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_boiler: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, format: 'date-time' })
|
||||||
|
@Type(() => Date)
|
||||||
|
@IsDate()
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
24
server/src/fuel/dto/create-limit.ts
Normal file
24
server/src/fuel/dto/create-limit.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNumber, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateLimitDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_boiler: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
month: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
year: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
26
server/src/fuel/dto/create-transfer.ts
Normal file
26
server/src/fuel/dto/create-transfer.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsDate, IsNumber, IsUUID } from "class-validator"
|
||||||
|
|
||||||
|
export class CreateTransferDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_out: string
|
||||||
|
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_in: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, format: 'date-time' })
|
||||||
|
@Type(() => Date)
|
||||||
|
@IsDate()
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
22
server/src/fuel/dto/expense.ts
Normal file
22
server/src/fuel/dto/expense.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsDate, IsNumber, IsUUID } from "class-validator"
|
||||||
|
|
||||||
|
export class FuelExpenseDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_boiler: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, format: 'date-time' })
|
||||||
|
@Type(() => Date)
|
||||||
|
@IsDate()
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
24
server/src/fuel/dto/limit.ts
Normal file
24
server/src/fuel/dto/limit.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { IsNumber, IsUUID } from "class-validator"
|
||||||
|
|
||||||
|
export class FuelLimitDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_boiler: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
month: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
year: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
26
server/src/fuel/dto/transfer.ts
Normal file
26
server/src/fuel/dto/transfer.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsDate, IsNumber, IsUUID } from "class-validator"
|
||||||
|
|
||||||
|
export class FuelTransferDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_out: string
|
||||||
|
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
@IsUUID()
|
||||||
|
id_in: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_fuel: number
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, format: 'date-time' })
|
||||||
|
@Type(() => Date)
|
||||||
|
@IsDate()
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
value: number
|
||||||
|
}
|
18
server/src/fuel/fuel.controller.spec.ts
Normal file
18
server/src/fuel/fuel.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { FuelController } from './fuel.controller';
|
||||||
|
|
||||||
|
describe('FuelController', () => {
|
||||||
|
let controller: FuelController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [FuelController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<FuelController>(FuelController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
73
server/src/fuel/fuel.controller.ts
Normal file
73
server/src/fuel/fuel.controller.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Body, Controller, Get, Post } from '@nestjs/common'
|
||||||
|
import { FuelService } from './fuel.service'
|
||||||
|
import { CreateExpenseDto } from './dto/create-expense'
|
||||||
|
import { CreateLimitDto } from './dto/create-limit'
|
||||||
|
import { CreateTransferDto } from './dto/create-transfer'
|
||||||
|
import { FuelLimitDto } from './dto/limit'
|
||||||
|
import { ApiOkResponse } from '@nestjs/swagger'
|
||||||
|
import { FuelExpenseDto } from './dto/expense'
|
||||||
|
import { FuelTransferDto } from './dto/transfer'
|
||||||
|
|
||||||
|
@Controller('fuel')
|
||||||
|
export class FuelController {
|
||||||
|
constructor(private readonly fuelService: FuelService) { }
|
||||||
|
|
||||||
|
@Get('/columns')
|
||||||
|
async getColumnDataTypes() {
|
||||||
|
return this.fuelService.getColumnDataTypes('BoilersFuelsExpenses')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Fuel limits
|
||||||
|
@Get('/limits')
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'List of fuel expenses',
|
||||||
|
type: FuelLimitDto,
|
||||||
|
isArray: true,
|
||||||
|
})
|
||||||
|
async getBoilersFuelsLimits(): Promise<FuelLimitDto[]> {
|
||||||
|
return this.fuelService.getBoilersFuelsLimits()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/limits')
|
||||||
|
async addBoilersFuelsLimit(@Body() createLimitDto: CreateLimitDto) {
|
||||||
|
return this.fuelService.addBoilersFuelsLimit(createLimitDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Fuel expenses
|
||||||
|
@Get('/expenses')
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'List of fuel expenses',
|
||||||
|
type: FuelExpenseDto,
|
||||||
|
isArray: true,
|
||||||
|
})
|
||||||
|
async getBoilersFuelsExpenses(): Promise<FuelExpenseDto[]> {
|
||||||
|
return this.fuelService.getBoilersFuelsExpenses()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/expenses')
|
||||||
|
async addBoilersFuelsExpense(@Body() createExpenseDto: CreateExpenseDto) {
|
||||||
|
return this.fuelService.addBoilersFuelsExpense(createExpenseDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Fuel transfer
|
||||||
|
@Get('/transfer')
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'List of fuel expenses',
|
||||||
|
type: FuelTransferDto,
|
||||||
|
isArray: true,
|
||||||
|
})
|
||||||
|
async getFuelsTransfer(): Promise<FuelTransferDto[]> {
|
||||||
|
return this.fuelService.getFuelsTransfer()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/transfer')
|
||||||
|
async addFuelTransfer(@Body() createTransferDto: CreateTransferDto) {
|
||||||
|
return this.fuelService.addFuelsTransfer(createTransferDto)
|
||||||
|
}
|
||||||
|
}
|
9
server/src/fuel/fuel.module.ts
Normal file
9
server/src/fuel/fuel.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { FuelController } from './fuel.controller';
|
||||||
|
import { FuelService } from './fuel.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [FuelController],
|
||||||
|
providers: [FuelService]
|
||||||
|
})
|
||||||
|
export class FuelModule {}
|
18
server/src/fuel/fuel.service.spec.ts
Normal file
18
server/src/fuel/fuel.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { FuelService } from './fuel.service';
|
||||||
|
|
||||||
|
describe('FuelService', () => {
|
||||||
|
let service: FuelService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [FuelService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<FuelService>(FuelService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
68
server/src/fuel/fuel.service.ts
Normal file
68
server/src/fuel/fuel.service.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { CreateExpenseDto } from './dto/create-expense';
|
||||||
|
import { CreateLimitDto } from './dto/create-limit';
|
||||||
|
import { CreateTransferDto } from './dto/create-transfer';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FuelService {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource('fuelConnection') private dataSource: DataSource
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getColumnDataTypes(table_name: string): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT
|
||||||
|
COLUMN_NAME,
|
||||||
|
DATA_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_NAME = '${table_name}'
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBoilersFuelsExpenses(): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT *
|
||||||
|
FROM "BoilersFuelsExpenses";
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBoilersFuelsLimits(): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT *
|
||||||
|
FROM "BoilersFuelsLimits";
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBoilersFuelsExpense(createExpenseDto: CreateExpenseDto): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
INSERT INTO dbo.BoilersFuelsExpenses (id_boiler, id_fuel, date, value) VALUES ($1, $2, $3, $4)
|
||||||
|
`, [createExpenseDto.id_boiler, createExpenseDto.id_fuel, createExpenseDto.date, createExpenseDto.value])
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBoilersFuelsLimit(createLimitDto: CreateLimitDto): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
INSERT INTO dbo.BoilersFuelsLimits (id_boiler, id_fuel, value, month, year) VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`, [createLimitDto.id_boiler, createLimitDto.id_fuel, createLimitDto.value, createLimitDto.month, createLimitDto.year])
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFuelsTransfer(): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM "FuelsTransfer";
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async addFuelsTransfer(createTransferDto: CreateTransferDto): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
INSERT INTO dbo.FuelsTransfer (id_out, id_in, id_fuel, date, value) VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`, [createTransferDto.id_out, createTransferDto.id_in, createTransferDto.id_fuel, createTransferDto.date, createTransferDto.value])
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
24
server/src/general/dto/get-cities.ts
Normal file
24
server/src/general/dto/get-cities.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger"
|
||||||
|
import { IsNumberString, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
export class GetCitiesDTO {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
limit?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
offset?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
search?: string
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
id?: number
|
||||||
|
}
|
19
server/src/general/dto/get-objects.ts
Normal file
19
server/src/general/dto/get-objects.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger"
|
||||||
|
import { IsNumber, IsNumberString, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
export class GetObjectsDTO {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
offset?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
limit?: number
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumberString()
|
||||||
|
@IsOptional()
|
||||||
|
city_id?: number
|
||||||
|
}
|
17
server/src/general/dto/get-tcb-params.ts
Normal file
17
server/src/general/dto/get-tcb-params.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"
|
||||||
|
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
export class GetTCBParamsDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
vtable: string
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
id_city: number
|
||||||
|
}
|
18
server/src/general/general.controller.spec.ts
Normal file
18
server/src/general/general.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GeneralController } from './general.controller';
|
||||||
|
|
||||||
|
describe('GeneralController', () => {
|
||||||
|
let controller: GeneralController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [GeneralController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<GeneralController>(GeneralController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
67
server/src/general/general.controller.ts
Normal file
67
server/src/general/general.controller.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Controller, Get, Param, ParseUUIDPipe, Query } from '@nestjs/common';
|
||||||
|
import { GeneralService } from './general.service';
|
||||||
|
import { GetCitiesDTO } from './dto/get-cities';
|
||||||
|
import { GetObjectsDTO } from './dto/get-objects';
|
||||||
|
|
||||||
|
@Controller('/general')
|
||||||
|
export class GeneralController {
|
||||||
|
constructor(private readonly generalService: GeneralService) { }
|
||||||
|
|
||||||
|
@Get('/regions')//✅
|
||||||
|
async getRegions() {
|
||||||
|
return this.generalService.getRegions()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/districts')//✅
|
||||||
|
async getDistricts(@Query('region_id') region_id: number) {
|
||||||
|
return this.generalService.getDistricts(region_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/cities')//✅
|
||||||
|
async getCities(@Query() getCitiesDTO: GetCitiesDTO) {
|
||||||
|
const { offset, limit, search, id } = getCitiesDTO
|
||||||
|
|
||||||
|
return this.generalService.getCities(offset, limit, search, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/types')//✅
|
||||||
|
async getTypes() {
|
||||||
|
return this.generalService.getTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/objects/all')//✅
|
||||||
|
async getObjects(@Query() getObjectsDTO: GetObjectsDTO) {
|
||||||
|
const { offset, limit, city_id } = getObjectsDTO
|
||||||
|
return this.generalService.getObjects(offset, limit, city_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/objects/list')// ✅
|
||||||
|
async getObjectsList(@Query('city_id') city_id: number, @Query('year') year: number, @Query('planning') planning: number, @Query('type') type?: number) {
|
||||||
|
return this.generalService.getObjectsList(city_id, year, planning, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/objects/by-id/:id')//✅
|
||||||
|
async getObjectById(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||||
|
return this.generalService.getObjectById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/values')// ✅
|
||||||
|
async getValues(@Query('object_id') object_id: string) {
|
||||||
|
return this.generalService.getValuesByObjectId(object_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/params')//✅
|
||||||
|
async getParams(@Query('param_id') param_id: number) {
|
||||||
|
return this.generalService.getParamsById(param_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/params/tcb')// ✅
|
||||||
|
async getTcbParams(@Query('vtable') vtable: string, @Query('id_city') id_city: number, @Query('id') id: number) {
|
||||||
|
return this.generalService.getTCBParams(vtable, id_city, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/search/objects')// ✅
|
||||||
|
async getSearchObjects(@Query('q') q: string, @Query('id_city') id_city: number, @Query('year') year: number) {
|
||||||
|
return this.generalService.getSearchObjects(q, id_city, year)
|
||||||
|
}
|
||||||
|
}
|
9
server/src/general/general.module.ts
Normal file
9
server/src/general/general.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { GeneralController } from './general.controller';
|
||||||
|
import { GeneralService } from './general.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [GeneralController],
|
||||||
|
providers: [GeneralService]
|
||||||
|
})
|
||||||
|
export class GeneralModule {}
|
18
server/src/general/general.service.spec.ts
Normal file
18
server/src/general/general.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GeneralService } from './general.service';
|
||||||
|
|
||||||
|
describe('GeneralService', () => {
|
||||||
|
let service: GeneralService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [GeneralService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<GeneralService>(GeneralService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
250
server/src/general/general.service.ts
Normal file
250
server/src/general/general.service.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GeneralService {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource('emsConnection')
|
||||||
|
private dataSource: DataSource
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getRegions(): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..vRegions;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDistricts(region_id: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT c.*, d.name AS district_name
|
||||||
|
FROM ${generalDatabase}..vCities c
|
||||||
|
JOIN ${generalDatabase}..vDistricts d ON d.id_region = c.id_region AND d.id = c.id_district
|
||||||
|
WHERE c.id_region = ${region_id};
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCities(offset?: number, limit?: number, search?: string, id?: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..Cities
|
||||||
|
${id ? `WHERE id = ${id}` : ''}
|
||||||
|
${search ? `WHERE name LIKE '%${search || ''}%'` : ''}
|
||||||
|
ORDER BY id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTypes(): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..tTypes
|
||||||
|
ORDER BY id
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjects(offset?: number, limit?: number, city_id?: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..vObjects
|
||||||
|
${city_id ? `WHERE id_city = ${Number(city_id)}` : ''}
|
||||||
|
ORDER BY id_object
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GisDB
|
||||||
|
async getObjectsList(city_id: number, year: number, planning: number, type?: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
const gisDatabase = 'New_Gis'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(type ? `
|
||||||
|
WITH cte_split(type_id, split_value, caption_params) AS
|
||||||
|
(
|
||||||
|
-- anchor member
|
||||||
|
SELECT DISTINCT
|
||||||
|
type_id,
|
||||||
|
CAST(LEFT(caption_params, CHARINDEX(',', caption_params + ',') - 1) AS VARCHAR(255)), -- Explicitly casting to VARCHAR
|
||||||
|
STUFF(caption_params, 1, CHARINDEX(',', caption_params + ','), '')
|
||||||
|
FROM ${gisDatabase}..caption_params
|
||||||
|
WHERE city_id = -1 AND user_id = -1
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- recursive member
|
||||||
|
SELECT
|
||||||
|
type_id,
|
||||||
|
CAST(LEFT(caption_params, CHARINDEX(',', caption_params + ',') - 1) AS VARCHAR(255)), -- Explicitly casting to VARCHAR
|
||||||
|
STUFF(caption_params, 1, CHARINDEX(',', caption_params + ','), '')
|
||||||
|
FROM cte_split
|
||||||
|
WHERE caption_params > ''
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
o.object_id,
|
||||||
|
o.type,
|
||||||
|
o.id_city,
|
||||||
|
o.year,
|
||||||
|
o.planning,
|
||||||
|
string_agg(cast(v.value as varchar), ',') as caption
|
||||||
|
FROM ${generalDatabase}..vObjects o
|
||||||
|
JOIN cte_split c ON o.type = c.type_id
|
||||||
|
JOIN ${generalDatabase}..tParameters p ON p.id = split_value
|
||||||
|
LEFT JOIN ${generalDatabase}..tValues v
|
||||||
|
ON
|
||||||
|
v.id_param = split_value
|
||||||
|
AND v.id_object = o.object_id
|
||||||
|
AND (v.date_po IS NULL)
|
||||||
|
AND (v.date_s < DATEFROMPARTS(${Number(year) + 1},01,01))
|
||||||
|
|
||||||
|
WHERE
|
||||||
|
o.id_city = ${city_id}
|
||||||
|
AND o.year = ${year}
|
||||||
|
AND o.type = ${type}
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN TRY_CAST(o.planning AS BIT) IS NOT NULL THEN TRY_CAST(o.planning AS BIT)
|
||||||
|
WHEN o.planning = 'TRUE' THEN 1
|
||||||
|
WHEN o.planning = 'FALSE' THEN 0
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) = ${planning}
|
||||||
|
GROUP BY object_id, type, id_city, year, planning;
|
||||||
|
`:
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
${generalDatabase}..tTypes.id AS id,
|
||||||
|
${generalDatabase}..tTypes.name AS name,
|
||||||
|
COUNT(vo.type) AS count,
|
||||||
|
tr.r,
|
||||||
|
tr.g,
|
||||||
|
tr.b
|
||||||
|
FROM
|
||||||
|
${generalDatabase}..vObjects vo
|
||||||
|
JOIN
|
||||||
|
${generalDatabase}..tTypes ON vo.type = ${generalDatabase}..tTypes.id
|
||||||
|
LEFT JOIN ${gisDatabase}..TypeRoles tr ON tr.id = ${generalDatabase}..tTypes.id
|
||||||
|
WHERE
|
||||||
|
vo.id_city = ${city_id} AND vo.year = ${year}
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN TRY_CAST(vo.planning AS BIT) IS NOT NULL THEN TRY_CAST(vo.planning AS BIT)
|
||||||
|
WHEN vo.planning = 'TRUE' THEN 1
|
||||||
|
WHEN vo.planning = 'FALSE' THEN 0
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) = ${planning}
|
||||||
|
GROUP BY
|
||||||
|
${generalDatabase}..tTypes.id,
|
||||||
|
${generalDatabase}..tTypes.name,
|
||||||
|
tr.r,
|
||||||
|
tr.g,
|
||||||
|
tr.b;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectById(id: string): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
const gisDatabase = 'New_Gis'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..vObjects
|
||||||
|
WHERE id_object = '${id}'
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValuesByObjectId(object_id: string): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT id_object, id_param, CAST(v.value AS varchar(max)) AS value,
|
||||||
|
date_s,
|
||||||
|
date_po,
|
||||||
|
id_user
|
||||||
|
FROM ${generalDatabase}..tValues v
|
||||||
|
JOIN ${generalDatabase}..tParameters p ON v.id_param = p.id
|
||||||
|
WHERE id_object = '${object_id}'
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getParamsById(param_id: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT * FROM ${generalDatabase}..TParameters
|
||||||
|
WHERE id = '${param_id}'
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
tcbParamQuery = (vtable: string, id_city: number) => {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
|
||||||
|
switch (vtable) {
|
||||||
|
case 'vStreets':
|
||||||
|
return `SELECT * FROM ${generalDatabase}..${vtable} WHERE id_city = ${id_city};`
|
||||||
|
case 'vBoilers':
|
||||||
|
return `SELECT * FROM ${generalDatabase}..${vtable} WHERE id_city = ${id_city};`
|
||||||
|
default:
|
||||||
|
return `SELECT * FROM ${generalDatabase}..${vtable};`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTCBParams(vtable: string, id_city: number, id?: number): Promise<any[]> {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
const query = id ? `
|
||||||
|
SELECT * FROM ${generalDatabase}..${vtable} WHERE id = '${id}'
|
||||||
|
` : this.tcbParamQuery(vtable, id_city)
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(query)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSearchObjects(q: string, id_city: number, year: number) {
|
||||||
|
const generalDatabase = 'nGeneral'
|
||||||
|
const gisDatabase = 'New_Gis'
|
||||||
|
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
WITH RankedValues AS (
|
||||||
|
SELECT
|
||||||
|
id_object,
|
||||||
|
date_s,
|
||||||
|
CAST(value AS varchar(max)) AS value,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY id_object ORDER BY date_s DESC) AS rn,
|
||||||
|
o.id_city AS id_city,
|
||||||
|
o.year AS year
|
||||||
|
FROM ${generalDatabase}..tValues
|
||||||
|
JOIN ${generalDatabase}..tObjects o ON o.id = id_object
|
||||||
|
WHERE CAST(value AS varchar(max)) LIKE '%${q}%'
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id_object,
|
||||||
|
date_s,
|
||||||
|
value,
|
||||||
|
id_city,
|
||||||
|
year
|
||||||
|
FROM RankedValues
|
||||||
|
WHERE rn = 1 AND id_city = ${id_city} AND year = ${year};
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
7
server/src/gis/dto/bound.ts
Normal file
7
server/src/gis/dto/bound.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn } from "typeorm"
|
||||||
|
|
||||||
|
// @Entity()
|
||||||
|
// export class Bound {
|
||||||
|
// @PrimaryGeneratedColumn()
|
||||||
|
// id:
|
||||||
|
// }
|
18
server/src/gis/gis.controller.spec.ts
Normal file
18
server/src/gis/gis.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GisController } from './gis.controller';
|
||||||
|
|
||||||
|
describe('GisController', () => {
|
||||||
|
let controller: GisController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [GisController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<GisController>(GisController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
42
server/src/gis/gis.controller.ts
Normal file
42
server/src/gis/gis.controller.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common';
|
||||||
|
import { GisService } from './gis.service';
|
||||||
|
|
||||||
|
@Controller('gis')
|
||||||
|
export class GisController {
|
||||||
|
constructor(private readonly gisService: GisService) { }
|
||||||
|
|
||||||
|
@Get('/type-roles')
|
||||||
|
async getTypeRoles() {
|
||||||
|
return await this.gisService.getTypeRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/bounds/:entity_type')
|
||||||
|
async getBoundsByEntityType(@Param('entity_type') entity_type: 'region' | 'district' | 'city') {
|
||||||
|
return await this.gisService.getBoundsByEntityType(entity_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/bounds/:entity_type/:entity_id')
|
||||||
|
async getBoundsByEntityTypeAndId(@Param('entity_type') entity_type: 'region' | 'district' | 'city', @Param('entity_id', new ParseIntPipe()) entity_id: number) {
|
||||||
|
return await this.gisService.getBoundsByEntityTypeAndId(entity_type, entity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/images/all')
|
||||||
|
async getImages(@Query('offset') offset: number, @Query('limit') limit: number, @Query('city_id') city_id: number) {
|
||||||
|
return await this.gisService.getImages(offset, limit, city_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/figures/all')
|
||||||
|
async getFigures(@Query('offset') offset: number, @Query('limit') limit: number, @Query('year') year: number, @Query('city_id') city_id: number) {
|
||||||
|
return await this.gisService.getFigures(offset, limit, year, city_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/lines/all')
|
||||||
|
async getLines(@Query('year') year: number, @Query('city_id') city_id: number) {
|
||||||
|
return await this.gisService.getLines(year, city_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/regions/borders')
|
||||||
|
async getRegionBorders() {
|
||||||
|
return await this.gisService.getRegionBorders()
|
||||||
|
}
|
||||||
|
}
|
9
server/src/gis/gis.module.ts
Normal file
9
server/src/gis/gis.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { GisController } from './gis.controller';
|
||||||
|
import { GisService } from './gis.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [GisController],
|
||||||
|
providers: [GisService]
|
||||||
|
})
|
||||||
|
export class GisModule {}
|
18
server/src/gis/gis.service.spec.ts
Normal file
18
server/src/gis/gis.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GisService } from './gis.service';
|
||||||
|
|
||||||
|
describe('GisService', () => {
|
||||||
|
let service: GisService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [GisService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<GisService>(GisService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
110
server/src/gis/gis.service.ts
Normal file
110
server/src/gis/gis.service.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GisService {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource('sqliteConnection')
|
||||||
|
private dataSource: DataSource,
|
||||||
|
|
||||||
|
@InjectDataSource('emsConnection')
|
||||||
|
private emsDataSource: DataSource
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getTypeRoles(): Promise<any[]> {
|
||||||
|
const result = await this.emsDataSource.query(`
|
||||||
|
SELECT * FROM New_Gis..TypeRoles;
|
||||||
|
`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBoundsByEntityType(entity_type: 'region' | 'district' | 'city'): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT entity_id, entity_type, geometry FROM bounds
|
||||||
|
WHERE entity_type = $1
|
||||||
|
`, [entity_type])
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
if (result.length > 0) {
|
||||||
|
const geometries = result.map((bound: { id: string, entity_id: number, entity_type: string, geometry: string, published_at: string, deleted_at: string | null }) => {
|
||||||
|
return {
|
||||||
|
...(JSON.parse(bound.geometry)),
|
||||||
|
properties: {
|
||||||
|
id: bound.id,
|
||||||
|
entity_id: bound.entity_id,
|
||||||
|
entity_type: bound.entity_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return geometries
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('not found')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('not found')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBoundsByEntityTypeAndId(entity_type: 'region' | 'district' | 'city', entity_id: number): Promise<any[]> {
|
||||||
|
const result = await this.dataSource.query(`
|
||||||
|
SELECT entity_id, entity_type, geometry FROM bounds
|
||||||
|
WHERE entity_type = $1
|
||||||
|
AND entity_id = $2
|
||||||
|
`, [entity_type, entity_id])
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
if (result.length > 0) {
|
||||||
|
return JSON.parse(result[0].geometry)
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('not found')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('not found')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImages(offset: number, limit: number, city_id: number): Promise<any[]> {
|
||||||
|
const result = await this.emsDataSource.query(`
|
||||||
|
SELECT * FROM images
|
||||||
|
${city_id ? `WHERE city_id = ${city_id}` : ''}
|
||||||
|
ORDER BY city_id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFigures(offset: number, limit: number, year: number, city_id: number): Promise<any[]> {
|
||||||
|
const result = await this.emsDataSource.query(`
|
||||||
|
SELECT * FROM figures f
|
||||||
|
JOIN nGeneral..vObjects o ON f.object_id = o.object_id WHERE o.id_city = ${city_id} AND f.year = ${year}
|
||||||
|
ORDER BY f.year
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLines(year: number, city_id: number): Promise<any[]> {
|
||||||
|
const result = await this.emsDataSource.query(
|
||||||
|
`
|
||||||
|
SELECT * FROM New_Gis..lines l
|
||||||
|
JOIN nGeneral..vObjects o ON l.object_id = o.object_id WHERE o.id_city = ${city_id} AND l.year = ${year};
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRegionBorders(): Promise<any[]> {
|
||||||
|
const result = await this.emsDataSource.query(
|
||||||
|
`
|
||||||
|
SELECT * FROM New_Gis..visual_regions
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
12
server/src/interfaces/map.ts
Normal file
12
server/src/interfaces/map.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface SatelliteMapsProviders {
|
||||||
|
google: 'google';
|
||||||
|
yandex: 'yandex';
|
||||||
|
}
|
||||||
|
export type SatelliteMapsProvider = SatelliteMapsProviders[keyof SatelliteMapsProviders]
|
||||||
|
|
||||||
|
export interface Coordinate {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Extent = [number, number, number, number]
|
23
server/src/main.ts
Normal file
23
server/src/main.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule, {
|
||||||
|
cors: true
|
||||||
|
});
|
||||||
|
app.enableCors()
|
||||||
|
app.useGlobalPipes(new ValidationPipe({ transform: true }))
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('Fuel API')
|
||||||
|
.setDescription('API test')
|
||||||
|
.setVersion('0.1')
|
||||||
|
.addTag('test')
|
||||||
|
.build()
|
||||||
|
const documentFactory = () => SwaggerModule.createDocument(app, config)
|
||||||
|
SwaggerModule.setup('docs', app, documentFactory)
|
||||||
|
|
||||||
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
}
|
||||||
|
bootstrap();
|
52
server/src/tiles/dto/upload.ts
Normal file
52
server/src/tiles/dto/upload.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger"
|
||||||
|
import { IsNumberString } from "class-validator"
|
||||||
|
|
||||||
|
export class UploadDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
extentMinX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
extentMinY: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
extentMaxX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
extentMaxY: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
blX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
blY: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
tlX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
tlY: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
trX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
trY: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
brX: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumberString()
|
||||||
|
brY: number
|
||||||
|
}
|
18
server/src/tiles/tiles.controller.spec.ts
Normal file
18
server/src/tiles/tiles.controller.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { TilesController } from './tiles.controller';
|
||||||
|
|
||||||
|
describe('TilesController', () => {
|
||||||
|
let controller: TilesController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [TilesController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<TilesController>(TilesController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
82
server/src/tiles/tiles.controller.ts
Normal file
82
server/src/tiles/tiles.controller.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Body, Controller, Get, NotFoundException, Param, ParseEnumPipe, ParseIntPipe, Post, StreamableFile, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||||
|
import { TilesService } from './tiles.service';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { createReadStream, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { dirname } from 'node:path';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { UploadDTO } from './dto/upload';
|
||||||
|
|
||||||
|
type TileProvider = 'google' | 'yandex' | 'custom'
|
||||||
|
|
||||||
|
const tileFolder = join(__dirname, '..', '..', '..', 'storage', 'tile_data')
|
||||||
|
const uploadDir = join(__dirname, '..', '..', '..', 'storage', 'temp')
|
||||||
|
|
||||||
|
@Controller('tiles')
|
||||||
|
export class TilesController {
|
||||||
|
constructor(private readonly tileService: TilesService) { }
|
||||||
|
|
||||||
|
@Post('/upload')
|
||||||
|
@UseInterceptors(FileInterceptor('file', {
|
||||||
|
dest: '../../storage/temp'
|
||||||
|
}))
|
||||||
|
async uploadFile(
|
||||||
|
@UploadedFile() file: Express.Multer.File,
|
||||||
|
@Body() body: UploadDTO
|
||||||
|
) {
|
||||||
|
//await this.tileService.processUpload(file, body)
|
||||||
|
return { message: 'Uploaded successfully', path: file.path }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/tile/:provider/:z/:x/:y')//✅
|
||||||
|
async getTile(@Param('provider') provider: TileProvider, @Param('z', new ParseIntPipe()) z: string, @Param('x', new ParseIntPipe()) x: string, @Param('y', new ParseIntPipe()) y: string) {
|
||||||
|
const tilePath = provider === 'custom' ? join(tileFolder, provider, z.toString(), x.toString(), `${y}.png`) : join(tileFolder, provider, z.toString(), x.toString(), `${y}.jpg`)
|
||||||
|
|
||||||
|
if (existsSync(tilePath)) {
|
||||||
|
const file = createReadStream(tilePath)
|
||||||
|
return new StreamableFile(file, {
|
||||||
|
type: 'image/jpeg',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (provider !== 'custom') {
|
||||||
|
try {
|
||||||
|
const tileData = await this.tileService.fetchTileFromAPI(provider, z, x, y)
|
||||||
|
|
||||||
|
mkdirSync(dirname(tilePath), { recursive: true })
|
||||||
|
|
||||||
|
writeFileSync(tilePath, tileData)
|
||||||
|
|
||||||
|
return new StreamableFile(tileData, {
|
||||||
|
type: 'image/jpeg',
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException(`Tile is not generated or not provided`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/static/:city_id')
|
||||||
|
async getStatic(@Param('city_id', new ParseIntPipe()) city_id: number) {
|
||||||
|
const staticFolder = join(__dirname, '..', '..', '..', 'storage', 'static')
|
||||||
|
|
||||||
|
const tilePath1 = join(staticFolder, `${city_id}.jpg`)
|
||||||
|
const tilePath2 = join(staticFolder, `${city_id}.png`)
|
||||||
|
|
||||||
|
if (existsSync(tilePath1)) {
|
||||||
|
const file = createReadStream(tilePath1)
|
||||||
|
return new StreamableFile(file, {
|
||||||
|
type: 'image/jpeg',
|
||||||
|
})
|
||||||
|
} else if (existsSync(tilePath2)) {
|
||||||
|
const file = createReadStream(tilePath2)
|
||||||
|
return new StreamableFile(file, {
|
||||||
|
type: 'image/png',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException(`Static image for city_id = ${city_id} is not provided`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
server/src/tiles/tiles.module.ts
Normal file
9
server/src/tiles/tiles.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TilesController } from './tiles.controller';
|
||||||
|
import { TilesService } from './tiles.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [TilesController],
|
||||||
|
providers: [TilesService]
|
||||||
|
})
|
||||||
|
export class TilesModule {}
|
18
server/src/tiles/tiles.service.spec.ts
Normal file
18
server/src/tiles/tiles.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { TilesService } from './tiles.service';
|
||||||
|
|
||||||
|
describe('TilesService', () => {
|
||||||
|
let service: TilesService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [TilesService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<TilesService>(TilesService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
65
server/src/tiles/tiles.service.ts
Normal file
65
server/src/tiles/tiles.service.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { UploadDTO } from './dto/upload';
|
||||||
|
import { generateTilesForZoomLevel } from './utils/tiles';
|
||||||
|
import { join } from 'path';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
export interface Coordinate {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileFolder = join(__dirname, '..', '..', '..', 'storage', 'tile_data')
|
||||||
|
const uploadDir = join(__dirname, '..', '..', '..', 'storage', 'temp')
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TilesService {
|
||||||
|
async fetchTileFromAPI(provider: string, z: string, x: string, y: string): Promise<Buffer> {
|
||||||
|
const url = provider === 'google'
|
||||||
|
? `https://khms2.google.com/kh/v=984?x=${x}&y=${y}&z=${z}`
|
||||||
|
: `https://core-sat.maps.yandex.net/tiles?l=sat&x=${x}&y=${y}&z=${z}&scale=1&lang=ru_RU`
|
||||||
|
|
||||||
|
const response = await axios.get(url, { responseType: 'arraybuffer' })
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async processUpload(file: Express.Multer.File, body: UploadDTO) {
|
||||||
|
const {
|
||||||
|
extentMinX,
|
||||||
|
extentMinY,
|
||||||
|
extentMaxX,
|
||||||
|
extentMaxY,
|
||||||
|
blX,
|
||||||
|
blY,
|
||||||
|
tlX,
|
||||||
|
tlY,
|
||||||
|
trX,
|
||||||
|
trY,
|
||||||
|
brX,
|
||||||
|
brY
|
||||||
|
} = body
|
||||||
|
|
||||||
|
const bottomLeft: Coordinate = { x: blX, y: blY }
|
||||||
|
const topLeft: Coordinate = { x: tlX, y: tlY }
|
||||||
|
const topRight: Coordinate = { x: trX, y: trY }
|
||||||
|
const bottomRight: Coordinate = { x: brX, y: brY }
|
||||||
|
|
||||||
|
Logger.log(`generating to ${uploadDir} ${tileFolder} ${randomUUID().toString()}`)
|
||||||
|
if (file) {
|
||||||
|
for (let z = 0; z <= 21; z++) {
|
||||||
|
await generateTilesForZoomLevel(
|
||||||
|
uploadDir,
|
||||||
|
tileFolder,
|
||||||
|
file,
|
||||||
|
[extentMinX, extentMinY, extentMaxX, extentMaxY],
|
||||||
|
bottomLeft,
|
||||||
|
topLeft,
|
||||||
|
topRight,
|
||||||
|
bottomRight,
|
||||||
|
z,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
169
server/src/tiles/utils/tiles.ts
Normal file
169
server/src/tiles/utils/tiles.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { Logger } from "@nestjs/common"
|
||||||
|
import { existsSync, mkdirSync } from "fs"
|
||||||
|
import { join } from "path"
|
||||||
|
import * as sharp from 'sharp'
|
||||||
|
import { epsg3857extent } from "src/constants/ems"
|
||||||
|
import { Coordinate, Extent } from "src/interfaces/map"
|
||||||
|
|
||||||
|
function getTilesPerSide(zoom: number) {
|
||||||
|
return Math.pow(2, zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(value: number, min: number, max: number) {
|
||||||
|
return (value - min) / (max - min)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTileIndex(normalized: number, tilesPerSide: number) {
|
||||||
|
return Math.floor(normalized * tilesPerSide)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) {
|
||||||
|
const tilesPerSide = getTilesPerSide(zoom)
|
||||||
|
const minX = extent[0]
|
||||||
|
const minY = extent[1]
|
||||||
|
const maxX = extent[2]
|
||||||
|
const maxY = extent[3]
|
||||||
|
const xNormalized = normalize(x, minX, maxX)
|
||||||
|
const yNormalized = normalize(y, minY, maxY)
|
||||||
|
const tileX = getTileIndex(xNormalized, tilesPerSide)
|
||||||
|
const tileY = getTileIndex(1 - yNormalized, tilesPerSide)
|
||||||
|
return { tileX, tileY }
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRotationAngle(bottomLeft: Coordinate, bottomRight: Coordinate) {
|
||||||
|
const deltaX = bottomRight.x - bottomLeft.x
|
||||||
|
const deltaY = bottomRight.y - bottomLeft.y
|
||||||
|
const angle = -Math.atan2(deltaY, deltaX)
|
||||||
|
return angle
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundUpToNearest(number: number, mod: number) {
|
||||||
|
return Math.floor(number / mod) * mod
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateTilesForZoomLevel(uploadDir: string, tileFolder: string, file: Express.Multer.File, polygonExtent: Extent, bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate, zoomLevel: number) {
|
||||||
|
const angleDegrees = calculateRotationAngle(bottomLeft, bottomRight) * 180 / Math.PI
|
||||||
|
|
||||||
|
const { tileX: blX, tileY: blY } = getGridCellPosition(bottomLeft.x, bottomLeft.y, epsg3857extent, zoomLevel)
|
||||||
|
const { tileX: tlX, tileY: tlY } = getGridCellPosition(topLeft.x, topLeft.y, epsg3857extent, zoomLevel)
|
||||||
|
const { tileX: trX, tileY: trY } = getGridCellPosition(topRight.x, topRight.y, epsg3857extent, zoomLevel)
|
||||||
|
const { tileX: brX, tileY: brY } = getGridCellPosition(bottomRight.x, topRight.y, epsg3857extent, zoomLevel)
|
||||||
|
|
||||||
|
const minX = Math.min(blX, tlX, trX, brX)
|
||||||
|
const maxX = Math.max(blX, tlX, trX, brX)
|
||||||
|
const minY = Math.min(blY, tlY, trY, brY)
|
||||||
|
const maxY = Math.max(blY, tlY, trY, brY)
|
||||||
|
|
||||||
|
const mapWidth = Math.abs(epsg3857extent[0] - epsg3857extent[2])
|
||||||
|
const mapHeight = Math.abs(epsg3857extent[1] - epsg3857extent[3])
|
||||||
|
|
||||||
|
const tilesH = Math.sqrt(Math.pow(4, zoomLevel))
|
||||||
|
const tileWidth = mapWidth / (Math.sqrt(Math.pow(4, zoomLevel)))
|
||||||
|
const tileHeight = mapHeight / (Math.sqrt(Math.pow(4, zoomLevel)))
|
||||||
|
|
||||||
|
|
||||||
|
let minPosX = minX - (tilesH / 2)
|
||||||
|
let maxPosX = maxX - (tilesH / 2) + 1
|
||||||
|
let minPosY = -(minY - (tilesH / 2))
|
||||||
|
let maxPosY = -(maxY - (tilesH / 2) + 1)
|
||||||
|
|
||||||
|
const newMinX = tileWidth * minPosX
|
||||||
|
const newMaxX = tileWidth * maxPosX
|
||||||
|
const newMinY = tileHeight * maxPosY
|
||||||
|
const newMaxY = tileHeight * minPosY
|
||||||
|
|
||||||
|
|
||||||
|
const paddingLeft = Math.abs(polygonExtent[0] - newMinX)
|
||||||
|
const paddingRight = Math.abs(polygonExtent[2] - newMaxX)
|
||||||
|
const paddingTop = Math.abs(polygonExtent[3] - newMaxY)
|
||||||
|
const paddingBottom = Math.abs(polygonExtent[1] - newMinY)
|
||||||
|
|
||||||
|
const pixelWidth = Math.abs(minX - (maxX + 1)) * 256
|
||||||
|
const pixelHeight = Math.abs(minY - (maxY + 1)) * 256
|
||||||
|
|
||||||
|
const width = Math.abs(newMinX - newMaxX)
|
||||||
|
|
||||||
|
try {
|
||||||
|
let perPixel = width / pixelWidth
|
||||||
|
|
||||||
|
// constraint to original image width
|
||||||
|
Logger.log("sharping step 0")
|
||||||
|
|
||||||
|
Logger.log("initializing pixel paddings")
|
||||||
|
const paddingLeftPixel = paddingLeft / perPixel
|
||||||
|
const paddingRightPixel = paddingRight / perPixel
|
||||||
|
const paddingTopPixel = paddingTop / perPixel
|
||||||
|
const paddingBottomPixel = paddingBottom / perPixel
|
||||||
|
|
||||||
|
const boundsWidthPixel = Math.abs(polygonExtent[0] - polygonExtent[2]) / perPixel
|
||||||
|
const boundsHeightPixel = Math.abs(polygonExtent[1] - polygonExtent[3]) / perPixel
|
||||||
|
|
||||||
|
Logger.log("initializing pixel paddings")
|
||||||
|
if (!existsSync(join(tileFolder, 'custom', zoomLevel.toString()))) {
|
||||||
|
mkdirSync(join(tileFolder, 'custom', zoomLevel.toString()), { recursive: true })
|
||||||
|
Logger.log('created folder custom')
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log(`sharping step 1 ${join(uploadDir, file.filename)}`)
|
||||||
|
const initialZoomImage = await sharp(join(uploadDir, file.filename))
|
||||||
|
.rotate(Math.ceil(angleDegrees), {
|
||||||
|
background: '#00000000'
|
||||||
|
})
|
||||||
|
.resize({
|
||||||
|
width: Math.ceil(boundsWidthPixel),
|
||||||
|
height: Math.ceil(boundsHeightPixel),
|
||||||
|
background: '#00000000'
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
top: Math.ceil(paddingTopPixel),
|
||||||
|
left: Math.ceil(paddingLeftPixel),
|
||||||
|
bottom: Math.ceil(paddingBottomPixel),
|
||||||
|
right: Math.ceil(paddingRightPixel),
|
||||||
|
background: '#00000000'
|
||||||
|
})
|
||||||
|
.toFormat('png')
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
|
||||||
|
Logger.log('sharping step 2')
|
||||||
|
if (initialZoomImage) {
|
||||||
|
await sharp(initialZoomImage.data.buffer)
|
||||||
|
.resize({
|
||||||
|
width: roundUpToNearest(Math.ceil(boundsWidthPixel) + Math.ceil(paddingLeftPixel) + Math.ceil(paddingRightPixel), Math.abs(minX - (maxX + 1))),
|
||||||
|
height: roundUpToNearest(Math.ceil(boundsHeightPixel) + Math.ceil(paddingTopPixel) + Math.ceil(paddingBottomPixel), Math.abs(minY - (maxY + 1))),
|
||||||
|
})
|
||||||
|
.toFile(join(tileFolder, 'custom', zoomLevel.toString(), zoomLevel.toString() + '.png'))
|
||||||
|
.then(async (res) => {
|
||||||
|
let left = 0
|
||||||
|
for (let x = minX; x <= maxX; x++) {
|
||||||
|
if (!existsSync(join(tileFolder, 'custom', zoomLevel.toString(), x.toString()))) {
|
||||||
|
mkdirSync(join(tileFolder, 'custom', zoomLevel.toString(), x.toString()), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let top = 0
|
||||||
|
for (let y = minY; y <= maxY; y++) {
|
||||||
|
console.log(`z: ${zoomLevel} x: ${x} y: ${y}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sharp(join(tileFolder, 'custom', zoomLevel.toString(), zoomLevel.toString() + '.png'))
|
||||||
|
.extract({
|
||||||
|
width: res.width / Math.abs(minX - (maxX + 1)),
|
||||||
|
height: res.height / Math.abs(minY - (maxY + 1)),
|
||||||
|
left: left,
|
||||||
|
top: top
|
||||||
|
})
|
||||||
|
.toFile(join(tileFolder, 'custom', zoomLevel.toString(), x.toString(), y.toString() + '.png'))
|
||||||
|
.then(() => {
|
||||||
|
top = top + res.height / Math.abs(minY - (maxY + 1))
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
left = left + res.width / Math.abs(minX - (maxX + 1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
25
server/test/app.e2e-spec.ts
Normal file
25
server/test/app.e2e-spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import * as request from 'supertest';
|
||||||
|
import { App } from 'supertest/types';
|
||||||
|
import { AppModule } from '../src/app.module';
|
||||||
|
|
||||||
|
describe('AppController (e2e)', () => {
|
||||||
|
let app: INestApplication<App>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/ (GET)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get('/')
|
||||||
|
.expect(200)
|
||||||
|
.expect('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
9
server/test/jest-e2e.json
Normal file
9
server/test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testRegex": ".e2e-spec.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
}
|
||||||
|
}
|
4
server/tsconfig.build.json
Normal file
4
server/tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
21
server/tsconfig.json
Normal file
21
server/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "ES2023",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"noFallthroughCasesInSwitch": false
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user