Start Umbau zu postgres

This commit is contained in:
Andreas Knuth 2024-04-14 22:52:19 +02:00
parent e784b424b0
commit 7d10080069
21 changed files with 5441 additions and 272 deletions

View File

@ -29,6 +29,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/serve-static": "^4.0.1",
"drizzle-orm": "^0.30.8",
"handlebars": "^4.7.8",
"ky": "^1.2.0",
"nest-winston": "^1.9.4",
@ -38,6 +39,7 @@
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.11.5",
"redis": "^4.6.13",
"redis-om": "^0.4.3",
"reflect-metadata": "^0.2.0",
@ -58,6 +60,7 @@
"@types/passport-google-oauth20": "^2.0.14",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@types/pg": "^8.11.5",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",

View File

@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';
import { FileService } from './file/file.service.js';
@ -23,11 +23,13 @@ import { ListingsModule } from './listings/listings.module.js';
import { SelectOptionsModule } from './select-options/select-options.module.js';
import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js';
import { ImageModule } from './image/image.module.js';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@Module({
imports: [ConfigModule.forRoot(), MailModule, AuthModule,
imports: [ConfigModule.forRoot({isGlobal: true}), MailModule, AuthModule,
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'pictures'), // `public` ist das Verzeichnis, wo Ihre statischen Dateien liegen
}),
@ -52,9 +54,13 @@ const __dirname = path.dirname(__filename);
ListingsModule,
SelectOptionsModule,
RedisModule,
ImageModule
ImageModule,
],
controllers: [AppController, SubscriptionsController],
providers: [AppService, FileService],
})
export class AppModule {}
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestDurationMiddleware).forRoutes('*');
}
}

View File

@ -0,0 +1,12 @@
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

@ -0,0 +1,27 @@
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 { ConfigService } from '@nestjs/config';
import { jsonb, varchar } from 'drizzle-orm/pg-core';
import { PG_CONNECTION } from './schema.js';
@Module({
providers: [
{
provide: PG_CONNECTION,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({
connectionString,
// ssl: true,
});
return drizzle(pool, { schema });
},
},
],
exports: [PG_CONNECTION],
})
export class DrizzleModule {}

View File

@ -0,0 +1,15 @@
import { integer, serial, text, pgTable } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import { jsonb, varchar } from 'drizzle-orm/pg-core';
export const PG_CONNECTION = 'PG_CONNECTION';
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

