44 Commits

Author SHA1 Message Date
5230ef1230 images based on http-server, filter dropdowns 2024-05-10 17:19:36 +02:00
d508415de4 show all listings, Bug Fixes 2024-05-09 16:10:01 +02:00
6b61c19bd7 Bug Fixing overall 2024-05-06 20:13:09 +02:00
bb5a408cdc cleanup + Property images 2024-05-05 15:30:10 +02:00
9121ca1a69 Marketing 2024-04-24 17:10:44 +02:00
4230867608 Rework of major pages 2024-04-24 14:31:32 +02:00
9e03620be7 format on save, resolve compile errors, functionality 1. stage 2024-04-23 17:32:21 +02:00
7f0f21b598 Umbau auf postgres 2. step 2024-04-22 22:26:44 +02:00
c90d6b72b7 Komplettumstieg auf drizzle 2024-04-20 20:48:18 +02:00
c4cdcf4505 Umstellung postgres 2. part 2024-04-15 22:05:20 +02:00
7d10080069 Start Umbau zu postgres 2024-04-14 22:52:19 +02:00
e784b424b0 200 datasets 2024-04-08 17:15:22 +02:00
4a9798c85e redis-om 2024-04-08 17:11:14 +02:00
5c6dfa1e0d output 2024-04-08 17:00:06 +02:00
8aea819496 data & perftest 2024-04-08 16:56:30 +02:00
fb69d2e4b3 favicon & json data 2024-04-07 19:46:58 +02:00
e5b7779492 removed unneeded files 2024-04-07 10:34:28 +02:00
a437851f6d drag & drop renewed, imageCropper revisited, imageOrder persisted, css quirks 2024-03-31 19:44:08 +02:00
89bb85a512 image-cropper component, drag & drop bilder 2024-03-29 20:59:34 +01:00
840d7a63b1 broker direcrtory renewed, imageservice updated, demo data 2024-03-25 20:17:49 +01:00
73ab12a694 Aufteilung details in user & listings, listings by user 2024-03-24 20:42:59 +01:00
a2c613c38f cropper & imageservice 2024-03-23 22:35:47 +01:00
d5210d3df4 areasServed ist string[] 2024-03-18 18:18:13 +01:00
fd91adda57 Image Upload, spinner aktivirt, listings & details überarbeitet 2024-03-18 18:17:04 +01:00
2b27ab8ba5 Ansichten verbessert - 1. Teil 2024-03-17 20:29:53 +01:00
25b201a9c6 Neue Email Adresse 2024-03-16 11:48:10 +01:00
b8cabf4f0c statische Webpage 2024-03-15 15:40:28 +01:00
f3868da8f8 Auth korrigiert & mail based on SES 2024-03-12 20:26:58 +01:00
be146fdc6a fix inputnumber, Umbau auf redis-om, Neubenamung 2024-03-11 18:27:43 +01:00
6ad40b6dca geo coding, user service, listing for business 2024-03-10 20:59:23 +01:00
06f349d1c3 Umstellung auf ACL 2024-03-01 15:35:51 -06:00
c7fccd46d9 changed path 2024-03-01 14:57:03 -06:00
3b0196bd66 change to tls & remote 2024-03-01 14:45:25 -06:00
244fbe78ed test change 2024-03-01 13:19:04 -06:00
56648b47a2 realm changed for localhost 2024-02-29 17:53:51 -06:00
f3b29a362e add SEO & Term of use & privacy 2024-02-29 13:19:10 -06:00
fa3b9e104a removed 2024-02-29 12:27:44 -06:00
798b84aa88 remove Professionals from SelectList 2024-02-29 12:21:56 -06:00
Andreas Knuth
0e01b6ba09 changed path 2024-02-29 12:08:39 -06:00
Andreas Knuth
3f9ffc1616 deleted 2024-02-29 12:06:16 -06:00
29a2eacd0f removed docker folder 2024-02-29 11:57:21 -06:00
2eb34adc7c auth controller 2024-02-29 11:24:23 -06:00
99bcc77abf gitea 2024-02-29 11:05:51 -06:00
32a987f556 changed 2024-02-29 11:04:31 -06:00
262 changed files with 186765 additions and 4381 deletions

2
.gitignore vendored
View File

@@ -67,5 +67,3 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
package-lock.json package-lock.json
*.jar *.jar
gitea
auth

View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -0,0 +1,45 @@
{
"env": {
"es2021": true,
"browser": true
},
"extends": [
"airbnb-base",
"airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"eslint-config-prettier",
"plugin:cypress/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": ["./tsconfig.json"]
},
"plugins": ["@typescript-eslint"],
"rules": {
"import/no-unresolved": ["off"],
"import/prefer-default-export": ["off"],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": ["error"],
"@typescript-eslint/lines-between-class-members": ["off"],
"no-param-reassign": ["off"],
"max-classes-per-file": ["off"],
"no-shadow": ["off"],
"class-methods-use-this": ["off"],
"react/jsx-filename-extension": ["off"],
"import/no-cycle": ["off"],
"radix": ["off"],
"no-promise-executor-return": ["off"],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "enumMember",
"format": ["UPPER_CASE", "PascalCase"]
}
],
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
"spaced-comment": ["off"],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
}
}

View File

@@ -55,4 +55,4 @@ pids
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
public pictures

View File

@@ -1,4 +1,18 @@
{ {
"arrowParens": "avoid",
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 220,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true, "singleQuote": true,
"trailingComma": "all" "tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false
} }

View File

@@ -17,6 +17,49 @@
"sourceMaps": true, "sourceMaps": true,
"stopOnEntry": false, "stopOnEntry": false,
"console": "integratedTerminal", "console": "integratedTerminal",
} },
{
"type": "node",
"request": "launch",
"name": "Debug Current TS File",
"program": "${workspaceFolder}/dist/src/drizzle/${fileBasenameNoExtension}.js",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"sourceMaps": true,
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "generateDefs",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/dist/src/drizzle/generateDefs.js",
"outFiles": [
"${workspaceFolder}/dist/src/drizzle/**/*.js"
],
"sourceMaps": true,
"smartStep": true,
},
{
"type": "node",
"request": "launch",
"name": "generateTypes",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/dist/src/drizzle/generateTypes.js",
"outFiles": [
"${workspaceFolder}/dist/src/drizzle/**/*.js"
],
"sourceMaps": true,
"smartStep": true,
},
] ]
} }

28
bizmatch-server/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"editor.suggestSelection": "first",
"vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
"explorer.confirmDelete": false,
"typescript.updateImportsOnFileMove.enabled": "always",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"prettier.printWidth": 240,
"git.autofetch": false,
"git.autorefresh": true
}

View File

@@ -1 +0,0 @@
FT.CREATE listingsIndex ON JSON PREFIX 1 listings: SCHEMA $.location AS location TAG SORTABLE $.price AS price NUMERIC SORTABLE $.listingsCategory AS listingsCategory TAG SORTABLE $.type AS type TAG SORTABLE

1326
bizmatch-server/broker.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
/// <reference types="multer" />
import { FileService } from '../file/file.service.js';
export declare class AccountController {
private fileService;
constructor(fileService: FileService);
uploadFile(file: Express.Multer.File, id: string): void;
}

View File

@@ -1,2 +0,0 @@
export declare class AccountService {
}

View File

@@ -1,6 +0,0 @@
import { AppService } from './app.service.js';
export declare class AppController {
private readonly appService;
constructor(appService: AppService);
getHello(): string;
}

View File

@@ -1,2 +0,0 @@
export declare class AppModule {
}

View File

@@ -1,3 +0,0 @@
export declare class AppService {
getHello(): string;
}

View File

