Umstellung postgres 2. part

This commit is contained in:
2024-04-15 22:05:20 +02:00
parent 7d10080069
commit c4cdcf4505
17 changed files with 18327 additions and 1957 deletions

View File

@@ -1 +0,0 @@
FT.CREATE listingsIndex ON JSON PREFIX 1 listings: SCHEMA $.location AS location TAG SORTABLE $.price AS price NUMERIC SORTABLE $.listingsCategory AS listingsCategory TAG SORTABLE $.type AS type TAG SORTABLE

View File

@@ -1,12 +0,0 @@
import { jsonb, varchar } from 'drizzle-orm/pg-core';
import { pgTable, text, primaryKey } from 'drizzle-orm/pg-core';
export const businesses_json = pgTable('businesses_json', {
id: varchar('id', { length: 255 }).primaryKey(),
data: jsonb('data'),
});
export type BusinessesJson = {
id: string;
data: Record<string, any>;
};

View File

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
import { drizzle } from 'drizzle-orm/node-postgres';
import pkg from 'pg';
const { Pool } = pkg;
import * as schema from './businesses_json.model.js';
import * as schema from './schema.js';
import { ConfigService } from '@nestjs/config';
import { jsonb, varchar } from 'drizzle-orm/pg-core';
import { PG_CONNECTION } from './schema.js';

View File

