From 538b3aa0dc09be0ca0d46c2894bdd59e43b3cfc4 Mon Sep 17 00:00:00 2001 From: Lenin Chavez <102402199+Hyp3Boy@users.noreply.github.com> Date: Mon, 28 Oct 2024 01:23:11 -0500 Subject: [PATCH] feat: add district name in complaints with only coordinates --- package-lock.json | 53 ++++++++++++++++++++-- package.json | 1 + src/complaints/complaints.controller.ts | 13 ++++++ src/complaints/complaints.module.ts | 6 ++- src/complaints/complaints.service.ts | 32 ++++++++----- src/complaints/dto/create-complaint.dto.ts | 4 -- src/complaints/dto/update-complaint.dto.ts | 4 -- src/complaints/geolocation.service.ts | 53 ++++++++++++++++++++++ src/model/district.entity.ts | 6 +-- 9 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 src/complaints/geolocation.service.ts diff --git a/package-lock.json b/package-lock.json index ec6e00b..1414eaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@nestjs/axios": "^3.1.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", @@ -1719,6 +1720,17 @@ "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", "license": "MIT" }, + "node_modules/@nestjs/axios": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.1.0.tgz", + "integrity": "sha512-CpeK2ickH//ml+H7kX+QPIpeTwER4yedVcw6GPe6Nv58cmKTa0sb+3A3It7ChKD4deW4UKNvZIpYkUk18q78YQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, "node_modules/@nestjs/cli": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz", @@ -3109,9 +3121,19 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3881,7 +3903,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4188,7 +4209,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -5087,6 +5107,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -5160,7 +5200,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -8026,6 +8065,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 8063220..f387a02 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@nestjs/axios": "^3.1.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", diff --git a/src/complaints/complaints.controller.ts b/src/complaints/complaints.controller.ts index 878252c..f4cc8f7 100644 --- a/src/complaints/complaints.controller.ts +++ b/src/complaints/complaints.controller.ts @@ -41,6 +41,19 @@ export class ComplaintsController { } } + @Get('user/:dni') + async findAllComplaintsFromUser(@Param('dni') dni: string) { + try { + const complaints = await this.complaintsService.findAllComplaintsFromUser(dni); + if (!complaints) { + throw new NotFoundException(`Complaints with DNI ${dni} not found`); + } + return complaints; + } catch (error) { + throw new NotFoundException(error.message); + } + } + @Patch(':id') async update(@Param('id') id: string, @Body() updateComplaintDto: UpdateComplaintDto) { try { diff --git a/src/complaints/complaints.module.ts b/src/complaints/complaints.module.ts index acdc9b9..981093a 100644 --- a/src/complaints/complaints.module.ts +++ b/src/complaints/complaints.module.ts @@ -8,10 +8,12 @@ import { ComplaintCategory } from 'src/model/complaint_category.entity'; import { District } from 'src/model/district.entity'; import { ComplaintState } from 'src/model/complaint_state.entity'; import { ComplaintStateService } from './complaints-state.service'; +import { GeolocationService } from './geolocation.service'; +import { HttpModule } from '@nestjs/axios'; @Module({ - imports: [TypeOrmModule.forFeature([Complaint, User, ComplaintCategory, District, ComplaintState])], + imports: [HttpModule, TypeOrmModule.forFeature([Complaint, User, ComplaintCategory, District, ComplaintState])], controllers: [ComplaintsController], - providers: [ComplaintsService, ComplaintStateService], + providers: [ComplaintsService, ComplaintStateService, GeolocationService], }) export class ComplaintsModule {} diff --git a/src/complaints/complaints.service.ts b/src/complaints/complaints.service.ts index b8c62f6..f1d99e6 100644 --- a/src/complaints/complaints.service.ts +++ b/src/complaints/complaints.service.ts @@ -6,8 +6,7 @@ import { Complaint } from 'src/model/complaint.entity'; import { Repository } from 'typeorm'; import { User } from 'src/model/user.entity'; import { ComplaintCategory } from 'src/model/complaint_category.entity'; -import { District } from 'src/model/district.entity'; - +import { GeolocationService } from './geolocation.service'; @Injectable() export class ComplaintsService { constructor( @@ -17,8 +16,7 @@ export class ComplaintsService { private readonly userRepository: Repository, @InjectRepository(ComplaintCategory) private readonly categoryRepository: Repository, - @InjectRepository(District) - private readonly districtRepository: Repository, + private readonly geolocationService: GeolocationService, ) {} async findAllComplaints() { @@ -34,11 +32,25 @@ export class ComplaintsService { }); } + async findAllComplaintsFromUser(dni: string): Promise { + return await this.complaintRepository.find({ + relations: [ + 'user', // Incluye la relación con el usuario + 'category', // Incluye la relación con la categoría de quejas + 'district', // Incluye la relación con el distrito + 'complaints_image', // Incluye las imágenes de la queja + 'comments', // Incluye los comentarios + 'complaintState', // Incluye el estado de la queja + ], + where: { user: { dni } }, + }); + } + async create(createComplaintDto: CreateComplaintDto): Promise { // Convertir latitude y longitude a un string tipo "point" para la base de datos createComplaintDto.created_at = new Date(createComplaintDto.created_at); createComplaintDto.updated_at = new Date(createComplaintDto.updated_at); - const { latitude, longitude, userId, categoryId, districtId } = createComplaintDto; + const { latitude, longitude, userId, categoryId } = createComplaintDto; const formattedUbication = `(${latitude}, ${longitude})`; const user = await this.userRepository.findOne({ where: { dni: userId } }); @@ -51,9 +63,9 @@ export class ComplaintsService { throw new Error(`Category with ID ${categoryId} not found`); } - const district = await this.districtRepository.findOne({ where: { id: districtId } }); + const district = await this.geolocationService.findOrCreateDistrict(latitude, longitude); if (!district) { - throw new Error(`District with ID ${districtId} not found`); + throw new Error(`District with ID ${district} not found`); } // Crear la entidad con las relaciones utilizando objetos parciales @@ -84,7 +96,7 @@ export class ComplaintsService { } // Extrae la información del DTO - const { latitude, longitude, categoryId, districtId } = updateComplaintDto; + const { latitude, longitude, categoryId } = updateComplaintDto; // Si hay coordenadas nuevas, conviértalas a formato "point" if (latitude !== undefined && longitude !== undefined) { @@ -98,10 +110,6 @@ export class ComplaintsService { complaint.category = { id: categoryId } as ComplaintCategory; } - if (districtId !== undefined) { - complaint.district = { id: districtId } as District; - } - if (updateComplaintDto.description !== undefined) { complaint.description = updateComplaintDto.description; } diff --git a/src/complaints/dto/create-complaint.dto.ts b/src/complaints/dto/create-complaint.dto.ts index 207fb2e..5c7bc99 100644 --- a/src/complaints/dto/create-complaint.dto.ts +++ b/src/complaints/dto/create-complaint.dto.ts @@ -26,10 +26,6 @@ export class CreateComplaintDto { @IsNumber() categoryId: number; - @IsNotEmpty() - @IsNumber() - districtId: number; - @IsNotEmpty() @IsDateString() created_at: Date | string; diff --git a/src/complaints/dto/update-complaint.dto.ts b/src/complaints/dto/update-complaint.dto.ts index 98635a7..6c4550d 100644 --- a/src/complaints/dto/update-complaint.dto.ts +++ b/src/complaints/dto/update-complaint.dto.ts @@ -16,8 +16,4 @@ export class UpdateComplaintDto { @IsOptional() @IsNumber() categoryId?: number; - - @IsOptional() - @IsNumber() - districtId?: number; } diff --git a/src/complaints/geolocation.service.ts b/src/complaints/geolocation.service.ts new file mode 100644 index 0000000..b6348c3 --- /dev/null +++ b/src/complaints/geolocation.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { District } from 'src/model/district.entity'; +import { firstValueFrom } from 'rxjs'; +import { HttpService } from '@nestjs/axios'; + +@Injectable() +export class GeolocationService { + constructor( + private readonly httpService: HttpService, + @InjectRepository(District) + private readonly districtRepository: Repository, + ) {} + + async findOrCreateDistrict(latitude: number, longitude: number): Promise { + // Llamada a la API de Google para obtener la información de ubicación + const response = await firstValueFrom( + this.httpService.get(`https://maps.googleapis.com/maps/api/geocode/json`, { + params: { + latlng: `${latitude},${longitude}`, + key: process.env.GOOGLE_API_KEY, + }, + }), + ); + + const results = response.data.results; + const districtName = this.extractDistrictFromResults(results); + + // Verificar si el distrito ya existe en la base de datos + let district = await this.districtRepository.findOne({ where: { name: districtName } }); + + if (!district) { + // Si el distrito no existe, créalo y guárdalo + district = this.districtRepository.create({ name: districtName }); + await this.districtRepository.save(district); + } + + return district; + } + + private extractDistrictFromResults(results: any[]): string { + // Busca en los resultados un objeto que tenga el tipo 'locality' en su array de types + const districtResult = results.find((result) => result.types.includes('locality') && result.types.includes('political')); + + // Si encuentra el distrito, devuelve su nombre completo; de lo contrario, devuelve null + if (districtResult) { + const districtComponent = districtResult.address_components.find((component) => component.types.includes('locality')); + return districtComponent ? districtComponent.long_name : null; + } + return null; + } +} diff --git a/src/model/district.entity.ts b/src/model/district.entity.ts index 4f4402b..aa2f95d 100644 --- a/src/model/district.entity.ts +++ b/src/model/district.entity.ts @@ -1,11 +1,9 @@ -import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { Entity, OneToMany, PrimaryColumn } from 'typeorm'; import { Complaint } from './complaint.entity'; @Entity({ name: 'district' }) export class District { - @PrimaryGeneratedColumn() - id: number; - @Column({ type: 'varchar', length: 255 }) + @PrimaryColumn({ type: 'varchar', length: 255 }) name: string; @OneToMany(() => Complaint, (complaint) => complaint.district)