Validierung Part II, neue Komponenten
This commit is contained in:
@@ -9,8 +9,9 @@ import { rimraf } from 'rimraf';
|
||||
import sharp from 'sharp';
|
||||
import winston from 'winston';
|
||||
import { BusinessListing, CommercialPropertyListing, User, UserData } from '../models/db.model.js';
|
||||
import { emailToDirName, KeyValueStyle } from '../models/main.model.js';
|
||||
import { createDefaultUser, emailToDirName, KeyValueStyle } from '../models/main.model.js';
|
||||
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
||||
import { toDrizzleUser } from '../utils.js';
|
||||
import * as schema from './schema.js';
|
||||
const typesOfBusiness: Array<KeyValueStyle> = [
|
||||
{ name: 'Automotive', value: '1', icon: 'fa-solid fa-car', textColorClass: 'text-green-400' },
|
||||
@@ -73,7 +74,7 @@ fs.ensureDirSync(`./pictures/property`);
|
||||
//for (const userData of usersData) {
|
||||
for (let index = 0; index < usersData.length; index++) {
|
||||
const userData = usersData[index];
|
||||
const user: User = { id: undefined, firstname: '', lastname: '', email: '' };
|
||||
const user: User = createDefaultUser('', '', ''); //{ id: undefined, firstname: '', lastname: '', email: '' };
|
||||
user.licensedIn = [];
|
||||
userData.licensedIn.forEach(l => {
|
||||
console.log(l['value'], l['name']);
|
||||
@@ -114,7 +115,7 @@ for (let index = 0; index < usersData.length; index++) {
|
||||
//sleep(200);
|
||||
const u = await db
|
||||
.insert(schema.users)
|
||||
.values(user)
|
||||
.values(toDrizzleUser(user))
|
||||
.returning({ insertedId: schema.users.id, gender: schema.users.gender, email: schema.users.email, firstname: schema.users.firstname, lastname: schema.users.lastname });
|
||||
generatedUserData.push(u[0]);
|
||||
i++;
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
// customerSubType?: 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
||||
// created?: Date;
|
||||
// updated?: Date;
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// }
|
||||
export interface UserData {
|
||||
id?: string;
|
||||
@@ -46,30 +49,108 @@ export type Gender = 'male' | 'female';
|
||||
export type CustomerType = 'buyer' | 'professional';
|
||||
export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
||||
export type ListingsCategory = 'commercialProperty' | 'business';
|
||||
export interface User {
|
||||
id: string; // UUID as a string
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber?: string;
|
||||
description?: string;
|
||||
companyName?: string;
|
||||
companyOverview?: string;
|
||||
companyWebsite?: string;
|
||||
companyLocation?: string;
|
||||
offeredServices?: string;
|
||||
areasServed?: AreasServed[];
|
||||
hasProfile?: boolean;
|
||||
hasCompanyLogo?: boolean;
|
||||
licensedIn?: LicensedIn[];
|
||||
gender?: Gender;
|
||||
customerType?: CustomerType;
|
||||
customerSubType?: CustomerSubType;
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
}
|
||||
// export interface User {
|
||||
// id: string; // UUID as a string
|
||||
// firstname: string;
|
||||
// lastname: string;
|
||||
// email: string;
|
||||
// phoneNumber?: string;
|
||||
// description?: string;
|
||||
// companyName?: string;
|
||||
// companyOverview?: string;
|
||||
// companyWebsite?: string;
|
||||
// companyLocation?: string;
|
||||
// offeredServices?: string;
|
||||
// areasServed?: AreasServed[];
|
||||
// hasProfile?: boolean;
|
||||
// hasCompanyLogo?: boolean;
|
||||
// licensedIn?: LicensedIn[];
|
||||
// gender?: Gender;
|
||||
// customerType?: CustomerType;
|
||||
// customerSubType?: CustomerSubType;
|
||||
// created?: Date;
|
||||
// updated?: Date;
|
||||
// latitude?: number;
|
||||
// longitude?: number;
|
||||
// }
|
||||
// export interface AreasServed {
|
||||
// county: string;
|
||||
// state: string;
|
||||
// }
|
||||
// export interface LicensedIn {
|
||||
// registerNo: string;
|
||||
// state: string;
|
||||
// }
|
||||
// --------------------------------
|
||||
//
|
||||
// --------------------------------
|
||||
export const GenderEnum = z.enum(['male', 'female']);
|
||||
export const CustomerTypeEnum = z.enum(['buyer', 'professional']);
|
||||
export const CustomerSubTypeEnum = z.enum(['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']);
|
||||
export const ListingsCategoryEnum = z.enum(['commercialProperty', 'business']);
|
||||
export const AreasServedSchema = z.object({
|
||||
county: z.string().nonempty('County is required'),
|
||||
state: z.string().nonempty('State is required'),
|
||||
});
|
||||
|
||||
export const LicensedInSchema = z.object({
|
||||
registerNo: z.string().nonempty('Registration number is required'),
|
||||
state: z.string().nonempty('State is required'),
|
||||
});
|
||||
|
||||
export const UserSchema = z
|
||||
.object({
|
||||
id: z.string().uuid('Invalid ID format. Must be a valid UUID').optional(),
|
||||
firstname: z.string().min(2, 'First name must be at least 2 characters long'),
|
||||
lastname: z.string().min(2, 'Last name must be at least 2 characters long'),
|
||||
email: z.string().email('Invalid email address'),
|
||||
phoneNumber: z.string().optional().nullable(),
|
||||
description: z.string().min(10, 'Description must be at least 10 characters long').optional().nullable(),
|
||||
companyName: z.string().optional().nullable(),
|
||||
companyOverview: z.string().min(10, 'Company overview must be at least 10 characters long').optional().nullable(),
|
||||
companyWebsite: z.string().url('Invalid company website URL').optional().nullable(),
|
||||
companyLocation: z.string().optional().nullable(), // Additional validation for US locations could be implemented here
|
||||
offeredServices: z.string().min(10, 'Offered services must be at least 10 characters long').optional().nullable(),
|
||||
areasServed: z.array(AreasServedSchema).optional().nullable(),
|
||||
hasProfile: z.boolean().optional().nullable(),
|
||||
hasCompanyLogo: z.boolean().optional().nullable(),
|
||||
licensedIn: z.array(LicensedInSchema).optional().nullable(),
|
||||
gender: GenderEnum.optional().nullable(),
|
||||
customerType: CustomerTypeEnum.optional().nullable(),
|
||||
customerSubType: CustomerSubTypeEnum.optional().nullable(),
|
||||
created: z.date().optional().nullable(),
|
||||
updated: z.date().optional().nullable(),
|
||||
latitude: z.number().optional().nullable(),
|
||||
longitude: z.number().optional().nullable(),
|
||||
})
|
||||
.refine(
|
||||
data => {
|
||||
if (data.customerType === 'professional') {
|
||||
return !!data.customerSubType && !!data.phoneNumber && !!data.companyOverview && !!data.description && !!data.offeredServices && !!data.companyLocation && data.areasServed && data.areasServed.length > 0;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'For professional customers, additional fields are required: customer subtype, phone number, company overview, description, offered services, company location, and at least one area served',
|
||||
path: ['customerType'],
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
data => {
|
||||
if (data.customerType === 'professional') {
|
||||
return /\(\d{3}\) \d{3}-\d{4}$/.test(data.phoneNumber || '');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'Phone number must be in US format: +1 (XXX) XXX-XXXX',
|
||||
path: ['phoneNumber'],
|
||||
},
|
||||
);
|
||||
|
||||
export type AreasServed = z.infer<typeof AreasServedSchema>;
|
||||
export type LicensedIn = z.infer<typeof LicensedInSchema>;
|
||||
export type User = z.infer<typeof UserSchema>;
|
||||
export interface BusinessListing {
|
||||
id: string; // UUID as a string
|
||||
email: string; // References users.email
|
||||
@@ -131,11 +212,3 @@ export interface CommercialPropertyListing {
|
||||
longitude?: number; // double precision
|
||||
// embedding?: number[]; // Uncomment if needed for vector embedding
|
||||
}
|
||||
export interface AreasServed {
|
||||
county: string;
|
||||
state: string;
|
||||
}
|
||||
export interface LicensedIn {
|
||||
registerNo: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
@@ -281,3 +281,87 @@ export interface ValidationMessage {
|
||||
field: string;
|
||||
message: string;
|
||||
}
|
||||
export function createDefaultUser(email: string, firstname: string, lastname: string): User {
|
||||
return {
|
||||
id: undefined,
|
||||
email,
|
||||
firstname,
|
||||
lastname,
|
||||
phoneNumber: null,
|
||||
description: null,
|
||||
companyName: null,
|
||||
companyOverview: null,
|
||||
companyWebsite: null,
|
||||
companyLocation: null,
|
||||
offeredServices: null,
|
||||
areasServed: [],
|
||||
hasProfile: false,
|
||||
hasCompanyLogo: false,
|
||||
licensedIn: [],
|
||||
gender: null,
|
||||
customerType: 'buyer',
|
||||
customerSubType: null,
|
||||
created: new Date(),
|
||||
updated: new Date(),
|
||||
};
|
||||
}
|
||||
export function createDefaultCommercialPropertyListing(): CommercialPropertyListing {
|
||||
return {
|
||||
id: undefined,
|
||||
serialId: undefined,
|
||||
email: '',
|
||||
type: null,
|
||||
title: '',
|
||||
description: '',
|
||||
city: '',
|
||||
state: '',
|
||||
price: null,
|
||||
favoritesForUser: [],
|
||||
hideImage: false,
|
||||
draft: false,
|
||||
zipCode: null,
|
||||
county: '',
|
||||
imageOrder: [],
|
||||
imagePath: '',
|
||||
created: null,
|
||||
updated: null,
|
||||
visits: null,
|
||||
lastVisit: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
listingsCategory: 'commercialProperty',
|
||||
};
|
||||
}
|
||||
export function createDefaultBusinessListing(): BusinessListing {
|
||||
return {
|
||||
id: undefined,
|
||||
email: '',
|
||||
type: null,
|
||||
title: '',
|
||||
description: '',
|
||||
city: '',
|
||||
state: '',
|
||||
price: null,
|
||||
favoritesForUser: [],
|
||||
draft: false,
|
||||
realEstateIncluded: false,
|
||||
leasedLocation: false,
|
||||
franchiseResale: false,
|
||||
salesRevenue: null,
|
||||
cashFlow: null,
|
||||
supportAndTraining: '',
|
||||
employees: null,
|
||||
established: null,
|
||||
internalListingNumber: null,
|
||||
reasonForSale: '',
|
||||
brokerLicencing: '',
|
||||
internals: '',
|
||||
created: null,
|
||||
updated: null,
|
||||
visits: null,
|
||||
lastVisit: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
listingsCategory: 'business',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { and, count, eq, ilike, inArray, or, SQL, sql } from 'drizzle-orm';
|
||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { ZodError } from 'zod';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { User } from '../models/db.model.js';
|
||||
import { emailToDirName, JwtUser, UserListingCriteria } from '../models/main.model.js';
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
import { User, UserSchema } from '../models/db.model.js';
|
||||
import { createDefaultUser, emailToDirName, JwtUser, UserListingCriteria } from '../models/main.model.js';
|
||||
import { getDistanceQuery, toDrizzleUser } from '../utils.js';
|
||||
|
||||
type CustomerSubType = (typeof customerSubTypeEnum.enumValues)[number];
|
||||
@Injectable()
|
||||
@@ -111,9 +112,8 @@ export class UserService {
|
||||
.from(schema.users)
|
||||
.where(sql`email = ${email}`)) as User[];
|
||||
if (users.length === 0) {
|
||||
const user: User = { id: undefined, email, firstname: jwtuser.firstname, lastname: jwtuser.lastname, customerType: 'buyer' };
|
||||
this.saveUser(user);
|
||||
return user;
|
||||
const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname) };
|
||||
return await this.saveUser(user);
|
||||
} else {
|
||||
const user = users[0];
|
||||
user.hasCompanyLogo = this.fileService.hasCompanyLogo(emailToDirName(user.email));
|
||||
@@ -132,17 +132,33 @@ export class UserService {
|
||||
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
|
||||
return user;
|
||||
}
|
||||
async saveUser(user: any): Promise<User> {
|
||||
if (user.id) {
|
||||
user.created = new Date(user.created);
|
||||
async saveUser(user: User): Promise<User> {
|
||||
try {
|
||||
user.updated = new Date();
|
||||
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
|
||||
return updateUser as User;
|
||||
} else {
|
||||
user.created = new Date();
|
||||
user.updated = new Date();
|
||||
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
|
||||
return newUser as User;
|
||||
if (user.id) {
|
||||
user.created = new Date(user.created);
|
||||
} else {
|
||||
user.created = new Date();
|
||||
}
|
||||
const validatedUser = UserSchema.parse(user);
|
||||
const drizzleUser = toDrizzleUser(validatedUser);
|
||||
if (user.id) {
|
||||
const [updateUser] = await this.conn.update(schema.users).set(drizzleUser).where(eq(schema.users.id, user.id)).returning();
|
||||
return updateUser as User;
|
||||
} else {
|
||||
const drizzleUser = toDrizzleUser(user);
|
||||
const [newUser] = await this.conn.insert(schema.users).values(drizzleUser).returning();
|
||||
return newUser as User;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// async findUser(criteria: UserListingCriteria) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { businesses, commercials, users } from './drizzle/schema.js';
|
||||
import { AreasServedSchema, CustomerSubTypeEnum, CustomerTypeEnum, GenderEnum, LicensedInSchema, User } from './models/db.model.js';
|
||||
export const EARTH_RADIUS_KM = 6371; // Erdradius in Kilometern
|
||||
export const EARTH_RADIUS_MILES = 3959; // Erdradius in Meilen
|
||||
|
||||
@@ -28,3 +30,33 @@ export const getDistanceQuery = (schema: typeof businesses | typeof commercials
|
||||
))
|
||||
`;
|
||||
};
|
||||
|
||||
export function toDrizzleUser(user: User): {
|
||||
email: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
phoneNumber?: string;
|
||||
description?: string;
|
||||
companyName?: string;
|
||||
companyOverview?: string;
|
||||
companyWebsite?: string;
|
||||
companyLocation?: string;
|
||||
offeredServices?: string;
|
||||
areasServed?: (typeof AreasServedSchema._type)[];
|
||||
hasProfile?: boolean;
|
||||
hasCompanyLogo?: boolean;
|
||||
licensedIn?: (typeof LicensedInSchema._type)[];
|
||||
gender?: z.infer<typeof GenderEnum>;
|
||||
customerType?: z.infer<typeof CustomerTypeEnum>;
|
||||
customerSubType?: z.infer<typeof CustomerSubTypeEnum>;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
} {
|
||||
const { id, created, updated, ...drizzleUser } = user;
|
||||
return {
|
||||
...drizzleUser,
|
||||
email: drizzleUser.email,
|
||||
firstname: drizzleUser.firstname,
|
||||
lastname: drizzleUser.lastname,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user