@@ -1,14 +1,32 @@
import { integer, serial, text, pgTable } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import { jsonb, varchar } from 'drizzle-orm/pg-core';
import { integer, serial, text, pgTable, timestamp, jsonb, varchar } from 'drizzle-orm/pg-core';
import { relations, sql } from 'drizzle-orm';
export const PG_CONNECTION = 'PG_CONNECTION';
export const businesses_json = pgTable('businesses_json', {
id: varchar('id', { length: 255 }).primaryKey(),
export const businesses_json = pgTable('businesses', {
id: varchar('id', { length: 255 }).primaryKey().default(sql`uuid_generate_v4()`),
data: jsonb('data'),
created: timestamp('created'),
updated: timestamp('updated'),
visits: integer('visits'),
last_visit: timestamp('last_visit'),
});
export const commercials_json = pgTable('commercials', {
id: varchar('id', { length: 255 }).primaryKey().default(sql`uuid_generate_v4()`),
data: jsonb('data'),
created: timestamp('created'),
updated: timestamp('updated'),
visits: integer('visits'),
last_visit: timestamp('last_visit'),
});
export const users = pgTable('users', {
id: varchar('id', { length: 255 }).primaryKey().default(sql`uuid_generate_v4()`),
data: jsonb('data'),
created: timestamp('created'),
updated: timestamp('updated'),
visits: integer('visits'),
last_visit: timestamp('last_visit'),
});
export type BusinessesJson = {
id: string;
data: Record<string, any>;

View File

@@ -4,7 +4,8 @@ import { convertStringToNullUndefined } from '../utils.js';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { ListingCriteria } from 'src/models/main.model.js';
import { ListingCriteria } from '../models/main.model.js';
import { businesses_json } from '../drizzle/schema.js';
@Controller('listings/business')
export class BusinessListingsController {
@@ -13,43 +14,34 @@ export class BusinessListingsController {
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get()
findAll(): any {
this.logger.info(`start findAll Listing`);
return this.listingsService.findListings();
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.findById(id);
return this.listingsService.findById(id,businesses_json);
}
@Get('user/:userid')
findByUserId(@Param('userid') userid:string): any {
return this.listingsService.findByUserId(userid);
return this.listingsService.findByUserId(userid,businesses_json);
}
@Post('search')
find(@Body() criteria: ListingCriteria): any {
return this.listingsService.findByState(criteria.state);
return this.listingsService.findListingsByCriteria(criteria,businesses_json);
}
@Post()
create(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.createListing(listing)
this.listingsService.createListing(listing,businesses_json)
}
@Put()
update(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.updateListing(listing.id,listing)
this.listingsService.updateListing(listing.id,listing,businesses_json)
}
@Delete(':id')
deleteById(@Param('id') id:string){
this.listingsService.deleteListing(id)
this.listingsService.deleteListing(id,businesses_json)
}
// @Delete('deleteAll')
// deleteAll(){
// this.listingsService.deleteAllBusinessListings()
// }
}

View File

@@ -4,7 +4,8 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from '../file/file.service.js';
import { CommercialPropertyListing, ImageProperty } from 'src/models/main.model.js';
import { CommercialPropertyListing, ImageProperty, ListingCriteria } from '../models/main.model.js';
import { commercials_json } from '../drizzle/schema.js';
@Controller('listings/commercialProperty')
export class CommercialPropertyListingsController {
@@ -12,40 +13,36 @@ export class CommercialPropertyListingsController {
constructor(private readonly listingsService:ListingsService,private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
// @Get(':id')
// findById(@Param('id') id:string): any {
// return this.listingsService.getCommercialPropertyListingById(id);
// }
// @Post('search')
// find(@Body() criteria: any): any {
// return this.listingsService.findCommercialPropertyListings(criteria);
// }
// @Put('imageOrder/:id')
// async changeImageOrder(@Param('id') id:string,@Body() imageOrder: ImageProperty[]) {
// this.listingsService.updateImageOrder(id, imageOrder)
// }
// /**
// * @param listing creates a new listing
// */
// @Post()
// save(@Body() listing: any){
// this.logger.info(`Save Listing`);
// this.listingsService.saveListing(listing)
// }
// /**
// * @param id deletes a listing
// */
// @Delete(':id')
// deleteById(@Param('id') id:string){
// this.listingsService.deleteCommercialPropertyListing(id)
// }
// @Delete('deleteAll')
// deleteAll(){
// this.listingsService.deleteAllcommercialListings()
// }
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.findById(id,commercials_json);
}
@Get('user/:userid')
findByUserId(@Param('userid') userid:string): any {
return this.listingsService.findByUserId(userid,commercials_json);
}
@Post('search')
find(@Body() criteria: ListingCriteria): any {
return this.listingsService.findByState(criteria.state,commercials_json);
}
@Post()
create(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.createListing(listing,commercials_json)
}
@Put()
update(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.updateListing(listing.id,listing,commercials_json)
}
@Delete(':id')
deleteById(@Param('id') id:string){
this.listingsService.deleteListing(id,commercials_json)
}
@Put('imageOrder/:id')
async changeImageOrder(@Param('id') id:string,@Body() imageOrder: ImageProperty[]) {
this.listingsService.updateImageOrder(id, imageOrder)
}
}

View File

@@ -10,7 +10,6 @@ import { BrokerListingsController } from './broker-listings.controller.js';
import { UserService } from '../user/user.service.js';
import { Client, Connection } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import { businesses_json } from '../drizzle/businesses_json.model.js';
import { DrizzleModule } from '../drizzle/drizzle.module.js';

View File

@@ -5,85 +5,113 @@ import {
ListingCriteria,
ListingType,
ImageProperty,
ListingCategory
ListingCategory,
ResponseBusinessListing
} from '../models/main.model.js';
import { convertStringToNullUndefined } from '../utils.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { EntityData, EntityId, Schema, SchemaDefinition } from 'redis-om';
import { eq, ilike, sql } from 'drizzle-orm';
import { BusinessesJson, PG_CONNECTION, businesses_json } from '../drizzle/schema.js';
import { SQL, eq, ilike, sql } from 'drizzle-orm';
import { BusinessesJson, PG_CONNECTION, businesses_json, commercials_json } from '../drizzle/schema.js';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as schema from '../drizzle/schema.js';
import { PgTableFn, PgTableWithColumns, QueryBuilder } from 'drizzle-orm/pg-core';
@Injectable()
export class ListingsService {
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,){
// this.businessListingRepository = new Repository(this.businessListingSchema, redis);
// this.commercialPropertyListingRepository = new Repository(this.commercialPropertyListingSchema, redis)
// this.businessListingRepository.createIndex();
// this.commercialPropertyListingRepository.createIndex();
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,) {
}
private buildWhereClause(criteria: ListingCriteria):SQL {
const finalSql = sql`1=1`;
finalSql.append(criteria.type?sql` AND data->>'type' = ${criteria.type}` : sql``)
finalSql.append(criteria.state ? sql` AND data->>'state' = ${criteria.state}` : sql``)
finalSql.append(criteria.minPrice ? sql` AND CAST(data->>'price' AS NUMERIC) >= ${parseFloat(criteria.minPrice)}` : sql``)
finalSql.append(criteria.maxPrice ? sql` AND CAST(data->>'price' AS NUMERIC) < ${parseFloat(criteria.maxPrice)}` : sql``)
finalSql.append(criteria.realEstateChecked !== undefined ? sql` AND CAST(data->>'realEstateIncluded' AS BOOLEAN) = ${criteria.realEstateChecked}` : sql``)
finalSql.append(criteria.title ? sql` AND LOWER(data->>'title') LIKE LOWER('%' || ${criteria.title} || '%')` : sql``)
return finalSql
}
// ##############################################################
// Listings general
// ##############################################################
private async findListings(table: typeof businesses_json | typeof commercials_json, criteria: ListingCriteria ,start = 0, length = 12): Promise<any> {
const whereClause = this.buildWhereClause(criteria)
const [data, total] = await Promise.all([
(await this.conn.select({ id:table.id, data: table.data }).from(table).where(whereClause).offset(start).limit(length)).map(e=>{
const ret = e.data as any
ret.id = e.id
return ret
}),
this.conn.select({ count: sql`count(*)` }).from(table).where(whereClause).then((result) => Number(result[0].count)),
]);
return { total, data };
}
async findById(id: string, table: typeof businesses_json | typeof commercials_json): Promise<BusinessListing|CommercialPropertyListing> {
const result = await this.conn.select({ data: table.data }).from(table).where(sql`${table.id} = ${id}`)
return result[0].data as BusinessListing
}
// ##############################################################
// ##############################################################
async createListing(newListing: { id: string; data: BusinessesJson }): Promise<BusinessesJson> {
const [createdListing] = await this.conn.insert(businesses_json).values(newListing).returning();
async findListingsByCriteria(criteria: ListingCriteria, table: typeof businesses_json | typeof commercials_json): Promise<{ data: Record<string, any>[]; total: number }> {
const start = criteria.start ? criteria.start : 0;
const length = criteria.length ? criteria.length : 12;
return await this.findListings(table, criteria, start, length)
}
async findByPriceRange(minPrice: number, maxPrice: number, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson[]> {
return this.conn.select().from(table).where(sql`${table.data}->>'price' BETWEEN ${minPrice} AND ${maxPrice}`);
}
async findByState(state: string, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson[]> {
return this.conn.select().from(table).where(sql`${table.data}->>'state' = ${state}`);
}
async findByUserId(userId: string, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson[]> {
return this.conn.select().from(table).where(sql`${table.data}->>'userId' = ${userId}`);
}
async findByTitleContains(title: string, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson[]> {
return this.conn.select().from(table).where(sql`${table.data}->>'title' ILIKE '%' || ${title} || '%'`);
}
async createListing(data: BusinessListing, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson> {
const newListing = { data, created: data.created, updated: data.updated, visits: 0, last_visit: null }
const [createdListing] = await this.conn.insert(table).values(newListing).returning();
return createdListing as BusinessesJson;
}
async updateListing(id: string, data: BusinessListing): Promise<BusinessesJson> {
const [updateListing] = await this.conn.update(businesses_json).set(data).where(eq(businesses_json.id, id)).returning();
async updateListing(id: string, data: BusinessListing | CommercialPropertyListing, table: typeof businesses_json | typeof commercials_json): Promise<BusinessesJson> {
const [updateListing] = await this.conn.update(table).set(data).where(eq(table.id, id)).returning();
return updateListing as BusinessesJson;
}
async deleteListing(id: string): Promise<void> {
await this.conn.delete(businesses_json).where(eq(businesses_json.id, id));
async deleteListing(id: string, table: typeof businesses_json | typeof commercials_json): Promise<void> {
await this.conn.delete(table).where(eq(table.id, id));
}
async findByPriceRange(minPrice: number, maxPrice: number): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'price' BETWEEN ${minPrice} AND ${maxPrice}`);
}
// ##############################################################
// Images for commercial Properties
// ##############################################################
async findByState(state: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'state' = ${state}`);
async updateImageOrder(id: string, imageOrder: ImageProperty[]) {
const listing = await this.findById(id, commercials_json) as unknown as CommercialPropertyListing
listing.imageOrder = imageOrder;
await this.updateListing(listing.id, listing, commercials_json)
}
async findById(id: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.id} = ${id}`);
async deleteImage(id: string, name: string,) {
const listing = await this.findById(id, commercials_json) as unknown as CommercialPropertyListing
const index = listing.imageOrder.findIndex(im => im.name === name);
if (index > -1) {
listing.imageOrder.splice(index, 1);
await this.updateListing(listing.id, listing, commercials_json)
}
}
async findByUserId(userId: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'userId' = ${userId}`);
async addImage(id: string, imagename: string) {
const listing = await this.findById(id, commercials_json) as unknown as CommercialPropertyListing
listing.imageOrder.push({ name: imagename, code: '', id: '' });
await this.updateListing(listing.id, listing, commercials_json)
}
// async findByTitleContains(title: string): Promise<BusinessesJson[]> {
// return this.conn.select().from(businesses_json).where(ilike(sql`${businesses_json.data}->>'title'`, `%${title}%`));
// }
async findByTitleContains(title: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'title' ILIKE '%' || ${title} || '%'`);
}
async findListings(start = 0, size = 48): Promise<{ data: Record<string, any>[]; total: number }> {
// return this.conn.select({ data: businesses_json.data }).from(businesses_json).offset(start).limit(size);
const [data, total] = await Promise.all([
this.conn.select({ data: businesses_json.data }).from(businesses_json).offset(start).limit(size),
this.conn.select({ count: sql`count(*)` }).from(businesses_json).then((result) => Number(result[0].count)),
]);
return { data, total };
}
// ##############################################################
// ##############################################################
// 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<CommercialPropertyListing>{
// return await this.commercialPropertyListingRepository.fetch(id) as unknown as CommercialPropertyListing;
// }
@@ -171,25 +199,8 @@ export class ListingsService {
// 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);
// }
}

View File

@@ -4,6 +4,7 @@ import { convertStringToNullUndefined } from '../utils.js';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { businesses_json, commercials_json } from '../drizzle/schema.js';
@Controller('listings/undefined')
export class UnknownListingsController {
@@ -11,19 +12,15 @@ export class UnknownListingsController {
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get(':id')
async findById(@Param('id') id:string): Promise<any> {
const result = await this.listingsService.findById(id,businesses_json);
if (result){
return result
} else {
return await this.listingsService.findById(id,commercials_json);
}
}
// @Get(':id')
// async findById(@Param('id') id:string): Promise<any> {
// const result = await this.listingsService.getBusinessListingById(id);
// if (result.id){
// return result
// } else {
// return await this.listingsService.getCommercialPropertyListingById(id);
// }
// }
// @Get('repo/:repo')
// async getAllByRepo(@Param('repo') repo:string): Promise<any> {
// return await this.listingsService.getIdsForRepo(repo);
// }
}