@ -21,7 +21,7 @@ export class ImageController {
@UseInterceptors(FileInterceptor('file'),)
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
const imagename = await this.fileService.storePropertyPicture(file,id);
await this.listingService.addImage(id,imagename);
// await this.listingService.addImage(id,imagename);
}
@Post('uploadProfile/:id')
@ -38,16 +38,16 @@ export class ImageController {
@Get(':id')
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
const result = await this.listingService.getCommercialPropertyListingById(id);
const listing = result as CommercialPropertyListing;
if (listing.imageOrder){
return listing.imageOrder
} else {
const imageOrder = await this.fileService.getPropertyImages(id);
listing.imageOrder=imageOrder;
this.listingService.saveListing(listing);
return imageOrder;
}
// const result = await this.listingService.getCommercialPropertyListingById(id);
// const listing = result as CommercialPropertyListing;
// if (listing.imageOrder){
// return listing.imageOrder
// } else {
// const imageOrder = await this.fileService.getPropertyImages(id);
// listing.imageOrder=imageOrder;
// this.listingService.saveListing(listing);
// return imageOrder;
// }
}
@Get('profileImages/:userids')
async getProfileImagesForUsers(@Param('userids') userids:string): Promise<any> {
@ -61,7 +61,7 @@ export class ImageController {
@Delete('propertyPicture/:listingid/:imagename')
async deletePropertyImagesById(@Param('listingid') listingid:string,@Param('imagename') imagename:string): Promise<any> {
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`);
await this.listingService.deleteImage(listingid,imagename);
// await this.listingService.deleteImage(listingid,imagename);
}
@Delete('logo/:userid/')
async deleteLogoImagesById(@Param('id') id:string): Promise<any> {

View File

@ -4,51 +4,52 @@ 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';
@Controller('listings/business')
export class BusinessListingsController {
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
constructor(private readonly listingsService:ListingsService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get()
findAll(): any {
return this.listingsService.getAllBusinessListings();
this.logger.info(`start findAll Listing`);
return this.listingsService.findListings();
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.getBusinessListingById(id);
return this.listingsService.findById(id);
}
@Get('user/:userid')
findByUserId(@Param('userid') userid:string): any {
return this.listingsService.getBusinessListingByUserId(userid);
return this.listingsService.findByUserId(userid);
}
@Post('search')
find(@Body() criteria: any): any {
return this.listingsService.findBusinessListings(criteria);
find(@Body() criteria: ListingCriteria): any {
return this.listingsService.findByState(criteria.state);
}
/**
* @param listing creates a new listing
*/
@Post()
save(@Body() listing: any){
create(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.saveListing(listing)
this.listingsService.createListing(listing)
}
@Put()
update(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.updateListing(listing.id,listing)
}
/**
* @param id deletes a listing
*/
@Delete(':id')
deleteById(@Param('id') id:string){
this.listingsService.deleteBusinessListing(id)
this.listingsService.deleteListing(id)
}
@Delete('deleteAll')
deleteAll(){
this.listingsService.deleteAllBusinessListings()
}
// @Delete('deleteAll')
// deleteAll(){
// this.listingsService.deleteAllBusinessListings()
// }
}

View File

@ -13,39 +13,39 @@ export class CommercialPropertyListingsController {
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.getCommercialPropertyListingById(id);
}
// @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);
}
// @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)
}
// @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()
}
// /**
// * @param id deletes a listing
// */
// @Delete(':id')
// deleteById(@Param('id') id:string){
// this.listingsService.deleteCommercialPropertyListing(id)
// }
// @Delete('deleteAll')
// deleteAll(){
// this.listingsService.deleteAllcommercialListings()
// }
}

View File

@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Injectable, Module, OnModuleInit } from '@nestjs/common';
import { BusinessListingsController } from './business-listings.controller.js';
import { ListingsService } from './listings.service.js';
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
@ -8,9 +8,15 @@ import { UnknownListingsController } from './unknown-listings.controller.js';
import { UserModule } from '../user/user.module.js';
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';
@Module({
imports: [RedisModule],
imports: [RedisModule,DrizzleModule
],
controllers: [BusinessListingsController, CommercialPropertyListingsController,UnknownListingsController,BrokerListingsController],
providers: [ListingsService,FileService,UserService],
exports: [ListingsService],

View File

@ -10,177 +10,186 @@ import {
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';
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 { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as schema from '../drizzle/schema.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<CommercialPropertyListing>{
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<any> {
// 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<any> {
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;
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();
}
async find(criteria:ListingCriteria, listings: any[]): Promise<any> {
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 createListing(newListing: { id: string; data: BusinessesJson }): Promise<BusinessesJson> {
const [createdListing] = await this.conn.insert(businesses_json).values(newListing).returning();
return createdListing as BusinessesJson;
}
async updateImageOrder(id:string,imageOrder: ImageProperty[]){
const listing = await this.getCommercialPropertyListingById(id) as unknown as CommercialPropertyListing
listing.imageOrder=imageOrder;
this.saveListing(listing);
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();
return updateListing as BusinessesJson;
}
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 deleteListing(id: string): Promise<void> {
await this.conn.delete(businesses_json).where(eq(businesses_json.id, id));
}
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);
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}`);
}
async findByState(state: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'state' = ${state}`);
}
async findById(id: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.id} = ${id}`);
}
async findByUserId(userId: string): Promise<BusinessesJson[]> {
return this.conn.select().from(businesses_json).where(sql`${businesses_json.data}->>'userId' = ${userId}`);
}
// 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;
// }
// 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<any> {
// // let listings = await this.getAllBusinessListings();
// // return this.find(criteria,listings);
// const from=criteria.start?criteria.start:0
// const size=criteria.length?criteria.length:24
// this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
// const result = await this.redis.ft.search('business:index','*',{LIMIT:{from,size}});
// this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
// return result
// }
// async findCommercialPropertyListings(criteria:ListingCriteria): Promise<any> {
// 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<any> {
// 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);
// }
}

View File

@ -12,18 +12,18 @@ export class UnknownListingsController {
}
@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);
}
// @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);
// }
}

View File

@ -3,22 +3,23 @@ import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: 'REDIS_OPTIONS',
useValue: {
url: 'redis://localhost:6379'
}
},
{
inject: ['REDIS_OPTIONS'],
provide: 'REDIS_CLIENT',
useFactory: async (options: { url: string }) => {
const client = createClient(options);
await client.connect();
return client;
}
}],
exports:['REDIS_CLIENT']
// {
// provide: 'REDIS_OPTIONS',
// useValue: {
// url: 'redis://localhost:6379'
// }
// },
// {
// inject: ['REDIS_OPTIONS'],
// provide: 'REDIS_CLIENT',
// useFactory: async (options: { url: string }) => {
// const client = createClient(options);
// await client.connect();
// return client;
// }
// }
],
// exports:['REDIS_CLIENT']
})
export class RedisModule {}
export const REDIS_CLIENT = "REDIS_CLIENT";

View File

@ -0,0 +1,16 @@
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RequestDurationMiddleware implements NestMiddleware {
private readonly logger = new Logger(RequestDurationMiddleware.name);
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
this.logger.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
}
}

View File

