import { Inject, Injectable } from '@nestjs/common'; import { and, asc, count, desc, eq, inArray, or, SQL, sql } from 'drizzle-orm'; import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; import * as schema from '../drizzle/schema'; import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema'; import { FileService } from '../file/file.service'; import { GeoService } from '../geo/geo.service'; import { User, UserSchema } from '../models/db.model'; import { createDefaultUser, emailToDirName, JwtUser, UserListingCriteria } from '../models/main.model'; import { getDistanceQuery, splitName } from '../utils'; type CustomerSubType = (typeof customerSubTypeEnum.enumValues)[number]; @Injectable() export class UserService { constructor( @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, @Inject(PG_CONNECTION) private conn: NodePgDatabase, private fileService: FileService, private geoService: GeoService, ) { } private getWhereConditions(criteria: UserListingCriteria): SQL[] { const whereConditions: SQL[] = []; whereConditions.push(sql`(${schema.users_json.data}->>'customerType') = 'professional'`); if (criteria.city && criteria.searchType === 'exact') { whereConditions.push(sql`(${schema.users_json.data}->'location'->>'name') ILIKE ${criteria.city.name}`); } if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) { const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name); const distanceQuery = getDistanceQuery(schema.users_json, cityGeo.latitude, cityGeo.longitude); whereConditions.push(sql`${distanceQuery} <= ${criteria.radius}`); } if (criteria.types && criteria.types.length > 0) { // whereConditions.push(inArray(schema.users.customerSubType, criteria.types)); whereConditions.push(inArray(sql`${schema.users_json.data}->>'customerSubType'`, criteria.types as CustomerSubType[])); } if (criteria.brokerName) { const { firstname, lastname } = splitName(criteria.brokerName); whereConditions.push(sql`(${schema.users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${schema.users_json.data}->>'lastname') ILIKE ${`%${lastname}%`}`); } if (criteria.companyName) { whereConditions.push(sql`(${schema.users_json.data}->>'companyName') ILIKE ${`%${criteria.companyName}%`}`); } if (criteria.counties && criteria.counties.length > 0) { whereConditions.push(or(...criteria.counties.map(county => sql`(${schema.users_json.data}->'location'->>'county') ILIKE ${`%${county}%`}`))); } if (criteria.state) { whereConditions.push(sql`(${schema.users_json.data}->'location'->>'state') = ${criteria.state}`); } //never show user which denied whereConditions.push(sql`(${schema.users_json.data}->>'showInDirectory')::boolean IS TRUE`); return whereConditions; } async searchUserListings(criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { const start = criteria.start ? criteria.start : 0; const length = criteria.length ? criteria.length : 12; const query = this.conn.select().from(schema.users_json); const whereConditions = this.getWhereConditions(criteria); if (whereConditions.length > 0) { const whereClause = and(...whereConditions); query.where(whereClause); } // Sortierung switch (criteria.sortBy) { case 'nameAsc': query.orderBy(asc(sql`${schema.users_json.data}->>'lastname'`)); break; case 'nameDesc': query.orderBy(desc(sql`${schema.users_json.data}->>'lastname'`)); break; default: // Keine spezifische Sortierung, Standardverhalten kann hier eingefügt werden break; } // Paginierung query.limit(length).offset(start); const data = await query; const results = data.map(u => ({ id: u.id, email: u.email, ...(u.data as User) }) as User); const totalCount = await this.getUserListingsCount(criteria); return { results, totalCount, }; } async getUserListingsCount(criteria: UserListingCriteria): Promise { const countQuery = this.conn.select({ value: count() }).from(schema.users_json); const whereConditions = this.getWhereConditions(criteria); if (whereConditions.length > 0) { const whereClause = and(...whereConditions); countQuery.where(whereClause); } const [{ value: totalCount }] = await countQuery; return totalCount; } async getUserByMail(email: string, jwtuser?: JwtUser) { const users = await this.conn.select().from(schema.users_json).where(eq(schema.users_json.email, email)); if (users.length === 0) { const user: User = { id: undefined, customerType: 'professional', ...createDefaultUser(email, '', '', null) }; const u = await this.saveUser(user, false); return u; } else { const user = { id: users[0].id, email: users[0].email, ...(users[0].data as User) } as User; user.hasCompanyLogo = this.fileService.hasCompanyLogo(emailToDirName(user.email)); user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email)); return user; } } async getUserById(id: string) { const users = await this.conn.select().from(schema.users_json).where(eq(schema.users_json.id, id)); const user = { id: users[0].id, email: users[0].email, ...(users[0].data as User) } as User; user.hasCompanyLogo = this.fileService.hasCompanyLogo(emailToDirName(user.email)); user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email)); return user; } async getAllUser() { const users = await this.conn.select().from(schema.users_json); return users.map(u => ({ id: u.id, email: u.email, ...(u.data as User) }) as User); } async saveUser(user: User, processValidation = true): Promise { try { user.updated = new Date(); if (user.id) { user.created = new Date(user.created); } else { user.created = new Date(); } let validatedUser = user; if (processValidation) { validatedUser = UserSchema.parse(user); } //const drizzleUser = convertUserToDrizzleUser(validatedUser); const { id: _, ...rest } = validatedUser; const drizzleUser = { email: user.email, data: rest }; if (user.id) { const [updateUser] = await this.conn.update(schema.users_json).set(drizzleUser).where(eq(schema.users_json.id, user.id)).returning(); return { id: updateUser.id, email: updateUser.email, ...(updateUser.data as User) } as User; } else { const [newUser] = await this.conn.insert(schema.users_json).values(drizzleUser).returning(); return { id: newUser.id, email: newUser.email, ...(newUser.data as User) } as User; } } catch (error) { throw error; } } async addFavorite(id: string, user: JwtUser): Promise { const existingUser = await this.getUserById(id); if (!existingUser) return; const favorites = existingUser.favoritesForUser || []; if (!favorites.includes(user.email)) { existingUser.favoritesForUser = [...favorites, user.email]; const { id: _, ...rest } = existingUser; const drizzleUser = { email: existingUser.email, data: rest }; await this.conn.update(schema.users_json).set(drizzleUser).where(eq(schema.users_json.id, id)); } } async deleteFavorite(id: string, user: JwtUser): Promise { const existingUser = await this.getUserById(id); if (!existingUser) return; const favorites = existingUser.favoritesForUser || []; if (favorites.includes(user.email)) { existingUser.favoritesForUser = favorites.filter(email => email !== user.email); const { id: _, ...rest } = existingUser; const drizzleUser = { email: existingUser.email, data: rest }; await this.conn.update(schema.users_json).set(drizzleUser).where(eq(schema.users_json.id, id)); } } async getFavoriteUsers(user: JwtUser): Promise { const data = await this.conn .select() .from(schema.users_json) .where(sql`${schema.users_json.data}->'favoritesForUser' ? ${user.email}`); return data.map(u => ({ id: u.id, email: u.email, ...(u.data as User) }) as User); } }