Image Upload, spinner aktivirt, listings & details überarbeitet

This commit is contained in:
2024-03-18 18:17:04 +01:00
parent 2b27ab8ba5
commit fd91adda57
31 changed files with 582 additions and 263 deletions

View File

@@ -1,19 +0,0 @@
import { Controller, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from '../file/file.service.js';
@Controller('account')
export class AccountController {
constructor(private fileService:FileService){}
@Post('uploadProfile/:id')
@UseInterceptors(FileInterceptor('file'),)
uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
this.fileService.storeProfilePicture(file,id);
}
@Post('uploadCompanyLogo/:id')
@UseInterceptors(FileInterceptor('file'),)
uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
this.fileService.storeCompanyLogo(file,id);
}
}

View File

@@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { AccountController } from './account.controller.js';
import { AccountService } from './account.service.js';
import { FileService } from '../file/file.service.js';
@Module({
controllers: [AccountController],
providers: [AccountService,FileService]
})
export class AccountModule {}

View File

@@ -10,8 +10,6 @@ import { SelectOptionsService } from './select-options/select-options.service.js
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
import { RedisModule } from './redis/redis.module.js';
import { ListingsService } from './listings/listings.service.js';
import { AccountController } from './account/account.controller.js';
import { AccountService } from './account/account.service.js';
import { ServeStaticModule } from '@nestjs/serve-static';
import path, { join } from 'path';
import { fileURLToPath } from 'url';
@@ -22,9 +20,9 @@ import { AuthModule } from './auth/auth.module.js';
import { GeoModule } from './geo/geo.module.js';
import { UserModule } from './user/user.module.js';
import { ListingsModule } from './listings/listings.module.js';
import { AccountModule } from './account/account.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';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -52,9 +50,9 @@ const __dirname = path.dirname(__filename);
GeoModule,
UserModule,
ListingsModule,
AccountModule,
SelectOptionsModule,
RedisModule
RedisModule,
ImageModule
],
controllers: [AppController, SubscriptionsController],
providers: [AppService, FileService],

View File