@ -25,9 +25,9 @@ export class UserService {
}, {
dataStructure: 'JSON'
})
constructor(@Inject(REDIS_CLIENT) private readonly redis: any,private fileService:FileService){
this.userRepository = new Repository(this.userSchema, redis)
this.userRepository.createIndex();
constructor(private fileService:FileService){
// this.userRepository = new Repository(this.userSchema, redis)
// this.userRepository.createIndex();
}
async getUserById( id:string){
const user = await this.userRepository.fetch(id) as UserEntity;

View File

@ -56,7 +56,7 @@
<div class="surface-200 h-full">
<div class="wrapper">
<div class="grid">
@for (listing of filteredListings; track listing.id) {
@for (listing of listings; track listing.id) {
<div *ngIf="listing.listingsCategory==='business'" class="col-12 lg:col-3 p-3">
<div class="shadow-2 border-round surface-card mb-3 h-full flex-column justify-content-between flex">
<div class="p-4 h-full flex flex-column">

View File

@ -73,10 +73,11 @@ export class ListingsComponent {
if (this.category==='business' || this.category==='commercialProperty'){
this.users=[]
this.listings=await this.listingsService.getListings(this.criteria);
this.setStates();
this.filteredListings=[...this.listings];
//this.filteredListings=[...this.listings];
this.totalRecords=this.listings.length
this.filteredListings=[...this.listings].splice(this.first,this.rows);
//this.filteredListings=[...this.listings].splice(this.first,this.rows);
this.cdRef.markForCheck();
this.cdRef.detectChanges();
} else {
@ -112,9 +113,13 @@ export class ListingsComponent {
this.cdRef.detectChanges();
}
onPageChange(event: any) {
this.first = event.first;
this.rows = event.rows;
this.filteredListings=[...this.listings].splice(this.first,this.rows);
//this.first = event.first;
//this.rows = event.rows;
//this.filteredListings=[...this.listings].splice(this.first,this.rows);
this.criteria.start=event.first;
this.criteria.length=event.rows;
this.criteria.page=event.page;
this.criteria.pageCount=event.pageCount;
}
imageErrorHandler(listing: ListingType) {
listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann

View File

@ -68,6 +68,10 @@ export type ListingType =
| CommercialPropertyListing;
export interface ListingCriteria {
start:number,
length:number,
page:number,
pageCount:number,
type:string,
state:string,
minPrice:string,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
[]

View File

@ -11,17 +11,20 @@
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.4.5"
},
"dependencies": {
"@types/node": "^20.12.7",
"currency.js": "^2.0.4",
"fs-extra": "^11.2.0",
"inquirer": "^9.2.17",
"ioredis": "^5.3.2",
"node-fetch": "^3.3.2",
"pg": "^8.11.5",
"puppeteer": "^22.1.0",
"redis": "^4.6.13",
"redis-om": "^0.4.3",
"uuidv4": "^6.2.13",
"winston": "^3.13.0",
"yargs": "^17.7.2"
}

View File

@ -0,0 +1,85 @@
import pkg from 'pg';
const { Pool } = pkg;
import fsextra from 'fs-extra';
const { fstat, readFileSync, writeJsonSync } = fsextra;
import { v4 as uuidv4 } from 'uuid';
// PostgreSQL Verbindungskonfiguration
const pool = new Pool({
user: 'bizmatch',
host: 'localhost',
database: 'bizmatch',
password: 'xieng7Seih',
port: 5432,
});
// Typdefinition für das JSON-Objekt
interface BusinessListing {
userId?: string;
listingsCategory: string;
title: string;
description: string;
type: string;
state: string;
city: string;
id: string;
price: number;
salesRevenue: number;
leasedLocation: boolean;
established: number;
employees: number;
reasonForSale: string;
supportAndTraining: string;
cashFlow: number;
brokerLicencing: string;
internalListingNumber: number;
realEstateIncluded: boolean;
franchiseResale: boolean;
draft: boolean;
internals: string;
created: Date;
}
// Funktion zum Einlesen und Importieren von JSON-Daten
async function importJsonData(filePath: string): Promise<void> {
try {
const data: string = readFileSync(filePath, 'utf8');
const jsonData: BusinessListing[] = JSON.parse(data); // Erwartet ein Array von Objekten
const out: BusinessListing[] =[]
// Daten für jedes Listing in die Datenbank einfügen
for (const listing of jsonData) {
// const uuid = uuidv4();
// listing.id=uuid;
const values = [
listing.userId, listing.listingsCategory, listing.title, listing.description,
listing.type, listing.state, listing.city, listing.id, listing.price, listing.salesRevenue,
listing.leasedLocation, listing.established, listing.employees,
listing.reasonForSale, listing.supportAndTraining, listing.cashFlow, listing.brokerLicencing,
listing.internalListingNumber, listing.realEstateIncluded, listing.franchiseResale,
listing.draft, listing.internals, listing.created, new Date(), 0, null
];
const json_values = [
listing.id, listing
]
await pool.query(`INSERT INTO businesses
(user_id, listings_category, title, description, type, state, city, id, price, sales_revenue, leased_location,
established, employees, reason_for_sale, support_and_training, cash_flow, broker_licencing, internal_listing_number,
real_estate_included, franchise_resale, draft, internals, created, updated, visits, last_visit)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)`, values);
await pool.query('INSERT INTO businesses_json (id, data) VALUES ($1,$2)', json_values);
// out.push(listing);
}
writeJsonSync('./data/businesses_.json',out);
console.log('All data imported successfully.');
} catch (err) {
console.error('Error importing data:', err.message);
} finally {
// Schließen der Verbindung zum Pool
await pool.end();
}
}
// Passen Sie den Dateipfad an Ihre spezifischen Bedürfnisse an
importJsonData('./data/businesses_.json');