"use strict"; const db = require("../../models/index"); const sequelize = require("sequelize"); const Op = sequelize.Op; const { AD_CATEGORY } = require("../../common/enums"); const getSearchRequest = async searchRequestId => { try { return await db.SearchRequest.findByPk(searchRequestId); } catch (error) { console.log("searchrequest.js", error); return null; } }; const createSearchRequest = async (searchRequestFields = {}) => { return await db.SearchRequest.create(searchRequestFields); }; const findSearchRequestsForRealEstate = async realEstate => { const { price, area, adType, realEstateType, locationLat, locationLong, accessRoadType, balcony, newBuilding, elevator, gardenSize, numberOfRooms, numberOfFloors, floor } = realEstate; if (!locationLat || !locationLong) { return []; } const stGeometry = sequelize.fn( "ST_GEOMFROMTEXT", `POINT (${locationLong} ${locationLat})`, 4326 ); const areaToSearchColumn = sequelize.col("areaToSearch"); const contains = sequelize.fn("ST_Contains", areaToSearchColumn, stGeometry); const geoSearchQueryPart = sequelize.where(contains, true); //Needed for defining which attribute should exist or not const realEstateTypeObject = AD_CATEGORY[realEstateType]; // ?? Needed to decide on including incomplete RealEstates data let checkForIncompleteWanted = false; //Attributes are checked separately to make different query parts //If real estate price is number then it searches for req that have priceMin and priceMax //If real estate price is null it searches for req that accept ads without price //User always defines price and area (sliders) - not null in search req let priceQuery = {}; if (price != null) { priceQuery = { [Op.and]: [ { priceMin: { [Op.lte]: price } }, { priceMax: { [Op.gte]: price } } ] }; } else { priceQuery = { includeWithoutPrice: { [Op.eq]: true } }; } let areaQuery = {}; if (area != null) { areaQuery = { [Op.and]: [ { sizeMin: { [Op.lte]: area } }, { sizeMax: { [Op.gte]: area } } ] }; } else { checkForIncompleteWanted = true; } //Other attributes can be defined or not depending on RealEstate type //we check what to include in query based on real estate type object let gardenSizeQuery = {}; if (realEstateTypeObject.hasGardenSize) { if (gardenSize != null) { gardenSizeQuery = { [Op.and]: [ { gardenSizeMin: { [Op.lte]: gardenSize } }, { gardenSizeMax: { [Op.gte]: gardenSize } } ] }; } else { checkForIncompleteWanted = true; } } let numberOfRoomsQuery = {}; if (realEstateTypeObject.hasNumberOfRoom) { if (numberOfRooms != null) { //If real estate has defined number of rooms ex. 3 it returns req // that accepts 3 rooms or ones that don't have defined number - null //Ex. they didnt choose advanced filters at all numberOfRoomsQuery = { [Op.and]: [ { numberOfRoomsMin: { [Op.or]: { [Op.lte]: numberOfRooms, [Op.is]: null } } }, { numberOfRoomsMax: { [Op.or]: { [Op.gte]: numberOfRooms, [Op.is]: null } } } ] }; } else { // If real estate dont have defined number of rooms ex. null //It returns requests that didn't choose number of rooms - also null //Or ones that picked some values but also picked to includeIncomplete ads (or default) numberOfRoomsQuery = { [Op.or]: [ { [Op.and]: [ { numberOfRoomsMin: { [Op.is]: null } }, { numberOfRoomsMax: { [Op.is]: null } } ] }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] }; } } //Same logic for number of Floors and floors let numberOfFloorsQuery = {}; if (realEstateTypeObject.hasNumberOfFloors) { if (numberOfFloors != null) { numberOfFloorsQuery = { [Op.and]: [ { numberOfFloorsMin: { [Op.or]: { [Op.lte]: numberOfFloors, [Op.is]: null } } }, { numberOfFloorsMax: { [Op.or]: { [Op.gte]: numberOfFloors, [Op.is]: null } } } ] }; } else { numberOfFloorsQuery = { [Op.or]: [ { [Op.and]: [ { numberOfFloorsMin: { [Op.is]: null } }, { numberOfFloorsMax: { [Op.is]: null } } ] }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] }; } } let floorQuery = {}; if (realEstateTypeObject.hasFloorProp) { if (floor != null) { floorQuery = { [Op.and]: [ { floorMin: { [Op.or]: { [Op.lte]: floor, [Op.is]: null } } }, { floorMax: { [Op.or]: { [Op.gte]: floor, [Op.is]: null } } } ] }; } else { floorQuery = { [Op.or]: [ { [Op.and]: [ { floorMin: { [Op.is]: null } }, { floorMax: { [Op.is]: null } } ] }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] }; } } //Logic for balcony, newBuilding and elevator //If user dont check checkbox for ex. elevator it does not mean he only wants no elevator //If real estate characteristic =true find all req, one that wants charachertistic or dont care - dont need query //If real estate characteristic = false, find all req exept for ones that wants characteristic to be true //If real estate characteristic = null, dont know if true or false, find req that dont care or want char and want incomplete ads let balconyQuery = {}; if (realEstateTypeObject.hasBalconyProp && balcony !== true) { if (balcony === false) { balconyQuery = { balcony: { [Op.ne]: true } }; } else if (balcony === null) { balconyQuery = { [Op.or]: [ { balcony: { [Op.ne]: true } }, { [Op.and]: [ { balcony: { [Op.eq]: true } }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] } ] }; } } let newBuildingQuery = {}; if (realEstateTypeObject.hasNewBuildingProp && newBuilding !== true) { if (newBuilding === false) { newBuildingQuery = { newBuilding: { [Op.ne]: true } }; } else if (newBuilding === null) { newBuildingQuery = { [Op.or]: [ { newBuilding: { [Op.ne]: true } }, { [Op.and]: [ { newBuilding: { [Op.eq]: true } }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] } ] }; } } let elevatorQuery = {}; if (realEstateTypeObject.hasElevatorProp && elevator !== true) { if (elevator === false) { elevatorQuery = { elevator: { [Op.ne]: true } }; } else if (elevator === null) { elevatorQuery = { [Op.or]: [ { elevator: { [Op.ne]: true } }, { [Op.and]: [ { elevator: { [Op.eq]: true } }, { includeIncompleteAds: { [Op.or]: { [Op.eq]: true, [Op.is]: null } } } ] } ] }; } } //General query consists of each individual query const query = { adType, realEstateType, subscribed: true, [Op.and]: [ geoSearchQueryPart, priceQuery, areaQuery, gardenSizeQuery, numberOfRoomsQuery, numberOfFloorsQuery, floorQuery, balconyQuery, newBuildingQuery, elevatorQuery ] }; //AccessRoadType is defined - should exists for each ad and estate type if (accessRoadType != null) { query.accessRoadType = { [Op.or]: { [Op.like]: "ANY", [Op.eq]: accessRoadType } }; } else { //Null values are returned for user request that wanted ANY acces road type query.accessRoadType = { [Op.eq]: "ANY" }; } //Tag to check if incomplete ads are accepted in query which is default if (checkForIncompleteWanted) { query.includeIncompleteAds = { [Op.or]: { [Op.eq]: true, [Op.is]: null } }; } return await db.SearchRequest.findAll({ where: query }); }; module.exports = { getSearchRequest, createSearchRequest, findSearchRequestsForRealEstate };