fix(tactical): resolve DTO types and TS1272 build error for isolatedModules
This commit is contained in:
+127
-16
@@ -12,7 +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;
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GeocodingService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
@@ -22,26 +22,32 @@ const cache_manager_1 = require("@nestjs/cache-manager");
|
||||
const place_syria_entity_1 = require("./entities/place-syria.entity");
|
||||
const place_jordan_entity_1 = require("./entities/place-jordan.entity");
|
||||
const place_egypt_entity_1 = require("./entities/place-egypt.entity");
|
||||
const place_iraq_entity_1 = require("./entities/place-iraq.entity");
|
||||
const osm_area_entity_1 = require("./entities/osm-area.entity");
|
||||
const osm_point_with_area_entity_1 = require("./entities/osm-point-with-area.entity");
|
||||
let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
placesSyriaRepository;
|
||||
placesJordanRepository;
|
||||
placesEgyptRepository;
|
||||
placesIraqRepository;
|
||||
osmAreasRepository;
|
||||
osmPointsRepository;
|
||||
cacheManager;
|
||||
logger = new common_1.Logger(GeocodingService_1.name);
|
||||
DB_TIMEOUT_MS = 1100;
|
||||
constructor(placesSyriaRepository, placesJordanRepository, placesEgyptRepository, osmAreasRepository, osmPointsRepository, cacheManager) {
|
||||
constructor(placesSyriaRepository, placesJordanRepository, placesEgyptRepository, placesIraqRepository, osmAreasRepository, osmPointsRepository, cacheManager) {
|
||||
this.placesSyriaRepository = placesSyriaRepository;
|
||||
this.placesJordanRepository = placesJordanRepository;
|
||||
this.placesEgyptRepository = placesEgyptRepository;
|
||||
this.placesIraqRepository = placesIraqRepository;
|
||||
this.osmAreasRepository = osmAreasRepository;
|
||||
this.osmPointsRepository = osmPointsRepository;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
getRepositoryForCoords(lat, lng) {
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 38.7 && lng <= 48.8) {
|
||||
return this.placesIraqRepository;
|
||||
}
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) {
|
||||
if (lat > 32.5 && lng > 35.8)
|
||||
return this.placesSyriaRepository;
|
||||
@@ -55,6 +61,9 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
identifyRegion(lat, lng) {
|
||||
if (lat === undefined || lng === undefined)
|
||||
return undefined;
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 38.7 && lng <= 48.8) {
|
||||
return 'iraq';
|
||||
}
|
||||
if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) {
|
||||
if (lat > 32.5 && lng > 35.8)
|
||||
return 'syria';
|
||||
@@ -70,6 +79,8 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
return 'places_jordan';
|
||||
if (repo === this.placesEgyptRepository)
|
||||
return 'places_egypt';
|
||||
if (repo === this.placesIraqRepository)
|
||||
return 'places_iraq';
|
||||
return 'places_syria';
|
||||
}
|
||||
async searchPlaces(query, lat, lon, radius = 20000, country) {
|
||||
@@ -100,7 +111,7 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
queryParams.push(lat, lon, radius);
|
||||
}
|
||||
let regionCondition = '';
|
||||
if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) {
|
||||
if (targetRegion && ['syria', 'jordan', 'egypt', 'iraq'].includes(targetRegion)) {
|
||||
regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`;
|
||||
}
|
||||
const sqlQuery = `
|
||||
@@ -227,6 +238,8 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
getRepoByTableName(tableName) {
|
||||
if (tableName === 'places_egypt')
|
||||
return this.placesEgyptRepository;
|
||||
if (tableName === 'places_iraq')
|
||||
return this.placesIraqRepository;
|
||||
if (tableName === 'places_jordan')
|
||||
return this.placesJordanRepository;
|
||||
return this.placesSyriaRepository;
|
||||
@@ -263,7 +276,7 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
const normalizedQuery = normalizedQueryRes?.nq || cleanQuery.toLowerCase();
|
||||
let queryParams = [`${normalizedQuery}%`];
|
||||
let regionCondition = '';
|
||||
if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) {
|
||||
if (targetRegion && ['syria', 'jordan', 'egypt', 'iraq'].includes(targetRegion)) {
|
||||
regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`;
|
||||
}
|
||||
const sqlQuery = `
|
||||
@@ -297,10 +310,44 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
return { results: [] };
|
||||
}
|
||||
}
|
||||
async findIraqiAddress(lat, lng) {
|
||||
const MAX_DISTANCE_M = 150;
|
||||
try {
|
||||
const rows = await this.osmPointsRepository.query(`
|
||||
WITH p AS (
|
||||
SELECT ST_Transform(ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326), 3857) AS g3857,
|
||||
ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)::geography AS geog
|
||||
)
|
||||
SELECT l.name,
|
||||
ST_Distance(ST_Transform(l.way, 4326)::geography, p.geog) AS distance
|
||||
FROM planet_osm_line l, p
|
||||
WHERE l.name ~ '^[0-9]{2,4}-[0-9]{1,3}$'
|
||||
ORDER BY l.way <-> p.g3857
|
||||
LIMIT 1
|
||||
`, [lng, lat]);
|
||||
const row = rows?.[0];
|
||||
if (!row || Number(row.distance) > MAX_DISTANCE_M)
|
||||
return null;
|
||||
const [mahalla, zuqaq] = String(row.name).split('-');
|
||||
return {
|
||||
mahalla,
|
||||
zuqaq,
|
||||
distance: Number(row.distance),
|
||||
text: `محلة ${mahalla}، زقاق ${zuqaq}`,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warn(`Iraqi address lookup failed: ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async reverseGeocode(lat, lng) {
|
||||
try {
|
||||
const repo = this.getRepositoryForCoords(lat, lng);
|
||||
const tableName = this.getTableNameForRepo(repo);
|
||||
const iraqiAddressPromise = this.identifyRegion(lat, lng) === 'iraq'
|
||||
? this.findIraqiAddress(lat, lng)
|
||||
: Promise.resolve(null);
|
||||
const queryPromises = [];
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
@@ -357,7 +404,20 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
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);
|
||||
queryPromises.push(repo.query(`
|
||||
SELECT
|
||||
id::text, COALESCE(name, 'Street') as name, COALESCE(name, 'طريق معتمد') as name_ar, 'street' as category,
|
||||
'' as neighbourhood, '' as district, '' as governorate,
|
||||
ST_Y(ST_Centroid(geometry::geometry))::text as latitude, ST_X(ST_Centroid(geometry::geometry))::text as longitude, '' as address, 'approved_road' as source,
|
||||
ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance
|
||||
FROM approved_roads
|
||||
WHERE geometry IS NOT NULL AND name IS NOT NULL AND trim(name) != ''
|
||||
ORDER BY geometry::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5
|
||||
`, [lng, lat]).catch(() => []));
|
||||
const [results, iraqiAddress] = await Promise.all([
|
||||
Promise.allSettled(queryPromises),
|
||||
iraqiAddressPromise,
|
||||
]);
|
||||
let allResults = [];
|
||||
results.forEach(res => {
|
||||
if (res.status === 'fulfilled' && res.value)
|
||||
@@ -368,7 +428,17 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
.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);
|
||||
const ZUQAQ_NAME_RE = /^[0-9]{2,4}-[0-9]{1,3}$/;
|
||||
const rawName = r.name_ar || r.name;
|
||||
const nameIsZuqaq = typeof rawName === 'string' && ZUQAQ_NAME_RE.test(rawName);
|
||||
const fullAddressParts = [
|
||||
nameIsZuqaq ? null : rawName,
|
||||
r.address,
|
||||
iraqiAddress?.text,
|
||||
r.neighbourhood,
|
||||
r.district,
|
||||
r.governorate,
|
||||
].filter(Boolean);
|
||||
let humanReadable = r.name_ar || r.name;
|
||||
if (distance <= 20) {
|
||||
humanReadable = r.name_ar || r.name;
|
||||
@@ -382,8 +452,14 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
const districtPart = r.neighbourhood || r.district || '';
|
||||
humanReadable = [streetPart, districtPart].filter(Boolean).join('، ');
|
||||
}
|
||||
if (iraqiAddress) {
|
||||
const landmark = distance <= 70 && !nameIsZuqaq ? rawName : null;
|
||||
humanReadable = [iraqiAddress.text, landmark].filter(Boolean).join(' — ');
|
||||
}
|
||||
return {
|
||||
...r,
|
||||
mahalla: iraqiAddress?.mahalla,
|
||||
zuqaq: iraqiAddress?.zuqaq,
|
||||
latitude: parseFloat(r.latitude),
|
||||
longitude: parseFloat(r.longitude),
|
||||
human_readable_address: humanReadable,
|
||||
@@ -396,9 +472,20 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
extractCoords(data) {
|
||||
const lat = Number(data?.lat ?? data?.latitude);
|
||||
const lng = Number(data?.lng ?? data?.longitude);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
|
||||
throw new common_1.HttpException('Invalid coordinates: provide numeric lat/lng (or latitude/longitude)', common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lng < -180 || lng > 180) {
|
||||
throw new common_1.HttpException(`Coordinates out of range: lat=${lat}, lng=${lng}`, common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return { lat, lng };
|
||||
}
|
||||
async addPlace(data) {
|
||||
try {
|
||||
const lat = Number(data.latitude), lng = Number(data.longitude);
|
||||
const { lat, lng } = this.extractCoords(data);
|
||||
const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo);
|
||||
const newPlace = repo.create({ ...data, latitude: lat, longitude: lng, created_at: new Date(), location: { type: 'Point', coordinates: [lng, lat] } });
|
||||
const savedPlace = await repo.save(newPlace);
|
||||
@@ -406,12 +493,14 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
return { ...savedPlace, latitude: lat, longitude: lng, location: `POINT(${lng} ${lat})` };
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof common_1.HttpException)
|
||||
throw error;
|
||||
throw new common_1.HttpException(error.message, common_1.HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
async upsertPlace(data) {
|
||||
try {
|
||||
const lat = Number(data.latitude), lng = Number(data.longitude);
|
||||
const { lat, lng } = this.extractCoords(data);
|
||||
const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo);
|
||||
const existing = await repo.query(`SELECT id, name FROM ${tableName} WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) < 15 LIMIT 1`, [lng, lat]);
|
||||
if (existing && existing.length > 0) {
|
||||
@@ -427,20 +516,39 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
}
|
||||
async upsertBatch(places) {
|
||||
const results = [];
|
||||
for (const place of places) {
|
||||
const errors = [];
|
||||
for (const [index, place] of places.entries()) {
|
||||
try {
|
||||
const r = await this.addPlace(place);
|
||||
results.push({ id: r.id, action: 'created' });
|
||||
}
|
||||
catch (e) { }
|
||||
catch (e) {
|
||||
if (errors.length < 50) {
|
||||
errors.push({
|
||||
index,
|
||||
name: place?.name,
|
||||
reason: e instanceof common_1.HttpException ? e.message : (e?.message ?? 'unknown'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { total: places.length, processed: results.length, created: results.length, updated: 0 };
|
||||
const failed = places.length - results.length;
|
||||
return {
|
||||
total: places.length,
|
||||
processed: results.length,
|
||||
created: results.length,
|
||||
updated: 0,
|
||||
failed,
|
||||
errors,
|
||||
errorsTruncated: failed > errors.length,
|
||||
};
|
||||
}
|
||||
async getRecentPlaces(limit = 50) {
|
||||
const s = await this.placesSyriaRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
const j = await this.placesJordanRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
const e = await this.placesEgyptRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
return [...s, ...j, ...e].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit);
|
||||
const i = await this.placesIraqRepository.find({ order: { created_at: 'DESC' }, take: limit });
|
||||
return [...s, ...j, ...e, ...i].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit);
|
||||
}
|
||||
async getAllPlacesGeoJSON() {
|
||||
try {
|
||||
@@ -448,6 +556,7 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_syria
|
||||
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_jordan
|
||||
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_egypt
|
||||
UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_iraq
|
||||
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
|
||||
@@ -478,6 +587,7 @@ let GeocodingService = GeocodingService_1 = class GeocodingService {
|
||||
case 'syria': return this.placesSyriaRepository;
|
||||
case 'jordan': return this.placesJordanRepository;
|
||||
case 'egypt': return this.placesEgyptRepository;
|
||||
case 'iraq': return this.placesIraqRepository;
|
||||
default: throw new common_1.HttpException('Invalid country: ' + country, common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -488,9 +598,10 @@ exports.GeocodingService = GeocodingService = GeocodingService_1 = __decorate([
|
||||
__param(0, (0, typeorm_1.InjectRepository)(place_syria_entity_1.PlaceSyria)),
|
||||
__param(1, (0, typeorm_1.InjectRepository)(place_jordan_entity_1.PlaceJordan)),
|
||||
__param(2, (0, typeorm_1.InjectRepository)(place_egypt_entity_1.PlaceEgypt)),
|
||||
__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", [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])
|
||||
__param(3, (0, typeorm_1.InjectRepository)(place_iraq_entity_1.PlaceIraq)),
|
||||
__param(4, (0, typeorm_1.InjectRepository)(osm_area_entity_1.OsmArea)),
|
||||
__param(5, (0, typeorm_1.InjectRepository)(osm_point_with_area_entity_1.OsmPointWithArea)),
|
||||
__param(6, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
|
||||
__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, typeof (_f = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _f : Object, Object])
|
||||
], GeocodingService);
|
||||
//# sourceMappingURL=geocoding.service.js.map
|
||||
Reference in New Issue
Block a user