173 lines
6.6 KiB
TypeScript
173 lines
6.6 KiB
TypeScript
import { Op, sequelize, House as Hou } from "../../../db/mysql/db.config";
|
|
import { House } from "../../../domain/house";
|
|
import type { ConstrueHouseCommand } from "../../command/house/construeHouseCommand";
|
|
|
|
export class ConstrueHouseCommandHandler {
|
|
private constructor() {}
|
|
|
|
public static async create(construeHouseCommand: ConstrueHouseCommand) {
|
|
const houseCandidates: House[] = [];
|
|
let i = 0;
|
|
|
|
construeHouseCommand.rawHouseList().forEach((houseCandidate: object) => {
|
|
houseCandidates.push(
|
|
House.create(
|
|
`${++i}`,
|
|
houseCandidate.title,
|
|
houseCandidate.url,
|
|
houseCandidate.neighborhood,
|
|
houseCandidate.area,
|
|
houseCandidate.rooms,
|
|
houseCandidate.price,
|
|
houseCandidate.description,
|
|
houseCandidate.pic,
|
|
houseCandidate.baths,
|
|
houseCandidate.level,
|
|
houseCandidate.phone
|
|
));
|
|
});
|
|
|
|
const proposedHouses = ConstrueHouseCommandHandler.findHouseByTitleAndExactPriceAndRoomsAndBaths(houseCandidates);
|
|
|
|
return proposedHouses;
|
|
}
|
|
|
|
async execute(): Promise<void> {
|
|
}
|
|
|
|
private static async findHouseByTitleAndExactPriceAndRoomsAndBaths(houseCandidates: any): Promise<String[]> {
|
|
const mergedHouses: any[] = [];
|
|
|
|
// Usamos un bucle for normal para manejar bien el await dentro del ciclo
|
|
for (let i = 0; i < houseCandidates.length; i++) {
|
|
const houseCandidate = houseCandidates[i];
|
|
const sanitizedTitle = houseCandidate.title.replace(/'/g, "\\'");
|
|
|
|
// Esperamos a que la búsqueda en la base de datos se resuelva
|
|
const matchingHouses = await Hou.findAll({
|
|
where: {
|
|
[Op.and]: [
|
|
{ price: houseCandidate.rawPrice },
|
|
{ rooms: houseCandidate.rooms },
|
|
{ area: houseCandidate.area },
|
|
{ baths: houseCandidate.baths },
|
|
sequelize.literal(`MATCH(title) AGAINST('${sanitizedTitle}' IN NATURAL LANGUAGE MODE)`)
|
|
]
|
|
}
|
|
});
|
|
|
|
const resultHouse = ConstrueHouseCommandHandler.mergeHouseProperties(matchingHouses);
|
|
|
|
// Si hay coincidencias, añadimos los resultados al array de casas encontradas
|
|
if (matchingHouses.length > 0) {
|
|
mergedHouses.push(resultHouse);
|
|
|
|
// Eliminamos todas las casas que coincidan con los resultados obtenidos
|
|
houseCandidates = houseCandidates.filter((candidate: any) => {
|
|
return !matchingHouses.some((matchedHouse: any) => {
|
|
// Aquí puedes definir los criterios para eliminar la casa original.
|
|
// Por ejemplo, comparando el ID, precio, habitaciones, etc.
|
|
return (
|
|
candidate.rawPrice === matchedHouse.price &&
|
|
candidate.rooms === matchedHouse.rooms &&
|
|
candidate.area === matchedHouse.area &&
|
|
candidate.baths === matchedHouse.baths
|
|
);
|
|
});
|
|
});
|
|
|
|
// Como hemos modificado el array, restamos 1 al índice
|
|
i = -1; // Reiniciamos el índice para volver a iterar el array actualizado
|
|
}
|
|
}
|
|
|
|
return mergedHouses;
|
|
|
|
// const sanitizedTitle = house.title.replace(/'/g, "\\'");
|
|
|
|
// const matchingHouses = Hou.findAll({
|
|
// where: {
|
|
// [Op.and]: [
|
|
// {price: house.rawPrice},
|
|
// {rooms: house.rooms},
|
|
// {area: house.area},
|
|
// {baths: house.baths},
|
|
// sequelize.literal(`MATCH(title) AGAINST('${sanitizedTitle}' IN NATURAL LANGUAGE MODE)`)
|
|
// ]
|
|
// }
|
|
// });
|
|
|
|
// return matchingHouses;
|
|
}
|
|
|
|
private static mergeHouseProperties(houses: any[]): any {
|
|
if (!Array.isArray(houses)) {
|
|
throw new Error("El parámetro 'houses' debe ser un array.");
|
|
}
|
|
|
|
const mergedHouse = {
|
|
id: ConstrueHouseCommandHandler.getLongestString(houses.map(h => h.id)) || Math.floor(Math.random() * 100000000).toString().padStart(8, '0'),
|
|
title: ConstrueHouseCommandHandler.getLongestString(houses.map(h => h.title)),
|
|
url: ConstrueHouseCommandHandler.joinByComma(houses.map(h => h.url)),
|
|
neighborhood: ConstrueHouseCommandHandler.getLongestString(houses.map(h => h.neighborhood)),
|
|
area: houses[0]?.area || 0, // Asumo que el área es la misma en todos los duplicados
|
|
rooms: houses[0]?.rooms || 0, // Asumo que el número de habitaciones es el mismo
|
|
price: houses[0]?.price || 0, // Asumo que el precio es el mismo
|
|
description: ConstrueHouseCommandHandler.getLongestString(houses.map(h => h.description)),
|
|
pic: ConstrueHouseCommandHandler.joinByComma(houses.map(h => h.pic)),
|
|
baths: houses[0]?.baths || 0, // Asumo que el número de baños es el mismo
|
|
level: ConstrueHouseCommandHandler.getLongestString(houses.map(h => h.level)),
|
|
phone: ConstrueHouseCommandHandler.joinByComma(houses.map(h => h.phone)) // Concatenar los teléfonos
|
|
};
|
|
|
|
return mergedHouse;
|
|
}
|
|
|
|
private static getLongestString(strings: string[]): string {
|
|
const preSelectedString = strings
|
|
.filter(str => str && !str.startsWith('<a href=')) // Ignora las cadenas que comienzan con '<a href='
|
|
.reduce((longest, current) => current && current.length > longest.length ? current : longest, "");
|
|
|
|
const alternativeString = strings.reduce((longest, current) => current && current.length > longest.length ? current : longest, "");
|
|
|
|
return preSelectedString !== "" ? preSelectedString : ConstrueHouseCommandHandler.parseLinks(alternativeString);
|
|
}
|
|
|
|
private static joinByComma(items: string[]): string {
|
|
return items.filter(Boolean).join(', ');
|
|
}
|
|
|
|
private static parseLinks(htmlString) {
|
|
// Regex para identificar las etiquetas <a> con el href y el contenido interno
|
|
const anchorTagRegex = /<a href="\/comprar\/(.*?)18.*?"[^>]*>(.*?)<\/a>/g;
|
|
let match;
|
|
let result = "";
|
|
|
|
while ((match = anchorTagRegex.exec(htmlString)) !== null) {
|
|
let urlPart = match[1]; // La parte que queremos desde el href
|
|
let innerText = match[2].trim(); // El texto dentro de las etiquetas <a>
|
|
|
|
// Si innerText está vacío, tomamos la parte de la URL
|
|
if (!innerText) {
|
|
result= ConstrueHouseCommandHandler.snakeToSpaceCase(urlPart);
|
|
} else {
|
|
result = innerText;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static snakeToSpaceCase(snakeCaseString) {
|
|
// Reemplazar tanto los guiones bajos (_) como los guiones medios (-) por espacios
|
|
let spaceCaseString = snakeCaseString.replace(/[_-]/g, ' ');
|
|
|
|
// Capitalizar la primera letra de cada palabra
|
|
spaceCaseString = spaceCaseString.split(' ').map(word => {
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}).join(' ');
|
|
|
|
return spaceCaseString;
|
|
}
|
|
|
|
} |