186 lines
8.3 KiB
TypeScript
186 lines
8.3 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
import { and, eq, gte, ilike, lte, ne, or, sql } from 'drizzle-orm';
|
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|
import { BusinessListing, CommercialPropertyListing } from 'src/models/db.model.js';
|
|
import { Logger } from 'winston';
|
|
import * as schema from '../drizzle/schema.js';
|
|
import { PG_CONNECTION, businesses, commercials } from '../drizzle/schema.js';
|
|
import { FileService } from '../file/file.service.js';
|
|
import { JwtUser, ListingCriteria, emailToDirName } from '../models/main.model.js';
|
|
|
|
@Injectable()
|
|
export class ListingsService {
|
|
constructor(
|
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
|
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
|
private fileService: FileService,
|
|
) {}
|
|
private getConditions(criteria: ListingCriteria, table: typeof businesses | typeof commercials, user: JwtUser): any[] {
|
|
const conditions = [];
|
|
if (criteria.type) {
|
|
conditions.push(eq(table.type, criteria.type));
|
|
}
|
|
if (criteria.state) {
|
|
conditions.push(eq(table.state, criteria.state));
|
|
}
|
|
if (criteria.minPrice) {
|
|
conditions.push(gte(table.price, criteria.minPrice));
|
|
}
|
|
if (criteria.maxPrice) {
|
|
conditions.push(lte(table.price, criteria.maxPrice));
|
|
}
|
|
if (criteria.realEstateChecked) {
|
|
conditions.push(eq(businesses.realEstateIncluded, true));
|
|
}
|
|
if (criteria.title) {
|
|
conditions.push(ilike(table.title, `%${criteria.title}%`));
|
|
}
|
|
return conditions;
|
|
}
|
|
// ##############################################################
|
|
// Listings general
|
|
// ##############################################################
|
|
|
|
async findCommercialPropertyListings(criteria: ListingCriteria, user: JwtUser): Promise<any> {
|
|
const start = criteria.start ? criteria.start : 0;
|
|
const length = criteria.length ? criteria.length : 12;
|
|
const conditions = this.getConditions(criteria, commercials, user);
|
|
if (!user || (!user?.roles?.includes('ADMIN') ?? false)) {
|
|
conditions.push(or(eq(commercials.draft, false), eq(commercials.imagePath, emailToDirName(user?.username))));
|
|
}
|
|
const [data, total] = await Promise.all([
|
|
this.conn
|
|
.select()
|
|
.from(commercials)
|
|
.where(and(...conditions))
|
|
.offset(start)
|
|
.limit(length),
|
|
this.conn
|
|
.select({ count: sql`count(*)` })
|
|
.from(commercials)
|
|
.where(and(...conditions))
|
|
.then(result => Number(result[0].count)),
|
|
]);
|
|
return { total, data };
|
|
}
|
|
async findBusinessListings(criteria: ListingCriteria, user: JwtUser): Promise<any> {
|
|
const start = criteria.start ? criteria.start : 0;
|
|
const length = criteria.length ? criteria.length : 12;
|
|
const conditions = this.getConditions(criteria, businesses, user);
|
|
if (!user || (!user?.roles?.includes('ADMIN') ?? false)) {
|
|
conditions.push(or(eq(businesses.draft, false), eq(businesses.imageName, emailToDirName(user?.username))));
|
|
}
|
|
const [data, total] = await Promise.all([
|
|
this.conn
|
|
.select()
|
|
.from(businesses)
|
|
.where(and(...conditions))
|
|
.offset(start)
|
|
.limit(length),
|
|
this.conn
|
|
.select({ count: sql`count(*)` })
|
|
.from(businesses)
|
|
.where(and(...conditions))
|
|
.then(result => Number(result[0].count)),
|
|
]);
|
|
return { total, data };
|
|
}
|
|
async findCommercialPropertiesById(id: string, user: JwtUser): Promise<CommercialPropertyListing> {
|
|
let result = await this.conn
|
|
.select()
|
|
.from(commercials)
|
|
.where(and(sql`${commercials.id} = ${id}`));
|
|
result = result.filter(r => !r.draft || r.imagePath === emailToDirName(user?.username) || user?.roles.includes('ADMIN'));
|
|
return result[0] as CommercialPropertyListing;
|
|
}
|
|
async findBusinessesById(id: string, user: JwtUser): Promise<CommercialPropertyListing> {
|
|
let result = await this.conn
|
|
.select()
|
|
.from(businesses)
|
|
.where(and(sql`${businesses.id} = ${id}`));
|
|
result = result.filter(r => !r.draft || r.imageName === emailToDirName(user?.username) || user?.roles.includes('ADMIN'));
|
|
return result[0] as BusinessListing;
|
|
}
|
|
async findCommercialPropertiesByEmail(email: string, user: JwtUser): Promise<CommercialPropertyListing[]> {
|
|
const conditions = [];
|
|
conditions.push(eq(commercials.imagePath, emailToDirName(email)));
|
|
if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) {
|
|
conditions.push(ne(commercials.draft, true));
|
|
}
|
|
return (await this.conn
|
|
.select()
|
|
.from(commercials)
|
|
.where(and(...conditions))) as CommercialPropertyListing[];
|
|
}
|
|
async findBusinessesByEmail(email: string, user: JwtUser): Promise<BusinessListing[]> {
|
|
const conditions = [];
|
|
conditions.push(eq(businesses.imageName, emailToDirName(email)));
|
|
if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) {
|
|
conditions.push(ne(businesses.draft, true));
|
|
}
|
|
return (await this.conn
|
|
.select()
|
|
.from(businesses)
|
|
.where(and(...conditions))) as CommercialPropertyListing[];
|
|
}
|
|
async findByImagePath(imagePath: string, serial: string): Promise<CommercialPropertyListing> {
|
|
const result = await this.conn
|
|
.select()
|
|
.from(commercials)
|
|
.where(and(sql`${commercials.imagePath} = ${imagePath}`, sql`${commercials.serialId} = ${serial}`));
|
|
return result[0] as CommercialPropertyListing;
|
|
}
|
|
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
|
data.created = new Date();
|
|
data.updated = new Date();
|
|
const [createdListing] = await this.conn.insert(table).values(data).returning();
|
|
return createdListing as BusinessListing | CommercialPropertyListing;
|
|
}
|
|
|
|
async updateCommercialPropertyListing(id: string, data: CommercialPropertyListing): Promise<BusinessListing | CommercialPropertyListing> {
|
|
data.updated = new Date();
|
|
data.created = new Date(data.created);
|
|
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
|
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
|
if (difference.length > 0) {
|
|
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
|
data.imageOrder = imageOrder;
|
|
}
|
|
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
|
return updateListing as BusinessListing | CommercialPropertyListing;
|
|
}
|
|
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing | CommercialPropertyListing> {
|
|
data.updated = new Date();
|
|
data.created = new Date(data.created);
|
|
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
|
return updateListing as BusinessListing | CommercialPropertyListing;
|
|
}
|
|
async deleteListing(id: string, table: typeof businesses | typeof commercials): Promise<void> {
|
|
await this.conn.delete(table).where(eq(table.id, id));
|
|
}
|
|
async getStates(table: typeof businesses | typeof commercials): Promise<any[]> {
|
|
return await this.conn
|
|
.select({ state: table.state, count: sql<number>`count(${table.id})`.mapWith(Number) })
|
|
.from(table)
|
|
.groupBy(sql`${table.state}`)
|
|
.orderBy(sql`count desc`);
|
|
}
|
|
// ##############################################################
|
|
// Images for commercial Properties
|
|
// ##############################################################
|
|
async deleteImage(imagePath: string, serial: string, name: string) {
|
|
const listing = (await this.findByImagePath(imagePath, serial)) as unknown as CommercialPropertyListing;
|
|
const index = listing.imageOrder.findIndex(im => im === name);
|
|
if (index > -1) {
|
|
listing.imageOrder.splice(index, 1);
|
|
await this.updateCommercialPropertyListing(listing.id, listing);
|
|
}
|
|
}
|
|
async addImage(imagePath: string, serial: string, imagename: string) {
|
|
const listing = (await this.findByImagePath(imagePath, serial)) as unknown as CommercialPropertyListing;
|
|
listing.imageOrder.push(imagename);
|
|
await this.updateCommercialPropertyListing(listing.id, listing);
|
|
}
|
|
}
|