@@ -1,66 +0,0 @@
[
{
"id":"1",
"userId":"1",
"listingsCategory": "business",
"title": "Industrial Service Company In Corpus Christi For Sale - 1954",
"summary": ["Asking price: $5,500,000","Sales revenue: $1,200,000","Net profit: $650,000"],
"description": ["This company services a wide variety of industries. Asking price includes Business and the Real Estate and is approx 30,000 sq ft with room for expansion including approx 5 acres. Absentee run business."],
"type": "2",
"location": "Texas",
"price":5500000,
"salesRevenue":1200000,
"cashFlow":650000,
"brokerLicencing":"TREC Broker #516788",
"established":1954,
"realEstateIncluded":true
},
{
"id":"2",
"userId":"1",
"listingsCategory": "business",
"title": "Coastal Bend Manufacturing Business Plastic Injection For Sale - 1950",
"summary": ["Asking price: $165,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
"description": [""],
"type": "12",
"location": "Texas",
"price":165000,
"salesRevenue":null,
"cashFlow":null,
"brokerLicencing":"TREC Broker #516788",
"established":1950,
"realEstateIncluded":false
},
{
"id":"3",
"userId":"1",
"listingsCategory": "business",
"title": "Corner Property On Everhart South-side Corpus Christi For Sale - 1944",
"summary": ["Asking price: $830,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
"description": [""],
"type": "3",
"location": "Texas",
"price":830000,
"salesRevenue":null,
"cashFlow":null,
"brokerLicencing":"TREC Broker #516788",
"established":1944,
"realEstateIncluded":false
},
{
"id":"4",
"userId":"1",
"listingsCategory": "business",
"title": "Corpus Christi Dessert Business For Sale - 1941",
"summary": ["Asking price: $124,900","Sales revenue: $225,000","Net profit: $50,000"],
"description": [""],
"type": "13",
"location": "Texas",
"price":830000,
"salesRevenue":225000,
"cashFlow":50000,
"brokerLicencing":"TREC Broker #516788",
"established":1941,
"realEstateIncluded":false
}
]

View File

@@ -1,14 +0,0 @@
[{
"id":"1",
"userId":"e0811669-c7eb-4e5e-a699-e8334d5c5b01",
"level":"Business Broker",
"start":"2024-02-12T21:54:20.603Z",
"modified":"2024-02-12T21:54:20.603Z",
"end":"9999-02-12T21:54:20.603Z",
"status":"active",
"invoices":[{
"date":"2024-02-12T21:54:20.603Z",
"id":"C991853B99",
"price":0
}]
}]

View File

@@ -1,8 +0,0 @@
/// <reference types="multer" />
export declare class FileService {
private subscriptions;
constructor();
private loadSubscriptions;
getSubscriptions(): any;
storeFile(file: Express.Multer.File, id: string): Promise<void>;
}

View File

@@ -1,13 +0,0 @@
import { ListingsService } from './listings.service.js';
import { Logger } from 'winston';
export declare class ListingsController {
private readonly listingsService;
private readonly logger;
constructor(listingsService: ListingsService, logger: Logger);
findAll(): any;
findById(id: string): any;
find(criteria: any): any;
updateById(id: string, listing: any): void;
create(listing: any): void;
deleteById(id: string): void;
}

View File

@@ -1,13 +0,0 @@
import { BusinessListing, InvestmentsListing, ListingCriteria, ProfessionalsBrokersListing } from '../models/main.model.js';
import { RedisService } from '../redis/redis.service.js';
import { Logger } from 'winston';
export declare class ListingsService {
private redisService;
private readonly logger;
constructor(redisService: RedisService, logger: Logger);
setListing(value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing, id?: string): Promise<void>;
getListingById(id: string): Promise<any>;
deleteListing(id: string): void;
getAllListings(start?: number, end?: number): Promise<any>;
find(criteria: ListingCriteria): Promise<any>;
}

View File

@@ -1,7 +0,0 @@
import { MailService } from './mail.service.js';
import { MailInfo } from '../models/server.model.js';
export declare class MailController {
private mailService;
constructor(mailService: MailService);
sendEMail(id: string, mailInfo: MailInfo): any;
}

View File

@@ -1,2 +0,0 @@
export declare class MailModule {
}

View File

@@ -1,9 +0,0 @@
import { MailerService } from '@nestjs-modules/mailer';
import { AuthService } from '../auth/auth.service.js';
import { MailInfo } from '../models/server.model.js';
export declare class MailService {
private mailerService;
private authService;
constructor(mailerService: MailerService, authService: AuthService);
sendInquiry(userId: string, mailInfo: MailInfo): Promise<void>;
}

View File

@@ -1,5 +0,0 @@
<p>Hey {{ name }},</p>
<p>You got an inquiry a</p>
<p>
{{inquiry}}
</p>

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,126 +0,0 @@
export interface KeyValue {
name: string;
value: string;
}
export interface KeyValueStyle {
name: string;
value: string;
icon: string;
bgColorClass: string;
textColorClass: string;
}
export type SelectOption<T = number> = {
value: T;
label: string;
};
export interface Listing {
id: string;
userId: string;
title: string;
description: Array<string>;
location: string;
favoritesForUser: Array<string>;
hideImage?: boolean;
created: Date;
updated: Date;
}
export interface BusinessListing extends Listing {
listingsCategory: 'business';
summary: Array<string>;
type: string;
price?: number;
realEstateIncluded?: boolean;
salesRevenue?: number;
cashFlow?: number;
netProfit?: number;
inventory?: string;
employees?: number;
established?: number;
reasonForSale?: string;
brokerLicencing?: string;
internals?: string;
}
export interface ProfessionalsBrokersListing extends Listing {
listingsCategory: 'professionals_brokers';
summary: string;
address?: string;
email?: string;
website?: string;
category?: 'Professionals' | 'Broker';
}
export interface InvestmentsListing extends Listing {
listingsCategory: 'investment';
email?: string;
website?: string;
phoneNumber?: string;
}
export type ListingType = BusinessListing | ProfessionalsBrokersListing | InvestmentsListing;
export interface ListingCriteria {
type: string;
location: string;
minPrice: string;
maxPrice: string;
realEstateChecked: boolean;
listingsCategory: 'business' | 'professionals_brokers' | 'investment';
category: 'professional|broker';
}
export interface User {
id: string;
username: string;
firstname: string;
lastname: string;
email: string;
}
export interface Subscription {
id: string;
userId: string;
level: string;
start: Date;
modified: Date;
end: Date;
status: string;
invoices: Array<Invoice>;
}
export interface Invoice {
id: string;
date: Date;
price: number;
}
export interface JwtToken {
exp: number;
iat: number;
auth_time: number;
jti: string;
iss: string;
aud: string;
sub: string;
typ: string;
azp: string;
nonce: string;
session_state: string;
acr: string;
realm_access: Realmaccess;
resource_access: Resourceaccess;
scope: string;
sid: string;
email_verified: boolean;
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
user_id: string;
}
interface Resourceaccess {
account: Realmaccess;
}
interface Realmaccess {
roles: string[];
}
export interface PageEvent {
first: number;
rows: number;
page: number;
pageCount: number;
}
export {};

View File

@@ -1,129 +0,0 @@
export interface KeyValue {
name: string;
value: string;
}
export interface KeyValueStyle {
name: string;
value: string;
icon:string;
bgColorClass:string;
textColorClass:string;
}
export type SelectOption<T = number> = {
value: T;
label: string;
};
export interface Listing {
id: string;
userId: string;
title: string;
description: Array<string>;
location: string;//enum
favoritesForUser:Array<string>;
hideImage?:boolean;
created:Date;
updated:Date;
}
export interface BusinessListing extends Listing {
listingsCategory: 'business'; //enum
summary: Array<string>;
type: string; //enum
price?: number;
realEstateIncluded?: boolean;
salesRevenue?: number;
cashFlow?: number;
netProfit?: number;
inventory?: string;
employees?: number;
established?: number;
reasonForSale?: string;
brokerLicencing?: string;
internals?: string;
}
export interface ProfessionalsBrokersListing extends Listing {
listingsCategory: 'professionals_brokers'; //enum
summary: string;
address?: string;
email?: string;
website?: string;
category?: 'Professionals' | 'Broker';
}
export interface InvestmentsListing extends Listing {
listingsCategory: 'investment'; //enum
email?: string;
website?: string;
phoneNumber?: string;
}
export type ListingType =
| BusinessListing
| ProfessionalsBrokersListing
| InvestmentsListing;
export interface ListingCriteria {
type:string,
location:string,
minPrice:string,
maxPrice:string,
realEstateChecked:boolean,
listingsCategory:'business'|'professionals_brokers'|'investment',
category:'professional|broker'
}
export interface User {
id: string;
username: string;
firstname: string;
lastname: string;
email: string;
}
export interface Subscription {
id: string;
userId:string
level: string;
start: Date;
modified: Date;
end: Date;
status: string;
invoices: Array<Invoice>;
}
export interface Invoice {
id: string,
date: Date,
price: number
}
export interface JwtToken {
exp: number;
iat: number;
auth_time: number;
jti: string;
iss: string;
aud: string;
sub: string;
typ: string;
azp: string;
nonce: string;
session_state: string;
acr: string;
realm_access: Realmaccess;
resource_access: Resourceaccess;
scope: string;
sid: string;
email_verified: boolean;
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
user_id: string;
}
interface Resourceaccess {
account: Realmaccess;
}
interface Realmaccess {
roles: string[];
}
export interface PageEvent {
first: number;
rows: number;
page: number;
pageCount: number;
}

View File

@@ -1,11 +0,0 @@
export interface MailInfo {
sender: Sender;
userId: string;
}
export interface Sender {
name: string;
email: string;
phoneNumber: string;
state: string;
comments: string;
}

View File

@@ -1,5 +0,0 @@
import { RedisService } from './redis.service.js';
export declare class RedisController {
private redisService;
constructor(redisService: RedisService);
}

View File

@@ -1,2 +0,0 @@
export declare class RedisModule {
}

View File

@@ -1,11 +0,0 @@
export declare const LISTINGS = "LISTINGS";
export declare const SUBSCRIPTIONS = "SUBSCRIPTIONS";
export declare const USERS = "USERS";
export declare class RedisService {
private redis;
setJson(id: string, value: any): Promise<void>;
delete(id: string): Promise<void>;
getJson(id: string, prefix: string): Promise<any>;
getId(prefix: 'LISTINGS' | 'SUBSCRIPTIONS' | 'USERS'): Promise<string>;
search(prefix: 'LISTINGS' | 'SUBSCRIPTIONS' | 'USERS', clause: string): Promise<any>;
}

View File

@@ -1,6 +0,0 @@
import { SelectOptionsService } from './select-options.service.js';
export declare class SelectOptionsController {
private selectOptionsService;
constructor(selectOptionsService: SelectOptionsService);
getSelectOption(): any;
}

View File

@@ -1,10 +0,0 @@
import { KeyValue, KeyValueStyle } from '../models/main.model.js';
export declare class SelectOptionsService {
constructor();
typesOfBusiness: Array<KeyValueStyle>;
prices: Array<KeyValue>;
listingCategories: Array<KeyValue>;
categories: Array<KeyValueStyle>;
private usStates;
locations: Array<any>;
}

View File

@@ -1,6 +0,0 @@
import { FileService } from '../file/file.service.js';
export declare class SubscriptionsController {
private readonly fileService;
constructor(fileService: FileService);
findAll(): any;
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
export declare function convertStringToNullUndefined(value: any): any;

View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: "./src/drizzle/schema.ts",
out: "./src/drizzle/migrations",
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL,
},
verbose: true,
strict: true,
})

View File

@@ -4,7 +4,10 @@
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true, "deleteOutDir": true,
"assets": ["assets/**/*","**/*.hbs"], "assets": [
"assets/**/*",
"**/*.hbs"
],
"watchAssets": true "watchAssets": true
} }
} }

View File

@@ -18,7 +18,12 @@
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json",
"generate": "drizzle-kit generate:pg",
"drop": "drizzle-kit drop",
"migrate": "tsx src/drizzle/migrate.ts",
"import": "tsx src/drizzle/import.ts",
"generateTypes": "tsx src/drizzle/generateTypes.ts src/drizzle/schema.ts src/models/db.model.ts"
}, },
"dependencies": { "dependencies": {
"@nestjs-modules/mailer": "^1.10.3", "@nestjs-modules/mailer": "^1.10.3",
@@ -29,8 +34,10 @@
"@nestjs/passport": "^10.0.3", "@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@nestjs/serve-static": "^4.0.1", "@nestjs/serve-static": "^4.0.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.30.8",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"ioredis": "^5.3.2",
"ky": "^1.2.0", "ky": "^1.2.0",
"nest-winston": "^1.9.4", "nest-winston": "^1.9.4",
"nodemailer": "^6.9.10", "nodemailer": "^6.9.10",
@@ -39,12 +46,19 @@
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"pg": "^8.11.5",
"redis": "^4.6.13",
"redis-om": "^0.4.3",
"reflect-metadata": "^0.2.0", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"sharp": "^0.33.2",
"tsx": "^4.7.2",
"urlcat": "^3.1.0", "urlcat": "^3.1.0",
"winston": "^3.11.0" "winston": "^3.11.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/parser": "^7.24.4",
"@babel/traverse": "^7.24.1",
"@nestjs/cli": "^10.0.0", "@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0", "@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0", "@nestjs/testing": "^10.0.0",
@@ -56,14 +70,20 @@
"@types/passport-google-oauth20": "^2.0.14", "@types/passport-google-oauth20": "^2.0.14",
"@types/passport-jwt": "^4.0.1", "@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38", "@types/passport-local": "^1.0.38",
"@types/pg": "^8.11.5",
"@types/supertest": "^6.0.0", "@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
"commander": "^12.0.0",
"drizzle-kit": "^0.20.16",
"eslint": "^8.42.0", "eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"kysely-codegen": "^0.15.0",
"pg-to-ts": "^4.1.1",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"rimraf": "^5.0.5",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",

View File

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

View File

@@ -1,33 +1,30 @@
import { Module } from '@nestjs/common'; import { MiddlewareConsumer, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston';
import path from 'path';
import { fileURLToPath } from 'url';
import * as winston from 'winston';
import { AppController } from './app.controller.js'; import { AppController } from './app.controller.js';
import { AppService } from './app.service.js'; import { AppService } from './app.service.js';
import { ListingsController } from './listings/listings.controller.js';
import { FileService } from './file/file.service.js';
import { AuthService } from './auth/auth.service.js';
import { AuthController } from './auth/auth.controller.js';
import { ConfigModule } from '@nestjs/config';
import { SelectOptionsController } from './select-options/select-options.controller.js';
import { SelectOptionsService } from './select-options/select-options.service.js';
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
import { RedisModule } from './redis/redis.module.js';
import { ListingsService } from './listings/listings.service.js';
import { AccountController } from './account/account.controller.js';
import { AccountService } from './account/account.service.js';
import { ServeStaticModule } from '@nestjs/serve-static';
import path, { join } from 'path';
import { fileURLToPath } from 'url';
import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { MailModule } from './mail/mail.module.js';
import { AuthModule } from './auth/auth.module.js'; import { AuthModule } from './auth/auth.module.js';
import { FileService } from './file/file.service.js';
import { GeoModule } from './geo/geo.module.js';
import { ImageModule } from './image/image.module.js';
import { ListingsModule } from './listings/listings.module.js';
import { MailModule } from './mail/mail.module.js';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
import { SelectOptionsModule } from './select-options/select-options.module.js';
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
import { UserModule } from './user/user.module.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@Module({ @Module({
imports: [ConfigModule.forRoot(), RedisModule, MailModule, AuthModule, imports: [
ServeStaticModule.forRoot({ ConfigModule.forRoot({ isGlobal: true }),
rootPath: join(__dirname, '..', 'public'), // `public` ist das Verzeichnis, wo Ihre statischen Dateien liegen MailModule,
}), AuthModule,
WinstonModule.forRoot({ WinstonModule.forRoot({
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
@@ -43,9 +40,18 @@ const __dirname = path.dirname(__filename);
// other transports... // other transports...
], ],
// other options // other options
}) }),
GeoModule,
UserModule,
ListingsModule,
SelectOptionsModule,
ImageModule,
], ],
controllers: [AppController, ListingsController, SelectOptionsController, SubscriptionsController, AccountController], controllers: [AppController, SubscriptionsController],
providers: [AppService, FileService, SelectOptionsService, ListingsService, AccountService], providers: [AppService, FileService],
}) })
export class AppModule {} export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestDurationMiddleware).forRoutes('*');
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
import { Controller, Get, Param, Put } from '@nestjs/common';
import { AuthService } from './auth.service.js';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Get()
getAccessToken(): any {
return this.authService.getAccessToken();
}
@Get('users')
getUsers(): any {
return this.authService.getUsers();
}
@Get('user/:userid')
getUser(@Param('userid') userId: string): any {
return this.authService.getUser(userId);
}
@Get('groups')
getGroups(): any {
return this.authService.getGroups();
}
@Get('user/:userid/groups') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth
getGroupsForUsers(@Param('userid') userId: string): any {
return this.authService.getGroupsForUser(userId);
}
@Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth
getLastLogin(@Param('userid') userId: string): any {
return this.authService.getLastLogin(userId);
}
@Put('user/:userid/group/:groupid') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth //
addUser2Group(@Param('userid') userId: string,@Param('groupid') groupId: string): any {
return this.authService.addUser2Group(userId,groupId);
}
}

