feat: implement billing, usage tracking, and administrative geocoding features in API with infrastructure updates

This commit is contained in:
Hamza-Ayed
2026-07-28 04:43:23 +03:00
parent 2f7c2066f7
commit dc9d546733
133 changed files with 3599 additions and 356 deletions
+2 -1
View File
@@ -12,6 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AdminBoundariesService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminBoundariesService = void 0;
const common_1 = require("@nestjs/common");
@@ -132,6 +133,6 @@ exports.AdminBoundariesService = AdminBoundariesService;
exports.AdminBoundariesService = AdminBoundariesService = AdminBoundariesService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
__metadata("design:paramtypes", [typeorm_2.Repository])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object])
], AdminBoundariesService);
//# sourceMappingURL=admin-boundaries.service.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"admin-boundaries.service.js","sourceRoot":"","sources":["../../src/geocoding/admin-boundaries.service.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"admin-boundaries.service.js","sourceRoot":"","sources":["../../src/geocoding/admin-boundaries.service.ts"],"names":[],"mappings":"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"}
+2 -4
View File
@@ -15,6 +15,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var AdministrativeLinkingService_1;
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdministrativeLinkingService = void 0;
const common_1 = require("@nestjs/common");
@@ -277,9 +278,6 @@ exports.AdministrativeLinkingService = AdministrativeLinkingService = Administra
__param(1, (0, typeorm_1.InjectRepository)(neighborhood_point_entity_1.NeighborhoodPoint)),
__param(2, (0, typeorm_1.InjectRepository)(neighborhood_polygon_entity_1.NeighborhoodPolygon)),
__param(3, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
__metadata("design:paramtypes", [typeorm_2.DataSource,
typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.DataSource !== "undefined" && typeorm_2.DataSource) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, typeof (_c = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _c : Object, typeof (_d = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _d : Object])
], AdministrativeLinkingService);
//# sourceMappingURL=administrative-linking.service.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
import { BasePlace } from './base-place.entity';
export declare enum CandidateStatus {
PENDING = "PENDING",
APPROVED = "APPROVED",
REJECTED = "REJECTED"
}
export declare enum CountryCode {
JORDAN = "JORDAN",
SYRIA = "SYRIA",
EGYPT = "EGYPT"
}
export declare class MapCandidate extends BasePlace {
status: CandidateStatus;
submittedBy: string;
rejectionReason: string;
country: CountryCode;
metadata: any;
verified_name: string;
confidence_score: number;
verification_metadata: any;
}
@@ -0,0 +1,84 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapCandidate = exports.CountryCode = exports.CandidateStatus = void 0;
const typeorm_1 = require("typeorm");
const base_place_entity_1 = require("./base-place.entity");
var CandidateStatus;
(function (CandidateStatus) {
CandidateStatus["PENDING"] = "PENDING";
CandidateStatus["APPROVED"] = "APPROVED";
CandidateStatus["REJECTED"] = "REJECTED";
})(CandidateStatus || (exports.CandidateStatus = CandidateStatus = {}));
var CountryCode;
(function (CountryCode) {
CountryCode["JORDAN"] = "JORDAN";
CountryCode["SYRIA"] = "SYRIA";
CountryCode["EGYPT"] = "EGYPT";
})(CountryCode || (exports.CountryCode = CountryCode = {}));
let MapCandidate = class MapCandidate extends base_place_entity_1.BasePlace {
status;
submittedBy;
rejectionReason;
country;
metadata;
verified_name;
confidence_score;
verification_metadata;
};
exports.MapCandidate = MapCandidate;
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: CandidateStatus,
default: CandidateStatus.PENDING
}),
(0, typeorm_1.Index)(),
__metadata("design:type", String)
], MapCandidate.prototype, "status", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
(0, typeorm_1.Index)(),
__metadata("design:type", String)
], MapCandidate.prototype, "submittedBy", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
__metadata("design:type", String)
], MapCandidate.prototype, "rejectionReason", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: CountryCode,
default: CountryCode.JORDAN
}),
(0, typeorm_1.Index)(),
__metadata("design:type", String)
], MapCandidate.prototype, "country", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
__metadata("design:type", Object)
], MapCandidate.prototype, "metadata", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], MapCandidate.prototype, "verified_name", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
__metadata("design:type", Number)
], MapCandidate.prototype, "confidence_score", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
__metadata("design:type", Object)
], MapCandidate.prototype, "verification_metadata", void 0);
exports.MapCandidate = MapCandidate = __decorate([
(0, typeorm_1.Entity)('map_candidates')
], MapCandidate);
//# sourceMappingURL=map-candidate.entity.js.map
@@ -0,0 +1 @@
{"version":3,"file":"map-candidate.entity.js","sourceRoot":"","sources":["../../../src/geocoding/entities/map-candidate.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAgD;AAChD,2DAAgD;AAEhD,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,sCAAmB,CAAA;IACnB,wCAAqB,CAAA;IACrB,wCAAqB,CAAA;AACvB,CAAC,EAJW,eAAe,+BAAf,eAAe,QAI1B;AAED,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,gCAAiB,CAAA;IACjB,8BAAe,CAAA;IACf,8BAAe,CAAA;AACjB,CAAC,EAJW,WAAW,2BAAX,WAAW,QAItB;AAGM,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,6BAAS;IAOzC,MAAM,CAAkB;IAIxB,WAAW,CAAS;IAGpB,eAAe,CAAS;IAQxB,OAAO,CAAc;IAGrB,QAAQ,CAAM;IAGd,aAAa,CAAS;IAGtB,gBAAgB,CAAS;IAGzB,qBAAqB,CAAM;CAC5B,CAAA;AAnCY,oCAAY;AAOvB;IANC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,eAAe,CAAC,OAAO;KACjC,CAAC;IACD,IAAA,eAAK,GAAE;;4CACgB;AAIxB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,eAAK,GAAE;;iDACY;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACjB;AAQxB;IANC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,WAAW,CAAC,MAAM;KAC5B,CAAC;IACD,IAAA,eAAK,GAAE;;6CACa;AAGrB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CAC5B;AAGd;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACL;AAGtB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;sDACX;AAGzB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2DACf;uBAlChB,YAAY;IADxB,IAAA,gBAAM,EAAC,gBAAgB,CAAC;GACZ,YAAY,CAmCxB"}
+2 -1
View File
@@ -12,6 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var GeocodingInitService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeocodingInitService = void 0;
const common_1 = require("@nestjs/common");
@@ -99,6 +100,6 @@ exports.GeocodingInitService = GeocodingInitService;
exports.GeocodingInitService = GeocodingInitService = GeocodingInitService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria)),
__metadata("design:paramtypes", [typeorm_2.Repository])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object])
], GeocodingInitService);
//# sourceMappingURL=geocoding-init.service.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"geocoding-init.service.js","sourceRoot":"","sources":["../../src/geocoding/geocoding-init.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAkE;AAClE,6CAAmD;AACnD,qCAAqC;AACrC,sEAA2D;AAGpD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAKZ;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEhE,YAEmB,IAA4B;QAA5B,SAAI,GAAJ,IAAI,CAAwB;IAC5C,CAAC;IAEJ,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QACnE,IAAI,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACjE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAGjE,MAAM,MAAM,GAAG,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;YACjE,MAAM,aAAa,GAAG;gBACpB,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,gBAAgB,EAAE;gBACjD,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,aAAa,EAAE;gBAC9C,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,iBAAiB,EAAE;gBAClD,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,iBAAiB,EAAE;aACpD,CAAC;YAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;wFAGwD,KAAK,wBAAwB,OAAO,CAAC,GAAG;8BAClG,KAAK,kBAAkB,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,GAAG;;;WAGvE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAGD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;OAiBrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,+FAA+F,CAAC,CAAC;YACvH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,6FAA6F,CAAC,CAAC;YAGrH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,qIAAqI,CAAC,CAAC;YAC7J,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mIAAmI,CAAC,CAAC;YAE3J,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAC;QACjH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;CACF,CAAA;AA5FY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,+BAAU,CAAC,CAAA;qCACN,oBAAU;GALxB,oBAAoB,CA4FhC"}
{"version":3,"file":"geocoding-init.service.js","sourceRoot":"","sources":["../../src/geocoding/geocoding-init.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAkE;AAClE,6CAAmD;AACnD,qCAAqC;AACrC,sEAA2D;AAGpD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAKZ;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEhE,YAEmB,IAA4B;QAA5B,SAAI,GAAJ,IAAI,CAAwB;IAC5C,CAAC;IAEJ,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QACnE,IAAI,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACjE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAGjE,MAAM,MAAM,GAAG,CAAC,eAAe,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;YACjE,MAAM,aAAa,GAAG;gBACpB,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,gBAAgB,EAAE;gBACjD,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,aAAa,EAAE;gBAC9C,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,iBAAiB,EAAE;gBAClD,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,iBAAiB,EAAE;aACpD,CAAC;YAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;wFAGwD,KAAK,wBAAwB,OAAO,CAAC,GAAG;8BAClG,KAAK,kBAAkB,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,GAAG;;;WAGvE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAGD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;OAiBrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;OAKrB,CAAC,CAAC;YAGH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,+FAA+F,CAAC,CAAC;YACvH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,6FAA6F,CAAC,CAAC;YAGrH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,qIAAqI,CAAC,CAAC;YAC7J,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,mIAAmI,CAAC,CAAC;YAE3J,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAC;QACjH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;CACF,CAAA;AA5FY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,+BAAU,CAAC,CAAA;yDACN,oBAAU,oBAAV,oBAAU;GALxB,oBAAoB,CA4FhC"}
+19 -23
View File
@@ -2,6 +2,7 @@ import { GeocodingService } from './geocoding.service';
import { AdminBoundariesService } from './admin-boundaries.service';
import { JordanResearchService } from './jordan-research.service';
import { AdministrativeLinkingService } from './administrative-linking.service';
import { MapRefinementService } from './map-refinement.service';
import { SearchQueryDto } from './dto/search-query.dto';
import { ReverseGeocodeDto } from './dto/reverse-geocode.dto';
export declare class GeocodingController {
@@ -9,38 +10,33 @@ export declare class GeocodingController {
private readonly adminBoundariesService;
private readonly jordanResearchService;
private readonly adminLinkingService;
constructor(geocodingService: GeocodingService, adminBoundariesService: AdminBoundariesService, jordanResearchService: JordanResearchService, adminLinkingService: AdministrativeLinkingService);
private readonly refinementService;
constructor(geocodingService: GeocodingService, adminBoundariesService: AdminBoundariesService, jordanResearchService: JordanResearchService, adminLinkingService: AdministrativeLinkingService, refinementService: MapRefinementService);
search(queryDto: SearchQueryDto): Promise<{
results: any;
source: string;
did_you_mean?: undefined;
} | {
results: never[];
did_you_mean: any;
source?: undefined;
} | {
results: any[];
source?: undefined;
did_you_mean?: undefined;
}>;
reverse(reverseDto: ReverseGeocodeDto): Promise<any>;
addPlace(placeData: any): Promise<{
latitude: number;
longitude: number;
location: string;
id: number;
name: string;
name_ar: string;
name_en: string;
address: string;
category: string;
neighbourhood: string;
city: string;
description: string;
created_at: Date;
autocomplete(q: string, country?: string): Promise<{
results: any;
source: string;
governorate_id: number;
district_id: number;
sub_district_id: number;
neighborhood_id: number;
} | {
results: any;
source?: undefined;
}>;
reverse(reverseDto: ReverseGeocodeDto): Promise<any[]>;
addPlace(req: any, placeData: any): Promise<import("./entities/map-candidate.entity").MapCandidate>;
deletePlace(country: string, name?: string, id?: string): Promise<{
success: boolean;
affected: number | null | undefined;
affected: any;
}>;
upsertPlace(placeData: any): Promise<{
id: any;
@@ -54,7 +50,7 @@ export declare class GeocodingController {
created: number;
updated: number;
}>;
getPlaces(limit?: string): Promise<(import("./entities/place-syria.entity").PlaceSyria | import("./entities/place-jordan.entity").PlaceJordan | import("./entities/place-egypt.entity").PlaceEgypt)[]>;
getPlaces(limit?: string): Promise<any[]>;
getGeoJSON(): Promise<{
type: string;
features: any;
@@ -72,7 +68,7 @@ export declare class GeocodingController {
osm: any;
overture: any;
wikidata: any;
static_files: {};
static_files: any;
hdx: string;
gadm: string;
};
+37 -7
View File
@@ -19,7 +19,9 @@ const geocoding_service_1 = require("./geocoding.service");
const admin_boundaries_service_1 = require("./admin-boundaries.service");
const jordan_research_service_1 = require("./jordan-research.service");
const administrative_linking_service_1 = require("./administrative-linking.service");
const map_refinement_service_1 = require("./map-refinement.service");
const api_key_guard_1 = require("../common/guards/api-key.guard");
const admin_guard_1 = require("../common/guards/admin.guard");
const rate_limiter_guard_1 = require("../common/guards/rate-limiter.guard");
const search_query_dto_1 = require("./dto/search-query.dto");
const reverse_geocode_dto_1 = require("./dto/reverse-geocode.dto");
@@ -28,22 +30,30 @@ let GeocodingController = class GeocodingController {
adminBoundariesService;
jordanResearchService;
adminLinkingService;
constructor(geocodingService, adminBoundariesService, jordanResearchService, adminLinkingService) {
refinementService;
constructor(geocodingService, adminBoundariesService, jordanResearchService, adminLinkingService, refinementService) {
this.geocodingService = geocodingService;
this.adminBoundariesService = adminBoundariesService;
this.jordanResearchService = jordanResearchService;
this.adminLinkingService = adminLinkingService;
this.refinementService = refinementService;
}
async search(queryDto) {
const { q, lat, lng, radius, country } = queryDto;
return this.geocodingService.searchPlaces(q, lat, lng, radius, country);
}
async autocomplete(q, country) {
if (!q || q.trim().length < 2)
return { results: [] };
return this.geocodingService.autocomplete(q, country);
}
async reverse(reverseDto) {
const { lat, lng } = reverseDto;
return this.geocodingService.reverseGeocode(lat, lng);
}
async addPlace(placeData) {
return this.geocodingService.addPlace(placeData);
async addPlace(req, placeData) {
const userId = req.user?.uid || req.tenant?.id || 'api_key_user';
return this.refinementService.suggestPlace(placeData, userId);
}
async deletePlace(country, name, id) {
if (id) {
@@ -91,6 +101,17 @@ __decorate([
__metadata("design:paramtypes", [search_query_dto_1.SearchQueryDto]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "search", null);
__decorate([
(0, common_1.Get)('autocomplete'),
(0, swagger_1.ApiOperation)({ summary: 'Fast autocomplete for live typing' }),
(0, swagger_1.ApiQuery)({ name: 'q', required: true }),
(0, swagger_1.ApiQuery)({ name: 'country', required: false }),
__param(0, (0, common_1.Query)('q')),
__param(1, (0, common_1.Query)('country')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "autocomplete", null);
__decorate([
(0, common_1.Get)('reverse'),
(0, swagger_1.ApiOperation)({ summary: 'Reverse Geocoding (Lat/Lng to Address)' }),
@@ -101,14 +122,16 @@ __decorate([
], GeocodingController.prototype, "reverse", null);
__decorate([
(0, common_1.Post)('places'),
(0, swagger_1.ApiOperation)({ summary: 'Add a new location (User Submitted)' }),
__param(0, (0, common_1.Body)()),
(0, swagger_1.ApiOperation)({ summary: 'Add a new location (User Submitted - Goes to Audit)' }),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], GeocodingController.prototype, "addPlace", null);
__decorate([
(0, common_1.Delete)('places'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Delete a place by name or ID' }),
(0, swagger_1.ApiQuery)({ name: 'name', required: false }),
(0, swagger_1.ApiQuery)({ name: 'id', required: false, type: Number }),
@@ -122,6 +145,7 @@ __decorate([
], GeocodingController.prototype, "deletePlace", null);
__decorate([
(0, common_1.Post)('upsert-place'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Add or Update a location (Automated Scraper)' }),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
@@ -130,6 +154,7 @@ __decorate([
], GeocodingController.prototype, "upsertPlace", null);
__decorate([
(0, common_1.Post)('upsert-batch'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Add or Update multiple locations in bulk' }),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
@@ -153,6 +178,7 @@ __decorate([
], GeocodingController.prototype, "getGeoJSON", null);
__decorate([
(0, common_1.Post)('import-boundaries'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Import administrative boundaries from a local GeoJSON file on the server' }),
(0, swagger_1.ApiQuery)({ name: 'country', required: true }),
(0, swagger_1.ApiQuery)({ name: 'filePath', required: true }),
@@ -171,6 +197,7 @@ __decorate([
], GeocodingController.prototype, "zarqaResearch", null);
__decorate([
(0, common_1.Post)('admin/sync-neighborhoods'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Sync neighborhood points from OSM for a bbox' }),
(0, swagger_1.ApiQuery)({ name: 'bbox', required: false }),
(0, swagger_1.ApiQuery)({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }),
@@ -182,6 +209,7 @@ __decorate([
], GeocodingController.prototype, "syncNeighborhoods", null);
__decorate([
(0, common_1.Post)('admin/generate-voronoi'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Generate Voronoi polygons for neighborhoods' }),
(0, swagger_1.ApiQuery)({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }),
__param(0, (0, common_1.Query)('country')),
@@ -191,6 +219,7 @@ __decorate([
], GeocodingController.prototype, "generateVoronoi", null);
__decorate([
(0, common_1.Post)('admin/link-places'),
(0, common_1.UseGuards)(admin_guard_1.AdminGuard),
(0, swagger_1.ApiOperation)({ summary: 'Link places to administrative hierarchy' }),
(0, swagger_1.ApiQuery)({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }),
__param(0, (0, common_1.Query)('country')),
@@ -206,6 +235,7 @@ exports.GeocodingController = GeocodingController = __decorate([
__metadata("design:paramtypes", [geocoding_service_1.GeocodingService,
admin_boundaries_service_1.AdminBoundariesService,
jordan_research_service_1.JordanResearchService,
administrative_linking_service_1.AdministrativeLinkingService])
administrative_linking_service_1.AdministrativeLinkingService,
map_refinement_service_1.MapRefinementService])
], GeocodingController);
//# sourceMappingURL=geocoding.controller.js.map
File diff suppressed because one or more lines are too long
+11 -4
View File
@@ -56,8 +56,12 @@ const jordan_research_service_1 = require("./jordan-research.service");
const administrative_linking_service_1 = require("./administrative-linking.service");
const neighborhood_point_entity_1 = require("./entities/neighborhood-point.entity");
const neighborhood_polygon_entity_1 = require("./entities/neighborhood-polygon.entity");
const map_candidate_entity_1 = require("./entities/map-candidate.entity");
const map_refinement_service_1 = require("./map-refinement.service");
const map_refinement_controller_1 = require("./map-refinement.controller");
const cache_manager_1 = require("@nestjs/cache-manager");
const redisStore = __importStar(require("cache-manager-redis-store"));
const index_refresh_service_1 = require("./index-refresh.service");
let GeocodingModule = class GeocodingModule {
};
exports.GeocodingModule = GeocodingModule;
@@ -72,7 +76,8 @@ exports.GeocodingModule = GeocodingModule = __decorate([
osm_point_with_area_entity_1.OsmPointWithArea,
admin_boundary_entity_1.AdminBoundary,
neighborhood_point_entity_1.NeighborhoodPoint,
neighborhood_polygon_entity_1.NeighborhoodPolygon
neighborhood_polygon_entity_1.NeighborhoodPolygon,
map_candidate_entity_1.MapCandidate
]),
cache_manager_1.CacheModule.register({
store: redisStore,
@@ -81,15 +86,17 @@ exports.GeocodingModule = GeocodingModule = __decorate([
ttl: 3600,
}),
],
controllers: [geocoding_controller_1.GeocodingController],
controllers: [geocoding_controller_1.GeocodingController, map_refinement_controller_1.MapRefinementController],
providers: [
geocoding_service_1.GeocodingService,
geocoding_init_service_1.GeocodingInitService,
admin_boundaries_service_1.AdminBoundariesService,
jordan_research_service_1.JordanResearchService,
administrative_linking_service_1.AdministrativeLinkingService
administrative_linking_service_1.AdministrativeLinkingService,
map_refinement_service_1.MapRefinementService,
index_refresh_service_1.IndexRefreshService
],
exports: [geocoding_service_1.GeocodingService],
exports: [geocoding_service_1.GeocodingService, map_refinement_service_1.MapRefinementService],
})
], GeocodingModule);
//# sourceMappingURL=geocoding.module.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"geocoding.module.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,2DAAuD;AACvD,qEAAgE;AAChE,iEAA6D;AAC7D,sEAA2D;AAC3D,wEAA6D;AAC7D,sEAA2D;AAC3D,gEAAqD;AACrD,sFAAyE;AACzE,4EAAiE;AACjE,yEAAoE;AACpE,uEAAkE;AAClE,qFAAgF;AAChF,oFAAyE;AACzE,wFAA6E;AAC7E,yDAAoD;AACpD,sEAAwD;AA+BjD,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IA7B3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC;gBACvB,+BAAU;gBACV,iCAAW;gBACX,+BAAU;gBACV,yBAAO;gBACP,6CAAgB;gBAChB,qCAAa;gBACb,6CAAiB;gBACjB,iDAAmB;aACpB,CAAC;YACF,2BAAW,CAAC,QAAQ,CAAC;gBACnB,KAAK,EAAE,UAAU;gBACjB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW;gBAC3C,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM,EAAE,EAAE,CAAC;gBACpD,GAAG,EAAE,IAAI;aACV,CAAC;SACH;QACD,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE;YACT,oCAAgB;YAChB,6CAAoB;YACpB,iDAAsB;YACtB,+CAAqB;YACrB,6DAA4B;SAC7B;QACD,OAAO,EAAE,CAAC,oCAAgB,CAAC;KAC5B,CAAC;GACW,eAAe,CAAG"}
{"version":3,"file":"geocoding.module.js","sourceRoot":"","sources":["../../src/geocoding/geocoding.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,2DAAuD;AACvD,qEAAgE;AAChE,iEAA6D;AAC7D,sEAA2D;AAC3D,wEAA6D;AAC7D,sEAA2D;AAC3D,gEAAqD;AACrD,sFAAyE;AACzE,4EAAiE;AACjE,yEAAoE;AACpE,uEAAkE;AAClE,qFAAgF;AAChF,oFAAyE;AACzE,wFAA6E;AAC7E,0EAA+D;AAC/D,qEAAgE;AAChE,2EAAsE;AACtE,yDAAoD;AACpD,sEAAwD;AACxD,mEAA8D;AAkCvD,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAhC3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC;gBACvB,+BAAU;gBACV,iCAAW;gBACX,+BAAU;gBACV,yBAAO;gBACP,6CAAgB;gBAChB,qCAAa;gBACb,6CAAiB;gBACjB,iDAAmB;gBACnB,mCAAY;aACb,CAAC;YACF,2BAAW,CAAC,QAAQ,CAAC;gBACnB,KAAK,EAAE,UAAU;gBACjB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW;gBAC3C,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM,EAAE,EAAE,CAAC;gBACpD,GAAG,EAAE,IAAI;aACV,CAAC;SACH;QACD,WAAW,EAAE,CAAC,0CAAmB,EAAE,mDAAuB,CAAC;QAC3D,SAAS,EAAE;YACT,oCAAgB;YAChB,6CAAoB;YACpB,iDAAsB;YACtB,+CAAqB;YACrB,6DAA4B;YAC5B,6CAAoB;YACpB,2CAAmB;SACpB;QACD,OAAO,EAAE,CAAC,oCAAgB,EAAE,6CAAoB,CAAC;KAClD,CAAC;GACW,eAAe,CAAG"}
+17 -22
View File
@@ -22,33 +22,28 @@ export declare class GeocodingService {
searchPlaces(query: string, lat?: number, lon?: number, radius?: number, country?: string): Promise<{
results: any;
source: string;
did_you_mean?: undefined;
} | {
results: never[];
did_you_mean: any;
source?: undefined;
} | {
results: any[];
source?: undefined;
did_you_mean?: undefined;
}>;
private formatResults;
private getRepoByTableName;
reverseGeocode(lat: number, lng: number): Promise<any>;
addPlace(data: Partial<BasePlace>): Promise<{
latitude: number;
longitude: number;
location: string;
id: number;
name: string;
name_ar: string;
name_en: string;
address: string;
category: string;
neighbourhood: string;
city: string;
description: string;
created_at: Date;
private logFailedSearch;
autocomplete(query: string, country?: string): Promise<{
results: any;
source: string;
governorate_id: number;
district_id: number;
sub_district_id: number;
neighborhood_id: number;
} | {
results: any;
source?: undefined;
}>;
reverseGeocode(lat: number, lng: number): Promise<any[]>;
addPlace(data: Partial<BasePlace>): Promise<any>;
upsertPlace(data: Partial<BasePlace>): Promise<{
id: any;
action: string;
@@ -59,18 +54,18 @@ export declare class GeocodingService {
created: number;
updated: number;
}>;
getRecentPlaces(limit?: number): Promise<(PlaceSyria | PlaceJordan | PlaceEgypt)[]>;
getRecentPlaces(limit?: number): Promise<any[]>;
getAllPlacesGeoJSON(): Promise<{
type: string;
features: any;
}>;
deletePlacesByName(name: string, country: string): Promise<{
success: boolean;
affected: number;
affected: any;
}>;
deletePlaceById(id: number, country: string): Promise<{
success: boolean;
affected: number | null | undefined;
affected: any;
}>;
private getRepoByCountry;
}
+240 -52
View File
@@ -12,6 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var GeocodingService_1;
var _a, _b, _c, _d, _e;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeocodingService = void 0;
const common_1 = require("@nestjs/common");
@@ -73,9 +74,16 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
}
async searchPlaces(query, lat, lon, radius = 20000, country) {
try {
const cleanQuery = query.trim();
let cleanQuery = query.trim();
if (!cleanQuery)
return { results: [] };
let relativePrefix = '';
const relativeQueryRegex = /^(قرب|بالقرب من|قريب من|عند|بجانب|جنب|حد|بجوار|مقابل|قبال|خلف|ورا|وراء)\s+(.+)$/i;
const match = cleanQuery.match(relativeQueryRegex);
if (match) {
relativePrefix = match[1];
cleanQuery = match[2];
}
const hasLocation = lat !== undefined && lon !== undefined;
const geoSegment = hasLocation ? `${lat.toFixed(2)}_${lon.toFixed(2)}` : 'global';
const cacheKey = `geo_search:${country || 'auto'}:${geoSegment}:${cleanQuery.toLowerCase()}`;
@@ -83,56 +91,93 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
if (cached)
return { results: cached, source: 'cache_hit' };
let targetRegion = country?.toLowerCase() || this.identifyRegion(lat, lon);
const primaryTables = targetRegion && ['syria', 'egypt', 'jordan'].includes(targetRegion)
? [`places_${targetRegion}`] : ['places_jordan', 'places_syria', 'places_egypt'];
const queryPromises = [];
primaryTables.forEach(tableName => {
const repo = this.getRepoByTableName(tableName);
queryPromises.push(repo.query(`
SELECT
p.id, p.name, p.name_ar, p.name_en, p.category,
n.name_ar as neighbourhood, d.name_ar as district, g.name_ar as governorate,
p.latitude, p.longitude, p.address, '${tableName.replace('places_', '')}' as region, 'user_place' as source,
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
GREATEST(similarity(COALESCE(p.name_ar, ''), $1), similarity(COALESCE(p.name, ''), $1)) as relevance
FROM ${tableName} p
LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id
LEFT JOIN admin_boundaries d ON p.district_id = d.id
LEFT JOIN admin_boundaries g ON p.governorate_id = g.id
WHERE (p.name_ar % $1 OR p.name % $1)
AND ($2::float IS NULL OR ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
ORDER BY (p.name_ar <-> $1) ASC LIMIT 10
`, [cleanQuery, lat || null, lon || null, radius]));
});
queryPromises.push(this.osmPointsRepository.query(`
const [normalizedQueryRes] = await this.osmPointsRepository.query(`SELECT normalize_arabic($1) as nq`, [cleanQuery]);
const normalizedQuery = normalizedQueryRes?.nq || cleanQuery.toLowerCase();
let queryParams = [normalizedQuery];
let locationCondition = '';
if (hasLocation) {
locationCondition = `AND ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4`;
queryParams.push(lat, lon, radius);
}
let regionCondition = '';
if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) {
regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`;
}
const sqlQuery = `
SELECT
o.osm_id as id, o.name, o.name_ar, o.name_en, COALESCE(o.amenity, o.shop, 'place') as category,
o.latitude, o.longitude, o.addr_street as address, 'osm_global' as source,
CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance,
GREATEST(similarity(COALESCE(o.name, ''), $1), similarity(COALESCE(o.name_ar, ''), $1)) as relevance
FROM osm_points_with_area o
WHERE (o.name % $1 OR o.name_ar % $1)
AND ($2::float IS NULL OR ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $4)
ORDER BY (o.name <-> $1) ASC LIMIT 10
`, [cleanQuery, lat || null, lon || null, radius]));
const executionResults = await Promise.race([
Promise.allSettled(queryPromises),
id, name, name_ar, category,
'' as neighbourhood, '' as district, '' as governorate,
latitude, longitude, address, region, source, popularity_score,
${hasLocation ? 'ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326))' : '0'} as distance,
similarity(normalized_name, $1) as relevance
FROM unified_search_index
WHERE normalized_name % $1
${locationCondition}
${regionCondition}
ORDER BY (normalized_name <-> $1) ASC
LIMIT 50
`;
const allResults = await Promise.race([
this.osmPointsRepository.query(sqlQuery, queryParams),
new Promise((_, reject) => setTimeout(() => reject(new Error('QUERY_TIMEOUT')), this.DB_TIMEOUT_MS))
]).catch(e => {
this.logger.warn(`Search optimization threshold hit: ${e.message}`);
return [];
});
let allResults = [];
if (Array.isArray(executionResults)) {
executionResults.forEach(res => {
if (res.status === 'fulfilled' && res.value)
allResults.push(...res.value);
});
const formatted = this.formatResults(allResults, hasLocation, relativePrefix);
const placeIds = formatted.map(r => r.id);
if (placeIds.length > 0) {
try {
const gates = await this.osmPointsRepository.query(`SELECT place_id, gate_name_ar, gate_name_en, latitude, longitude, is_main_gate
FROM place_gates
WHERE place_id = ANY($1)`, [placeIds]);
if (gates.length > 0) {
formatted.forEach(r => {
const placeGates = gates.filter((g) => g.place_id === r.id).map((g) => ({
name_ar: g.gate_name_ar,
name_en: g.gate_name_en,
latitude: parseFloat(g.latitude),
longitude: parseFloat(g.longitude),
is_main_gate: g.is_main_gate
}));
if (placeGates.length > 0) {
r.gates = placeGates;
}
});
}
}
catch (err) {
this.logger.warn('Failed to fetch POI gates, table might not exist yet.');
}
}
const formatted = this.formatResults(allResults, hasLocation);
if (formatted.length > 0) {
await this.cacheManager.set(cacheKey, formatted, 3600000);
}
else {
this.logFailedSearch(cleanQuery, normalizedQuery, targetRegion, lat, lon).catch(err => {
this.logger.error('Failed to log zero-result search:', err);
});
try {
const whereClause = regionCondition ? `WHERE ${regionCondition.substring(4)}` : '';
const fallbackQuery = `
SELECT name_ar, name, (normalized_name <-> $1) as dist
FROM unified_search_index
${whereClause}
ORDER BY normalized_name <-> $1 ASC
LIMIT 1
`;
const suggestions = await this.osmPointsRepository.query(fallbackQuery, [normalizedQuery]);
if (suggestions.length > 0 && suggestions[0].dist < 0.6) {
return {
results: [],
did_you_mean: suggestions[0].name_ar || suggestions[0].name
};
}
}
catch (err) {
this.logger.warn('Did You Mean fallback failed: ' + err.message);
}
}
return { results: formatted };
}
catch (e) {
@@ -140,12 +185,15 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
return { results: [] };
}
}
formatResults(results, hasLocation) {
formatResults(results, hasLocation, relativePrefix = '') {
const seenStreets = new Set();
const maxPopularity = Math.max(...results.map(r => r.popularity_score || 10), 100);
return results
.map(r => {
const textScore = Number(r.relevance);
const popularityScore = (r.popularity_score || 10) / maxPopularity;
const proximityBonus = hasLocation ? Math.max(0, 1 - (Number(r.distance) / 10000)) : 0;
const totalScore = (Number(r.relevance) * 0.7) + (proximityBonus * 0.3);
const totalScore = (textScore * 0.5) + (popularityScore * 0.3) + (proximityBonus * 0.2);
return { ...r, totalScore };
})
.sort((a, b) => b.totalScore - a.totalScore)
@@ -162,8 +210,12 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
.map(r => {
const addressParts = [r.neighbourhood, r.district, r.governorate].filter(Boolean);
const full_address = addressParts.length > 0 ? addressParts.join('، ') : (r.address || '');
const nameAr = r.name_ar || r.name;
const displayName = relativePrefix ? `${relativePrefix} ${nameAr}` : nameAr;
return {
...r,
name: displayName,
name_ar: displayName,
latitude: parseFloat(r.latitude),
longitude: parseFloat(r.longitude),
distance_km: r.distance ? (Number(r.distance) / 1000).toFixed(2) : null,
@@ -179,11 +231,78 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
return this.placesJordanRepository;
return this.placesSyriaRepository;
}
async logFailedSearch(query, normalizedQuery, country, lat, lon) {
const q = `
INSERT INTO failed_searches (query_text, normalized_query, country, latitude, longitude)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (normalized_query, COALESCE(country, 'global'))
DO UPDATE SET search_count = failed_searches.search_count + 1, last_seen_at = CURRENT_TIMESTAMP;
`;
await this.osmPointsRepository.query(q, [query, normalizedQuery, country || null, lat || null, lon || null]);
}
async autocomplete(query, country) {
try {
let cleanQuery = query.trim();
if (cleanQuery.length < 2)
return { results: [] };
let relativePrefix = '';
const relativeQueryRegex = /^(قرب|بالقرب من|قريب من|عند|بجانب|جنب|حد|بجوار|مقابل|قبال|خلف|ورا|وراء)\s+(.+)$/i;
const match = cleanQuery.match(relativeQueryRegex);
if (match) {
relativePrefix = match[1];
cleanQuery = match[2];
if (cleanQuery.length < 2)
return { results: [] };
}
const targetRegion = country?.toLowerCase();
const cacheKey = `geo_auto:${targetRegion || 'auto'}:${cleanQuery.toLowerCase()}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached)
return { results: cached, source: 'cache_hit' };
const [normalizedQueryRes] = await this.osmPointsRepository.query(`SELECT normalize_arabic($1) as nq`, [cleanQuery]);
const normalizedQuery = normalizedQueryRes?.nq || cleanQuery.toLowerCase();
let queryParams = [`${normalizedQuery}%`];
let regionCondition = '';
if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) {
regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`;
}
const sqlQuery = `
SELECT
id, name, name_ar, category, region, source, address
FROM unified_search_index
WHERE normalized_name LIKE $1
${regionCondition}
ORDER BY LENGTH(normalized_name) ASC
LIMIT 7
`;
const results = await this.osmPointsRepository.query(sqlQuery, queryParams);
const formatted = results.map(r => {
const nameAr = r.name_ar || r.name;
const displayName = relativePrefix ? `${relativePrefix} ${nameAr}` : nameAr;
return {
id: r.id,
name: displayName,
category: r.category,
region: r.region,
address: r.address
};
});
if (formatted.length > 0) {
await this.cacheManager.set(cacheKey, formatted, 3600000);
}
return { results: formatted };
}
catch (e) {
this.logger.error('Autocomplete failed:', e);
return { results: [] };
}
}
async reverseGeocode(lat, lng) {
try {
const repo = this.getRepositoryForCoords(lat, lng);
const tableName = this.getTableNameForRepo(repo);
const query = `
const queryPromises = [];
queryPromises.push(repo.query(`
SELECT
p.id, p.name, p.name_ar, p.category,
n.name_ar as neighbourhood,
@@ -196,9 +315,81 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
LEFT JOIN admin_boundaries d ON p.sub_district_id = d.id
LEFT JOIN admin_boundaries g ON p.governorate_id = g.id
WHERE p.location IS NOT NULL
ORDER BY p.location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 3
`;
return await repo.query(query, [lng, lat]);
ORDER BY p.location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
`, [lng, lat]));
queryPromises.push(repo.query(`
SELECT
o.osm_id::text as id, o.name, o.name_ar, COALESCE(o.amenity, o.shop, 'place') as category,
'' as neighbourhood, '' as district, '' as governorate,
o.latitude, o.longitude, o.addr_street as address, 'osm_global' as source,
ST_DistanceSphere(o.geom, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
FROM osm_points_with_area o
WHERE o.geom IS NOT NULL AND (o.name IS NOT NULL OR o.name_ar IS NOT NULL)
ORDER BY o.geom <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
`, [lng, lat]));
queryPromises.push(repo.query(`
SELECT
id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, COALESCE(names->>'primary', names->>'common', 'Street') as name_ar, 'street' as category,
'' as neighbourhood, '' as district, '' as governorate,
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
FROM overture_segment
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
`, [lng, lat]));
queryPromises.push(repo.query(`
SELECT
id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, COALESCE(names->>'primary', names->>'common', 'Building') as name_ar, 'building' as category,
'' as neighbourhood, '' as district, '' as governorate,
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
FROM overture_building
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
`, [lng, lat]));
queryPromises.push(repo.query(`
SELECT
id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, COALESCE(names->>'primary', names->>'common', 'Place') as name_ar, 'place' as category,
'' as neighbourhood, '' as district, '' as governorate,
ST_Y(ST_Centroid(location))::text as latitude, ST_X(ST_Centroid(location))::text as longitude, '' as address, 'overture_global' as source,
ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
FROM overture_place
WHERE location IS NOT NULL AND (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL)
ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
`, [lng, lat]));
const results = await Promise.allSettled(queryPromises);
let allResults = [];
results.forEach(res => {
if (res.status === 'fulfilled' && res.value)
allResults.push(...res.value);
});
return allResults
.sort((a, b) => Number(a.distance) - Number(b.distance))
.slice(0, 5)
.map(r => {
const distance = Number(r.distance);
const fullAddressParts = [r.name_ar || r.name, r.address, r.neighbourhood, r.district, r.governorate].filter(Boolean);
let humanReadable = r.name_ar || r.name;
if (distance <= 20) {
humanReadable = r.name_ar || r.name;
}
else if (distance <= 70 && r.category !== 'street') {
const streetName = r.address ? `، ${r.address}` : '';
humanReadable = `أمام ${r.name_ar || r.name}${streetName}`;
}
else if (r.category === 'street' || distance > 70) {
const streetPart = r.category === 'street' ? (r.name_ar || r.name) : (r.address || r.name_ar || r.name);
const districtPart = r.neighbourhood || r.district || '';
humanReadable = [streetPart, districtPart].filter(Boolean).join('، ');
}
return {
...r,
latitude: parseFloat(r.latitude),
longitude: parseFloat(r.longitude),
human_readable_address: humanReadable,
full_address: fullAddressParts.join('، ')
};
});
}
catch (error) {
this.logger.error('Reverse geocoding error:', error);
@@ -259,6 +450,7 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_egypt
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, 'building' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_building WHERE names->>'primary' IS NOT NULL LIMIT 500
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, 'street' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_segment WHERE names->>'primary' IS NOT NULL LIMIT 500
UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, 'place' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_place WHERE names->>'primary' IS NOT NULL LIMIT 500
`;
const res = await this.placesSyriaRepository.query(q);
const features = res.map(p => ({
@@ -299,10 +491,6 @@ exports.GeocodingService = GeocodingService = GeocodingService_1 = __decorate([
__param(3, (0, typeorm_1.InjectRepository)(osm_area_entity_1.OsmArea)),
__param(4, (0, typeorm_1.InjectRepository)(osm_point_with_area_entity_1.OsmPointWithArea)),
__param(5, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository, Object])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, typeof (_c = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _c : Object, typeof (_d = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _d : Object, typeof (_e = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _e : Object, Object])
], GeocodingService);
//# sourceMappingURL=geocoding.service.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
import { Repository } from 'typeorm';
import { OsmPointWithArea } from './entities/osm-point-with-area.entity';
export declare class IndexRefreshService {
private readonly repo;
private readonly logger;
constructor(repo: Repository<OsmPointWithArea>);
handleCron(): Promise<void>;
}
+60
View File
@@ -0,0 +1,60 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var IndexRefreshService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexRefreshService = void 0;
const common_1 = require("@nestjs/common");
const schedule_1 = require("@nestjs/schedule");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const osm_point_with_area_entity_1 = require("./entities/osm-point-with-area.entity");
let IndexRefreshService = IndexRefreshService_1 = class IndexRefreshService {
repo;
logger = new common_1.Logger(IndexRefreshService_1.name);
constructor(repo) {
this.repo = repo;
}
async handleCron() {
this.logger.log('Starting background refresh for unified_search_index...');
try {
await this.repo.query('REFRESH MATERIALIZED VIEW CONCURRENTLY unified_search_index;');
this.logger.log('Successfully refreshed unified_search_index.');
}
catch (error) {
this.logger.error('Failed to refresh unified_search_index', error);
try {
this.logger.log('Attempting standard refresh (blocking)...');
await this.repo.query('REFRESH MATERIALIZED VIEW unified_search_index;');
this.logger.log('Successfully refreshed unified_search_index (standard).');
}
catch (fallbackError) {
this.logger.error('Fallback standard refresh also failed', fallbackError);
}
}
}
};
exports.IndexRefreshService = IndexRefreshService;
__decorate([
(0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_MINUTES),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], IndexRefreshService.prototype, "handleCron", null);
exports.IndexRefreshService = IndexRefreshService = IndexRefreshService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(osm_point_with_area_entity_1.OsmPointWithArea)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object])
], IndexRefreshService);
//# sourceMappingURL=index-refresh.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index-refresh.service.js","sourceRoot":"","sources":["../../src/geocoding/index-refresh.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,+CAAwD;AACxD,6CAAmD;AACnD,qCAAqC;AACrC,sFAAyE;AAGlE,IAAM,mBAAmB,2BAAzB,MAAM,mBAAmB;IAKX;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,qBAAmB,CAAC,IAAI,CAAC,CAAC;IAE/D,YAEmB,IAAkC;QAAlC,SAAI,GAAJ,IAAI,CAA8B;IAClD,CAAC;IAIE,AAAN,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;QAC3E,IAAI,CAAC;YAEH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;YACtF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAGnE,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;gBAC7D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;gBACzE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;YAC7E,CAAC;YAAC,OAAO,aAAa,EAAE,CAAC;gBACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,EAAE,aAAa,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AA7BY,kDAAmB;AAUxB;IADL,IAAA,eAAI,EAAC,yBAAc,CAAC,eAAe,CAAC;;;;qDAmBpC;8BA5BU,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,6CAAgB,CAAC,CAAA;yDACZ,oBAAU,oBAAV,oBAAU;GALxB,mBAAmB,CA6B/B"}
+1 -1
View File
@@ -16,7 +16,7 @@ export declare class JordanResearchService {
osm: any;
overture: any;
wikidata: any;
static_files: {};
static_files: any;
hdx: string;
gadm: string;
};
+2 -1
View File
@@ -48,6 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var JordanResearchService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JordanResearchService = void 0;
const common_1 = require("@nestjs/common");
@@ -178,6 +179,6 @@ exports.JordanResearchService = JordanResearchService;
exports.JordanResearchService = JordanResearchService = JordanResearchService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(place_jordan_entity_1.PlaceJordan)),
__metadata("design:paramtypes", [typeorm_2.Repository])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object])
], JordanResearchService);
//# sourceMappingURL=jordan-research.service.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"jordan-research.service.js","sourceRoot":"","sources":["../../src/geocoding/jordan-research.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,6CAAmD;AACnD,qCAAqC;AACrC,wEAA6D;AAC7D,kDAA0B;AAC1B,uCAAyB;AACzB,2CAA6B;AAGtB,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAKb;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,uBAAqB,CAAC,IAAI,CAAC,CAAC;IAEjE,YAEmB,SAAkC;QAAlC,cAAS,GAAT,SAAS,CAAyB;IAClD,CAAC;IAEI,KAAK,CAAC,cAAc,CAAC,GAAW,EAAE,IAAa,EAAE,SAAyB,KAAK,EAAE,OAAO,GAAG,CAAC;QAClG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG;oBACX,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE;wBACL,YAAY,EAAE,mEAAmE;wBACjF,QAAQ,EAAE,mDAAmD;qBAChE;iBACJ,CAAC;gBACF,MAAM,QAAQ,GAAG,MAAM,KAAK,MAAM;oBAC9B,CAAC,CAAC,MAAM,eAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;oBACrC,CAAC,CAAC,MAAM,eAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC/F,OAAO,QAAQ,CAAC,IAAI,CAAC;YACzB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;oBAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,UAAU,OAAO,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;oBAC5F,MAAM,KAAK,CAAC;gBAChB,CAAC;gBACD,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACpD,CAAC;QACL,CAAC;IACP,CAAC;IAKD,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,mLAAmL,CAAC;QAClM,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,yCAAyC,EAAE,QAAQ,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC/H,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC7B,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC5C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;gBACjG,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAC7G,CAAC,CAAC,CAAC;QACN,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,qBAAqB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IACrE,CAAC;IAKD,KAAK,CAAC,kBAAkB;QACtB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,sFAAsF,CAAC,CAAC;YACtI,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,wDAAwD,EAAE,CAAC;YAGpH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,0FAA0F,CAAC,CAAC;YACvI,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YAChD,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAEpH,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;oFAC4C,YAAY;;;;;;OAMzF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IAC1E,CAAC;IAKD,KAAK,CAAC,kBAAkB;QACtB,MAAM,MAAM,GAAG,6IAA6I,CAAC;QAC7J,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,mCAAmC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YAC3F,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC7G,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAAC,CAAC;IAC1E,CAAC;IAKD,KAAK,CAAC,gBAAgB;QAEpB,MAAM,KAAK,GAAG,CAAC,gCAAgC,EAAE,SAAS,EAAE,4BAA4B,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,CAAC,gCAAgC,EAAE,mBAAmB,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,EAAE,CAAC;QAEnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACpB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;oBACpC,OAAO,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;oBAChC,KAAK,GAAG,IAAI,CAAC;oBACb,MAAM;gBACV,CAAC;YACL,CAAC;YACD,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,2EAA2E,CAAC;QAC5G,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,mBAAmB;QACvB,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/D,IAAI,CAAC,aAAa,EAAE;YACpB,IAAI,CAAC,kBAAkB,EAAE;YACzB,IAAI,CAAC,kBAAkB,EAAE;YACzB,IAAI,CAAC,gBAAgB,EAAE;SACxB,CAAC,CAAC;QAEH,OAAO;YACL,IAAI,EAAE,2BAA2B;YACjC,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC1C,OAAO,EAAE;gBACP,GAAG;gBACH,QAAQ;gBACR,QAAQ;gBACR,YAAY,EAAE,WAAW;gBACzB,GAAG,EAAE,wDAAwD;gBAC7D,IAAI,EAAE,kCAAkC;aACzC;YACD,kBAAkB,EAAE,oLAAoL;SACzM,CAAC;IACJ,CAAC;CACF,CAAA;AAjIY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,0BAAgB,EAAC,iCAAW,CAAC,CAAA;qCACF,oBAAU;GAL7B,qBAAqB,CAiIjC"}
{"version":3,"file":"jordan-research.service.js","sourceRoot":"","sources":["../../src/geocoding/jordan-research.service.ts"],"names":[],"mappings":"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"}
+12
View File
@@ -0,0 +1,12 @@
import { MapRefinementService } from './map-refinement.service';
import { CandidateStatus } from './entities/map-candidate.entity';
export declare class MapRefinementController {
private readonly refinementService;
constructor(refinementService: MapRefinementService);
suggest(req: any, dto: any): Promise<import("./entities/map-candidate.entity").MapCandidate>;
list(status?: CandidateStatus): Promise<any[]>;
approve(id: string): Promise<any>;
reject(id: string, body: {
reason: string;
}): Promise<import("./entities/map-candidate.entity").MapCandidate>;
}
+87
View File
@@ -0,0 +1,87 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapRefinementController = void 0;
const common_1 = require("@nestjs/common");
const map_refinement_service_1 = require("./map-refinement.service");
const firebase_auth_guard_1 = require("../auth/guards/firebase-auth.guard");
const swagger_1 = require("@nestjs/swagger");
const map_candidate_entity_1 = require("./entities/map-candidate.entity");
let MapRefinementController = class MapRefinementController {
refinementService;
constructor(refinementService) {
this.refinementService = refinementService;
}
async suggest(req, dto) {
const userId = req.user?.uid || req.tenant?.id || 'anonymous';
return this.refinementService.suggestPlace(dto, userId);
}
async list(status) {
return this.refinementService.getCandidates(status);
}
async approve(id) {
return this.refinementService.approveCandidate(parseInt(id, 10));
}
async reject(id, body) {
return this.refinementService.rejectCandidate(parseInt(id, 10), body.reason);
}
};
exports.MapRefinementController = MapRefinementController;
__decorate([
(0, common_1.Post)('suggest'),
(0, swagger_1.ApiOperation)({ summary: 'Suggest a new location for the map (Client App)' }),
(0, swagger_1.ApiHeader)({ name: 'x-api-key', description: 'API Key for usage tracking' }),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "suggest", null);
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Get)('candidates'),
(0, swagger_1.ApiOperation)({ summary: 'List suggested places for review (Dashboard)' }),
__param(0, (0, common_1.Query)('status')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "list", null);
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Patch)('candidates/:id/approve'),
(0, swagger_1.ApiOperation)({ summary: 'Approve a candidate and move to production (Dashboard)' }),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "approve", null);
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Patch)('candidates/:id/reject'),
(0, swagger_1.ApiOperation)({ summary: 'Reject a candidate (Dashboard)' }),
__param(0, (0, common_1.Param)('id')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], MapRefinementController.prototype, "reject", null);
exports.MapRefinementController = MapRefinementController = __decorate([
(0, swagger_1.ApiTags)('map-refinement-places'),
(0, common_1.Controller)('map-refinement/places'),
__metadata("design:paramtypes", [map_refinement_service_1.MapRefinementService])
], MapRefinementController);
//# sourceMappingURL=map-refinement.controller.js.map
@@ -0,0 +1 @@
{"version":3,"file":"map-refinement.controller.js","sourceRoot":"","sources":["../../src/geocoding/map-refinement.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAkG;AAClG,qEAAgE;AAChE,4EAAuE;AACvE,6CAAkF;AAClF,0EAAkE;AAI3D,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IACL;IAA7B,YAA6B,iBAAuC;QAAvC,sBAAiB,GAAjB,iBAAiB,CAAsB;IAAG,CAAC;IAKlE,AAAN,KAAK,CAAC,OAAO,CAAQ,GAAQ,EAAU,GAAQ;QAE7C,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,IAAI,WAAW,CAAC;QAC9D,OAAO,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAMK,AAAN,KAAK,CAAC,IAAI,CAAkB,MAAwB;QAClD,OAAO,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;IAMK,AAAN,KAAK,CAAC,OAAO,CAAc,EAAU;QACnC,OAAO,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAMK,AAAN,KAAK,CAAC,MAAM,CAAc,EAAU,EAAU,IAAwB;QACpE,OAAO,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/E,CAAC;CACF,CAAA;AAnCY,0DAAuB;AAM5B;IAHL,IAAA,aAAI,EAAC,SAAS,CAAC;IACf,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAAC;IAC5E,IAAA,mBAAS,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAC7D,WAAA,IAAA,YAAG,GAAE,CAAA;IAAY,WAAA,IAAA,aAAI,GAAE,CAAA;;;;sDAIrC;AAMK;IAJL,IAAA,uBAAa,GAAE;IACf,IAAA,kBAAS,EAAC,uCAAiB,CAAC;IAC5B,IAAA,YAAG,EAAC,YAAY,CAAC;IACjB,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC;IAC9D,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;;;;mDAE1B;AAMK;IAJL,IAAA,uBAAa,GAAE;IACf,IAAA,kBAAS,EAAC,uCAAiB,CAAC;IAC5B,IAAA,cAAK,EAAC,wBAAwB,CAAC;IAC/B,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,wDAAwD,EAAE,CAAC;IACrE,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;sDAEzB;AAMK;IAJL,IAAA,uBAAa,GAAE;IACf,IAAA,kBAAS,EAAC,uCAAiB,CAAC;IAC5B,IAAA,cAAK,EAAC,uBAAuB,CAAC;IAC9B,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC;IAC9C,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;qDAE5C;kCAlCU,uBAAuB;IAFnC,IAAA,iBAAO,EAAC,uBAAuB,CAAC;IAChC,IAAA,mBAAU,EAAC,uBAAuB,CAAC;qCAEc,6CAAoB;GADzD,uBAAuB,CAmCnC"}
+19
View File
@@ -0,0 +1,19 @@
import { Repository, DataSource } from 'typeorm';
import { MapCandidate, CandidateStatus } from './entities/map-candidate.entity';
import { PlaceJordan } from './entities/place-jordan.entity';
import { PlaceSyria } from './entities/place-syria.entity';
import { PlaceEgypt } from './entities/place-egypt.entity';
export declare class MapRefinementService {
private dataSource;
private candidateRepository;
private jordanRepository;
private syriaRepository;
private egyptRepository;
private readonly logger;
constructor(dataSource: DataSource, candidateRepository: Repository<MapCandidate>, jordanRepository: Repository<PlaceJordan>, syriaRepository: Repository<PlaceSyria>, egyptRepository: Repository<PlaceEgypt>);
suggestPlace(dto: any, submittedBy: string): Promise<MapCandidate>;
runExternalVerification(candidateId: number): Promise<void>;
getCandidates(status?: CandidateStatus): Promise<any[]>;
approveCandidate(id: number): Promise<any>;
rejectCandidate(id: number, reason: string): Promise<MapCandidate>;
}
+208
View File
@@ -0,0 +1,208 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var MapRefinementService_1;
var _a, _b, _c, _d, _e;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapRefinementService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const map_candidate_entity_1 = require("./entities/map-candidate.entity");
const place_jordan_entity_1 = require("./entities/place-jordan.entity");
const place_syria_entity_1 = require("./entities/place-syria.entity");
const place_egypt_entity_1 = require("./entities/place-egypt.entity");
let MapRefinementService = MapRefinementService_1 = class MapRefinementService {
dataSource;
candidateRepository;
jordanRepository;
syriaRepository;
egyptRepository;
logger = new common_1.Logger(MapRefinementService_1.name);
constructor(dataSource, candidateRepository, jordanRepository, syriaRepository, egyptRepository) {
this.dataSource = dataSource;
this.candidateRepository = candidateRepository;
this.jordanRepository = jordanRepository;
this.syriaRepository = syriaRepository;
this.egyptRepository = egyptRepository;
}
async suggestPlace(dto, submittedBy) {
const lat = parseFloat(dto.lat || dto.latitude);
const lng = parseFloat(dto.lng || dto.longitude);
const country = (dto.country || 'JORDAN').toUpperCase();
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid coordinates: Latitude and Longitude must be numbers');
}
this.logger.log(`📍 Suggesting place: ${dto.name} at [${lat}, ${lng}]. Performing spatial enrichment...`);
const enrichment = await this.dataSource.query(`
SELECT
g.id as gov_id, g.name_ar as gov_name,
d.id as dist_id, d.name_ar as dist_name,
s.id as sub_id, s.name_ar as sub_name,
n.id as neigh_id, n.name_ar as neigh_name
FROM (SELECT ST_SetSRID(ST_MakePoint($1, $2), 4326) as p) p
LEFT JOIN admin_boundaries g ON g.admin_level = 4 AND ST_Within(p.p::geometry, g.geom::geometry)
LEFT JOIN admin_boundaries d ON d.admin_level = 6 AND ST_Within(p.p::geometry, d.geom::geometry)
LEFT JOIN admin_boundaries s ON s.admin_level = 8 AND ST_Within(p.p::geometry, s.geom::geometry)
LEFT JOIN neighborhood_polygons n ON ST_Within(p.p::geometry, n.geometry::geometry)
LIMIT 1
`, [lng, lat]);
const spatialData = enrichment[0] || {};
const isArabic = (text) => /[\u0600-\u06FF]/.test(text);
const nameAr = dto.name_ar || (isArabic(dto.name) ? dto.name : null);
const nameEn = dto.name_en || (!isArabic(dto.name) ? dto.name : null);
const candidate = this.candidateRepository.create({
name: dto.name,
name_ar: nameAr,
name_en: nameEn,
category: dto.category,
address: dto.address,
latitude: lat,
longitude: lng,
submittedBy,
status: map_candidate_entity_1.CandidateStatus.PENDING,
country: country,
location: {
type: 'Point',
coordinates: [lng, lat],
},
governorate_id: spatialData.gov_id,
district_id: spatialData.dist_id,
sub_district_id: spatialData.sub_id,
neighborhood_id: spatialData.neigh_id
});
const saved = await this.candidateRepository.save(candidate);
this.logger.log(`✅ Success: Candidate ${saved.id} enriched with Gov:${saved.governorate_id}, Neigh:${spatialData.neigh_id}`);
this.runExternalVerification(saved.id).catch(err => this.logger.error(`❌ Verification failed for ${saved.id}: ${err.message}`));
return {
...saved,
governorate_name: spatialData.gov_name,
district_name: spatialData.dist_name,
neighborhood_name: spatialData.neigh_name,
location_wkt: `POINT(${lng} ${lat})`
};
}
async runExternalVerification(candidateId) {
const candidate = await this.candidateRepository.findOne({ where: { id: candidateId } });
if (!candidate)
return;
this.logger.log(`🔍 Starting automated verification for candidate ${candidate.id}...`);
try {
const axios = require('axios');
const searchUrl = `https://duckduckgo.com/html/?q=${candidate.latitude},${candidate.longitude}`;
const response = await axios.get(searchUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
},
timeout: 10000
});
const html = response.data;
const titleMatch = html.match(/<title>([^<]+)<\/title>/);
if (titleMatch) {
let officialName = titleMatch[1].replace(' at DuckDuckGo', '').replace(' - DuckDuckGo', '').trim();
const isCoordinates = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/.test(officialName);
if (!isCoordinates && officialName.length > 5) {
candidate.verified_name = officialName;
const cleanUser = candidate.name.toLowerCase().trim();
const cleanOfficial = officialName.toLowerCase();
if (cleanOfficial.includes(cleanUser) || cleanUser.includes(cleanOfficial)) {
candidate.confidence_score = 90;
}
else {
candidate.confidence_score = 60;
}
this.logger.log(`🎯 Verified: Found "${officialName}" near suggestion. Trust: ${candidate.confidence_score}%`);
}
else {
candidate.confidence_score = 10;
this.logger.log(`⚠️ No specific POI name found for coordinates ${candidate.latitude},${candidate.longitude}`);
}
}
candidate.verification_metadata = {
last_check: new Date().toISOString(),
source: 'DDG_SCRAPE'
};
await this.candidateRepository.save(candidate);
}
catch (error) {
this.logger.error(`Automated verification error: ${error.message}`);
}
}
async getCandidates(status) {
const query = `
SELECT
c.*,
g.name_ar as governorate_name,
d.name_ar as district_name,
n.name_ar as neighborhood_name
FROM map_candidates c
LEFT JOIN admin_boundaries g ON c.governorate_id = g.id
LEFT JOIN admin_boundaries d ON c.district_id = d.id
LEFT JOIN neighborhood_polygons n ON c.neighborhood_id = n.id
${status ? 'WHERE c.status = $1' : ''}
ORDER BY c.created_at DESC
`;
return this.candidateRepository.query(query, status ? [status] : []);
}
async approveCandidate(id) {
const candidate = await this.candidateRepository.findOne({ where: { id } });
if (!candidate)
throw new common_1.NotFoundException('Candidate not found');
const placeData = {
latitude: candidate.latitude,
longitude: candidate.longitude,
name: candidate.name,
name_ar: candidate.name_ar,
name_en: candidate.name_en,
category: candidate.category,
address: candidate.address,
description: candidate.description,
location: candidate.location,
source: `suggested_by_${candidate.submittedBy}`,
};
let repo;
switch (candidate.country) {
case map_candidate_entity_1.CountryCode.SYRIA:
repo = this.syriaRepository;
break;
case map_candidate_entity_1.CountryCode.EGYPT:
repo = this.egyptRepository;
break;
default: repo = this.jordanRepository;
}
const approvedPlace = repo.create(placeData);
await repo.save(approvedPlace);
candidate.status = map_candidate_entity_1.CandidateStatus.APPROVED;
await this.candidateRepository.save(candidate);
this.logger.log(`✅ Approved candidate ${id}: ${candidate.name} moved to production.`);
return { success: true, place: approvedPlace };
}
async rejectCandidate(id, reason) {
const candidate = await this.candidateRepository.findOne({ where: { id } });
if (!candidate)
throw new common_1.NotFoundException('Candidate not found');
candidate.status = map_candidate_entity_1.CandidateStatus.REJECTED;
candidate.rejectionReason = reason;
return this.candidateRepository.save(candidate);
}
};
exports.MapRefinementService = MapRefinementService;
exports.MapRefinementService = MapRefinementService = MapRefinementService_1 = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, typeorm_1.InjectRepository)(map_candidate_entity_1.MapCandidate)),
__param(2, (0, typeorm_1.InjectRepository)(place_jordan_entity_1.PlaceJordan)),
__param(3, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria)),
__param(4, (0, typeorm_1.InjectRepository)(place_egypt_entity_1.PlaceEgypt)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.DataSource !== "undefined" && typeorm_2.DataSource) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, typeof (_c = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _c : Object, typeof (_d = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _d : Object, typeof (_e = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _e : Object])
], MapRefinementService);
//# sourceMappingURL=map-refinement.service.js.map
File diff suppressed because one or more lines are too long