import axios from "axios"; import { inject, injectable } from "inversify"; import { startSession } from "mongoose"; import { AddressRepo, CityRepo, ProvinceRepo } from "./address.repository"; import { SaveSellerAddressDTO, SaveUserAddressDTO } from "./DTO/userAddress.dto"; import { INominatimResponse } from "./interfaces/ILocation"; import { AddressMessage, CommonMessage, ShopMessage, UserMessage } from "../../common/enums/message.enum"; import { BadRequestError, InternalError } from "../../core/app/app.errors"; import { IOCTYPES } from "../../IOC/ioc.types"; import { UserRepository } from "../user/user.repository"; import { ICity } from "./models/city.model"; import { IProvince } from "./models/province.model"; import { OwnerRef } from "../shop/models/Abstraction/IShop"; import { ShopRepo } from "../shop/shop.repository"; @injectable() class AddressService { private NOMINATIM_URL = "https://nominatim.openstreetmap.org/reverse"; // https://nominatim.openstreetmap.org/reverse?lat=34.198770&lon=49.663600&format=json&accept-language=fa // private MAP_IR_URL = "https://map.ir/reverse"; // private MAP_IR_API_KEY = process.env.MAP_API_KEY; // private NEHSNAN_URL = "https://api.neshan.org/v5/reverse"; // private NESHAN_API_KEY = process.env.NESHAN_API_KEY; @inject(IOCTYPES.UserRepository) private userRepository: UserRepository; @inject(IOCTYPES.AddressRepo) addressRepo: AddressRepo; @inject(IOCTYPES.CityRepo) private cityRepo: CityRepo; @inject(IOCTYPES.ProvinceRepo) private provinceRepo: ProvinceRepo; @inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo; //############################################################# //############################################################# async getLocationWithGeo(geo: { lat: string; lon: string }) { const location = await this.getAddressFromCoordinates(geo.lat, geo.lon); let province: IProvince | null; let city: ICity | null; //TODO:fix this const provinceName = location?.address?.province?.replace(/^استان\s*/, "").trim() ?? location?.address?.state?.replace(/^استان\s*/, "").trim(); const cityName = location.address?.town ? location.address?.town?.replace(/^شهر\s*/, "").trim() : location?.address?.city; if (!provinceName || !cityName) throw new BadRequestError(AddressMessage.InvalidLocation); province = await this.provinceRepo.model.findOne({ name: provinceName }); if (!province) province = await this.provinceRepo.model.create({ name: provinceName }); city = await this.cityRepo.model.findOne({ name: cityName }); if (!city) city = await this.cityRepo.model.create({ name: cityName, province: province._id }); const displayNameArray = location.display_name.split(",").map((item) => item.trim()); const filteredDisplayNameArray = location.address.postcode ? displayNameArray.filter((item) => !item.includes(location.address.postcode as string)) : displayNameArray; const address = filteredDisplayNameArray.reverse().join(",").trim(); return { province: provinceName, provinceId: province._id, city: city.name, cityId: city._id, address, region: location.address.suburb, }; } //############################################################# //############################################################# async getUserAddress(addressId: string) { const userAddress = await this.addressRepo.findById(addressId); if (!userAddress) throw new BadRequestError([AddressMessage.UserShouldHaveAddress, AddressMessage.NotFound]); return userAddress; } //############################################################# //############################################################# async setUserAddress(userId: string, addressDto: SaveUserAddressDTO) { const session = await startSession(); session.startTransaction(); try { const address = await this.addressRepo.model.create( [ { address: addressDto.address, city: addressDto.cityId, province: addressDto.provinceId, plaque: addressDto.plaque, postalCode: addressDto.postalCode, lat: addressDto.lat, lon: addressDto.lon, }, ], { session }, ); const updatedUser = await this.userRepository.model.findByIdAndUpdate(userId, { address: address[0]._id }, { session }); if (!updatedUser) throw new BadRequestError(CommonMessage.NotValid); await session.commitTransaction(); return { message: UserMessage.UserUpdated, address: address[0], }; // } catch (error) { await session.abortTransaction(); throw error; // } finally { await session.endSession(); } } // async setSellerShopAddress(sellerId: string, addressDto: SaveSellerAddressDTO) { const session = await startSession(); session.startTransaction(); try { const shop = await this.shopRepo.model.findOne({ owner: sellerId, ownerRef: OwnerRef.SELLER }).session(session); if (!shop) throw new BadRequestError(ShopMessage.ShopNotFound); const address = await this.addressRepo.model.create( [ { address: addressDto.address, city: addressDto.cityId, province: addressDto.provinceId, plaque: addressDto.plaque, // postalCode: addressDto.postalCode, lat: addressDto.lat, lon: addressDto.lon, }, ], { session }, ); shop.address = address[0]._id; await shop.save({ session }); await session.commitTransaction(); return { message: AddressMessage.Updated, address: address[0], }; } catch (error) { await session.abortTransaction(); throw error; } finally { await session.endSession(); } } //helpers //########################## private async getAddressFromCoordinates(lat: string, lon: string) { try { const response = await axios.get(this.NOMINATIM_URL, { params: { lat, lon, format: "json", "accept-language": "fa" }, headers: { "User-Agent": "Mozilla/5.0", }, }); const location = response.data as INominatimResponse; if (!location) throw new BadRequestError("Address not found for the given coordinates."); return location; } catch (error) { console.log(error); throw new InternalError([`Failed to get address from coordinates: ${error}`]); } } } export { AddressService };