View File

@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import path, { join } from 'path';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js';
import { fileURLToPath } from 'url';
import { AuthService } from './auth.service.js';
import { AuthController } from './auth.controller.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@Module({
imports: [
],
providers: [AuthService],
controllers: [AuthController],
exports:[AuthService]
})
export class AuthModule {}

View File

@@ -0,0 +1,107 @@
import { Injectable } from '@nestjs/common';
// import got from 'got';
import ky from 'ky';
import urlcat from 'urlcat';
@Injectable()
export class AuthService {
public async getAccessToken() {
const form = new FormData();
form.append('grant_type', 'password');
form.append('username', process.env.user);
form.append('password', process.env.password);
try {
const params = new URLSearchParams();
params.append('grant_type', 'password');
params.append('username', process.env.user);
params.append('password', process.env.password);
const URL = `${process.env.host}${process.env.tokenURL}`;
const response = await ky.post(URL, {
body: params.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':'Basic YWRtaW4tY2xpOnE0RmJnazFkd0NaelFQZmt5VzhhM3NnckV5UHZlRUY3'
},
}).json();
return (<any>response).access_token;
} catch (error) {
if (error.name === 'HTTPError') {
const errorJson = await error.response.json();
console.error('Fehlerantwort vom Server:', errorJson);
} else {
console.error('Allgemeiner Fehler:', error);
}
}
}
public async getUsers(){
const token = await this.getAccessToken();
const URL = `${process.env.host}${process.env.usersURL}`;
const response = await ky.get(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
public async getUser(userid:string){
const token = await this.getAccessToken();
const URL = urlcat(process.env.host,process.env.userURL,{userid})
const response = await ky.get(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
public async getGroups(){
const token = await this.getAccessToken();
const URL = `${process.env.host}${process.env.groupsURL}`;
const response = await ky.get(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
public async getGroupsForUser(userid:string){
const token = await this.getAccessToken();
const URL = urlcat(process.env.host,process.env.userGroupsURL,{userid})
const response = await ky.get(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
public async getLastLogin(userid:string){
const token = await this.getAccessToken();
const URL = urlcat(process.env.host,process.env.lastLoginURL,{userid})
const response = await ky.get(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
public async addUser2Group(userid:string,groupid:string){
const token = await this.getAccessToken();
const URL = urlcat(process.env.host,process.env.addUser2GroupURL,{userid,groupid})
const response = await ky.put(URL, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':`Bearer ${token}`
},
}).json();
return response
}
}

View File

@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { drizzle } from 'drizzle-orm/node-postgres';
import pkg from 'pg';
const { Pool } = pkg;
import * as schema from './schema.js';
import { ConfigService } from '@nestjs/config';
import { jsonb, varchar } from 'drizzle-orm/pg-core';
import { PG_CONNECTION } from './schema.js';
@Module({
providers: [
{
provide: PG_CONNECTION,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({
connectionString,
// ssl: true,
});
return drizzle(pool, { schema, logger:true });
},
},
],
exports: [PG_CONNECTION],
})
export class DrizzleModule {}

View File

@@ -0,0 +1,143 @@
import fs from 'fs';
import ts from 'typescript';
export const TABLE_BO_MAPPING = {
'users':'User',
'businesses':'BusinessListing',
'commercials':'CommercialPropertyListing'
}
function generateInterfaceDefinitions(inputFile: string, outputFile: string): void {
const sourceFile = ts.createSourceFile(
inputFile,
fs.readFileSync(inputFile, 'utf8'),
ts.ScriptTarget.Latest,
true
);
let interfaceDefinitions = '';
ts.forEachChild(sourceFile, (node) => {
if (ts.isVariableStatement(node)) {
const variableDeclaration = node.declarationList.declarations[0];
if (variableDeclaration.initializer && ts.isCallExpression(variableDeclaration.initializer)) {
const initializer = variableDeclaration.initializer;
if (initializer.expression.getText(sourceFile) === 'pgTable') {
const tableName = initializer.arguments[0].getText(sourceFile).slice(1, -1); // Entfernen von zusätzlichen Anführungszeichen
const tableDefinition = initializer.arguments[1];
const interfaceName = TABLE_BO_MAPPING[tableName] ? TABLE_BO_MAPPING[tableName] : tableName.charAt(0).toUpperCase() + tableName.slice(1);
let interfaceDefinition = `export interface ${interfaceName} {\n`;
ts.forEachChild(tableDefinition, (propertyNode) => {
if (ts.isPropertyAssignment(propertyNode)) {
const propertyName = propertyNode.name.getText(sourceFile);
const propertyDefinition = propertyNode.initializer;
if (ts.isCallExpression(propertyDefinition)) {
const propertyType = getPropertyType(propertyDefinition, sourceFile);
const isOptional = !hasNonOptionalModifier(propertyDefinition.expression);
interfaceDefinition += ` ${propertyName}${isOptional ? '?' : ''}: ${propertyType};\n`;
}
}
});
interfaceDefinition += '}\n\n';
interfaceDefinitions += interfaceDefinition;
}
}
}
});
fs.writeFileSync(outputFile, interfaceDefinitions);
console.log(`Interface definitions generated successfully. Output file: ${outputFile}`);
}
function getPropertyType(propertyDefinition: ts.CallExpression, sourceFile: ts.SourceFile): string {
const typeFunction = getTypeFunctionName(propertyDefinition.expression, sourceFile);
let propertyType = '';
switch (typeFunction) {
case 'varchar':
case 'char':
case 'text':
case 'uuid':
propertyType = 'string';
break;
case 'integer':
case 'numeric':
case 'real':
case 'doublePrecision':
propertyType = 'number';
break;
case 'boolean':
propertyType = 'boolean';
break;
case 'timestamp':
propertyType = 'Date';
break;
default:
propertyType = 'any';
}
const isArray = hasArrayModifier(propertyDefinition.expression);
return isArray ? `${propertyType}[]` : propertyType;
}
function getTypeFunctionName(expression: ts.Expression, sourceFile: ts.SourceFile): string {
// Prüfen, ob die aktuelle Expression ein Identifier ist (SyntaxKind.Identifier hat den Wert 80)
if (expression.kind === ts.SyntaxKind.Identifier) {
return expression.getText(sourceFile);
}
// Wenn die Expression eine CallExpression oder eine PropertyAccessExpression ist,
// gehe zur nächsten Expression-Ebene
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
return getTypeFunctionName(expression.expression, sourceFile);
}
// Falls ein nicht unterstützter Expression-Typ vorliegt, gibt 'unknown' zurück
return 'unknown';
}
function hasArrayModifier(expression: ts.Expression): boolean {
// Prüfe, ob die aktuelle Expression eine CallExpression ist und der Funktionsname 'array' ist
if (ts.isPropertyAccessExpression(expression) && expression.name.getText() === 'array') {
return true;
}
// Wenn die Expression eine weitere CallExpression oder PropertyAccessExpression ist,
// prüfe rekursiv die nächste Ebene
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
return hasArrayModifier(expression.expression);
}
// Wenn keine weitere Ebene oder kein Array-Modifier gefunden wurde, gib false zurück
return false;
}
function hasNonOptionalModifier(expression: ts.Expression): boolean {
// Prüfe, ob die aktuelle Expression eine CallExpression ist und der Funktionsname 'notNull' ist
if (ts.isPropertyAccessExpression(expression) && (expression.name.getText() === 'notNull' || expression.name.getText() === 'primaryKey')) {
return true;
}
// Wenn die Expression eine weitere CallExpression oder PropertyAccessExpression ist,
// prüfe rekursiv die nächste Ebene
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
return hasNonOptionalModifier(expression.expression);
}
// Wenn keine weitere Ebene oder kein NotNull-Modifier gefunden wurde, gib false zurück
return false;
}
// Hauptprogramm
const inputFile = process.argv[2]|| './src/drizzle/schema.ts';
const outputFile = process.argv[3] || './model.ts';
if (!inputFile) {
console.error('Please provide an input file.');
process.exit(1);
}
generateInterfaceDefinitions(inputFile, outputFile);

View File

@@ -0,0 +1,153 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
import { join } from 'path';
import pkg from 'pg';
import { rimraf } from 'rimraf';
import sharp from 'sharp';
import { BusinessListing, CommercialPropertyListing, User } from 'src/models/db.model.js';
import * as schema from './schema.js';
const { Pool } = pkg;
const connectionString = process.env.DATABASE_URL;
// const pool = new Pool({connectionString})
const client = new Pool({ connectionString });
const db = drizzle(client, { schema, logger: true });
//Delete Content
await db.delete(schema.commercials);
await db.delete(schema.businesses);
await db.delete(schema.users);
//Broker
let filePath = `./data/broker.json`;
let data: string = readFileSync(filePath, 'utf8');
const userData: User[] = JSON.parse(data); // Erwartet ein Array von Objekten
const generatedUserData = [];
console.log(userData.length);
let i = 0,
male = 0,
female = 0;
const targetPathProfile = `./pictures/profile`;
deleteFilesOfDir(targetPathProfile);
const targetPathLogo = `./pictures/logo`;
deleteFilesOfDir(targetPathLogo);
for (const user of userData) {
delete user.id;
user.licensedIn = user.licensedIn.map(l => `${l['name']}|${l['value']}`);
user.hasCompanyLogo = true;
user.hasProfile = true;
const u = await db.insert(schema.users).values(user).returning({ insertedId: schema.users.id, gender: schema.users.gender });
generatedUserData.push(u[0].insertedId);
i++;
if (u[0].gender === 'male') {
male++;
const data = readFileSync(`./pictures/profile_base/Mann_${male}.jpg`);
await storeProfilePicture(data, u[0].insertedId);
} else {
female++;
const data = readFileSync(`./pictures/profile_base/Frau_${male}.jpg`);
await storeProfilePicture(data, u[0].insertedId);
}
const data = readFileSync(`./pictures/logos_base/${i}.jpg`);
await storeCompanyLogo(data, u[0].insertedId);
}
//Business Listings
filePath = `./data/businesses.json`;
data = readFileSync(filePath, 'utf8');
const businessJsonData = JSON.parse(data) as BusinessListing[]; // Erwartet ein Array von Objekten
for (const business of businessJsonData) {
delete business.id;
business.created = new Date(business.created);
business.userId = getRandomItem(generatedUserData);
await db.insert(schema.businesses).values(business);
}
//Corporate Listings
filePath = `./data/commercials.json`;
data = readFileSync(filePath, 'utf8');
const commercialJsonData = JSON.parse(data) as CommercialPropertyListing[]; // Erwartet ein Array von Objekten
for (const commercial of commercialJsonData) {
const id = commercial.id;
delete commercial.id;
commercial.imageOrder = getFilenames(id);
commercial.imagePath = id;
commercial.created = getRandomDateWithinLastYear();
commercial.userId = getRandomItem(generatedUserData);
await db.insert(schema.commercials).values(commercial);
}
//End
await client.end();
function getRandomItem<T>(arr: T[]): T {
if (arr.length === 0) {
throw new Error('The array is empty.');
}
const randomIndex = Math.floor(Math.random() * arr.length);
return arr[randomIndex];
}
function getFilenames(id: string): string[] {
try {
let filePath = `./pictures/property/${id}`;
return readdirSync(filePath);
} catch (e) {
return null;
}
}
function getRandomDateWithinLastYear(): Date {
const currentDate = new Date();
const lastYear = new Date(currentDate.getFullYear() - 1, currentDate.getMonth(), currentDate.getDate());
const timeDiff = currentDate.getTime() - lastYear.getTime();
const randomTimeDiff = Math.random() * timeDiff;
const randomDate = new Date(lastYear.getTime() + randomTimeDiff);
return randomDate;
}
async function storeProfilePicture(buffer: Buffer, userId: string) {
let quality = 50;
const output = await sharp(buffer)
.resize({ width: 300 })
.avif({ quality }) // Verwende AVIF
//.webp({ quality }) // Verwende Webp
.toBuffer();
await sharp(output).toFile(`./pictures/profile/${userId}.avif`);
}
async function storeCompanyLogo(buffer: Buffer, userId: string) {
let quality = 50;
const output = await sharp(buffer)
.resize({ width: 300 })
.avif({ quality }) // Verwende AVIF
//.webp({ quality }) // Verwende Webp
.toBuffer();
await sharp(output).toFile(`./pictures/logo/${userId}.avif`); // Ersetze Dateierweiterung
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
}
function deleteFilesOfDir(directoryPath) {
// Überprüfen, ob das Verzeichnis existiert
if (existsSync(directoryPath)) {
// Den Inhalt des Verzeichnisses synchron löschen
try {
readdirSync(directoryPath).forEach(file => {
const filePath = join(directoryPath, file);
// Wenn es sich um ein Verzeichnis handelt, rekursiv löschen
if (statSync(filePath).isDirectory()) {
rimraf.sync(filePath);
} else {
// Wenn es sich um eine Datei handelt, direkt löschen
unlinkSync(filePath);
}
});
console.log('Der Inhalt des Verzeichnisses wurde erfolgreich gelöscht.');
} catch (err) {
console.error('Fehler beim Löschen des Verzeichnisses:', err);
}
} else {
console.log('Das Verzeichnis existiert nicht.');
}
}

View File

@@ -0,0 +1,13 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import pkg from 'pg';
const { Pool } = pkg;
import * as schema from './schema.js';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
const connectionString = process.env.DATABASE_URL
const pool = new Pool({connectionString})
const db = drizzle(pool, { schema });
// This will run migrations on the database, skipping the ones already applied
await migrate(db, { migrationsFolder: './src/drizzle/migrations' });
// Don't forget to close the connection, otherwise the script will hang
await pool.end();

View File

@@ -0,0 +1,84 @@
CREATE TABLE IF NOT EXISTS "businesses" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"userId" uuid,
"type" integer,
"title" varchar(255),
"description" text,
"city" varchar(255),
"state" char(2),
"price" double precision,
"favoritesForUser" varchar(30)[],
"draft" boolean,
"listingsCategory" varchar(255),
"realEstateIncluded" boolean,
"leasedLocation" boolean,
"franchiseResale" boolean,
"salesRevenue" double precision,
"cashFlow" double precision,
"supportAndTraining" text,
"employees" integer,
"established" integer,
"internalListingNumber" integer,
"reasonForSale" varchar(255),
"brokerLicencing" varchar(255),
"internals" text,
"created" timestamp,
"updated" timestamp,
"visits" integer,
"lastVisit" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "commercials" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"userId" uuid,
"type" integer,
"title" varchar(255),
"description" text,
"city" varchar(255),
"state" char(2),
"price" double precision,
"favoritesForUser" varchar(30)[],
"hideImage" boolean,
"draft" boolean,
"zipCode" integer,
"county" varchar(255),
"email" varchar(255),
"website" varchar(255),
"phoneNumber" varchar(255),
"imageOrder" varchar(30)[],
"imagePath" varchar(50),
"created" timestamp,
"updated" timestamp,
"visits" integer,
"lastVisit" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"firstname" varchar(255) NOT NULL,
"lastname" varchar(255) NOT NULL,
"email" varchar(255) NOT NULL,
"phoneNumber" varchar(255),
"description" text,
"companyName" varchar(255),
"companyOverview" text,
"companyWebsite" varchar(255),
"companyLocation" varchar(255),
"offeredServices" text,
"areasServed" varchar(100)[],
"hasProfile" boolean,
"hasCompanyLogo" boolean,
"licensedIn" varchar(50)[]
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "businesses" ADD CONSTRAINT "businesses_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "commercials" ADD CONSTRAINT "commercials_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1 @@
ALTER TABLE "commercials" ALTER COLUMN "imageOrder" SET DATA TYPE varchar(200)[];

View File

@@ -0,0 +1,7 @@
DO $$ BEGIN
CREATE TYPE "gender" AS ENUM('male', 'female');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "gender" "gender";

View File

@@ -0,0 +1 @@
ALTER TABLE "commercials" ADD COLUMN "listingsCategory" varchar(255);

View File

@@ -0,0 +1,460 @@
{
"id": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "5",
"dialect": "pg",
"tables": {
"businesses": {
"name": "businesses",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"listingsCategory": {
"name": "listingsCategory",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"realEstateIncluded": {
"name": "realEstateIncluded",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"leasedLocation": {
"name": "leasedLocation",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"franchiseResale": {
"name": "franchiseResale",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"salesRevenue": {
"name": "salesRevenue",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"cashFlow": {
"name": "cashFlow",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"supportAndTraining": {
"name": "supportAndTraining",
"type": "text",
"primaryKey": false,
"notNull": false
},
"employees": {
"name": "employees",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"established": {
"name": "established",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"internalListingNumber": {
"name": "internalListingNumber",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"reasonForSale": {
"name": "reasonForSale",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"brokerLicencing": {
"name": "brokerLicencing",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"internals": {
"name": "internals",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"businesses_userId_users_id_fk": {
"name": "businesses_userId_users_id_fk",
"tableFrom": "businesses",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"commercials": {
"name": "commercials",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"hideImage": {
"name": "hideImage",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"zipCode": {
"name": "zipCode",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"county": {
"name": "county",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"website": {
"name": "website",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"imageOrder": {
"name": "imageOrder",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"imagePath": {
"name": "imagePath",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"commercials_userId_users_id_fk": {
"name": "commercials_userId_users_id_fk",
"tableFrom": "commercials",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"firstname": {
"name": "firstname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"lastname": {
"name": "lastname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyName": {
"name": "companyName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyOverview": {
"name": "companyOverview",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyWebsite": {
"name": "companyWebsite",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyLocation": {
"name": "companyLocation",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"offeredServices": {
"name": "offeredServices",
"type": "text",
"primaryKey": false,
"notNull": false
},
"areasServed": {
"name": "areasServed",
"type": "varchar(100)[]",
"primaryKey": false,
"notNull": false
},
"hasProfile": {
"name": "hasProfile",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"hasCompanyLogo": {
"name": "hasCompanyLogo",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"licensedIn": {
"name": "licensedIn",
"type": "varchar(50)[]",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,460 @@
{
"id": "3e4b8c5f-4474-4877-abec-38283408ee34",
"prevId": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
"version": "5",
"dialect": "pg",
"tables": {
"businesses": {
"name": "businesses",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"listingsCategory": {
"name": "listingsCategory",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"realEstateIncluded": {
"name": "realEstateIncluded",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"leasedLocation": {
"name": "leasedLocation",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"franchiseResale": {
"name": "franchiseResale",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"salesRevenue": {
"name": "salesRevenue",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"cashFlow": {
"name": "cashFlow",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"supportAndTraining": {
"name": "supportAndTraining",
"type": "text",
"primaryKey": false,
"notNull": false
},
"employees": {
"name": "employees",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"established": {
"name": "established",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"internalListingNumber": {
"name": "internalListingNumber",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"reasonForSale": {
"name": "reasonForSale",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"brokerLicencing": {
"name": "brokerLicencing",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"internals": {
"name": "internals",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"businesses_userId_users_id_fk": {
"name": "businesses_userId_users_id_fk",
"tableFrom": "businesses",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"commercials": {
"name": "commercials",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"hideImage": {
"name": "hideImage",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"zipCode": {
"name": "zipCode",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"county": {
"name": "county",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"website": {
"name": "website",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"imageOrder": {
"name": "imageOrder",
"type": "varchar(200)[]",
"primaryKey": false,
"notNull": false
},
"imagePath": {
"name": "imagePath",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"commercials_userId_users_id_fk": {
"name": "commercials_userId_users_id_fk",
"tableFrom": "commercials",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"firstname": {
"name": "firstname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"lastname": {
"name": "lastname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyName": {
"name": "companyName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyOverview": {
"name": "companyOverview",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyWebsite": {
"name": "companyWebsite",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyLocation": {
"name": "companyLocation",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"offeredServices": {
"name": "offeredServices",
"type": "text",
"primaryKey": false,
"notNull": false
},
"areasServed": {
"name": "areasServed",
"type": "varchar(100)[]",
"primaryKey": false,
"notNull": false
},
"hasProfile": {
"name": "hasProfile",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"hasCompanyLogo": {
"name": "hasCompanyLogo",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"licensedIn": {
"name": "licensedIn",
"type": "varchar(50)[]",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,474 @@
{
"id": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
"prevId": "3e4b8c5f-4474-4877-abec-38283408ee34",
"version": "5",
"dialect": "pg",
"tables": {
"businesses": {
"name": "businesses",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"listingsCategory": {
"name": "listingsCategory",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"realEstateIncluded": {
"name": "realEstateIncluded",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"leasedLocation": {
"name": "leasedLocation",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"franchiseResale": {
"name": "franchiseResale",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"salesRevenue": {
"name": "salesRevenue",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"cashFlow": {
"name": "cashFlow",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"supportAndTraining": {
"name": "supportAndTraining",
"type": "text",
"primaryKey": false,
"notNull": false
},
"employees": {
"name": "employees",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"established": {
"name": "established",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"internalListingNumber": {
"name": "internalListingNumber",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"reasonForSale": {
"name": "reasonForSale",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"brokerLicencing": {
"name": "brokerLicencing",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"internals": {
"name": "internals",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"businesses_userId_users_id_fk": {
"name": "businesses_userId_users_id_fk",
"tableFrom": "businesses",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"commercials": {
"name": "commercials",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"hideImage": {
"name": "hideImage",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"zipCode": {
"name": "zipCode",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"county": {
"name": "county",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"website": {
"name": "website",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"imageOrder": {
"name": "imageOrder",
"type": "varchar(200)[]",
"primaryKey": false,
"notNull": false
},
"imagePath": {
"name": "imagePath",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"commercials_userId_users_id_fk": {
"name": "commercials_userId_users_id_fk",
"tableFrom": "commercials",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"firstname": {
"name": "firstname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"lastname": {
"name": "lastname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyName": {
"name": "companyName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyOverview": {
"name": "companyOverview",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyWebsite": {
"name": "companyWebsite",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyLocation": {
"name": "companyLocation",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"offeredServices": {
"name": "offeredServices",
"type": "text",
"primaryKey": false,
"notNull": false
},
"areasServed": {
"name": "areasServed",
"type": "varchar(100)[]",
"primaryKey": false,
"notNull": false
},
"hasProfile": {
"name": "hasProfile",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"hasCompanyLogo": {
"name": "hasCompanyLogo",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"licensedIn": {
"name": "licensedIn",
"type": "varchar(50)[]",
"primaryKey": false,
"notNull": false
},
"gender": {
"name": "gender",
"type": "gender",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"gender": {
"name": "gender",
"values": {
"male": "male",
"female": "female"
}
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,480 @@
{
"id": "da786c6a-fd5f-4629-bd5e-3ecd42ab1f2c",
"prevId": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
"version": "5",
"dialect": "pg",
"tables": {
"businesses": {
"name": "businesses",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"listingsCategory": {
"name": "listingsCategory",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"realEstateIncluded": {
"name": "realEstateIncluded",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"leasedLocation": {
"name": "leasedLocation",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"franchiseResale": {
"name": "franchiseResale",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"salesRevenue": {
"name": "salesRevenue",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"cashFlow": {
"name": "cashFlow",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"supportAndTraining": {
"name": "supportAndTraining",
"type": "text",
"primaryKey": false,
"notNull": false
},
"employees": {
"name": "employees",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"established": {
"name": "established",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"internalListingNumber": {
"name": "internalListingNumber",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"reasonForSale": {
"name": "reasonForSale",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"brokerLicencing": {
"name": "brokerLicencing",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"internals": {
"name": "internals",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"businesses_userId_users_id_fk": {
"name": "businesses_userId_users_id_fk",
"tableFrom": "businesses",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"commercials": {
"name": "commercials",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"userId": {
"name": "userId",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"state": {
"name": "state",
"type": "char(2)",
"primaryKey": false,
"notNull": false
},
"price": {
"name": "price",
"type": "double precision",
"primaryKey": false,
"notNull": false
},
"favoritesForUser": {
"name": "favoritesForUser",
"type": "varchar(30)[]",
"primaryKey": false,
"notNull": false
},
"listingsCategory": {
"name": "listingsCategory",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"hideImage": {
"name": "hideImage",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"draft": {
"name": "draft",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"zipCode": {
"name": "zipCode",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"county": {
"name": "county",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"website": {
"name": "website",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"imageOrder": {
"name": "imageOrder",
"type": "varchar(200)[]",
"primaryKey": false,
"notNull": false
},
"imagePath": {
"name": "imagePath",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false
},
"created": {
"name": "created",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated": {
"name": "updated",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"visits": {
"name": "visits",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"lastVisit": {
"name": "lastVisit",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"commercials_userId_users_id_fk": {
"name": "commercials_userId_users_id_fk",
"tableFrom": "commercials",
"tableTo": "users",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"firstname": {
"name": "firstname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"lastname": {
"name": "lastname",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"phoneNumber": {
"name": "phoneNumber",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyName": {
"name": "companyName",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyOverview": {
"name": "companyOverview",
"type": "text",
"primaryKey": false,
"notNull": false
},
"companyWebsite": {
"name": "companyWebsite",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"companyLocation": {
"name": "companyLocation",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"offeredServices": {
"name": "offeredServices",
"type": "text",
"primaryKey": false,
"notNull": false
},
"areasServed": {
"name": "areasServed",
"type": "varchar(100)[]",
"primaryKey": false,
"notNull": false
},
"hasProfile": {
"name": "hasProfile",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"hasCompanyLogo": {
"name": "hasCompanyLogo",
"type": "boolean",
"primaryKey": false,
"notNull": false
},
"licensedIn": {
"name": "licensedIn",
"type": "varchar(50)[]",
"primaryKey": false,
"notNull": false
},
"gender": {
"name": "gender",
"type": "gender",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"gender": {
"name": "gender",
"values": {
"male": "male",
"female": "female"
}
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,34 @@
{
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1714913766996,
"tag": "0000_third_spacker_dave",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1714981666488,
"tag": "0001_rapid_daimon_hellstrom",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1714982539265,
"tag": "0002_black_zaladane",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1715254754561,
"tag": "0003_tough_hobgoblin",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,78 @@
import { boolean, char, doublePrecision, integer, pgEnum, pgTable, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
export const PG_CONNECTION = 'PG_CONNECTION';
export const genderEnum = pgEnum('gender', ['male', 'female']);
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
firstname: varchar('firstname', { length: 255 }).notNull(),
lastname: varchar('lastname', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }).notNull(),
phoneNumber: varchar('phoneNumber', { length: 255 }),
description: text('description'),
companyName: varchar('companyName', { length: 255 }),
companyOverview: text('companyOverview'),
companyWebsite: varchar('companyWebsite', { length: 255 }),
companyLocation: varchar('companyLocation', { length: 255 }),
offeredServices: text('offeredServices'),
areasServed: varchar('areasServed', { length: 100 }).array(),
hasProfile: boolean('hasProfile'),
hasCompanyLogo: boolean('hasCompanyLogo'),
licensedIn: varchar('licensedIn', { length: 50 }).array(),
gender: genderEnum('gender'),
});
export const businesses = pgTable('businesses', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('userId').references(() => users.id),
type: integer('type'),
title: varchar('title', { length: 255 }),
description: text('description'),
city: varchar('city', { length: 255 }),
state: char('state', { length: 2 }),
price: doublePrecision('price'),
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
draft: boolean('draft'),
listingsCategory: varchar('listingsCategory', { length: 255 }),
realEstateIncluded: boolean('realEstateIncluded'),
leasedLocation: boolean('leasedLocation'),
franchiseResale: boolean('franchiseResale'),
salesRevenue: doublePrecision('salesRevenue'),
cashFlow: doublePrecision('cashFlow'),
supportAndTraining: text('supportAndTraining'),
employees: integer('employees'),
established: integer('established'),
internalListingNumber: integer('internalListingNumber'),
reasonForSale: varchar('reasonForSale', { length: 255 }),
brokerLicencing: varchar('brokerLicencing', { length: 255 }),
internals: text('internals'),
created: timestamp('created'),
updated: timestamp('updated'),
visits: integer('visits'),
lastVisit: timestamp('lastVisit'),
});
export const commercials = pgTable('commercials', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('userId').references(() => users.id),
type: integer('type'),
title: varchar('title', { length: 255 }),
description: text('description'),
city: varchar('city', { length: 255 }),
state: char('state', { length: 2 }),
price: doublePrecision('price'),
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
listingsCategory: varchar('listingsCategory', { length: 255 }),
hideImage: boolean('hideImage'),
draft: boolean('draft'),
zipCode: integer('zipCode'),
county: varchar('county', { length: 255 }),
email: varchar('email', { length: 255 }),
website: varchar('website', { length: 255 }),
phoneNumber: varchar('phoneNumber', { length: 255 }),
imageOrder: varchar('imageOrder', { length: 200 }).array(),
imagePath: varchar('imagePath', { length: 50 }),
created: timestamp('created'),
updated: timestamp('updated'),
visits: integer('visits'),
lastVisit: timestamp('lastVisit'),
});

View File

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

View File

@@ -0,0 +1,18 @@
import { Controller, Get, Param } from '@nestjs/common';
import { GeoService } from './geo.service.js';
@Controller('geo')
export class GeoController {
constructor(private geoService:GeoService){}
@Get(':prefix')
findByPrefix(@Param('prefix') prefix:string): any {
return this.geoService.findCitiesStartingWith(prefix);
}
@Get(':prefix/:state')
findByPrefixAndState(@Param('prefix') prefix:string,@Param('state') state:string): any {
return this.geoService.findCitiesStartingWith(prefix,state);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { GeoController } from './geo.controller.js';
import { GeoService } from './geo.service.js';
@Module({
controllers: [GeoController],
providers: [GeoService]
})
export class GeoModule {}

View File

@@ -0,0 +1,40 @@
import { Injectable } from '@nestjs/common';
import { readFileSync } from 'fs';
import path, { join } from 'path';
import { City, Geo, State } from 'src/models/server.model.js';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@Injectable()
export class GeoService {
geo:Geo;
constructor() {
this.loadGeo();
}
private loadGeo(): void {
const filePath = join(__dirname,'../..', 'assets', 'geo.json');
const rawData = readFileSync(filePath, 'utf8');
this.geo = JSON.parse(rawData);
}
findCitiesStartingWith( prefix: string, state?:string): { city: string; state: string; state_code: string }[] {
const result: { city: string; state: string; state_code: string }[] = [];
this.geo.states.forEach((state: State) => {
state.cities.forEach((city: City) => {
if (city.name.toLowerCase().startsWith(prefix.toLowerCase())) {
result.push({
city: city.name,
state: state.name,
state_code: state.state_code
});
}
});
});
return state ? result.filter(e=>e.state_code.toLowerCase()===state.toLowerCase()) :result;
}
}

View File

@@ -0,0 +1,74 @@
import { Controller, Delete, Get, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { FileService } from '../file/file.service.js';
import { ListingsService } from '../listings/listings.service.js';
import { SelectOptionsService } from '../select-options/select-options.service.js';
import { commercials } from 'src/drizzle/schema.js';
import { CommercialPropertyListing } from 'src/models/db.model.js';
@Controller('image')
export class ImageController {
constructor(
private fileService: FileService,
private listingService: ListingsService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
private selectOptions: SelectOptionsService,
) {}
@Post('uploadPropertyPicture/:id')
@UseInterceptors(FileInterceptor('file'))
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
const imagename = await this.fileService.storePropertyPicture(file, id);
await this.listingService.addImage(id, imagename);
}
@Post('uploadProfile/:id')
@UseInterceptors(FileInterceptor('file'))
async uploadProfile(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
await this.fileService.storeProfilePicture(file, id);
}
@Post('uploadCompanyLogo/:id')
@UseInterceptors(FileInterceptor('file'))
async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
await this.fileService.storeCompanyLogo(file, id);
}
@Get(':id')
async getPropertyImagesById(@Param('id') id: string): Promise<any> {
const result = await this.listingService.findById(id, commercials);
const listing = result as CommercialPropertyListing;
if (listing.imageOrder) {
return listing.imageOrder;
} else {
const imageOrder = await this.fileService.getPropertyImages(id);
listing.imageOrder = imageOrder;
this.listingService.updateListing(listing.id, listing, commercials);
return imageOrder;
}
}
@Get('profileImages/:userids')
async getProfileImagesForUsers(@Param('userids') userids: string): Promise<any> {
return await this.fileService.getProfileImagesForUsers(userids);
}
@Get('companyLogos/:userids')
async getCompanyLogosForUsers(@Param('userids') userids: string): Promise<any> {
return await this.fileService.getCompanyLogosForUsers(userids);
}
@Delete('propertyPicture/:listingid/:imagename')
async deletePropertyImagesById(@Param('listingid') listingid: string, @Param('imagename') imagename: string): Promise<any> {
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`);
}
@Delete('logo/:userid/')
async deleteLogoImagesById(@Param('id') id: string): Promise<any> {
this.fileService.deleteImage(`pictures/property//${id}`);
}
@Delete('profile/:userid/')
async deleteProfileImagesById(@Param('id') id: string): Promise<any> {
this.fileService.deleteImage(`pictures/property//${id}`);
}
}

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { ImageController } from './image.controller.js';
import { ImageService } from './image.service.js';
import { FileService } from '../file/file.service.js';
import { SelectOptionsService } from '../select-options/select-options.service.js';
import { ListingsService } from '../listings/listings.service.js';
import { ListingsModule } from '../listings/listings.module.js';
@Module({
imports: [ListingsModule],
controllers: [ImageController],
providers: [ImageService,FileService,SelectOptionsService]
})
export class ImageModule {}

View File

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

View File

@@ -0,0 +1,21 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { FileService } from '../file/file.service.js';
import { convertStringToNullUndefined } from '../utils.js';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { UserService } from '../user/user.service.js';
@Controller('listings/professionals_brokers')
export class BrokerListingsController {
constructor(private readonly userService:UserService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Post('search')
find(@Body() criteria: any): any {
return this.userService.findUser(criteria);
}
}

View File

@@ -0,0 +1,47 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { businesses } from '../drizzle/schema.js';
import { ListingCriteria } from '../models/main.model.js';
import { ListingsService } from './listings.service.js';
@Controller('listings/business')
export class BusinessListingsController {
constructor(
private readonly listingsService: ListingsService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {}
@Get(':id')
findById(@Param('id') id: string): any {
return this.listingsService.findById(id, businesses);
}
@Get('user/:userid')
findByUserId(@Param('userid') userid: string): any {
return this.listingsService.findByUserId(userid, businesses);
}
@Post('search')
find(@Body() criteria: ListingCriteria): any {
return this.listingsService.findListingsByCriteria(criteria, businesses);
}
@Post()
create(@Body() listing: any) {
this.logger.info(`Save Listing`);
return this.listingsService.createListing(listing, businesses);
}
@Put()
update(@Body() listing: any) {
this.logger.info(`Save Listing`);
return this.listingsService.updateListing(listing.id, listing, businesses);
}
@Delete(':id')
deleteById(@Param('id') id: string) {
this.listingsService.deleteListing(id, businesses);
}
@Get('states/all')
getStates(): any {
return this.listingsService.getStates(businesses);
}
}

View File

@@ -0,0 +1,52 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { commercials } from '../drizzle/schema.js';
import { FileService } from '../file/file.service.js';
import { ListingCriteria } from '../models/main.model.js';
import { ListingsService } from './listings.service.js';
@Controller('listings/commercialProperty')
export class CommercialPropertyListingsController {
constructor(
private readonly listingsService: ListingsService,
private fileService: FileService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {}
@Get(':id')
findById(@Param('id') id: string): any {
return this.listingsService.findById(id, commercials);
}
@Get('user/:userid')
findByUserId(@Param('userid') userid: string): any {
return this.listingsService.findByUserId(userid, commercials);
}
@Post('search')
async find(@Body() criteria: ListingCriteria): Promise<any> {
return await this.listingsService.findListingsByCriteria(criteria, commercials);
}
@Get('states/all')
getStates(): any {
return this.listingsService.getStates(commercials);
}
@Post()
async create(@Body() listing: any) {
this.logger.info(`Save Listing`);
return await this.listingsService.createListing(listing, commercials);
}
@Put()
async update(@Body() listing: any) {
this.logger.info(`Save Listing`);
return await this.listingsService.updateListing(listing.id, listing, commercials);
}
@Delete(':id')
deleteById(@Param('id') id: string) {
this.listingsService.deleteListing(id, commercials);
}
@Put('imageOrder/:id')
async changeImageOrder(@Param('id') id: string, @Body() imageOrder: string[]) {
this.listingsService.updateImageOrder(id, imageOrder);
}
}

View File

@@ -1,59 +0,0 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { FileService } from '../file/file.service.js';
import { convertStringToNullUndefined } from '../utils.js';
import { RedisService } from '../redis/redis.service.js';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
@Controller('listings')
export class ListingsController {
// private readonly logger = new Logger(ListingsController.name);
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get()
findAll(): any {
return this.listingsService.getAllListings();
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.getListingById(id);
}
// @Get(':type/:location/:minPrice/:maxPrice/:realEstateChecked')
// find(@Param('type') type:string,@Param('location') location:string,@Param('minPrice') minPrice:string,@Param('maxPrice') maxPrice:string,@Param('realEstateChecked') realEstateChecked:boolean): any {
// return this.listingsService.find(type,location,minPrice,maxPrice,realEstateChecked);
// }
@Post('search')
find(@Body() criteria: any): any {
return this.listingsService.find(criteria);
}
/**
* @param listing updates a new listing
*/
@Put(':id')
updateById(@Param('id') id:string, @Body() listing: any){
this.logger.info(`Update by ID: ${id}`);
this.listingsService.setListing(listing,id)
}
/**
* @param listing creates a new listing
*/
@Post()
create(@Body() listing: any){
this.logger.info(`Create Listing`);
this.listingsService.setListing(listing)
}
/**
* @param id deletes a listing
*/
@Delete(':id')
deleteById(@Param('id') id:string){
this.listingsService.deleteListing(id)
}
}

View File

@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { DrizzleModule } from '../drizzle/drizzle.module.js';
import { FileService } from '../file/file.service.js';
import { UserService } from '../user/user.service.js';
import { BrokerListingsController } from './broker-listings.controller.js';
import { BusinessListingsController } from './business-listings.controller.js';
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
import { ListingsService } from './listings.service.js';
import { UnknownListingsController } from './unknown-listings.controller.js';
@Module({
imports: [DrizzleModule],
controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController],
providers: [ListingsService, FileService, UserService],
exports: [ListingsService],
})
export class ListingsModule {}

View File

@@ -1,86 +1,125 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { import { and, eq, gte, ilike, lte, sql } from 'drizzle-orm';
BusinessListing, import { NodePgDatabase } from 'drizzle-orm/node-postgres';
InvestmentsListing,
ListingCriteria,
ProfessionalsBrokersListing,
} from '../models/main.model.js';
import { LISTINGS, RedisService } from '../redis/redis.service.js';
import { convertStringToNullUndefined } from '../utils.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { BusinessListing, CommercialPropertyListing } from 'src/models/db.model.js';
import { Logger } from 'winston'; import { Logger } from 'winston';
import * as schema from '../drizzle/schema.js';
import { PG_CONNECTION, businesses, commercials } from '../drizzle/schema.js';
import { ListingCriteria } from '../models/main.model.js';
@Injectable() @Injectable()
export class ListingsService { export class ListingsService {
// private readonly logger = new Logger(ListingsService.name); constructor(
constructor(private redisService: RedisService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {} @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
async setListing( ) {}
value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing, private getConditions(criteria: ListingCriteria, table: typeof businesses | typeof commercials): any[] {
id?: string, const conditions = [];
) { if (criteria.type) {
if (!id) { conditions.push(eq(table.type, criteria.type));
id = await this.redisService.getId(LISTINGS);
value.id = id;
this.logger.info(`No ID - creating new one:${id}`)
} else {
this.logger.info(`ID available:${id}`)
} }
this.redisService.setJson(id, value); if (criteria.state) {
conditions.push(eq(table.state, criteria.state));
}
if (criteria.minPrice) {
conditions.push(gte(table.price, criteria.minPrice));
}
if (criteria.maxPrice) {
conditions.push(lte(table.price, criteria.maxPrice));
}
if (criteria.realEstateChecked) {
conditions.push(eq(businesses.realEstateIncluded, true));
}
if (criteria.title) {
conditions.push(ilike(table.title, `%${criteria.title}%`));
}
return conditions;
}
// ##############################################################
// Listings general
// ##############################################################
async findListingsByCriteria(criteria: ListingCriteria, table: typeof businesses | typeof commercials): Promise<{ data: Record<string, any>[]; total: number }> {
const start = criteria.start ? criteria.start : 0;
const length = criteria.length ? criteria.length : 12;
return await this.findListings(table, criteria, start, length);
}
private async findListings(table: typeof businesses | typeof commercials, criteria: ListingCriteria, start = 0, length = 12): Promise<any> {
const conditions = this.getConditions(criteria, table);
const [data, total] = await Promise.all([
this.conn
.select()
.from(table)
.where(and(...conditions))
.offset(start)
.limit(length),
this.conn
.select({ count: sql`count(*)` })
.from(table)
.where(and(...conditions))
.then(result => Number(result[0].count)),
]);
return { total, data };
}
async findById(id: string, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
const result = await this.conn
.select()
.from(table)
.where(sql`${table.id} = ${id}`);
return result[0] as BusinessListing | CommercialPropertyListing;
} }
async getListingById(id: string) { async findByUserId(userId: string, table: typeof businesses | typeof commercials): Promise<BusinessListing[] | CommercialPropertyListing[]> {
return await this.redisService.getJson(id, LISTINGS); return (await this.conn.select().from(table).where(eq(table.userId, userId))) as BusinessListing[] | CommercialPropertyListing[];
} }
deleteListing(id: string){
this.redisService.delete(id); async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
this.logger.info(`delete listing with ID:${id}`) data.created = new Date();
data.updated = new Date();
data.visits = 0;
data.lastVisit = null;
const [createdListing] = await this.conn.insert(table).values(data).returning();
return createdListing as BusinessListing | CommercialPropertyListing;
} }
async getAllListings(start?: number, end?: number) {
const searchResult = await this.redisService.search(LISTINGS, '*'); async updateListing(id: string, data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
const listings = searchResult.slice(1).reduce((acc, item, index, array) => { data.updated = new Date();
// Jedes zweite Element (beginnend mit dem ersten) ist ein JSON-String des Listings data.created = new Date(data.created);
if (index % 2 === 1) { const [updateListing] = await this.conn.update(table).set(data).where(eq(table.id, id)).returning();
try { return updateListing as BusinessListing | CommercialPropertyListing;
const listing = JSON.parse(item[1]); // Parsen des JSON-Strings }
acc.push(listing);
} catch (error) { async deleteListing(id: string, table: typeof businesses | typeof commercials): Promise<void> {
console.error('Fehler beim Parsen des JSON-Strings: ', error); await this.conn.delete(table).where(eq(table.id, id));
}
async getStates(table: typeof businesses | typeof commercials): Promise<any[]> {
return await this.conn
.select({ state: table.state, count: sql<number>`count(${table.id})`.mapWith(Number) })
.from(table)
.groupBy(sql`${table.state}`)
.orderBy(sql`count desc`);
}
// ##############################################################
// Images for commercial Properties
// ##############################################################
async updateImageOrder(id: string, imageOrder: string[]) {
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
listing.imageOrder = imageOrder;
await this.updateListing(listing.id, listing, commercials);
}
async deleteImage(id: string, name: string) {
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
const index = listing.imageOrder.findIndex(im => im === name);
if (index > -1) {
listing.imageOrder.splice(index, 1);
await this.updateListing(listing.id, listing, commercials);
} }
} }
return acc; async addImage(id: string, imagename: string) {
}, []); const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
return listings; listing.imageOrder.push(imagename);
} listing.imagePath = listing.id;
//criteria.type,criteria.location,criteria.minPrice,criteria.maxPrice,criteria.realEstateChecked,criteria.listingsCategory await this.updateListing(listing.id, listing, commercials);
//async find(type:string,location:string,minPrice:string,maxPrice:string,realEstateChecked:boolean,listingsCategory:string): Promise<any> {
async find(criteria:ListingCriteria): Promise<any> {
let listings = await this.getAllListings();
listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory);
if (convertStringToNullUndefined(criteria.type)){
console.log(criteria.type);
listings=listings.filter(l=>l.type===criteria.type);
}
if (convertStringToNullUndefined(criteria.location)){
console.log(criteria.location);
listings=listings.filter(l=>l.location===criteria.location);
}
if (convertStringToNullUndefined(criteria.minPrice)){
console.log(criteria.minPrice);
listings=listings.filter(l=>l.price>=Number(criteria.minPrice));
}
if (convertStringToNullUndefined(criteria.maxPrice)){
console.log(criteria.maxPrice);
listings=listings.filter(l=>l.price<=Number(criteria.maxPrice));
}
if (convertStringToNullUndefined(criteria.realEstateChecked)){
console.log(criteria.realEstateChecked);
listings=listings.filter(l=>l.realEstateIncluded);
}
if (convertStringToNullUndefined(criteria.category)){
console.log(criteria.category);
listings=listings.filter(l=>l.category===criteria.category);
}
return listings
} }
} }

View File

@@ -0,0 +1,27 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { FileService } from '../file/file.service.js';
import { convertStringToNullUndefined } from '../utils.js';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { businesses, commercials } from 'src/drizzle/schema.js';
@Controller('listings/undefined')
export class UnknownListingsController {
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get(':id')
async findById(@Param('id') id:string): Promise<any> {
const result = await this.listingsService.findById(id,businesses);
if (result){
return result
} else {
return await this.listingsService.findById(id,commercials);
}
}
}

View File

@@ -1,14 +1,13 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { Body, Controller, Post } from '@nestjs/common';
import { User } from 'src/models/db.model.js';
import { MailInfo } from 'src/models/main.model.js';
import { MailService } from './mail.service.js'; import { MailService } from './mail.service.js';
import { MailInfo } from '../models/server.model.js';
@Controller('mail') @Controller('mail')
export class MailController { export class MailController {
constructor(private mailService:MailService){ constructor(private mailService: MailService) {}
@Post()
} sendEMail(@Body() mailInfo: MailInfo): Promise<User> {
@Post(':id') return this.mailService.sendInquiry(mailInfo);
sendEMail(@Param('id') id:string,@Body() mailInfo: MailInfo): any {
return this.mailService.sendInquiry(id,mailInfo);
} }
} }

View File

@@ -1,25 +1,30 @@
import { Module } from '@nestjs/common';
import { MailService } from './mail.service.js';
import { MailController } from './mail.controller.js';
import { MailerModule } from '@nestjs-modules/mailer'; import { MailerModule } from '@nestjs-modules/mailer';
import path, { join } from 'path';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js';
import { Module } from '@nestjs/common';
import path, { join } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { AuthModule } from '../auth/auth.module.js'; import { DrizzleModule } from '../drizzle/drizzle.module.js';
import { FileService } from '../file/file.service.js';
import { UserModule } from '../user/user.module.js';
import { UserService } from '../user/user.service.js';
import { MailController } from './mail.controller.js';
import { MailService } from './mail.service.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const user = process.env.amazon_user;
const password = process.env.amazon_password;
@Module({ @Module({
imports: [AuthModule, imports: [
DrizzleModule,
UserModule,
MailerModule.forRoot({ MailerModule.forRoot({
// transport: 'smtps://user@example.com:topsecret@smtp.example.com',
// or
transport: { transport: {
host: 'smtp.gmail.com', host: 'email-smtp.us-east-2.amazonaws.com',
secure: true, secure: false,
port: 587,
auth: { auth: {
user: 'andreas.knuth@gmail.com', user: 'AKIAU6GDWVAQ2QNFLNWN',
pass: 'ksnh xjae dqbv xana', pass: 'BDE9nZv/ARbpotim1mIOir52WgIbpSi9cv1oJoH8oEf7',
}, },
}, },
defaults: { defaults: {
@@ -34,7 +39,7 @@ const __dirname = path.dirname(__filename);
}, },
}), }),
], ],
providers: [MailService], providers: [MailService, UserService, FileService],
controllers: [MailController] controllers: [MailController],
}) })
export class MailModule {} export class MailModule {}

View File

@@ -1,24 +1,41 @@
import { MailerService } from '@nestjs-modules/mailer'; import { MailerService } from '@nestjs-modules/mailer';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { AuthService } from '../auth/auth.service.js'; import path, { join } from 'path';
import { MailInfo } from '../models/server.model.js'; import { fileURLToPath } from 'url';
import { User } from 'src/models/main.model.js'; import { User } from '../models/db.model.js';
import { MailInfo } from '../models/main.model.js';
import { UserService } from '../user/user.service.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@Injectable() @Injectable()
export class MailService { export class MailService {
constructor(private mailerService: MailerService, private authService:AuthService) {} constructor(
private mailerService: MailerService,
private userService: UserService,
) {}
async sendInquiry(userId:string,mailInfo: MailInfo) { async sendInquiry(mailInfo: MailInfo): Promise<User> {
const user = await this.authService.getUser(userId) as User; //const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
const user = await this.userService.getUserByMail(mailInfo.email);
console.log(JSON.stringify(user));
await this.mailerService.sendMail({ await this.mailerService.sendMail({
to: user.email, to: user.email,
from: '"Bizmatch Team" <info@bizmatch.net>', // override default from from: '"Bizmatch Team" <info@bizmatch.net>', // override default from
subject: `Inquiry from ${mailInfo.sender.name}`, subject: `Inquiry from ${mailInfo.sender.name}`,
template: './inquiry', // `.hbs` extension is appended automatically //template: './inquiry', // `.hbs` extension is appended automatically
context: { // ✏️ filling curly brackets with content template: join(__dirname, '../..', 'mail/templates/inquiry.hbs'),
context: {
// ✏️ filling curly brackets with content
name: user.firstname, name: user.firstname,
inquiry:mailInfo.sender.comments inquiry: mailInfo.sender.comments,
internalListingNumber: mailInfo.listing.internalListingNumber,
title: mailInfo.listing.title,
iname: mailInfo.sender.name,
phone: mailInfo.sender.phoneNumber,
email: mailInfo.sender.email,
}, },
}); });
return user;
} }
} }

View File

@@ -1,6 +1,10 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js'; import { AppModule } from './app.module.js';
import * as express from 'express';
import path, { join } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('bizmatch'); app.setGlobalPrefix('bizmatch');

View File

@@ -0,0 +1,73 @@
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?: string[];
hasProfile?: boolean;
hasCompanyLogo?: boolean;
licensedIn?: string[];
}
export interface BusinessListing {
id: string;
userId?: string;
type?: number;
title?: string;
description?: string;
city?: string;
state?: string;
price?: number;
favoritesForUser?: string[];
draft?: boolean;
realEstateIncluded?: boolean;
leasedLocation?: boolean;
franchiseResale?: boolean;
salesRevenue?: number;
cashFlow?: number;
supportAndTraining?: string;
employees?: number;
established?: number;
internalListingNumber?: number;
reasonForSale?: string;
brokerLicencing?: string;
internals?: string;
created?: Date;
updated?: Date;
visits?: number;
lastVisit?: Date;
listingsCategory?: string;
}
export interface CommercialPropertyListing {
id: string;
userId?: string;
type?: number;
title?: string;
description?: string;
city?: string;
state?: string;
price?: number;
favoritesForUser?: string[];
hideImage?: boolean;
draft?: boolean;
zipCode?: number;
county?: string;
email?: string;
website?: string;
phoneNumber?: string;
imageOrder?: string[];
imagePath?: string;
created?: Date;
updated?: Date;
visits?: number;
lastVisit?: Date;
listingsCategory?: string;
}

View File

@@ -1 +0,0 @@
../../../common-models/src/main.model.ts

View File

@@ -0,0 +1,166 @@
import { BusinessListing, CommercialPropertyListing, User } from './db.model';
export interface StatesResult {
state: string;
count: number;
}
export interface KeyValue {
name: string;
value: string;
}
export interface KeyValueRatio {
label: string;
value: number;
}
export interface KeyValueStyle {
name: string;
value: string;
icon: string;
bgColorClass: string;
textColorClass: string;
}
export type SelectOption<T = number> = {
value: T;
label: string;
};
export type ImageType = {
name: 'propertyPicture' | 'companyLogo' | 'profile';
upload: string;
delete: string;
};
export type ListingCategory = {
name: 'business' | 'commercialProperty';
};
export type ListingType = BusinessListing | CommercialPropertyListing;
export type ResponseBusinessListingArray = {
data: BusinessListing[];
total: number;
};
export type ResponseBusinessListing = {
data: BusinessListing;
};
export type ResponseCommercialPropertyListingArray = {
data: CommercialPropertyListing[];
total: number;
};
export type ResponseCommercialPropertyListing = {
data: CommercialPropertyListing;
};
export type ResponseUsersArray = {
data: User[];
total: number;
};
export interface ListingCriteria {
start: number;
length: number;
page: number;
pageCount: number;
type: number;
state: string;
minPrice: number;
maxPrice: number;
realEstateChecked: boolean;
title: string;
category: 'professional|broker';
name: string;
}
export interface KeycloakUser {
id: string;
createdTimestamp: number;
username: string;
enabled: boolean;
totp: boolean;
emailVerified: boolean;
firstName: string;
lastName: string;
email: string;
disableableCredentialTypes: any[];
requiredActions: any[];
notBefore: number;
access: Access;
}
export interface Access {
manageGroupMembership: boolean;
view: boolean;
mapRoles: boolean;
impersonate: boolean;
manage: boolean;
}
export interface Subscription {
id: string;
userId: string;
level: string;
start: Date;
modified: Date;
end: Date;
status: string;
invoices: Array<Invoice>;
}
export interface Invoice {
id: string;
date: Date;
price: number;
}
export interface JwtToken {
exp: number;
iat: number;
auth_time: number;
jti: string;
iss: string;
aud: string;
sub: string;
typ: string;
azp: string;
nonce: string;
session_state: string;
acr: string;
realm_access: Realmaccess;
resource_access: Resourceaccess;
scope: string;
sid: string;
email_verified: boolean;
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
user_id: string;
}
interface Resourceaccess {
account: Realmaccess;
}
interface Realmaccess {
roles: string[];
}
export interface PageEvent {
first: number;
rows: number;
page: number;
pageCount: number;
}
export interface AutoCompleteCompleteEvent {
originalEvent: Event;
query: string;
}
export interface MailInfo {
sender: Sender;
userId: string;
email: string;
listing?: BusinessListing;
}
export interface Sender {
name?: string;
email?: string;
phoneNumber?: string;
state?: string;
comments?: string;
}
export interface ImageProperty {
id: string;
code: string;
name: string;
}

View File

@@ -1,11 +1,64 @@
export interface MailInfo { import { Entity } from "redis-om";
sender:Sender; export interface Geo {
userId:string; id: number;
}
export interface Sender {
name: string; name: string;
email:string; iso3: string;
phoneNumber:string; iso2: string;
state:string; numeric_code: string;
comments:string; phone_code: string;
capital: string;
currency: string;
currency_name: string;
currency_symbol: string;
tld: string;
native: string;
region: string;
region_id: string;
subregion: string;
subregion_id: string;
nationality: string;
timezones: Timezone[];
translations: Translations;
latitude: string;
longitude: string;
emoji: string;
emojiU: string;
states: State[];
}
export interface State {
id: number;
name: string;
state_code: string;
latitude: string;
longitude: string;
type: string;
cities: City[];
}
export interface City {
id: number;
name: string;
latitude: string;
longitude: string;
}
export interface Translations {
kr: string;
'pt-BR': string;
pt: string;
nl: string;
hr: string;
fa: string;
de: string;
es: string;
fr: string;
ja: string;
it: string;
cn: string;
tr: string;
}
export interface Timezone {
zoneName: string;
gmtOffset: number;
gmtOffsetName: string;
abbreviation: string;
tzName: string;
} }

View File

@@ -1,18 +0,0 @@
import { Body, Controller, Get, HttpStatus, Param, Post, Res } from '@nestjs/common';
import { Response } from 'express';
import { RedisService } from './redis.service.js';
@Controller('redis')
export class RedisController {
constructor(private redisService:RedisService){}
// @Get(':id')
// getById(@Param('id') id:string){
// return this.redisService.getListingById(id);
// }
// @Post(':id')
// updateById(@Body() listing: any){
// this.redisService.setListing(listing);
// }
}

View File

@@ -1,16 +0,0 @@
// redis.module.ts
import { Module } from '@nestjs/common';
@Module({
providers: [RedisService],
exports: [RedisService],
controllers: [RedisController],
})
export class RedisModule {}
// redis.service.ts
import { Injectable } from '@nestjs/common';
import { RedisService } from './redis.service.js';
import { RedisController } from './redis.controller.js';

View File

@@ -1,39 +0,0 @@
// redis.service.ts
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
import { BusinessListing, InvestmentsListing,ProfessionalsBrokersListing } from '../models/main.model.js';
export const LISTINGS = 'LISTINGS';
export const SUBSCRIPTIONS = 'SUBSCRIPTIONS';
export const USERS = 'USERS'
@Injectable()
export class RedisService {
private redis = new Redis(); // Verbindungsparameter nach Bedarf anpassen
// ######################################
// general methods
// ######################################
async setJson(id: string, value: any): Promise<void> {
await this.redis.call("JSON.SET", `${LISTINGS}:${id}`, "$", JSON.stringify(value));
}
async delete(id: string): Promise<void> {
await this.redis.del(`${LISTINGS}:${id}`);
}
async getJson(id: string, prefix:string): Promise<any> {
const result:string = await this.redis.call("JSON.GET", `${prefix}:${id}`) as string;
return JSON.parse(result);
}
async getId(prefix:'LISTINGS'|'SUBSCRIPTIONS'|'USERS'):Promise<string>{
const counter = await this.redis.call("INCR",`${prefix}_ID_COUNTER`) as number;
return counter.toString().padStart(15, '0')
}
async search(prefix:'LISTINGS'|'SUBSCRIPTIONS'|'USERS',clause:string):Promise<any>{
const result = await this.redis.call(`FT.SEARCH`, `${prefix}_INDEX`, `${clause}`, 'LIMIT', 0, 200);
return result;
}
}

View File

@@ -0,0 +1,16 @@
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RequestDurationMiddleware implements NestMiddleware {
private readonly logger = new Logger(RequestDurationMiddleware.name);
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
this.logger.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
}
}

View File

@@ -12,6 +12,7 @@ export class SelectOptionsController {
listingCategories:this.selectOptionsService.listingCategories, listingCategories:this.selectOptionsService.listingCategories,
categories:this.selectOptionsService.categories, categories:this.selectOptionsService.categories,
locations:this.selectOptionsService.locations, locations:this.selectOptionsService.locations,
typesOfCommercialProperty:this.selectOptionsService.typesOfCommercialProperty,
} }
} }
} }

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { SelectOptionsController } from './select-options.controller.js';
import { SelectOptionsService } from './select-options.service.js';
@Module({
controllers: [SelectOptionsController],
providers: [SelectOptionsService]
})
export class SelectOptionsModule {}

View File

@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { KeyValue, KeyValueStyle } from '../models/main.model.js'; import { ImageType, KeyValue, KeyValueStyle } from '../models/main.model.js';
@Injectable() @Injectable()
export class SelectOptionsService { export class SelectOptionsService {
constructor() {} constructor() {}
public typesOfBusiness: Array<KeyValueStyle> = [ public typesOfBusiness: Array<KeyValueStyle> = [
{ name: 'Automotive', value: '1', icon: 'fa-solid fa-car', bgColorClass: 'bg-green-100', textColorClass: 'text-green-600' }, { name: 'Automotive', value: '1', icon: 'fa-solid fa-car', bgColorClass: 'bg-green-100', textColorClass: 'text-green-600' },
@@ -20,6 +19,15 @@ export class SelectOptionsService {
{ name: 'Manufacturing', value: '12', icon: 'fa-solid fa-industry', bgColorClass: 'bg-red-100', textColorClass: 'text-red-600' }, { name: 'Manufacturing', value: '12', icon: 'fa-solid fa-industry', bgColorClass: 'bg-red-100', textColorClass: 'text-red-600' },
{ name: 'Food and Restaurant', value: '13', icon: 'fa-solid fa-utensils', bgColorClass: 'bg-primary-100', textColorClass: 'text-primary-600' }, { name: 'Food and Restaurant', value: '13', icon: 'fa-solid fa-utensils', bgColorClass: 'bg-primary-100', textColorClass: 'text-primary-600' },
]; ];
public typesOfCommercialProperty: Array<KeyValueStyle> = [
{ name: 'Retail', value: '100', icon: 'fa-solid fa-money-bill-wave', bgColorClass: 'bg-pink-100', textColorClass: 'text-pink-600' },
{ name: 'Land', value: '101', icon: 'pi pi-building', bgColorClass: 'bg-blue-100', textColorClass: 'text-blue-600' },
{ name: 'Industrial', value: '102', icon: 'fa-solid fa-industry', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
{ name: 'Office', value: '103', icon: 'fa-solid fa-umbrella', bgColorClass: 'bg-teal-100', textColorClass: 'text-teal-600' },
{ name: 'Mixed Use', value: '104', icon: 'fa-solid fa-rectangle-ad', bgColorClass: 'bg-orange-100', textColorClass: 'text-orange-600' },
{ name: 'Multifamily', value: '105', icon: 'pi pi-star', bgColorClass: 'bg-purple-100', textColorClass: 'text-purple-600' },
{ name: 'Uncategorized', value: '106', icon: 'pi pi-question', bgColorClass: 'bg-cyan-100', textColorClass: 'text-cyan-600' },
];
public prices: Array<KeyValue> = [ public prices: Array<KeyValue> = [
{ name: '$100K', value: '100000' }, { name: '$100K', value: '100000' },
{ name: '$250K', value: '250000' }, { name: '$250K', value: '250000' },
@@ -29,17 +37,20 @@ export class SelectOptionsService {
]; ];
public listingCategories: Array<KeyValue> = [ public listingCategories: Array<KeyValue> = [
{ name: 'Business', value: 'business' }, { name: 'Business', value: 'business' },
{ name: 'Professionals/Brokers Directory', value: 'professionals_brokers' }, { name: 'Commercial Property', value: 'commercialProperty' },
{ name: 'Investment Property', value: 'investment' }, ];
]
public categories: Array<KeyValueStyle> = [ public categories: Array<KeyValueStyle> = [
{ name: 'Broker', value: 'broker', icon: 'pi-image', bgColorClass: 'bg-green-100', textColorClass: 'text-green-600' }, { name: 'Broker', value: 'broker', icon: 'pi-image', bgColorClass: 'bg-green-100', textColorClass: 'text-green-600' },
{ name: 'Professional', value: 'professional', icon: 'pi-globe', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' }, { name: 'Professional', value: 'professional', icon: 'pi-globe', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
] ];
public imageTypes: ImageType[] = [
{ name: 'propertyPicture', upload: 'uploadPropertyPicture', delete: 'propertyPicture' },
{ name: 'companyLogo', upload: 'uploadCompanyLogo', delete: 'logo' },
{ name: 'profile', upload: 'uploadProfile', delete: 'profile' },
];
private usStates = [ private usStates = [
{ name: 'ALABAMA', abbreviation: 'AL' }, { name: 'ALABAMA', abbreviation: 'AL' },
{ name: 'ALASKA', abbreviation: 'AK' }, { name: 'ALASKA', abbreviation: 'AK' },
{ name: 'AMERICAN SAMOA', abbreviation: 'AS'},
{ name: 'ARIZONA', abbreviation: 'AZ' }, { name: 'ARIZONA', abbreviation: 'AZ' },
{ name: 'ARKANSAS', abbreviation: 'AR' }, { name: 'ARKANSAS', abbreviation: 'AR' },
{ name: 'CALIFORNIA', abbreviation: 'CA' }, { name: 'CALIFORNIA', abbreviation: 'CA' },
@@ -47,7 +58,6 @@ export class SelectOptionsService {
{ name: 'CONNECTICUT', abbreviation: 'CT' }, { name: 'CONNECTICUT', abbreviation: 'CT' },
{ name: 'DELAWARE', abbreviation: 'DE' }, { name: 'DELAWARE', abbreviation: 'DE' },
{ name: 'DISTRICT OF COLUMBIA', abbreviation: 'DC' }, { name: 'DISTRICT OF COLUMBIA', abbreviation: 'DC' },
{ name: 'FEDERATED STATES OF MICRONESIA', abbreviation: 'FM'},
{ name: 'FLORIDA', abbreviation: 'FL' }, { name: 'FLORIDA', abbreviation: 'FL' },
{ name: 'GEORGIA', abbreviation: 'GA' }, { name: 'GEORGIA', abbreviation: 'GA' },
{ name: 'GUAM', abbreviation: 'GU' }, { name: 'GUAM', abbreviation: 'GU' },
@@ -60,7 +70,6 @@ export class SelectOptionsService {
{ name: 'KENTUCKY', abbreviation: 'KY' }, { name: 'KENTUCKY', abbreviation: 'KY' },
{ name: 'LOUISIANA', abbreviation: 'LA' }, { name: 'LOUISIANA', abbreviation: 'LA' },
{ name: 'MAINE', abbreviation: 'ME' }, { name: 'MAINE', abbreviation: 'ME' },
{ name: 'MARSHALL ISLANDS', abbreviation: 'MH'},
{ name: 'MARYLAND', abbreviation: 'MD' }, { name: 'MARYLAND', abbreviation: 'MD' },
{ name: 'MASSACHUSETTS', abbreviation: 'MA' }, { name: 'MASSACHUSETTS', abbreviation: 'MA' },
{ name: 'MICHIGAN', abbreviation: 'MI' }, { name: 'MICHIGAN', abbreviation: 'MI' },
@@ -76,13 +85,11 @@ export class SelectOptionsService {
{ name: 'NEW YORK', abbreviation: 'NY' }, { name: 'NEW YORK', abbreviation: 'NY' },
{ name: 'NORTH CAROLINA', abbreviation: 'NC' }, { name: 'NORTH CAROLINA', abbreviation: 'NC' },
{ name: 'NORTH DAKOTA', abbreviation: 'ND' }, { name: 'NORTH DAKOTA', abbreviation: 'ND' },
{ name: 'NORTHERN MARIANA ISLANDS', abbreviation: 'MP'},
{ name: 'OHIO', abbreviation: 'OH' }, { name: 'OHIO', abbreviation: 'OH' },
{ name: 'OKLAHOMA', abbreviation: 'OK' }, { name: 'OKLAHOMA', abbreviation: 'OK' },
{ name: 'OREGON', abbreviation: 'OR' }, { name: 'OREGON', abbreviation: 'OR' },
{ name: 'PALAU', abbreviation: 'PW' }, { name: 'PALAU', abbreviation: 'PW' },
{ name: 'PENNSYLVANIA', abbreviation: 'PA' }, { name: 'PENNSYLVANIA', abbreviation: 'PA' },
{ name: 'PUERTO RICO', abbreviation: 'PR'},
{ name: 'RHODE ISLAND', abbreviation: 'RI' }, { name: 'RHODE ISLAND', abbreviation: 'RI' },
{ name: 'SOUTH CAROLINA', abbreviation: 'SC' }, { name: 'SOUTH CAROLINA', abbreviation: 'SC' },
{ name: 'SOUTH DAKOTA', abbreviation: 'SD' }, { name: 'SOUTH DAKOTA', abbreviation: 'SD' },
@@ -90,13 +97,11 @@ export class SelectOptionsService {
{ name: 'TEXAS', abbreviation: 'TX' }, { name: 'TEXAS', abbreviation: 'TX' },
{ name: 'UTAH', abbreviation: 'UT' }, { name: 'UTAH', abbreviation: 'UT' },
{ name: 'VERMONT', abbreviation: 'VT' }, { name: 'VERMONT', abbreviation: 'VT' },
{ name: 'VIRGIN ISLANDS', abbreviation: 'VI'},
{ name: 'VIRGINIA', abbreviation: 'VA' }, { name: 'VIRGINIA', abbreviation: 'VA' },
{ name: 'WASHINGTON', abbreviation: 'WA' }, { name: 'WASHINGTON', abbreviation: 'WA' },
{ name: 'WEST VIRGINIA', abbreviation: 'WV' }, { name: 'WEST VIRGINIA', abbreviation: 'WV' },
{ name: 'WISCONSIN', abbreviation: 'WI' }, { name: 'WISCONSIN', abbreviation: 'WI' },
{ name: 'WYOMING', abbreviation: 'WY' } { name: 'WYOMING', abbreviation: 'WY' },
] ];
public locations:Array<any> = [...this.usStates.map(state=>({name:state.name, value:state.abbreviation}))].concat({name:'CANADA',value:"CA"}); public locations: Array<any> = [...this.usStates.map(state => ({ name: state.name, value: state.abbreviation }))].concat({ name: 'CANADA', value: 'CA' });
} }

View File

@@ -0,0 +1,42 @@
import { Body, Controller, Get, Inject, Param, Post, Put, Query, Req } from '@nestjs/common';
import { UserService } from './user.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { User } from 'src/models/db.model.js';
@Controller('user')
export class UserController {
constructor(private userService: UserService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
@Get()
findByMail(@Query('mail') mail: string): any {
this.logger.info(`Searching for user with EMail: ${mail}`);
const user = this.userService.getUserByMail(mail);
this.logger.info(`Found user: ${JSON.stringify(user)}`);
return user;
}
@Get(':id')
findById(@Param('id') id: string): any {
this.logger.info(`Searching for user with ID: ${id}`);
const user = this.userService.getUserById(id);
this.logger.info(`Found user: ${JSON.stringify(user)}`);
return user;
}
@Post()
save(@Body() user: any): Promise<User> {
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
const savedUser = this.userService.saveUser(user);
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
return savedUser;
}
@Post('search')
find(@Body() criteria: any): any {
this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`);
const foundUsers = this.userService.findUser(criteria);
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
return foundUsers;
}
}

View File

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

View File

@@ -0,0 +1,77 @@
import { Inject, Injectable } from '@nestjs/common';
import { and, eq, ilike, or, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { PG_CONNECTION } from 'src/drizzle/schema.js';
import { User } from 'src/models/db.model.js';
import { Logger } from 'winston';
import * as schema from '../drizzle/schema.js';
import { FileService } from '../file/file.service.js';
import { ListingCriteria } from '../models/main.model.js';
@Injectable()
export class UserService {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
private fileService: FileService,
) {}
private getConditions(criteria: ListingCriteria): any[] {
const conditions = [];
if (criteria.state) {
conditions.push(sql`EXISTS (SELECT 1 FROM unnest(users."areasServed") AS area WHERE area LIKE '%' || ${criteria.state} || '%')`);
}
if (criteria.name) {
conditions.push(or(ilike(schema.users.firstname, `%${criteria.name}%`), ilike(schema.users.lastname, `%${criteria.name}%`)));
}
return conditions;
}
async getUserByMail(email: string) {
const users = (await this.conn
.select()
.from(schema.users)
.where(sql`email = ${email}`)) as User[];
const user = users[0];
user.hasCompanyLogo = this.fileService.hasCompanyLogo(user.id);
user.hasProfile = this.fileService.hasProfile(user.id);
return user;
}
async getUserById(id: string) {
const users = (await this.conn
.select()
.from(schema.users)
.where(sql`id = ${id}`)) as User[];
const user = users[0];
user.hasCompanyLogo = this.fileService.hasCompanyLogo(id);
user.hasProfile = this.fileService.hasProfile(id);
return user;
}
async saveUser(user: any): Promise<User> {
if (user.id) {
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
return updateUser as User;
} else {
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
return newUser as User;
}
}
async findUser(criteria: ListingCriteria) {
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 };
}
}

View File

@@ -1,4 +1,4 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] "exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
} }

View File

@@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2021", "target": "ES2021",
"module": "Node16", "module": "ESNext",
"moduleResolution": "Node16", "moduleResolution": "Node",
"declaration": true, "declaration": true,
"removeComments": true, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
@@ -16,7 +16,8 @@
"strictNullChecks": false, "strictNullChecks": false,
"noImplicitAny": false, "noImplicitAny": false,
"strictBindCallApply": false, "strictBindCallApply": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false, "noFallthroughCasesInSwitch": false,
"esModuleInterop":true
} }
} }

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Under Construction</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Welcome to bizmatch.net!</h1>
<p>We're currently under construction to bring you a new and improved experience. Our website is diligently being developed to ensure that we meet your needs with the highest quality of service.</p>
<p>Please check back soon for updates. In the meantime, feel free to <a href="mailto:info@bizmatch.net">contact us</a> for any inquiries or further information.</p>
<p>Thank you for your patience and support!</p>
<p>The bizmatch.net Team</p>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More