import { Inject, Injectable } from '@nestjs/common'; import { BusinessListing, CommercialPropertyListing, ListingCriteria, ListingType, ImageProperty, ListingCategory } from '../models/main.model.js'; import { convertStringToNullUndefined } from '../utils.js'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; import { EntityData, EntityId, Repository, Schema, SchemaDefinition } from 'redis-om'; import { REDIS_CLIENT } from '../redis/redis.module.js'; @Injectable() export class ListingsService { schemaNameBusiness:ListingCategory={name:'business'} schemaNameCommercial:ListingCategory={name:'commercialProperty'} businessListingRepository:Repository; commercialPropertyListingRepository:Repository; baseListingSchemaDef : SchemaDefinition = { id: { type: 'string' }, userId: { type: 'string' }, listingsCategory: { type: 'string' }, title: { type: 'string' }, description: { type: 'string' }, country: { type: 'string' }, state:{ type: 'string' }, city:{ type: 'string' }, zipCode: { type: 'number' }, type: { type: 'string' }, price: { type: 'number' }, favoritesForUser:{ type: 'string[]' }, hideImage:{ type: 'boolean' }, draft:{ type: 'boolean' }, created:{ type: 'date' }, updated:{ type: 'date' } } businessListingSchemaDef : SchemaDefinition = { ...this.baseListingSchemaDef, salesRevenue: { type: 'number' }, cashFlow: { type: 'number' }, employees: { type: 'number' }, established: { type: 'number' }, internalListingNumber: { type: 'number' }, realEstateIncluded:{ type: 'boolean' }, leasedLocation:{ type: 'boolean' }, franchiseResale:{ type: 'boolean' }, supportAndTraining: { type: 'string' }, reasonForSale: { type: 'string' }, brokerLicencing: { type: 'string' }, internals: { type: 'string' }, } commercialPropertyListingSchemaDef : SchemaDefinition = { ...this.baseListingSchemaDef, imageNames:{ type: 'string[]' }, } businessListingSchema = new Schema(this.schemaNameBusiness.name,this.businessListingSchemaDef, { dataStructure: 'JSON' }) commercialPropertyListingSchema = new Schema(this.schemaNameCommercial.name,this.commercialPropertyListingSchemaDef, { dataStructure: 'JSON' }) constructor(@Inject(REDIS_CLIENT) private readonly redis: any, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger){ this.businessListingRepository = new Repository(this.businessListingSchema, redis); this.commercialPropertyListingRepository = new Repository(this.commercialPropertyListingSchema, redis) this.businessListingRepository.createIndex(); this.commercialPropertyListingRepository.createIndex(); } async saveListing(listing: BusinessListing | CommercialPropertyListing) { const repo=listing.listingsCategory==='business'?this.businessListingRepository:this.commercialPropertyListingRepository; let result if (listing.id){ result = await repo.save(listing.id,listing as any) } else { result = await repo.save(listing as any) listing.id=result[EntityId]; result = await repo.save(listing.id,listing as any) } return result; } async getCommercialPropertyListingById(id: string): Promise{ return await this.commercialPropertyListingRepository.fetch(id) as unknown as CommercialPropertyListing; } async getBusinessListingById(id: string) { return await this.businessListingRepository.fetch(id) } async getBusinessListingByUserId(userid:string){ return await this.businessListingRepository.search().where('userId').equals(userid).return.all() } async deleteBusinessListing(id: string){ return await this.businessListingRepository.remove(id); } async deleteCommercialPropertyListing(id: string){ return await this.commercialPropertyListingRepository.remove(id); } async getAllBusinessListings(start?: number, end?: number) { return await this.businessListingRepository.search().return.all() } async getAllCommercialListings(start?: number, end?: number) { return await this.commercialPropertyListingRepository.search().return.all() } async findBusinessListings(criteria:ListingCriteria): Promise { // let listings = await this.getAllBusinessListings(); // return this.find(criteria,listings); this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`); const result = await this.redis.ft.search('business:index','*',{LIMIT:{from:0,size:50}}); this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`); return result.documents; } async findCommercialPropertyListings(criteria:ListingCriteria): Promise { let listings = await this.getAllCommercialListings(); return this.find(criteria,listings); } async deleteAllBusinessListings(){ const ids = await this.getIdsForRepo(this.schemaNameBusiness.name); this.businessListingRepository.remove(ids); } async deleteAllcommercialListings(){ const ids = await this.getIdsForRepo(this.schemaNameCommercial.name); this.commercialPropertyListingRepository.remove(ids); } async getIdsForRepo(repoName:string, maxcount=100000){ let cursor = 0; let ids = []; do { const reply = await this.redis.scan(cursor, { MATCH: `${repoName}:*`, COUNT: maxcount }); cursor = reply.cursor; // Extrahiere die ID aus jedem Schlüssel und füge sie zur Liste hinzu ids = ids.concat(reply.keys.map(key => key.split(':')[1]).filter(id=>id!='index')); } while (cursor !== 0); return ids; } async find(criteria:ListingCriteria, listings: any[]): Promise { listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory); if (convertStringToNullUndefined(criteria.type)){ console.log(criteria.type); listings=listings.filter(l=>l.type===criteria.type); } if (convertStringToNullUndefined(criteria.state)){ console.log(criteria.state); listings=listings.filter(l=>l.state===criteria.state); } if (convertStringToNullUndefined(criteria.minPrice)){ console.log(criteria.minPrice); listings=listings.filter(l=>l.price>=Number(criteria.minPrice)); } if (convertStringToNullUndefined(criteria.maxPrice)){ console.log(criteria.maxPrice); listings=listings.filter(l=>l.price<=Number(criteria.maxPrice)); } if (convertStringToNullUndefined(criteria.realEstateChecked)){ console.log(criteria.realEstateChecked); listings=listings.filter(l=>l.realEstateIncluded); } if (convertStringToNullUndefined(criteria.category)){ console.log(criteria.category); listings=listings.filter(l=>l.category===criteria.category); } return listings } async updateImageOrder(id:string,imageOrder: ImageProperty[]){ const listing = await this.getCommercialPropertyListingById(id) as unknown as CommercialPropertyListing listing.imageOrder=imageOrder; this.saveListing(listing); } async deleteImage(listingid:string,name:string,){ const listing = await this.getCommercialPropertyListingById(listingid) as unknown as CommercialPropertyListing const index = listing.imageOrder.findIndex(im=>im.name===name); if (index>-1){ listing.imageOrder.splice(index,1); this.saveListing(listing); } } async addImage(id:string,imagename: string){ const listing = await this.getCommercialPropertyListingById(id) as unknown as CommercialPropertyListing listing.imageOrder.push({name:imagename,code:'',id:''}); this.saveListing(listing); } }