reset criteria on home, show filter on home, new BOM generation, Schema overhaul
This commit is contained in:
@@ -11922,7 +11922,7 @@
|
||||
"description": "<h3>Thriving Caribbean Restaurant in the Heart of Washington, D.C.</h3><p>Well-established Caribbean restaurant with a loyal customer base. Known for its authentic cuisine and lively atmosphere.</p><p>Fully equipped kitchen and dining area. Experienced staff and management team in place. Strong financials and consistent growth. Perfect opportunity for someone in the food service industry or looking for a profitable investment.</p>",
|
||||
"type": 13,
|
||||
"state": "DC",
|
||||
"city": "Washington",
|
||||
"city": "Washington D.C.",
|
||||
"id": "b148ad7f-7f4a-4319-bc8c-df23cf85be3b",
|
||||
"price": 1500000,
|
||||
"salesRevenue": 2200000,
|
||||
@@ -12322,7 +12322,7 @@
|
||||
"description": "<h3>Thriving Indian Restaurant in the Heart of Washington, D.C.</h3><p>Well-established Indian restaurant with a loyal customer base. Known for its authentic cuisine and inviting atmosphere.</p><p>Fully equipped kitchen and dining area. Experienced staff and management team in place. Strong financials and consistent growth. Perfect opportunity for someone in the food service industry or looking for a profitable investment.</p>",
|
||||
"type": 13,
|
||||
"state": "DC",
|
||||
"city": "Washington",
|
||||
"city": "Washington D.C.",
|
||||
"id": "d8d0f4e3-fdbf-4c4b-b2bd-d8847f0d248f",
|
||||
"price": 1500000,
|
||||
"salesRevenue": 2200000,
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
"state": "HI",
|
||||
"hasImages": true,
|
||||
"price": 50000000,
|
||||
"city": "Maui",
|
||||
"city": "Hana",
|
||||
"description": "<p>Luxurious beachfront resort and spa on the island of Maui. This iconic property offers a once-in-a-lifetime opportunity to acquire a premier hospitality asset in a world-renowned destination.</p><h3>Resort Features:</h3><p>- 200 elegantly appointed guest rooms and suites<br>- Full-service spa and fitness center<br>- Multiple dining options, including fine dining and casual fare<br>- Infinity pool and direct beach access<br>- Event space for weddings and conferences</p><p>With a prime location on one of Maui's most stunning beaches, this resort and spa offers unparalleled potential for investors seeking a trophy asset in the highly sought-after Hawaiian market.</p>",
|
||||
"type": 105,
|
||||
"imageOrder": []
|
||||
@@ -932,7 +932,7 @@
|
||||
"state": "HI",
|
||||
"hasImages": true,
|
||||
"price": 75000000,
|
||||
"city": "Maui",
|
||||
"city": "Hana",
|
||||
"description": "<h2>Oceanfront Luxury Resort and Spa</h2><p>Exquisite luxury resort and spa situated on the pristine shores of Maui. This world-class property features elegant accommodations, top-tier amenities, and unparalleled ocean views, attracting discerning travelers from around the globe.</p><p>Resort Features:</p><p>- 200 beautifully appointed guest rooms and suites<br>- Full-service spa, fitness center, and infinity pool<br>- Gourmet dining options showcasing local cuisine<br>- Extensive meeting and event spaces<br>- Prime beachfront location with direct access to water activities</p><p>With a strong brand reputation, consistent occupancy, and a truly idyllic setting, this luxury resort and spa presents an exceptional opportunity for investors seeking a trophy asset in one of the world's most desirable destinations.</p>",
|
||||
"type": 104,
|
||||
"imageOrder": []
|
||||
@@ -2278,7 +2278,7 @@
|
||||
"state": "NY",
|
||||
"hasImages": true,
|
||||
"price": 500000000,
|
||||
"city": "New York",
|
||||
"city": "New York City",
|
||||
"description": "<p>Acquire this premier Class A office tower located in the heart of Midtown Manhattan, New York City. The property features state-of-the-art amenities, efficient floor plates, and unparalleled views of the city skyline, offering a prestigious address for top-tier tenants.</p><h3>Class A office tower features:</h3><p>- 1,000,000 square feet of Class A office space<br>- 95% occupancy rate with a diverse mix of creditworthy tenants<br>- LEED Platinum certified with advanced sustainability features<br>- Expansive lobby with 24/7 concierge and security services<br>- Multiple high-speed elevators and advanced building systems<br>- Prime Midtown location with access to transportation and amenities</p><p>Invest in this exceptional Class A office tower and benefit from the strong tenant demand and long-term value appreciation potential in the highly coveted Midtown Manhattan office market.</p>",
|
||||
"type": 103,
|
||||
"imageOrder": []
|
||||
@@ -2434,7 +2434,7 @@
|
||||
"state": "DC",
|
||||
"hasImages": true,
|
||||
"price": 225000000,
|
||||
"city": "Washington",
|
||||
"city": "Washington D.C.",
|
||||
"description": "<p>Acquire this stunning historic office building located in the heart of downtown Washington, D.C. The property features classic architecture, modern amenities, and a prime location just steps from the White House, offering a prestigious address for government relations and lobbying firms.</p><h3>Historic office building features:</h3><p>- 250,000 square feet of beautifully renovated office space<br>- Elegant lobbies and common areas with historic details<br>- State-of-the-art building systems and infrastructure<br>- Rooftop terrace with panoramic views of the Washington Monument<br>- On-site retail and dining amenities<br>- Unparalleled access to government agencies, embassies, and influential organizations</p><p>Invest in this exceptional historic office building and benefit from the strong and stable demand for prestigious office space in the heart of the nation's capital.</p>",
|
||||
"type": 103,
|
||||
"imageOrder": []
|
||||
@@ -2668,7 +2668,7 @@
|
||||
"state": "DC",
|
||||
"hasImages": true,
|
||||
"price": 400000000,
|
||||
"city": "Washington",
|
||||
"city": "Washington D.C.",
|
||||
"description": "<p>Acquire this iconic trophy office building located in the heart of Washington, D.C.'s central business district. The property features a stunning architectural design, premium amenities, and unparalleled views of the nation's capital, offering a prestigious address for top-tier tenants.</p><h3>Trophy office building features:</h3><p>- 600,000 square feet of Class A+ office space<br>- LEED Platinum certification for sustainability and energy efficiency<br>- Grand lobby with 24/7 concierge and security services<br>- Rooftop terrace with panoramic views of the Washington Monument and Capitol Building<br>- Private club with fine dining, fitness center, and conference facilities<br>- Direct access to multiple Metro lines and nearby amenities</p><p>Invest in this exceptional trophy office building and benefit from the strong and stable demand for premier office space in the nation's capital, driven by the presence of government agencies, lobbyists, and prestigious private sector tenants.</p>",
|
||||
"type": 103,
|
||||
"imageOrder": []
|
||||
|
||||
239
bizmatch-server/dbschema.ts
Normal file
239
bizmatch-server/dbschema.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
/**
|
||||
* AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
*
|
||||
* This file was automatically generated by pg-to-ts v.4.1.1
|
||||
* $ pg-to-ts generate -c postgresql://username:password@localhost:5432/bizmatch -t businesses -t commercials -t users -s public
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
export type Json = unknown;
|
||||
export type customerSubType = 'appraiser' | 'attorney' | 'broker' | 'cpa' | 'surveyor' | 'titleCompany';
|
||||
export type customerType = 'buyer' | 'professional';
|
||||
export type gender = 'female' | 'male';
|
||||
export type listingsCategory = 'business' | 'commercialProperty';
|
||||
|
||||
// Table businesses
|
||||
export interface Businesses {
|
||||
id: string;
|
||||
email: string | null;
|
||||
type: string | null;
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
city: string | null;
|
||||
state: string | null;
|
||||
zipCode: number | null;
|
||||
county: string | null;
|
||||
price: number | null;
|
||||
favoritesForUser: string[] | null;
|
||||
draft: boolean | null;
|
||||
listingsCategory: listingsCategory | null;
|
||||
realEstateIncluded: boolean | null;
|
||||
leasedLocation: boolean | null;
|
||||
franchiseResale: boolean | null;
|
||||
salesRevenue: number | null;
|
||||
cashFlow: number | null;
|
||||
supportAndTraining: string | null;
|
||||
employees: number | null;
|
||||
established: number | null;
|
||||
internalListingNumber: number | null;
|
||||
reasonForSale: string | null;
|
||||
brokerLicencing: string | null;
|
||||
internals: string | null;
|
||||
imageName: string | null;
|
||||
created: Date | null;
|
||||
updated: Date | null;
|
||||
visits: number | null;
|
||||
lastVisit: Date | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
}
|
||||
export interface BusinessesInput {
|
||||
id?: string;
|
||||
email?: string | null;
|
||||
type?: string | null;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
zipCode?: number | null;
|
||||
county?: string | null;
|
||||
price?: number | null;
|
||||
favoritesForUser?: string[] | null;
|
||||
draft?: boolean | null;
|
||||
listingsCategory?: listingsCategory | null;
|
||||
realEstateIncluded?: boolean | null;
|
||||
leasedLocation?: boolean | null;
|
||||
franchiseResale?: boolean | null;
|
||||
salesRevenue?: number | null;
|
||||
cashFlow?: number | null;
|
||||
supportAndTraining?: string | null;
|
||||
employees?: number | null;
|
||||
established?: number | null;
|
||||
internalListingNumber?: number | null;
|
||||
reasonForSale?: string | null;
|
||||
brokerLicencing?: string | null;
|
||||
internals?: string | null;
|
||||
imageName?: string | null;
|
||||
created?: Date | null;
|
||||
updated?: Date | null;
|
||||
visits?: number | null;
|
||||
lastVisit?: Date | null;
|
||||
latitude?: number | null;
|
||||
longitude?: number | null;
|
||||
}
|
||||
const businesses = {
|
||||
tableName: 'businesses',
|
||||
columns: ['id', 'email', 'type', 'title', 'description', 'city', 'state', 'zipCode', 'county', 'price', 'favoritesForUser', 'draft', 'listingsCategory', 'realEstateIncluded', 'leasedLocation', 'franchiseResale', 'salesRevenue', 'cashFlow', 'supportAndTraining', 'employees', 'established', 'internalListingNumber', 'reasonForSale', 'brokerLicencing', 'internals', 'imageName', 'created', 'updated', 'visits', 'lastVisit', 'latitude', 'longitude'],
|
||||
requiredForInsert: [],
|
||||
primaryKey: 'id',
|
||||
foreignKeys: { email: { table: 'users', column: 'email', $type: null as unknown as Users }, },
|
||||
$type: null as unknown as Businesses,
|
||||
$input: null as unknown as BusinessesInput
|
||||
} as const;
|
||||
|
||||
// Table commercials
|
||||
export interface Commercials {
|
||||
id: string;
|
||||
serialId: number;
|
||||
email: string | null;
|
||||
type: string | null;
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
city: string | null;
|
||||
state: string | null;
|
||||
price: number | null;
|
||||
favoritesForUser: string[] | null;
|
||||
listingsCategory: listingsCategory | null;
|
||||
hideImage: boolean | null;
|
||||
draft: boolean | null;
|
||||
zipCode: number | null;
|
||||
county: string | null;
|
||||
imageOrder: string[] | null;
|
||||
imagePath: string | null;
|
||||
created: Date | null;
|
||||
updated: Date | null;
|
||||
visits: number | null;
|
||||
lastVisit: Date | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
}
|
||||
export interface CommercialsInput {
|
||||
id?: string;
|
||||
serialId?: number;
|
||||
email?: string | null;
|
||||
type?: string | null;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
price?: number | null;
|
||||
favoritesForUser?: string[] | null;
|
||||
listingsCategory?: listingsCategory | null;
|
||||
hideImage?: boolean | null;
|
||||
draft?: boolean | null;
|
||||
zipCode?: number | null;
|
||||
county?: string | null;
|
||||
imageOrder?: string[] | null;
|
||||
imagePath?: string | null;
|
||||
created?: Date | null;
|
||||
updated?: Date | null;
|
||||
visits?: number | null;
|
||||
lastVisit?: Date | null;
|
||||
latitude?: number | null;
|
||||
longitude?: number | null;
|
||||
}
|
||||
const commercials = {
|
||||
tableName: 'commercials',
|
||||
columns: ['id', 'serialId', 'email', 'type', 'title', 'description', 'city', 'state', 'price', 'favoritesForUser', 'listingsCategory', 'hideImage', 'draft', 'zipCode', 'county', 'imageOrder', 'imagePath', 'created', 'updated', 'visits', 'lastVisit', 'latitude', 'longitude'],
|
||||
requiredForInsert: [],
|
||||
primaryKey: 'id',
|
||||
foreignKeys: { email: { table: 'users', column: 'email', $type: null as unknown as Users }, },
|
||||
$type: null as unknown as Commercials,
|
||||
$input: null as unknown as CommercialsInput
|
||||
} as const;
|
||||
|
||||
// Table users
|
||||
export interface Users {
|
||||
id: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber: string | null;
|
||||
description: string | null;
|
||||
companyName: string | null;
|
||||
companyOverview: string | null;
|
||||
companyWebsite: string | null;
|
||||
companyLocation: string | null;
|
||||
offeredServices: string | null;
|
||||
areasServed: Json | null;
|
||||
hasProfile: boolean | null;
|
||||
hasCompanyLogo: boolean | null;
|
||||
licensedIn: Json | null;
|
||||
gender: gender | null;
|
||||
customerType: customerType | null;
|
||||
customerSubType: customerSubType | null;
|
||||
created: Date | null;
|
||||
updated: Date | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
}
|
||||
export interface UsersInput {
|
||||
id?: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber?: string | null;
|
||||
description?: string | null;
|
||||
companyName?: string | null;
|
||||
companyOverview?: string | null;
|
||||
companyWebsite?: string | null;
|
||||
companyLocation?: string | null;
|
||||
offeredServices?: string | null;
|
||||
areasServed?: Json | null;
|
||||
hasProfile?: boolean | null;
|
||||
hasCompanyLogo?: boolean | null;
|
||||
licensedIn?: Json | null;
|
||||
gender?: gender | null;
|
||||
customerType?: customerType | null;
|
||||
customerSubType?: customerSubType | null;
|
||||
created?: Date | null;
|
||||
updated?: Date | null;
|
||||
latitude?: number | null;
|
||||
longitude?: number | null;
|
||||
}
|
||||
const users = {
|
||||
tableName: 'users',
|
||||
columns: ['id', 'firstname', 'lastname', 'email', 'phoneNumber', 'description', 'companyName', 'companyOverview', 'companyWebsite', 'companyLocation', 'offeredServices', 'areasServed', 'hasProfile', 'hasCompanyLogo', 'licensedIn', 'gender', 'customerType', 'customerSubType', 'created', 'updated', 'latitude', 'longitude'],
|
||||
requiredForInsert: ['firstname', 'lastname', 'email'],
|
||||
primaryKey: 'id',
|
||||
foreignKeys: {},
|
||||
$type: null as unknown as Users,
|
||||
$input: null as unknown as UsersInput
|
||||
} as const;
|
||||
|
||||
|
||||
export interface TableTypes {
|
||||
businesses: {
|
||||
select: Businesses;
|
||||
input: BusinessesInput;
|
||||
};
|
||||
commercials: {
|
||||
select: Commercials;
|
||||
input: CommercialsInput;
|
||||
};
|
||||
users: {
|
||||
select: Users;
|
||||
input: UsersInput;
|
||||
};
|
||||
}
|
||||
|
||||
export const tables = {
|
||||
businesses,
|
||||
commercials,
|
||||
users,
|
||||
}
|
||||
1
bizmatch-server/importlog.txt
Normal file
1
bizmatch-server/importlog.txt
Normal file
@@ -0,0 +1 @@
|
||||
Query: delete from "commercials"
|
||||
@@ -44,10 +44,13 @@ const logger = winston.createLogger({
|
||||
await db.delete(schema.commercials);
|
||||
await db.delete(schema.businesses);
|
||||
await db.delete(schema.users);
|
||||
let filePath = `./src/assets/geo.json`;
|
||||
const rawData = readFileSync(filePath, 'utf8');
|
||||
const geos = JSON.parse(rawData);
|
||||
|
||||
const sso = new SelectOptionsService();
|
||||
//Broker
|
||||
let filePath = `./data/broker.json`;
|
||||
filePath = `./data/broker.json`;
|
||||
let data: string = readFileSync(filePath, 'utf8');
|
||||
const usersData: UserData[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||
const generatedUserData = [];
|
||||
@@ -70,7 +73,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 = { firstname: '', lastname: '', email: '' };
|
||||
const user: User = { id: undefined, firstname: '', lastname: '', email: '' };
|
||||
user.licensedIn = [];
|
||||
userData.licensedIn.forEach(l => {
|
||||
console.log(l['value'], l['name']);
|
||||
@@ -91,6 +94,10 @@ for (let index = 0; index < usersData.length; index++) {
|
||||
user.companyOverview = userData.companyOverview;
|
||||
user.companyWebsite = userData.companyWebsite;
|
||||
user.companyLocation = userData.companyLocation;
|
||||
const [city, state] = user.companyLocation.split('-').map(e => e.trim());
|
||||
const cityGeo = geos.states.find(s => s.state_code === state).cities.find(c => c.name === city);
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
user.offeredServices = userData.offeredServices;
|
||||
user.gender = userData.gender;
|
||||
user.customerType = 'professional';
|
||||
@@ -142,6 +149,13 @@ for (let index = 0; index < commercialJsonData.length; index++) {
|
||||
commercial.email = user.email;
|
||||
commercial.draft = false;
|
||||
commercial.type = sso.typesOfCommercialProperty.find(e => e.oldValue === String(commercial.type)).value;
|
||||
const cityGeo = geos.states.find(s => s.state_code === commercial.state).cities.find(c => c.name === commercial.city);
|
||||
try {
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
} catch (e) {
|
||||
console.log(`----------------> ERROR ${commercial.state} - ${commercial.city}`);
|
||||
}
|
||||
// const reducedCommercial = {
|
||||
// city: commercial.city,
|
||||
// description: commercial.description,
|
||||
@@ -175,6 +189,13 @@ for (let index = 0; index < businessJsonData.length; index++) {
|
||||
const user = getRandomItem(generatedUserData);
|
||||
business.email = user.email;
|
||||
business.imageName = emailToDirName(user.email);
|
||||
const cityGeo = geos.states.find(s => s.state_code === business.state).cities.find(c => c.name === business.city);
|
||||
try {
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
} catch (e) {
|
||||
console.log(`----------------> ERROR ${business.state} - ${business.city}`);
|
||||
}
|
||||
// const embeddingText = JSON.stringify({
|
||||
// type: typesOfBusiness.find(b => b.value === String(business.type))?.name,
|
||||
// title: business.title,
|
||||
|
||||
@@ -16,18 +16,26 @@ EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."listingsCategory" AS ENUM('commercialProperty', 'business');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "businesses" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"email" varchar(255),
|
||||
"type" integer,
|
||||
"type" varchar(255),
|
||||
"title" varchar(255),
|
||||
"description" text,
|
||||
"city" varchar(255),
|
||||
"state" char(2),
|
||||
"zipCode" integer,
|
||||
"county" varchar(255),
|
||||
"price" double precision,
|
||||
"favoritesForUser" varchar(30)[],
|
||||
"draft" boolean,
|
||||
"listingsCategory" varchar(255),
|
||||
"listingsCategory" "listingsCategory",
|
||||
"realEstateIncluded" boolean,
|
||||
"leasedLocation" boolean,
|
||||
"franchiseResale" boolean,
|
||||
@@ -40,26 +48,27 @@ CREATE TABLE IF NOT EXISTS "businesses" (
|
||||
"reasonForSale" varchar(255),
|
||||
"brokerLicencing" varchar(255),
|
||||
"internals" text,
|
||||
"imagePath" varchar(200),
|
||||
"imageName" varchar(200),
|
||||
"created" timestamp,
|
||||
"updated" timestamp,
|
||||
"visits" integer,
|
||||
"lastVisit" timestamp,
|
||||
"embedding" vector(1536)
|
||||
"latitude" double precision,
|
||||
"longitude" double precision
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "commercials" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"serial_id" serial NOT NULL,
|
||||
"email" varchar(255),
|
||||
"type" integer,
|
||||
"type" varchar(255),
|
||||
"title" varchar(255),
|
||||
"description" text,
|
||||
"city" varchar(255),
|
||||
"state" char(2),
|
||||
"price" double precision,
|
||||
"favoritesForUser" varchar(30)[],
|
||||
"listingsCategory" varchar(255),
|
||||
"listingsCategory" "listingsCategory",
|
||||
"hideImage" boolean,
|
||||
"draft" boolean,
|
||||
"zipCode" integer,
|
||||
@@ -70,7 +79,8 @@ CREATE TABLE IF NOT EXISTS "commercials" (
|
||||
"updated" timestamp,
|
||||
"visits" integer,
|
||||
"lastVisit" timestamp,
|
||||
"embedding" vector(1536)
|
||||
"latitude" double precision,
|
||||
"longitude" double precision
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
@@ -94,7 +104,8 @@ CREATE TABLE IF NOT EXISTS "users" (
|
||||
"customerSubType" "customerSubType",
|
||||
"created" timestamp,
|
||||
"updated" timestamp,
|
||||
"embedding" vector(1536),
|
||||
"latitude" double precision,
|
||||
"longitude" double precision,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE "businesses" ALTER COLUMN "type" SET DATA TYPE varchar(255);--> statement-breakpoint
|
||||
ALTER TABLE "commercials" ALTER COLUMN "type" SET DATA TYPE varchar(255);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "commercials" RENAME COLUMN "serial_id" TO "serialId";
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "2d8edad3-5544-4cb1-a543-84c07737ea9f",
|
||||
"id": "aa3e53ed-4f1b-4e00-84ea-58939189a427",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "integer",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -51,6 +51,18 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"zipCode": {
|
||||
"name": "zipCode",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"county": {
|
||||
"name": "county",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "double precision",
|
||||
@@ -71,7 +83,8 @@
|
||||
},
|
||||
"listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"type": "varchar(255)",
|
||||
"type": "listingsCategory",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -147,8 +160,8 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"imagePath": {
|
||||
"name": "imagePath",
|
||||
"imageName": {
|
||||
"name": "imageName",
|
||||
"type": "varchar(200)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
@@ -177,9 +190,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -228,7 +247,7 @@
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "integer",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -270,7 +289,8 @@
|
||||
},
|
||||
"listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"type": "varchar(255)",
|
||||
"type": "listingsCategory",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -334,9 +354,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -488,9 +514,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -537,6 +569,14 @@
|
||||
"male",
|
||||
"female"
|
||||
]
|
||||
},
|
||||
"public.listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"commercialProperty",
|
||||
"business"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "93be31d4-beec-4ba8-8d4a-a52763342335",
|
||||
"prevId": "2d8edad3-5544-4cb1-a543-84c07737ea9f",
|
||||
"id": "ff415931-0de6-4c89-900f-c6fd64830b2e",
|
||||
"prevId": "aa3e53ed-4f1b-4e00-84ea-58939189a427",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -51,6 +51,18 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"zipCode": {
|
||||
"name": "zipCode",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"county": {
|
||||
"name": "county",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "double precision",
|
||||
@@ -71,7 +83,8 @@
|
||||
},
|
||||
"listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"type": "varchar(255)",
|
||||
"type": "listingsCategory",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -147,8 +160,8 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"imagePath": {
|
||||
"name": "imagePath",
|
||||
"imageName": {
|
||||
"name": "imageName",
|
||||
"type": "varchar(200)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
@@ -177,9 +190,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -214,8 +233,8 @@
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"serial_id": {
|
||||
"name": "serial_id",
|
||||
"serialId": {
|
||||
"name": "serialId",
|
||||
"type": "serial",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
@@ -270,7 +289,8 @@
|
||||
},
|
||||
"listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"type": "varchar(255)",
|
||||
"type": "listingsCategory",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
@@ -334,9 +354,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -488,9 +514,15 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "vector(1536)",
|
||||
"latitude": {
|
||||
"name": "latitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"longitude": {
|
||||
"name": "longitude",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
@@ -537,6 +569,14 @@
|
||||
"male",
|
||||
"female"
|
||||
]
|
||||
},
|
||||
"public.listingsCategory": {
|
||||
"name": "listingsCategory",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"commercialProperty",
|
||||
"business"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1720872296432,
|
||||
"tag": "0000_slim_nova",
|
||||
"when": 1721737805677,
|
||||
"tag": "0000_freezing_vengeance",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1721134224160,
|
||||
"tag": "0001_heavy_bloodscream",
|
||||
"when": 1721738173220,
|
||||
"tag": "0001_steady_phantom_reporter",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { boolean, char, doublePrecision, integer, jsonb, pgEnum, pgTable, serial, text, timestamp, uuid, varchar, vector } from 'drizzle-orm/pg-core';
|
||||
import { boolean, char, doublePrecision, integer, jsonb, pgEnum, pgTable, serial, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
||||
import { AreasServed, LicensedIn } from '../models/db.model';
|
||||
export const PG_CONNECTION = 'PG_CONNECTION';
|
||||
export const genderEnum = pgEnum('gender', ['male', 'female']);
|
||||
export const customerTypeEnum = pgEnum('customerType', ['buyer', 'professional']);
|
||||
export const customerSubTypeEnum = pgEnum('customerSubType', ['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']);
|
||||
export const listingsCategoryEnum = pgEnum('listingsCategory', ['commercialProperty', 'business']);
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
@@ -26,7 +27,9 @@ export const users = pgTable('users', {
|
||||
customerSubType: customerSubTypeEnum('customerSubType'),
|
||||
created: timestamp('created'),
|
||||
updated: timestamp('updated'),
|
||||
embedding: vector('embedding', { dimensions: 1536 }),
|
||||
latitude: doublePrecision('latitude'),
|
||||
longitude: doublePrecision('longitude'),
|
||||
// embedding: vector('embedding', { dimensions: 1536 }),
|
||||
});
|
||||
|
||||
export const businesses = pgTable('businesses', {
|
||||
@@ -37,10 +40,12 @@ export const businesses = pgTable('businesses', {
|
||||
description: text('description'),
|
||||
city: varchar('city', { length: 255 }),
|
||||
state: char('state', { length: 2 }),
|
||||
zipCode: integer('zipCode'),
|
||||
county: varchar('county', { length: 255 }),
|
||||
price: doublePrecision('price'),
|
||||
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
|
||||
draft: boolean('draft'),
|
||||
listingsCategory: varchar('listingsCategory', { length: 255 }),
|
||||
listingsCategory: listingsCategoryEnum('listingsCategory'), //varchar('listingsCategory', { length: 255 }),
|
||||
realEstateIncluded: boolean('realEstateIncluded'),
|
||||
leasedLocation: boolean('leasedLocation'),
|
||||
franchiseResale: boolean('franchiseResale'),
|
||||
@@ -53,18 +58,19 @@ export const businesses = pgTable('businesses', {
|
||||
reasonForSale: varchar('reasonForSale', { length: 255 }),
|
||||
brokerLicencing: varchar('brokerLicencing', { length: 255 }),
|
||||
internals: text('internals'),
|
||||
imageName: varchar('imagePath', { length: 200 }),
|
||||
imageName: varchar('imageName', { length: 200 }),
|
||||
created: timestamp('created'),
|
||||
updated: timestamp('updated'),
|
||||
visits: integer('visits'),
|
||||
lastVisit: timestamp('lastVisit'),
|
||||
// Neue Spalte für das OpenAI Embedding
|
||||
embedding: vector('embedding', { dimensions: 1536 }),
|
||||
latitude: doublePrecision('latitude'),
|
||||
longitude: doublePrecision('longitude'),
|
||||
// embedding: vector('embedding', { dimensions: 1536 }),
|
||||
});
|
||||
|
||||
export const commercials = pgTable('commercials', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
serialId: serial('serial_id'),
|
||||
serialId: serial('serialId'),
|
||||
email: varchar('email', { length: 255 }).references(() => users.email),
|
||||
type: varchar('type', { length: 255 }),
|
||||
title: varchar('title', { length: 255 }),
|
||||
@@ -73,7 +79,7 @@ export const commercials = pgTable('commercials', {
|
||||
state: char('state', { length: 2 }),
|
||||
price: doublePrecision('price'),
|
||||
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
|
||||
listingsCategory: varchar('listingsCategory', { length: 255 }),
|
||||
listingsCategory: listingsCategoryEnum('listingsCategory'), //listingsCategory: varchar('listingsCategory', { length: 255 }),
|
||||
hideImage: boolean('hideImage'),
|
||||
draft: boolean('draft'),
|
||||
zipCode: integer('zipCode'),
|
||||
@@ -84,5 +90,7 @@ export const commercials = pgTable('commercials', {
|
||||
updated: timestamp('updated'),
|
||||
visits: integer('visits'),
|
||||
lastVisit: timestamp('lastVisit'),
|
||||
embedding: vector('embedding', { dimensions: 1536 }),
|
||||
latitude: doublePrecision('latitude'),
|
||||
longitude: doublePrecision('longitude'),
|
||||
// embedding: vector('embedding', { dimensions: 1536 }),
|
||||
});
|
||||
|
||||
@@ -9,6 +9,16 @@ import { FileService } from '../file/file.service.js';
|
||||
import { BusinessListing, CommercialPropertyListing } from '../models/db.model';
|
||||
import { BusinessListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
|
||||
const EARTH_RADIUS_KM = 6371; // Erdradius in Kilometern
|
||||
|
||||
const getDistanceQuery = (lat: number, lon: number) => sql`
|
||||
${EARTH_RADIUS_KM} * 2 * ASIN(SQRT(
|
||||
POWER(SIN((${lat} - ${businesses.latitude}) * PI() / 180 / 2), 2) +
|
||||
COS(${lat} * PI() / 180) * COS(${businesses.latitude} * PI() / 180) *
|
||||
POWER(SIN((${lon} - ${businesses.longitude}) * PI() / 180 / 2), 2)
|
||||
))
|
||||
`;
|
||||
|
||||
@Injectable()
|
||||
export class BusinessListingService {
|
||||
constructor(
|
||||
@@ -142,7 +152,7 @@ export class BusinessListingService {
|
||||
const [{ value: totalCount }] = await countQuery;
|
||||
return totalCount;
|
||||
}
|
||||
async findBusinessesById(id: string, user: JwtUser): Promise<CommercialPropertyListing> {
|
||||
async findBusinessesById(id: string, user: JwtUser): Promise<BusinessListing> {
|
||||
let result = await this.conn
|
||||
.select()
|
||||
.from(businesses)
|
||||
@@ -159,7 +169,7 @@ export class BusinessListingService {
|
||||
return (await this.conn
|
||||
.select()
|
||||
.from(businesses)
|
||||
.where(and(...conditions))) as CommercialPropertyListing[];
|
||||
.where(and(...conditions))) as BusinessListing[];
|
||||
}
|
||||
// #### CREATE ########################################
|
||||
async createListing(data: BusinessListing): Promise<BusinessListing> {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Logger } from 'winston';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { commercials, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { BusinessListing, CommercialPropertyListing } from '../models/db.model';
|
||||
import { CommercialPropertyListing } from '../models/db.model';
|
||||
import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
|
||||
@Injectable()
|
||||
@@ -133,7 +133,7 @@ export class CommercialPropertyService {
|
||||
data.imageOrder = imageOrder;
|
||||
}
|
||||
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
||||
return updateListing as BusinessListing | CommercialPropertyListing;
|
||||
return updateListing as CommercialPropertyListing;
|
||||
}
|
||||
// ##############################################################
|
||||
// Images for commercial Properties
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
// }
|
||||
export interface UserData {
|
||||
id?: string;
|
||||
firstname: string;
|
||||
@@ -42,22 +42,53 @@ export interface UserData {
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
}
|
||||
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 BusinessListing {
|
||||
id: string;
|
||||
email?: string;
|
||||
id: string; // UUID as a string
|
||||
email: string; // References users.email
|
||||
type?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
price?: number;
|
||||
favoritesForUser?: 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;
|
||||
cashFlow?: number;
|
||||
salesRevenue?: number; // double precision
|
||||
cashFlow?: number; // double precision
|
||||
supportAndTraining?: string;
|
||||
employees?: number;
|
||||
established?: number;
|
||||
@@ -70,31 +101,35 @@ export interface BusinessListing {
|
||||
updated?: Date;
|
||||
visits?: number;
|
||||
lastVisit?: Date;
|
||||
listingsCategory?: 'commercialProperty' | 'business';
|
||||
latitude?: number; // double precision
|
||||
longitude?: number; // double precision
|
||||
}
|
||||
|
||||
export interface CommercialPropertyListing {
|
||||
id: string;
|
||||
serialId?: number;
|
||||
email?: string;
|
||||
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;
|
||||
price?: number;
|
||||
favoritesForUser?: 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[];
|
||||
imageOrder?: string[]; // Array of strings
|
||||
imagePath?: string;
|
||||
created?: Date;
|
||||
updated?: Date;
|
||||
visits?: number;
|
||||
lastVisit?: Date;
|
||||
listingsCategory?: 'commercialProperty' | 'business';
|
||||
latitude?: number; // double precision
|
||||
longitude?: number; // double precision
|
||||
// embedding?: number[]; // Uncomment if needed for vector embedding
|
||||
}
|
||||
export interface AreasServed {
|
||||
county: string;
|
||||
|
||||
@@ -103,7 +103,7 @@ export class UserService {
|
||||
.from(schema.users)
|
||||
.where(sql`email = ${email}`)) as User[];
|
||||
if (users.length === 0) {
|
||||
const user: User = { email, firstname: jwtuser.firstname, lastname: jwtuser.lastname, customerType: 'buyer' };
|
||||
const user: User = { id: undefined, email, firstname: jwtuser.firstname, lastname: jwtuser.lastname, customerType: 'buyer' };
|
||||
this.saveUser(user);
|
||||
return user;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user