Einbau Validation finished
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { ZodError } from 'zod';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { businesses, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { BusinessListing, CommercialPropertyListing } from '../models/db.model';
|
||||
import { BusinessListing, BusinessListingSchema } from '../models/db.model.js';
|
||||
import { BusinessListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
|
||||
@@ -169,17 +170,41 @@ export class BusinessListingService {
|
||||
}
|
||||
// #### CREATE ########################################
|
||||
async createListing(data: BusinessListing): Promise<BusinessListing> {
|
||||
data.created = new Date();
|
||||
data.updated = new Date();
|
||||
const [createdListing] = await this.conn.insert(businesses).values(data).returning();
|
||||
return createdListing as BusinessListing;
|
||||
try {
|
||||
data.created = new Date();
|
||||
data.updated = new Date();
|
||||
const validatedBusinessListing = BusinessListingSchema.parse(data);
|
||||
const [createdListing] = await this.conn.insert(businesses).values(validatedBusinessListing).returning();
|
||||
return createdListing as BusinessListing;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
// #### UPDATE Business ########################################
|
||||
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing | CommercialPropertyListing> {
|
||||
data.updated = new Date();
|
||||
data.created = new Date(data.created);
|
||||
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
||||
return updateListing as BusinessListing | CommercialPropertyListing;
|
||||
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing> {
|
||||
try {
|
||||
data.updated = new Date();
|
||||
data.created = new Date(data.created);
|
||||
const validatedBusinessListing = BusinessListingSchema.parse(data);
|
||||
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
||||
return updateListing as BusinessListing;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
// #### DELETE ########################################
|
||||
async deleteListing(id: string): Promise<void> {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { ZodError } from 'zod';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { commercials, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { CommercialPropertyListing } from '../models/db.model';
|
||||
import { CommercialPropertyListing, CommercialPropertyListingSchema } from '../models/db.model.js';
|
||||
import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
|
||||
@@ -123,23 +124,47 @@ export class CommercialPropertyService {
|
||||
}
|
||||
// #### CREATE ########################################
|
||||
async createListing(data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
||||
data.created = new Date();
|
||||
data.updated = new Date();
|
||||
const [createdListing] = await this.conn.insert(commercials).values(data).returning();
|
||||
return createdListing as CommercialPropertyListing;
|
||||
try {
|
||||
data.created = new Date();
|
||||
data.updated = new Date();
|
||||
const validatedCommercialPropertyListing = CommercialPropertyListingSchema.parse(data);
|
||||
const [createdListing] = await this.conn.insert(commercials).values(validatedCommercialPropertyListing).returning();
|
||||
return createdListing as CommercialPropertyListing;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
// #### UPDATE CommercialProps ########################################
|
||||
async updateCommercialPropertyListing(id: string, data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
||||
data.updated = new Date();
|
||||
data.created = new Date(data.created);
|
||||
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
||||
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
||||
if (difference.length > 0) {
|
||||
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
||||
data.imageOrder = imageOrder;
|
||||
try {
|
||||
data.updated = new Date();
|
||||
data.created = new Date(data.created);
|
||||
const validatedCommercialPropertyListing = CommercialPropertyListingSchema.parse(data);
|
||||
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
||||
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
||||
if (difference.length > 0) {
|
||||
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
||||
data.imageOrder = imageOrder;
|
||||
}
|
||||
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
||||
return updateListing as CommercialPropertyListing;
|
||||
} 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;
|
||||
}
|
||||
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
||||
return updateListing as CommercialPropertyListing;
|
||||
}
|
||||
// ##############################################################
|
||||
// Images for commercial Properties
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { MailerService } from '@nestjs-modules/mailer';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import path, { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { ZodError } from 'zod';
|
||||
import { SenderSchema } from '../models/db.model.js';
|
||||
import { ErrorResponse, MailInfo, isEmpty } from '../models/main.model.js';
|
||||
import { UserService } from '../user/user.service.js';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -15,9 +17,19 @@ export class MailService {
|
||||
) {}
|
||||
|
||||
async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||
//const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
||||
try {
|
||||
const validatedSender = SenderSchema.parse(mailInfo.sender);
|
||||
} 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;
|
||||
}
|
||||
const user = await this.userService.getUserByMail(mailInfo.email);
|
||||
console.log(JSON.stringify(user));
|
||||
if (isEmpty(mailInfo.sender.name)) {
|
||||
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||
}
|
||||
@@ -42,8 +54,17 @@ export class MailService {
|
||||
});
|
||||
}
|
||||
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||
if (isEmpty(mailInfo.sender.name)) {
|
||||
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||
try {
|
||||
const validatedSender = SenderSchema.parse(mailInfo.sender);
|
||||
} 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;
|
||||
}
|
||||
await this.mailerService.sendMail({
|
||||
to: 'support@bizmatch.net',
|
||||
|
||||
@@ -1,28 +1,5 @@
|
||||
// export interface User {
|
||||
// id?: 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?: 'male' | 'female';
|
||||
// customerType?: 'buyer' | 'professional';
|
||||
// customerSubType?: 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
||||
// created?: Date;
|
||||
// updated?: Date;
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// }
|
||||
export interface UserData {
|
||||
id?: string;
|
||||
firstname: string;
|
||||
@@ -49,45 +26,80 @@ 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 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']);
|
||||
const PropertyTypeEnum = z.enum(['retail', 'land', 'industrial', 'office', 'mixedUse', 'multifamily', 'uncategorized']);
|
||||
const TypeEnum = z.enum([
|
||||
'automotive',
|
||||
'industrialServices',
|
||||
'foodAndRestaurant',
|
||||
'realEstate',
|
||||
'retail',
|
||||
'oilfield',
|
||||
'service',
|
||||
'advertising',
|
||||
'agriculture',
|
||||
'franchise',
|
||||
'professional',
|
||||
'manufacturing',
|
||||
'uncategorized',
|
||||
]);
|
||||
|
||||
const USStates = z.enum([
|
||||
'AL',
|
||||
'AK',
|
||||
'AZ',
|
||||
'AR',
|
||||
'CA',
|
||||
'CO',
|
||||
'CT',
|
||||
'DE',
|
||||
'FL',
|
||||
'GA',
|
||||
'HI',
|
||||
'ID',
|
||||
'IL',
|
||||
'IN',
|
||||
'IA',
|
||||
'KS',
|
||||
'KY',
|
||||
'LA',
|
||||
'ME',
|
||||
'MD',
|
||||
'MA',
|
||||
'MI',
|
||||
'MN',
|
||||
'MS',
|
||||
'MO',
|
||||
'MT',
|
||||
'NE',
|
||||
'NV',
|
||||
'NH',
|
||||
'NJ',
|
||||
'NM',
|
||||
'NY',
|
||||
'NC',
|
||||
'ND',
|
||||
'OH',
|
||||
'OK',
|
||||
'OR',
|
||||
'PA',
|
||||
'RI',
|
||||
'SC',
|
||||
'SD',
|
||||
'TN',
|
||||
'TX',
|
||||
'UT',
|
||||
'VT',
|
||||
'VA',
|
||||
'WA',
|
||||
'WV',
|
||||
'WI',
|
||||
'WY',
|
||||
]);
|
||||
export const AreasServedSchema = z.object({
|
||||
county: z.string().nonempty('County is required'),
|
||||
state: z.string().nonempty('State is required'),
|
||||
@@ -98,56 +110,6 @@ export const LicensedInSchema = z.object({
|
||||
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'],
|
||||
// },
|
||||
// );
|
||||
|
||||
const phoneRegex = /^\+1 \(\d{3}\) \d{3}-\d{4}$/;
|
||||
|
||||
export const UserSchema = z
|
||||
@@ -238,64 +200,90 @@ export const UserSchema = z
|
||||
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
|
||||
type?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
city?: string;
|
||||
state?: string; // 2-character state code
|
||||
zipCode?: number;
|
||||
county?: string;
|
||||
price?: number; // double precision
|
||||
favoritesForUser?: string[]; // Array of strings
|
||||
draft?: boolean;
|
||||
listingsCategory?: ListingsCategory;
|
||||
realEstateIncluded?: boolean;
|
||||
leasedLocation?: boolean;
|
||||
franchiseResale?: boolean;
|
||||
salesRevenue?: number; // double precision
|
||||
cashFlow?: number; // double precision
|
||||
supportAndTraining?: string;
|
||||
employees?: number;
|
||||
established?: number;
|
||||
internalListingNumber?: number;
|
||||
reasonForSale?: string;
|
||||
brokerLicencing?: string;
|
||||
internals?: string;
|
||||
imageName?: string;
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
visits?: number;
|
||||
lastVisit?: Date;
|
||||
latitude?: number; // double precision
|
||||
longitude?: number; // double precision
|
||||
}
|
||||
|
||||
export interface CommercialPropertyListing {
|
||||
id: string; // UUID as a string
|
||||
serialId: number; // Serial ID
|
||||
email: string; // References users.email
|
||||
type?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
city?: string;
|
||||
state?: string; // 2-character state code
|
||||
price?: number; // double precision
|
||||
favoritesForUser?: string[]; // Array of strings
|
||||
listingsCategory?: ListingsCategory;
|
||||
hideImage?: boolean;
|
||||
draft?: boolean;
|
||||
zipCode?: number;
|
||||
county?: string;
|
||||
imageOrder?: string[]; // Array of strings
|
||||
imagePath?: string;
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
visits?: number;
|
||||
lastVisit?: Date;
|
||||
latitude?: number; // double precision
|
||||
longitude?: number; // double precision
|
||||
// embedding?: number[]; // Uncomment if needed for vector embedding
|
||||
}
|
||||
export const BusinessListingSchema = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
email: z.string().email(),
|
||||
type: z.string().refine(val => TypeEnum.safeParse(val).success, {
|
||||
message: 'Invalid type. Must be one of: ' + TypeEnum.options.join(', '),
|
||||
}),
|
||||
title: z.string().min(10),
|
||||
description: z.string().min(10),
|
||||
city: z.string(),
|
||||
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||
}),
|
||||
zipCode: z.number().int().positive().optional().nullable(),
|
||||
county: z.string().optional().nullable(),
|
||||
price: z.number().positive().max(100000000),
|
||||
favoritesForUser: z.array(z.string()),
|
||||
draft: z.boolean(),
|
||||
listingsCategory: ListingsCategoryEnum,
|
||||
realEstateIncluded: z.boolean().optional().nullable(),
|
||||
leasedLocation: z.boolean().optional().nullable(),
|
||||
franchiseResale: z.boolean().optional().nullable(),
|
||||
salesRevenue: z.number().positive().max(100000000),
|
||||
cashFlow: z.number().positive().max(100000000),
|
||||
supportAndTraining: z.string().min(5),
|
||||
employees: z.number().int().positive().max(100000).optional().nullable(),
|
||||
established: z.number().int().min(1800).max(2030).optional().nullable(),
|
||||
internalListingNumber: z.number().int().positive().optional().nullable(),
|
||||
reasonForSale: z.string().min(5).optional().nullable(),
|
||||
brokerLicencing: z.string().min(5).optional().nullable(),
|
||||
internals: z.string().min(5).optional().nullable(),
|
||||
imageName: z.string().optional().nullable(),
|
||||
created: z.date(),
|
||||
updated: z.date(),
|
||||
visits: z.number().int().positive().optional().nullable(),
|
||||
lastVisit: z.date().optional().nullable(),
|
||||
latitude: z.number().optional().nullable(),
|
||||
longitude: z.number().optional().nullable(),
|
||||
});
|
||||
export type BusinessListing = z.infer<typeof BusinessListingSchema>;
|
||||
|
||||
export const CommercialPropertyListingSchema = z
|
||||
.object({
|
||||
id: z.string().uuid().optional(),
|
||||
serialId: z.number().int().positive().optional(),
|
||||
email: z.string().email(),
|
||||
//type: PropertyTypeEnum.optional(),
|
||||
type: z.string().refine(val => PropertyTypeEnum.safeParse(val).success, {
|
||||
message: 'Invalid type. Must be one of: ' + PropertyTypeEnum.options.join(', '),
|
||||
}),
|
||||
title: z.string().min(10),
|
||||
description: z.string().min(10),
|
||||
city: z.string(), // You might want to add a custom validation for valid US cities
|
||||
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||
}), // You might want to add a custom validation for valid US states
|
||||
price: z.number().positive().max(100000000),
|
||||
favoritesForUser: z.array(z.string()),
|
||||
listingsCategory: ListingsCategoryEnum,
|
||||
draft: z.boolean(),
|
||||
zipCode: z.number().int().positive().nullable().optional(), // You might want to add a custom validation for valid US zip codes
|
||||
county: z.string().nullable().optional(), // You might want to add a custom validation for valid US counties
|
||||
imageOrder: z.array(z.string()),
|
||||
imagePath: z.string().nullable().optional(),
|
||||
created: z.date(),
|
||||
updated: z.date(),
|
||||
visits: z.number().int().positive().nullable().optional(),
|
||||
lastVisit: z.date().nullable().optional(),
|
||||
latitude: z.number().nullable().optional(),
|
||||
longitude: z.number().nullable().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type CommercialPropertyListing = z.infer<typeof CommercialPropertyListingSchema>;
|
||||
|
||||
export const SenderSchema = z.object({
|
||||
name: z.string().min(6, { message: 'Name must be at least 6 characters long' }),
|
||||
email: z.string().email({ message: 'Invalid email address' }),
|
||||
phoneNumber: z.string().regex(/^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/, {
|
||||
message: 'Invalid US phone number format',
|
||||
}),
|
||||
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||
}),
|
||||
comments: z.string().min(10, { message: 'Comments must be at least 10 characters long' }),
|
||||
});
|
||||
export type Sender = z.infer<typeof SenderSchema>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BusinessListing, CommercialPropertyListing, User } from './db.model';
|
||||
import { BusinessListing, CommercialPropertyListing, Sender, User } from './db.model.js';
|
||||
|
||||
export interface StatesResult {
|
||||
state: string;
|
||||
@@ -199,13 +199,13 @@ export interface MailInfo {
|
||||
url: string;
|
||||
listing?: BusinessListing;
|
||||
}
|
||||
export interface Sender {
|
||||
name?: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
state?: string;
|
||||
comments?: string;
|
||||
}
|
||||
// export interface Sender {
|
||||
// name?: string;
|
||||
// email?: string;
|
||||
// phoneNumber?: string;
|
||||
// state?: string;
|
||||
// comments?: string;
|
||||
// }
|
||||
export interface ImageProperty {
|
||||
id: string;
|
||||
code: string;
|
||||
@@ -309,20 +309,19 @@ export function createDefaultCommercialPropertyListing(): CommercialPropertyList
|
||||
return {
|
||||
id: undefined,
|
||||
serialId: undefined,
|
||||
email: '',
|
||||
email: null,
|
||||
type: null,
|
||||
title: '',
|
||||
description: '',
|
||||
city: '',
|
||||
state: '',
|
||||
title: null,
|
||||
description: null,
|
||||
city: null,
|
||||
state: null,
|
||||
price: null,
|
||||
favoritesForUser: [],
|
||||
hideImage: false,
|
||||
draft: false,
|
||||
zipCode: null,
|
||||
county: '',
|
||||
county: null,
|
||||
imageOrder: [],
|
||||
imagePath: '',
|
||||
imagePath: null,
|
||||
created: null,
|
||||
updated: null,
|
||||
visits: null,
|
||||
@@ -335,12 +334,12 @@ export function createDefaultCommercialPropertyListing(): CommercialPropertyList
|
||||
export function createDefaultBusinessListing(): BusinessListing {
|
||||
return {
|
||||
id: undefined,
|
||||
email: '',
|
||||
email: null,
|
||||
type: null,
|
||||
title: '',
|
||||
description: '',
|
||||
city: '',
|
||||
state: '',
|
||||
title: null,
|
||||
description: null,
|
||||
city: null,
|
||||
state: null,
|
||||
price: null,
|
||||
favoritesForUser: [],
|
||||
draft: false,
|
||||
@@ -349,13 +348,13 @@ export function createDefaultBusinessListing(): BusinessListing {
|
||||
franchiseResale: false,
|
||||
salesRevenue: null,
|
||||
cashFlow: null,
|
||||
supportAndTraining: '',
|
||||
supportAndTraining: null,
|
||||
employees: null,
|
||||
established: null,
|
||||
internalListingNumber: null,
|
||||
reasonForSale: '',
|
||||
brokerLicencing: '',
|
||||
internals: '',
|
||||
reasonForSale: null,
|
||||
brokerLicencing: null,
|
||||
internals: null,
|
||||
created: null,
|
||||
updated: null,
|
||||
visits: null,
|
||||
|
||||
@@ -21,18 +21,7 @@ export class UserService {
|
||||
private fileService: FileService,
|
||||
private geoService: GeoService,
|
||||
) {}
|
||||
// private getConditions(criteria: UserListingCriteria): any[] {
|
||||
// const conditions = [];
|
||||
// if (criteria.states?.length > 0) {
|
||||
// criteria.states.forEach(state => {
|
||||
// conditions.push(sql`${schema.users.areasServed} @> ${JSON.stringify([{ state: state }])}`);
|
||||
// });
|
||||
// }
|
||||
// if (criteria.firstname || criteria.lastname) {
|
||||
// conditions.push(or(ilike(schema.users.firstname, `%${criteria.lastname}%`), ilike(schema.users.lastname, `%${criteria.lastname}%`)));
|
||||
// }
|
||||
// return conditions;
|
||||
// }
|
||||
|
||||
private getWhereConditions(criteria: UserListingCriteria): SQL[] {
|
||||
const whereConditions: SQL[] = [];
|
||||
|
||||
@@ -161,25 +150,6 @@ export class UserService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// async findUser(criteria: UserListingCriteria) {
|
||||
// const start = criteria.start ? criteria.start : 0;
|
||||
// const length = criteria.length ? criteria.length : 12;
|
||||
// const conditions = this.getConditions(criteria);
|
||||
// const [data, total] = await Promise.all([
|
||||
// this.conn
|
||||
// .select()
|
||||
// .from(schema.users)
|
||||
// .where(and(...conditions))
|
||||
// .offset(start)
|
||||
// .limit(length),
|
||||
// this.conn
|
||||
// .select({ count: sql`count(*)` })
|
||||
// .from(schema.users)
|
||||
// .where(and(...conditions))
|
||||
// .then(result => Number(result[0].count)),
|
||||
// ]);
|
||||
// return { total, data };
|
||||
// }
|
||||
async getStates(): Promise<any[]> {
|
||||
const query = sql`SELECT jsonb_array_elements(${schema.users.areasServed}) ->> 'state' AS state, COUNT(DISTINCT ${schema.users.id}) AS count FROM ${schema.users} GROUP BY state ORDER BY count DESC`;
|
||||
const result = await this.conn.execute(query);
|
||||
|
||||
Reference in New Issue
Block a user