"use strict"; const db = require("../../models/index"); const sequelize = require("sequelize"); const Op = sequelize.Op; const { AD_CATEGORY } = require("../../common/enums"); const bulkUpsertRealEstates = async realEstateData => { try { const fieldsToUpdateIfDuplicate = [ "realEstateType", "adType", "price", "area", "streetNumber", "streetName", "locality", "municipality", "city", "region", "entity", "country", "locationLat", "locationLong", "title", "shortDescription", "longDescription", "gardenSize", "adStatus", "updatedAt", "renewedDate", "numberOfRooms", "numberOfFloors", "floor", "accessRoadType", "heatingType", "furnishingType", "balcony", "newBuilding", "elevator", "water", "electricity", "drainageSystem", "registeredInZkBooks", "recentlyAdapted", "parking", "garage", "gas", "antiTheftDoor", "airCondition", "phoneConnection", "cableTV", "internet", "basementAttic", "storeRoom", "videoSurveillance", "alarm", "suitableForStudents", "includingBills", "animalsAllowed", "pool", "urbanPlanPermit", "buildingPermit", "utilityConnection", "distanceToRiver", "numberOfViewsAgency" ]; const order = [["updatedAt", "desc"]]; return await db.RealEstate.bulkCreate(realEstateData, { updateOnDuplicate: fieldsToUpdateIfDuplicate, returning: true, order }); } catch (e) { console.log("Error bulk upserting realEstates : ", e); } }; const getRealEstateById = async id => { return db.RealEstate.findByPk(id); }; const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => { const { priceMin, priceMax, sizeMin, sizeMax, adType, realEstateType, areaToSearch, gardenSizeMin, gardenSizeMax, numberOfRoomsMin, numberOfRoomsMax, numberOfFloorsMin, numberOfFloorsMax, floorMin, floorMax, includeIncompleteAds, includeWithoutPrice, balcony, elevator, newBuilding, accessRoadType } = searchRequest; //Needed for defining which attribute should exist or not const realEstateTypeObject = AD_CATEGORY[realEstateType]; const longitudeColumn = sequelize.col("locationLong"); const latitudeColumn = sequelize.col("locationLat"); const pointGeometry = sequelize.fn( "ST_Point", longitudeColumn, latitudeColumn ); const pointWithSRID = sequelize.fn("ST_SetSRID", pointGeometry, 4326); const areaToSearchAsGeometry = sequelize.fn( "ST_GeomFromGeoJSON", JSON.stringify(areaToSearch) ); const areaToSearchWithSRID = sequelize.fn( "ST_SetSRID", areaToSearchAsGeometry, 4326 ); const contains = sequelize.fn( "ST_Contains", areaToSearchWithSRID, pointWithSRID ); const geoSearchQueryPart = sequelize.where(contains, true); //General queries contain only attributes that are defined for every searchreq //Query for case of complete ads const query = { adType, realEstateType, area: { [Op.lte]: sizeMax, [Op.gte]: sizeMin }, [Op.and]: geoSearchQueryPart }; //Query for case of incomplete ads const queryIncludeIncomplete = { adType, realEstateType, area: { [Op.or]: { [Op.and]: { [Op.lte]: sizeMax, [Op.gte]: sizeMin }, [Op.is]: null } }, [Op.and]: geoSearchQueryPart }; //Is user unchecked includeWithoutPrice FALSE then it shouldn't return null values of price //If not then null values are accepted (this is DEFAULT) //includeIncpompleteAds does not have effect on price query if (includeWithoutPrice) { query.price = { [Op.or]: { [Op.and]: { [Op.lte]: priceMax, [Op.gte]: priceMin }, [Op.is]: null } }; queryIncludeIncomplete.price = { [Op.or]: { [Op.and]: { [Op.lte]: priceMax, [Op.gte]: priceMin }, [Op.is]: null } }; } else { query.price = { [Op.and]: { [Op.lte]: priceMax, [Op.gte]: priceMin } }; queryIncludeIncomplete.price = { [Op.and]: { [Op.lte]: priceMax, [Op.gte]: priceMin } }; } //Every other attribute is checked separately and included in query only if it is defined for real estate type if ( realEstateTypeObject.hasGardenSize && gardenSizeMax != null && gardenSizeMin != null ) { query.gardenSize = { [Op.lte]: gardenSizeMax, [Op.gte]: gardenSizeMin }; queryIncludeIncomplete.gardenSize = { [Op.or]: { [Op.and]: { [Op.lte]: gardenSizeMax, [Op.gte]: gardenSizeMin }, [Op.is]: null } }; } if ( realEstateTypeObject.hasNumberOfRoom && numberOfRoomsMin != null && numberOfRoomsMax != null ) { query.numberOfRooms = { [Op.lte]: numberOfRoomsMax, [Op.gte]: numberOfRoomsMin }; queryIncludeIncomplete.numberOfRooms = { [Op.or]: { [Op.and]: { [Op.lte]: numberOfRoomsMax, [Op.gte]: numberOfRoomsMin }, [Op.is]: null } }; } if ( realEstateTypeObject.hasNumberOfFloors && numberOfFloorsMin != null && numberOfFloorsMax != null ) { query.numberOfFloors = { [Op.lte]: numberOfFloorsMax, [Op.gte]: numberOfFloorsMin }; queryIncludeIncomplete.numberOfFloors = { [Op.or]: { [Op.and]: { [Op.lte]: numberOfFloorsMax, [Op.gte]: numberOfFloorsMin }, [Op.is]: null } }; } if ( realEstateTypeObject.hasFloorProp && floorMin != null && floorMax != null ) { query.floor = { [Op.lte]: floorMax, [Op.gte]: floorMin }; queryIncludeIncomplete.floor = { [Op.or]: { [Op.and]: { [Op.lte]: floorMax, [Op.gte]: floorMin }, [Op.is]: null } }; } //Logic for balcony, newBuilding and elevator from users side //If true is checked, then I want characteristic to be true but, //if it is not checked, then I dont care - it can be null or false or true if (realEstateTypeObject.hasBalconyProp && balcony === true) { query.balcony = { [Op.eq]: balcony }; queryIncludeIncomplete.balcony = { [Op.or]: { [Op.eq]: balcony, [Op.is]: null } }; } if (realEstateTypeObject.hasNewBuildingProp && newBuilding === true) { query.newBuilding = { [Op.eq]: newBuilding }; queryIncludeIncomplete.newBuilding = { [Op.or]: { [Op.eq]: newBuilding, [Op.is]: null } }; } if (realEstateTypeObject.hasElevatorProp && elevator === true) { query.elevator = { [Op.eq]: elevator }; queryIncludeIncomplete.elevator = { [Op.or]: { [Op.eq]: elevator, [Op.is]: null } }; } //If user wants 'ANY' road type acces then it is not included in query - //returns every road type and null values if (accessRoadType !== "ANY") { query.accessRoadType = { [Op.eq]: accessRoadType }; queryIncludeIncomplete.accessRoadType = { [Op.or]: { [Op.eq]: accessRoadType, [Op.is]: null } }; } //When includeIncompleteAds are not defined - null it will consider it true const order = [["updatedAt", "desc"]]; return db.RealEstate.findAll({ where: includeIncompleteAds || includeIncompleteAds == null ? queryIncludeIncomplete : query, limit: maxResults, order }); }; module.exports = { bulkUpsertRealEstates, getRealEstateById, findRealEstatesForSearchRequest };