From 12a206ae85c68cbbc618d4676aea680a87efd949 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 28 Mar 2026 04:26:45 +0300 Subject: [PATCH] stable: mapping v1.0 restored and verified --- .../geocoding/entities/place-syria.entity.ts | 4 +- .../api/src/geocoding/geocoding.controller.ts | 6 +++ apps/api/src/geocoding/geocoding.service.ts | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/apps/api/src/geocoding/entities/place-syria.entity.ts b/apps/api/src/geocoding/entities/place-syria.entity.ts index 932d17a..a09828e 100644 --- a/apps/api/src/geocoding/entities/place-syria.entity.ts +++ b/apps/api/src/geocoding/entities/place-syria.entity.ts @@ -11,10 +11,10 @@ export class PlaceSyria { @Column({ type: 'decimal', precision: 11, scale: 8, nullable: true }) longitude: number; - @Column({ nullable: true, unique: true }) + @Column({ nullable: true }) name: string; - @Column({ nullable: true, unique: true }) + @Column({ nullable: true }) name_ar: string; @Column({ nullable: true }) diff --git a/apps/api/src/geocoding/geocoding.controller.ts b/apps/api/src/geocoding/geocoding.controller.ts index 58cb763..30ceb40 100644 --- a/apps/api/src/geocoding/geocoding.controller.ts +++ b/apps/api/src/geocoding/geocoding.controller.ts @@ -38,6 +38,12 @@ export class GeocodingController { return this.geocodingService.addPlace(placeData); } + @Post('upsert-place') + @ApiOperation({ summary: 'Add or Update a location (Automated Scraper)' }) + async upsertPlace(@Body() placeData: any) { + return this.geocodingService.upsertPlace(placeData); + } + @Get('places') @ApiOperation({ summary: 'Get recent user submitted places' }) async getPlaces(@Query('limit') limit?: number) { diff --git a/apps/api/src/geocoding/geocoding.service.ts b/apps/api/src/geocoding/geocoding.service.ts index 8647682..a4b4710 100644 --- a/apps/api/src/geocoding/geocoding.service.ts +++ b/apps/api/src/geocoding/geocoding.service.ts @@ -191,6 +191,49 @@ export class GeocodingService { } } + async upsertPlace(data: Partial) { + try { + if (!data.latitude || !data.longitude || (!data.name && !data.name_ar)) { + throw new HttpException('Missing required fields for upsert', HttpStatus.BAD_REQUEST); + } + + const lng = Number(data.longitude); + const lat = Number(data.latitude); + + // 1. Check for spatial match (within 10 meters) + const existingQuery = ` + SELECT id FROM places_syria + WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2), 4326)) < 10 + LIMIT 1 + `; + const existing = await this.placesRepository.query(existingQuery, [lng, lat]); + + if (existing && existing.length > 0) { + const id = existing[0].id; + this.logger.log(`Spatial match found for ${data.name || data.name_ar} (ID: ${id}). Updating...`); + + await this.placesRepository.update(id, { + name: data.name || undefined, + name_ar: data.name_ar || undefined, + address: data.address || undefined, + category: data.category || undefined, + description: data.description || undefined, + city: data.city || undefined, + neighbourhood: data.neighbourhood || undefined + }); + + return { id, action: 'updated' }; + } + + // 2. No matching place found, create new one + const result = await this.addPlace(data); + return { id: result.id, action: 'created' }; + } catch (error) { + this.logger.error('Upsert failed:', error.message); + throw error; + } + } + async getRecentPlaces(limit: number = 50) { try { return this.placesRepository.find({