@@ -1,18 +1,20 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { fstat, readFileSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs-extra';
import { ImageProperty } from 'src/models/main.model.js';
import { ImageProperty } from '../models/main.model.js';
import sharp from 'sharp';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@Injectable()
export class FileService {
private subscriptions: any;
constructor() {
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
this.loadSubscriptions();
fs.ensureDirSync(`./pictures`);
fs.ensureDirSync(`./pictures/profile`);
@@ -20,42 +22,60 @@ export class FileService {
fs.ensureDirSync(`./pictures/property`);
}
private loadSubscriptions(): void {
const filePath = join(__dirname,'..', 'assets', 'subscriptions.json');
const filePath = join(__dirname, '..', 'assets', 'subscriptions.json');
const rawData = readFileSync(filePath, 'utf8');
this.subscriptions = JSON.parse(rawData);
}
getSubscriptions() {
return this.subscriptions
}
async storeProfilePicture(file: Express.Multer.File,userId: string){
const suffix = file.mimetype.includes('png')?'png':'jpg'
await fs.outputFile(`./pictures/profile/${userId}`,file.buffer);
async storeProfilePicture(file: Express.Multer.File, userId: string) {
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
await fs.outputFile(`./pictures/profile/${userId}`, file.buffer);
}
async storeCompanyLogo(file: Express.Multer.File,userId: string){
const suffix = file.mimetype.includes('png')?'png':'jpg'
await fs.outputFile(`./pictures/logo/${userId}`,file.buffer);
hasProfile(userId: string){
return fs.existsSync(`./pictures/profile/${userId}`)
}
async getPropertyImages(listingId: string):Promise<ImageProperty[]>{
const result:ImageProperty[]=[]
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
}
hasCompanyLogo(userId: string){
return fs.existsSync(`./pictures/logo/${userId}`)
}
async getPropertyImages(listingId: string): Promise<ImageProperty[]> {
const result: ImageProperty[] = []
const directory = `./pictures/property/${listingId}`
if (fs.existsSync(directory)){
if (fs.existsSync(directory)) {
const files = await fs.readdir(directory);
files.forEach(f=>{
const image:ImageProperty={name:f,id:'',code:''};
files.forEach(f => {
const image: ImageProperty = { name: f, id: '', code: '' };
result.push(image)
})
return result;
} else {
return []
}
}
async storePropertyPicture(file: Express.Multer.File,listingId: string){
const suffix = file.mimetype.includes('png')?'png':'jpg'
async hasPropertyImages(listingId: string): Promise<boolean> {
const result: ImageProperty[] = []
const directory = `./pictures/property/${listingId}`
if (fs.existsSync(directory)) {
const files = await fs.readdir(directory);
return files.length>0
} else {
return false
}
}
async storePropertyPicture(file: Express.Multer.File, listingId: string) {
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
const directory = `./pictures/property/${listingId}`
fs.ensureDirSync(`${directory}`);
const imageName = await this.getNextImageName(directory);
await fs.outputFile(`${directory}/${imageName}`,file.buffer);
//await fs.outputFile(`${directory}/${imageName}`, file.buffer);
await this.resizeImageToAVIF(file.buffer,150 * 1024,imageName,directory);
}
async getNextImageName(directory) {
try {
@@ -64,7 +84,7 @@ export class FileService {
.map(file => parseInt(file, 10)) // Dateinamen direkt in Zahlen umwandeln
.filter(number => !isNaN(number)) // Sicherstellen, dass die Konvertierung gültig ist
.sort((a, b) => a - b); // Aufsteigend sortieren
const nextNumber = imageNumbers.length > 0 ? Math.max(...imageNumbers) + 1 : 1;
return `${nextNumber}`; // Keine Endung für den Dateinamen
} catch (error) {
@@ -72,4 +92,24 @@ export class FileService {
return null;
}
}
async resizeImageToAVIF(buffer: Buffer, maxSize: number,imageName:string,directory:string) {
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
let output;
let start = Date.now();
do {
output = await sharp(buffer)
.resize({ width: 1500 })
.avif({ quality }) // Verwende AVIF
//.webp({ quality }) // Verwende Webp
.toBuffer();
if (output.byteLength > maxSize) {
quality -= 5; // Justiere Qualität in feineren Schritten
}
} while (output.byteLength > maxSize && quality > 0);
let timeTaken = Date.now() - start;
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`)
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
}
}

View File

@@ -0,0 +1,35 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from '../file/file.service.js';
@Controller('image')
export class ImageController {
constructor(private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Post('uploadPropertyPicture/:id')
@UseInterceptors(FileInterceptor('file'),)
async uploadFile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
await this.fileService.storePropertyPicture(file,id);
}
@Get(':id')
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
return await this.fileService.getPropertyImages(id);
}
@Post('uploadProfile/:id')
@UseInterceptors(FileInterceptor('file'),)
uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
this.fileService.storeProfilePicture(file,id);
}
@Post('uploadCompanyLogo/:id')
@UseInterceptors(FileInterceptor('file'),)
uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
this.fileService.storeCompanyLogo(file,id);
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ImageController } from './image.controller.js';
import { ImageService } from './image.service.js';
import { FileService } from '../file/file.service.js';
@Module({
controllers: [ImageController],
providers: [ImageService,FileService]
})
export class ImageModule {}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AccountService {}
export class ImageService {}

View File

@@ -11,11 +11,7 @@ export class CommercialPropertyListingsController {
constructor(private readonly listingsService:ListingsService,private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get()
findAll(): any {
return this.listingsService.getAllCommercialListings();
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.getCommercialPropertyListingById(id);
@@ -43,14 +39,5 @@ export class CommercialPropertyListingsController {
this.listingsService.deleteCommercialPropertyListing(id)
}
@Post('uploadPropertyPicture/:id')
@UseInterceptors(FileInterceptor('file'),)
uploadFile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
this.fileService.storePropertyPicture(file,id);
}
@Get('images/:id')
getPropertyImagesById(@Param('id') id:string): any {
return this.fileService.getPropertyImages(id);
}
}

View File

@@ -95,8 +95,6 @@ export class ListingsService {
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);

View File

@@ -20,9 +20,9 @@ export class UserController {
return this.userService.saveUser(user);
}
// @Put()
// update(@Body() user: any):Promise<UserEntity>{
// this.logger.info(`update User`);
// return this.userService.saveUser(user);
// }
@Post('search')
find(@Body() criteria: any): any {
return this.userService.findUser(criteria);
}
}

View File

@@ -2,11 +2,12 @@ import { Module } from '@nestjs/common';
import { UserController } from './user.controller.js';
import { UserService } from './user.service.js';
import { RedisModule } from '../redis/redis.module.js';
import { FileService } from '../file/file.service.js';
@Module({
imports: [RedisModule],
controllers: [UserController],
providers: [UserService]
providers: [UserService,FileService]
})
export class UserModule {
}

View File

@@ -4,6 +4,7 @@ import { Entity, Repository, Schema } from 'redis-om';
import { ListingCriteria, User } from '../models/main.model.js';
import { REDIS_CLIENT } from '../redis/redis.module.js';
import { UserEntity } from '../models/server.model.js';
import { FileService } from '../file/file.service.js';
@Injectable()
export class UserService {
@@ -24,12 +25,15 @@ export class UserService {
}, {
dataStructure: 'JSON'
})
constructor(@Inject(REDIS_CLIENT) private readonly redis: any){
constructor(@Inject(REDIS_CLIENT) private readonly redis: any,private fileService:FileService){
this.userRepository = new Repository(this.userSchema, redis)
this.userRepository.createIndex();
}
async getUserById( id:string){
return await this.userRepository.fetch(id);
const user = await this.userRepository.fetch(id) as UserEntity;
user.hasCompanyLogo=this.fileService.hasCompanyLogo(id);
user.hasProfile=this.fileService.hasProfile(id);
return user;
}
async saveUser(user:any):Promise<UserEntity>{
return await this.userRepository.save(user.id,user) as UserEntity