Files
shop-api/src/modules/address/address.service.ts
T
mahyargdz be82059172 first
2025-09-14 15:15:03 +03:30

180 lines
6.5 KiB
TypeScript

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 };