377 lines
8.3 KiB
JavaScript
377 lines
8.3 KiB
JavaScript
"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 => {
|
|
try {
|
|
return db.RealEstate.findByPk(id);
|
|
} catch (error) {
|
|
console.log("realEstate.js", error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const createRealEstate = async (realEstateFields = {}) => {
|
|
return await db.RealEstate.create(realEstateFields);
|
|
};
|
|
|
|
const findRealEstateByAgencyId = async kiviId => {
|
|
try {
|
|
return db.RealEstate.findOne({
|
|
where: { agencyObjectId: kiviId }
|
|
});
|
|
} catch (error) {
|
|
console.log("realEstate.js", error);
|
|
return null;
|
|
}
|
|
};
|
|
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
|
|
}
|
|
};
|
|
}
|
|
//Query only for real estated that are not deleted
|
|
query.deleted = {
|
|
[Op.eq]: false
|
|
};
|
|
queryIncludeIncomplete.deleted = {
|
|
[Op.eq]: false
|
|
};
|
|
|
|
const order = [["updatedAt", "desc"]];
|
|
|
|
return db.RealEstate.findAll({
|
|
where: includeIncompleteAds ? queryIncludeIncomplete : query,
|
|
limit: maxResults,
|
|
order
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
bulkUpsertRealEstates,
|
|
getRealEstateById,
|
|
createRealEstate,
|
|
findRealEstatesForSearchRequest,
|
|
findRealEstateByAgencyId
|
|
};
|