Compare commits
77 Commits
99bcc77abf
...
primeng
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d5b7e3f39 | |||
| d488f90f48 | |||
| 044f8efa0f | |||
| 24a3d210f0 | |||
| 2465b8966b | |||
| e87222d3c1 | |||
| 902ab9caed | |||
| 44acbcd4d0 | |||
| b4cf17b8ea | |||
| 226d2ebc1e | |||
| 0473f74241 | |||
| c9d94e973a | |||
| f9d9c6ad9e | |||
| 5dc893da38 | |||
| c471629c6d | |||
| 13fb3cd4b8 | |||
| d6768b3da9 | |||
| 7fdc87fb0b | |||
| 0b7e33612a | |||
| 8fba3aa832 | |||
| 214327031c | |||
| dc9adb151d | |||
| 747435bfba | |||
| 782c254a33 | |||
| df4e2b00e2 | |||
| 0684b9534f | |||
| e0ecea5af2 | |||
| 327aef0f21 | |||
| cb73daf863 | |||
| 08c53e2eb2 | |||
| 492c03c2be | |||
| f51a298227 | |||
| 474d7c63d5 | |||
| d2e5562602 | |||
| aff55c5433 | |||
| 5230ef1230 | |||
| d508415de4 | |||
| 6b61c19bd7 | |||
| bb5a408cdc | |||
| 9121ca1a69 | |||
| 4230867608 | |||
| 9e03620be7 | |||
| 7f0f21b598 | |||
| c90d6b72b7 | |||
| c4cdcf4505 | |||
| 7d10080069 | |||
| e784b424b0 | |||
| 4a9798c85e | |||
| 5c6dfa1e0d | |||
| 8aea819496 | |||
| fb69d2e4b3 | |||
| e5b7779492 | |||
| a437851f6d | |||
| 89bb85a512 | |||
| 840d7a63b1 | |||
| 73ab12a694 | |||
| a2c613c38f | |||
| d5210d3df4 | |||
| fd91adda57 | |||
| 2b27ab8ba5 | |||
| 25b201a9c6 | |||
| b8cabf4f0c | |||
| f3868da8f8 | |||
| be146fdc6a | |||
| 6ad40b6dca | |||
| 06f349d1c3 | |||
| c7fccd46d9 | |||
| 3b0196bd66 | |||
| 244fbe78ed | |||
| 56648b47a2 | |||
| f3b29a362e | |||
| fa3b9e104a | |||
| 798b84aa88 | |||
|
|
0e01b6ba09 | ||
|
|
3f9ffc1616 | ||
| 29a2eacd0f | |||
| 2eb34adc7c |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -48,6 +48,9 @@ public
|
|||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
.env.local
|
||||||
|
.env.prod
|
||||||
|
.env.dev
|
||||||
|
.env.test
|
||||||
|
|
||||||
# temp directory
|
# temp directory
|
||||||
.temp
|
.temp
|
||||||
@@ -62,10 +65,7 @@ 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
|
||||||
|
|
||||||
*.js
|
|
||||||
*.map
|
*.map
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
*.jar
|
*.jar
|
||||||
gitea
|
|
||||||
auth
|
|
||||||
|
|||||||
16
bizmatch-server/.editorconfig
Normal file
16
bizmatch-server/.editorconfig
Normal 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
|
||||||
25
bizmatch-server/.eslintrc.js
Normal file
25
bizmatch-server/.eslintrc.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
45
bizmatch-server/.eslintrc.json
Normal file
45
bizmatch-server/.eslintrc.json
Normal 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 }]
|
||||||
|
}
|
||||||
|
}
|
||||||
3
bizmatch-server/.gitignore
vendored
3
bizmatch-server/.gitignore
vendored
@@ -55,4 +55,5 @@ 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
|
||||||
|
pictures_base
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
41
bizmatch-server/.vscode/launch.json
vendored
41
bizmatch-server/.vscode/launch.json
vendored
@@ -6,17 +6,46 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug Nest Framework",
|
"name": "Debug Nest Framework",
|
||||||
"runtimeExecutable": "npm",
|
"runtimeExecutable": "npm",
|
||||||
"runtimeArgs": [
|
"runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"],
|
||||||
"run",
|
|
||||||
"start:debug",
|
|
||||||
"--",
|
|
||||||
"--inspect-brk"
|
|
||||||
],
|
|
||||||
"autoAttachChildProcesses": true,
|
"autoAttachChildProcesses": true,
|
||||||
"restart": true,
|
"restart": true,
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": false,
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
|
"env": {
|
||||||
|
"HOST_NAME": "localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
28
bizmatch-server/.vscode/settings.json
vendored
Normal 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
|
||||||
|
}
|
||||||
@@ -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
1326
bizmatch-server/broker.json
Normal file
File diff suppressed because it is too large
Load Diff
1371
bizmatch-server/data/broker.json
Normal file
1371
bizmatch-server/data/broker.json
Normal file
File diff suppressed because it is too large
Load Diff
12493
bizmatch-server/data/businesses.json
Normal file
12493
bizmatch-server/data/businesses.json
Normal file
File diff suppressed because it is too large
Load Diff
2858
bizmatch-server/data/commercials.json
Normal file
2858
bizmatch-server/data/commercials.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export declare class AccountService {
|
|
||||||
}
|
|
||||||
6
bizmatch-server/dist/app.controller.d.ts
vendored
6
bizmatch-server/dist/app.controller.d.ts
vendored
@@ -1,6 +0,0 @@
|
|||||||
import { AppService } from './app.service.js';
|
|
||||||
export declare class AppController {
|
|
||||||
private readonly appService;
|
|
||||||
constructor(appService: AppService);
|
|
||||||
getHello(): string;
|
|
||||||
}
|
|
||||||
2
bizmatch-server/dist/app.module.d.ts
vendored
2
bizmatch-server/dist/app.module.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
export declare class AppModule {
|
|
||||||
}
|
|
||||||
3
bizmatch-server/dist/app.service.d.ts
vendored
3
bizmatch-server/dist/app.service.d.ts
vendored
@@ -1,3 +0,0 @@
|
|||||||
export declare class AppService {
|
|
||||||
getHello(): string;
|
|
||||||
}
|
|
||||||
66
bizmatch-server/dist/assets/listings.json
vendored
66
bizmatch-server/dist/assets/listings.json
vendored
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
14
bizmatch-server/dist/assets/subscriptions.json
vendored
14
bizmatch-server/dist/assets/subscriptions.json
vendored
@@ -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
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
8
bizmatch-server/dist/file/file.service.d.ts
vendored
8
bizmatch-server/dist/file/file.service.d.ts
vendored
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
2
bizmatch-server/dist/mail/mail.module.d.ts
vendored
2
bizmatch-server/dist/mail/mail.module.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
export declare class MailModule {
|
|
||||||
}
|
|
||||||
9
bizmatch-server/dist/mail/mail.service.d.ts
vendored
9
bizmatch-server/dist/mail/mail.service.d.ts
vendored
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<p>Hey {{ name }},</p>
|
|
||||||
<p>You got an inquiry a</p>
|
|
||||||
<p>
|
|
||||||
{{inquiry}}
|
|
||||||
</p>
|
|
||||||
1
bizmatch-server/dist/main.d.ts
vendored
1
bizmatch-server/dist/main.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export {};
|
|
||||||
126
bizmatch-server/dist/models/main.model.d.ts
vendored
126
bizmatch-server/dist/models/main.model.d.ts
vendored
@@ -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 {};
|
|
||||||
129
bizmatch-server/dist/models/main.model.ts
vendored
129
bizmatch-server/dist/models/main.model.ts
vendored
@@ -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;
|
|
||||||
}
|
|
||||||
11
bizmatch-server/dist/models/server.model.d.ts
vendored
11
bizmatch-server/dist/models/server.model.d.ts
vendored
@@ -1,11 +0,0 @@
|
|||||||
export interface MailInfo {
|
|
||||||
sender: Sender;
|
|
||||||
userId: string;
|
|
||||||
}
|
|
||||||
export interface Sender {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
phoneNumber: string;
|
|
||||||
state: string;
|
|
||||||
comments: string;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { RedisService } from './redis.service.js';
|
|
||||||
export declare class RedisController {
|
|
||||||
private redisService;
|
|
||||||
constructor(redisService: RedisService);
|
|
||||||
}
|
|
||||||
2
bizmatch-server/dist/redis/redis.module.d.ts
vendored
2
bizmatch-server/dist/redis/redis.module.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
export declare class RedisModule {
|
|
||||||
}
|
|
||||||
11
bizmatch-server/dist/redis/redis.service.d.ts
vendored
11
bizmatch-server/dist/redis/redis.service.d.ts
vendored
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { SelectOptionsService } from './select-options.service.js';
|
|
||||||
export declare class SelectOptionsController {
|
|
||||||
private selectOptionsService;
|
|
||||||
constructor(selectOptionsService: SelectOptionsService);
|
|
||||||
getSelectOption(): any;
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -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
1
bizmatch-server/dist/utils.d.ts
vendored
1
bizmatch-server/dist/utils.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export declare function convertStringToNullUndefined(value: any): any;
|
|
||||||
11
bizmatch-server/drizzle.config.ts
Normal file
11
bizmatch-server/drizzle.config.ts
Normal 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,
|
||||||
|
})
|
||||||
@@ -4,7 +4,10 @@
|
|||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"deleteOutDir": true,
|
"deleteOutDir": true,
|
||||||
"assets": ["assets/**/*","**/*.hbs"],
|
"assets": [
|
||||||
|
"assets/**/*",
|
||||||
|
"**/*.hbs"
|
||||||
|
],
|
||||||
"watchAssets": true
|
"watchAssets": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,16 +9,21 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "HOST_NAME=localhost nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "HOST_NAME=dev.bizmatch.net nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "HOST_NAME=www.bizmatch.net node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"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,12 @@
|
|||||||
"@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",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"ioredis": "^5.3.2",
|
"jwks-rsa": "^3.1.0",
|
||||||
"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 +48,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 +72,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",
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get, Request, UseGuards } from '@nestjs/common';
|
||||||
import { AppService } from './app.service.js';
|
import { AppService } from './app.service.js';
|
||||||
|
import { AuthService } from './auth/auth.service.js';
|
||||||
|
import { JwtAuthGuard } from './jwt-auth/jwt-auth.guard.js';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private readonly appService: AppService) {}
|
constructor(
|
||||||
|
private readonly appService: AppService,
|
||||||
|
private authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get()
|
@Get()
|
||||||
getHello(): string {
|
getHello(@Request() req): string {
|
||||||
return this.appService.getHello();
|
return req.user;
|
||||||
|
//return 'dfgdf';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,58 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
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 { 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);
|
||||||
|
|
||||||
|
function loadEnvFiles() {
|
||||||
|
// Load the .env file
|
||||||
|
dotenv.config();
|
||||||
|
console.log('Loaded .env file');
|
||||||
|
|
||||||
|
// Determine which additional env file to load
|
||||||
|
let envFilePath = '';
|
||||||
|
const host = process.env.HOST_NAME || '';
|
||||||
|
|
||||||
|
if (host.includes('localhost')) {
|
||||||
|
envFilePath = '.env.local';
|
||||||
|
} else if (host.includes('dev.bizmatch.net')) {
|
||||||
|
envFilePath = '.env.dev';
|
||||||
|
} else if (host.includes('www.bizmatch.net') || host.includes('bizmatch.net')) {
|
||||||
|
envFilePath = '.env.prod';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the additional env file if it exists
|
||||||
|
if (fs.existsSync(envFilePath)) {
|
||||||
|
dotenv.config({ path: envFilePath });
|
||||||
|
console.log(`Loaded ${envFilePath} file`);
|
||||||
|
} else {
|
||||||
|
console.log(`No additional .env file found for HOST_NAME: ${host}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEnvFiles();
|
||||||
@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 +68,19 @@ const __dirname = path.dirname(__filename);
|
|||||||
// other transports...
|
// other transports...
|
||||||
],
|
],
|
||||||
// other options
|
// other options
|
||||||
})
|
}),
|
||||||
|
GeoModule,
|
||||||
|
UserModule,
|
||||||
|
ListingsModule,
|
||||||
|
SelectOptionsModule,
|
||||||
|
ImageModule,
|
||||||
|
PassportModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, ListingsController, SelectOptionsController, SubscriptionsController, AccountController],
|
controllers: [AppController],
|
||||||
providers: [AppService, FileService, SelectOptionsService, ListingsService, AccountService],
|
providers: [AppService, FileService],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {
|
||||||
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer.apply(RequestDurationMiddleware).forRoutes('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1326
bizmatch-server/src/assets/broker.json
Normal file
1326
bizmatch-server/src/assets/broker.json
Normal file
File diff suppressed because it is too large
Load Diff
5018
bizmatch-server/src/assets/businesses.json
Normal file
5018
bizmatch-server/src/assets/businesses.json
Normal file
File diff suppressed because it is too large
Load Diff
1402
bizmatch-server/src/assets/commercials.json
Normal file
1402
bizmatch-server/src/assets/commercials.json
Normal file
File diff suppressed because it is too large
Load Diff
119811
bizmatch-server/src/assets/geo.json
Normal file
119811
bizmatch-server/src/assets/geo.json
Normal file
File diff suppressed because it is too large
Load Diff
40
bizmatch-server/src/auth/auth.controller.ts
Normal file
40
bizmatch-server/src/auth/auth.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
bizmatch-server/src/auth/auth.module.ts
Normal file
16
bizmatch-server/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { JwtStrategy } from '../jwt.strategy.js';
|
||||||
|
import { AuthController } from './auth.controller.js';
|
||||||
|
import { AuthService } from './auth.service.js';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
@Module({
|
||||||
|
imports: [PassportModule],
|
||||||
|
providers: [AuthService, JwtStrategy],
|
||||||
|
controllers: [AuthController],
|
||||||
|
exports: [AuthService],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
107
bizmatch-server/src/auth/auth.service.ts
Normal file
107
bizmatch-server/src/auth/auth.service.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
27
bizmatch-server/src/drizzle/drizzle.module.ts
Normal file
27
bizmatch-server/src/drizzle/drizzle.module.ts
Normal 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 {}
|
||||||
143
bizmatch-server/src/drizzle/generateTypes.ts
Normal file
143
bizmatch-server/src/drizzle/generateTypes.ts
Normal 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);
|
||||||
194
bizmatch-server/src/drizzle/import.ts
Normal file
194
bizmatch-server/src/drizzle/import.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import { join } from 'path';
|
||||||
|
import pkg from 'pg';
|
||||||
|
import { rimraf } from 'rimraf';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { BusinessListing, CommercialPropertyListing, User, UserData } from 'src/models/db.model.js';
|
||||||
|
import { emailToDirName } from 'src/models/main.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 usersData: UserData[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||||
|
const generatedUserData = [];
|
||||||
|
console.log(usersData.length);
|
||||||
|
let i = 0,
|
||||||
|
male = 0,
|
||||||
|
female = 0;
|
||||||
|
const targetPathProfile = `./pictures/profile`;
|
||||||
|
deleteFilesOfDir(targetPathProfile);
|
||||||
|
const targetPathLogo = `./pictures/logo`;
|
||||||
|
deleteFilesOfDir(targetPathLogo);
|
||||||
|
const targetPathProperty = `./pictures/property`;
|
||||||
|
deleteFilesOfDir(targetPathProperty);
|
||||||
|
fs.ensureDirSync(`./pictures/logo`);
|
||||||
|
fs.ensureDirSync(`./pictures/profile`);
|
||||||
|
fs.ensureDirSync(`./pictures/property`);
|
||||||
|
for (const userData of usersData) {
|
||||||
|
const user: User = { firstname: '', lastname: '', email: '' };
|
||||||
|
user.licensedIn = [];
|
||||||
|
userData.licensedIn.forEach(l => {
|
||||||
|
console.log(l['value'], l['name']);
|
||||||
|
user.licensedIn.push({ registerNo: l['value'], state: l['name'] });
|
||||||
|
});
|
||||||
|
user.areasServed = [];
|
||||||
|
user.areasServed = userData.areasServed.map(l => {
|
||||||
|
return { county: l.split(',')[0].trim(), state: l.split(',')[1].trim() };
|
||||||
|
});
|
||||||
|
user.hasCompanyLogo = true;
|
||||||
|
user.hasProfile = true;
|
||||||
|
user.firstname = userData.firstname;
|
||||||
|
user.lastname = userData.lastname;
|
||||||
|
user.email = userData.email;
|
||||||
|
user.phoneNumber = userData.phoneNumber;
|
||||||
|
user.description = userData.description;
|
||||||
|
user.companyName = userData.companyName;
|
||||||
|
user.companyOverview = userData.companyOverview;
|
||||||
|
user.companyWebsite = userData.companyWebsite;
|
||||||
|
user.companyLocation = userData.companyLocation;
|
||||||
|
user.offeredServices = userData.offeredServices;
|
||||||
|
user.gender = userData.gender;
|
||||||
|
user.created = new Date();
|
||||||
|
user.updated = new Date();
|
||||||
|
const u = await db.insert(schema.users).values(user).returning({ insertedId: schema.users.id, gender: schema.users.gender, email: schema.users.email });
|
||||||
|
generatedUserData.push(u[0]);
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (u[0].gender === 'male') {
|
||||||
|
male++;
|
||||||
|
const data = readFileSync(`./pictures_base/profile/Mann_${male}.jpg`);
|
||||||
|
await storeProfilePicture(data, emailToDirName(u[0].email));
|
||||||
|
} else {
|
||||||
|
female++;
|
||||||
|
const data = readFileSync(`./pictures_base/profile/Frau_${female}.jpg`);
|
||||||
|
await storeProfilePicture(data, emailToDirName(u[0].email));
|
||||||
|
}
|
||||||
|
const data = readFileSync(`./pictures_base/logo/${i}.jpg`);
|
||||||
|
await storeCompanyLogo(data, emailToDirName(u[0].email));
|
||||||
|
}
|
||||||
|
//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.updated = new Date(business.created);
|
||||||
|
const user = getRandomItem(generatedUserData);
|
||||||
|
business.userId = user.insertedId;
|
||||||
|
business.imageName = emailToDirName(user.email);
|
||||||
|
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;
|
||||||
|
const user = getRandomItem(generatedUserData);
|
||||||
|
commercial.imageOrder = getFilenames(id);
|
||||||
|
commercial.imagePath = emailToDirName(user.email);
|
||||||
|
const insertionDate = getRandomDateWithinLastYear();
|
||||||
|
commercial.created = insertionDate;
|
||||||
|
commercial.updated = insertionDate;
|
||||||
|
commercial.userId = user.insertedId;
|
||||||
|
commercial.draft = false;
|
||||||
|
const result = await db.insert(schema.commercials).values(commercial).returning();
|
||||||
|
//fs.ensureDirSync(`./pictures/property/${result[0].imagePath}/${result[0].serialId}`);
|
||||||
|
try {
|
||||||
|
fs.copySync(`./pictures_base/property/${id}`, `./pictures/property/${result[0].imagePath}/${result[0].serialId}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`----- No pictures available for ${id} ------`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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_base/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, adjustedEmail: 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/${adjustedEmail}.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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
13
bizmatch-server/src/drizzle/migrate.ts
Normal file
13
bizmatch-server/src/drizzle/migrate.ts
Normal 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();
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "gender" AS ENUM('male', 'female');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "businesses" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"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,
|
||||||
|
"imagePath" varchar(200),
|
||||||
|
"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,
|
||||||
|
"serial_id" serial NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"type" integer,
|
||||||
|
"title" varchar(255),
|
||||||
|
"description" text,
|
||||||
|
"city" varchar(255),
|
||||||
|
"state" char(2),
|
||||||
|
"price" double precision,
|
||||||
|
"favoritesForUser" varchar(30)[],
|
||||||
|
"listingsCategory" varchar(255),
|
||||||
|
"hideImage" boolean,
|
||||||
|
"draft" boolean,
|
||||||
|
"zipCode" integer,
|
||||||
|
"county" varchar(255),
|
||||||
|
"email" varchar(255),
|
||||||
|
"website" varchar(255),
|
||||||
|
"phoneNumber" varchar(255),
|
||||||
|
"imageOrder" varchar(200)[],
|
||||||
|
"imagePath" varchar(200),
|
||||||
|
"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" jsonb,
|
||||||
|
"hasProfile" boolean,
|
||||||
|
"hasCompanyLogo" boolean,
|
||||||
|
"licensedIn" jsonb,
|
||||||
|
"gender" "gender",
|
||||||
|
"created" timestamp,
|
||||||
|
"updated" timestamp
|
||||||
|
);
|
||||||
|
--> 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 $$;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "customerType" AS ENUM('buyer', 'broker', 'professional');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD COLUMN "customerType" "customerType";
|
||||||
504
bizmatch-server/src/drizzle/migrations/meta/0000_snapshot.json
Normal file
504
bizmatch-server/src/drizzle/migrations/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
{
|
||||||
|
"id": "fc58c59b-ac5c-406e-8fdb-b05de40aed17",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(200)",
|
||||||
|
"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()"
|
||||||
|
},
|
||||||
|
"serial_id": {
|
||||||
|
"name": "serial_id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"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(200)",
|
||||||
|
"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": "jsonb",
|
||||||
|
"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": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
518
bizmatch-server/src/drizzle/migrations/meta/0001_snapshot.json
Normal file
518
bizmatch-server/src/drizzle/migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,518 @@
|
|||||||
|
{
|
||||||
|
"id": "0bc02618-4414-4e90-8c44-808737611da7",
|
||||||
|
"prevId": "fc58c59b-ac5c-406e-8fdb-b05de40aed17",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(200)",
|
||||||
|
"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()"
|
||||||
|
},
|
||||||
|
"serial_id": {
|
||||||
|
"name": "serial_id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"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(200)",
|
||||||
|
"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": "jsonb",
|
||||||
|
"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": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"customerType": {
|
||||||
|
"name": "customerType",
|
||||||
|
"type": "customerType",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"customerType": {
|
||||||
|
"name": "customerType",
|
||||||
|
"values": {
|
||||||
|
"buyer": "buyer",
|
||||||
|
"broker": "broker",
|
||||||
|
"professional": "professional"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
bizmatch-server/src/drizzle/migrations/meta/_journal.json
Normal file
20
bizmatch-server/src/drizzle/migrations/meta/_journal.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1716495198537,
|
||||||
|
"tag": "0000_burly_bruce_banner",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1717085220861,
|
||||||
|
"tag": "0001_wet_mephistopheles",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
86
bizmatch-server/src/drizzle/schema.ts
Normal file
86
bizmatch-server/src/drizzle/schema.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { boolean, char, doublePrecision, integer, jsonb, pgEnum, pgTable, serial, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
||||||
|
import { AreasServed, LicensedIn } from 'src/models/db.model';
|
||||||
|
|
||||||
|
export const PG_CONNECTION = 'PG_CONNECTION';
|
||||||
|
export const genderEnum = pgEnum('gender', ['male', 'female']);
|
||||||
|
export const customerTypeEnum = pgEnum('customerType', ['buyer', 'broker', 'professional']);
|
||||||
|
|
||||||
|
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: jsonb('areasServed').$type<AreasServed[]>(),
|
||||||
|
hasProfile: boolean('hasProfile'),
|
||||||
|
hasCompanyLogo: boolean('hasCompanyLogo'),
|
||||||
|
licensedIn: jsonb('licensedIn').$type<LicensedIn[]>(),
|
||||||
|
gender: genderEnum('gender'),
|
||||||
|
customerType: customerTypeEnum('customerType'),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
|
});
|
||||||
|
|
||||||
|
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'),
|
||||||
|
imageName: varchar('imagePath', { length: 200 }),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
|
visits: integer('visits'),
|
||||||
|
lastVisit: timestamp('lastVisit'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const commercials = pgTable('commercials', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
serialId: serial('serial_id'),
|
||||||
|
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: 200 }),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
|
visits: integer('visits'),
|
||||||
|
lastVisit: timestamp('lastVisit'),
|
||||||
|
});
|
||||||
@@ -1,29 +1,149 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { fstat, readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import path, { join } from 'path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { ImageProperty, Subscription } from '../models/main.model.js';
|
||||||
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`);
|
||||||
}
|
}
|
||||||
|
// ############
|
||||||
|
// Subscriptions
|
||||||
|
// ############
|
||||||
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(): Subscription[] {
|
||||||
return this.subscriptions
|
return this.subscriptions;
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// Profile
|
||||||
|
// ############
|
||||||
|
async storeProfilePicture(file: Express.Multer.File, adjustedEmail: 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/${adjustedEmail}.avif`);
|
||||||
|
}
|
||||||
|
hasProfile(adjustedEmail: string) {
|
||||||
|
return fs.existsSync(`./pictures/profile/${adjustedEmail}.avif`);
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// Logo
|
||||||
|
// ############
|
||||||
|
async storeCompanyLogo(file: Express.Multer.File, adjustedEmail: 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/${adjustedEmail}.avif`); // Ersetze Dateierweiterung
|
||||||
|
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
||||||
|
}
|
||||||
|
hasCompanyLogo(adjustedEmail: string) {
|
||||||
|
return fs.existsSync(`./pictures/logo/${adjustedEmail}.avif`) ? true : false;
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// Property
|
||||||
|
// ############
|
||||||
|
async getPropertyImages(imagePath: string, serial: string): Promise<string[]> {
|
||||||
|
const result: string[] = [];
|
||||||
|
const directory = `./pictures/property/${imagePath}/${serial}`;
|
||||||
|
if (fs.existsSync(directory)) {
|
||||||
|
const files = await fs.readdir(directory);
|
||||||
|
files.forEach(f => {
|
||||||
|
result.push(f);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async hasPropertyImages(imagePath: string, serial: string): Promise<boolean> {
|
||||||
|
const result: ImageProperty[] = [];
|
||||||
|
const directory = `./pictures/property/${imagePath}/${serial}`;
|
||||||
|
if (fs.existsSync(directory)) {
|
||||||
|
const files = await fs.readdir(directory);
|
||||||
|
return files.length > 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async storePropertyPicture(file: Express.Multer.File, imagePath: string, serial: string): Promise<string> {
|
||||||
|
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg';
|
||||||
|
const directory = `./pictures/property/${imagePath}/${serial}`;
|
||||||
|
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`;
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// utils
|
||||||
|
// ############
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
deleteImage(path: string) {
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDirectoryIfExists(imagePath) {
|
||||||
|
const dirPath = `pictures/property/${imagePath}`;
|
||||||
|
try {
|
||||||
|
const exists = fs.pathExistsSync();
|
||||||
|
if (exists) {
|
||||||
|
fs.removeSync(dirPath);
|
||||||
|
console.log(`Directory ${dirPath} was deleted.`);
|
||||||
|
} else {
|
||||||
|
console.log(`Directory ${dirPath} does not exist.`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error while deleting ${dirPath}:`, error);
|
||||||
}
|
}
|
||||||
async storeFile(file: Express.Multer.File,id: string){
|
|
||||||
const suffix = file.mimetype.includes('png')?'png':'jpg'
|
|
||||||
await fs.outputFile(`./public/profile_${id}`,file.buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
bizmatch-server/src/geo/geo.controller.ts
Normal file
18
bizmatch-server/src/geo/geo.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
bizmatch-server/src/geo/geo.module.ts
Normal file
9
bizmatch-server/src/geo/geo.module.ts
Normal 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 {}
|
||||||
40
bizmatch-server/src/geo/geo.service.ts
Normal file
40
bizmatch-server/src/geo/geo.service.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
55
bizmatch-server/src/image/image.controller.ts
Normal file
55
bizmatch-server/src/image/image.controller.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Controller, Delete, 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';
|
||||||
|
|
||||||
|
@Controller('image')
|
||||||
|
export class ImageController {
|
||||||
|
constructor(
|
||||||
|
private fileService: FileService,
|
||||||
|
private listingService: ListingsService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
private selectOptions: SelectOptionsService,
|
||||||
|
) {}
|
||||||
|
// ############
|
||||||
|
// Property
|
||||||
|
// ############
|
||||||
|
@Post('uploadPropertyPicture/:imagePath/:serial')
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('imagePath') imagePath: string, @Param('serial') serial: string) {
|
||||||
|
const imagename = await this.fileService.storePropertyPicture(file, imagePath, serial);
|
||||||
|
await this.listingService.addImage(imagePath, serial, imagename);
|
||||||
|
}
|
||||||
|
@Delete('propertyPicture/:imagePath/:serial/:imagename')
|
||||||
|
async deletePropertyImagesById(@Param('imagePath') imagePath: string, @Param('serial') serial: string, @Param('imagename') imagename: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property/${imagePath}/${serial}/${imagename}`);
|
||||||
|
await this.listingService.deleteImage(imagePath, serial, imagename);
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// Profile
|
||||||
|
// ############
|
||||||
|
@Post('uploadProfile/:email')
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
async uploadProfile(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) {
|
||||||
|
await this.fileService.storeProfilePicture(file, adjustedEmail);
|
||||||
|
}
|
||||||
|
@Delete('profile/:email/')
|
||||||
|
async deleteProfileImagesById(@Param('email') email: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/profile/${email}.avif`);
|
||||||
|
}
|
||||||
|
// ############
|
||||||
|
// Logo
|
||||||
|
// ############
|
||||||
|
@Post('uploadCompanyLogo/:email')
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) {
|
||||||
|
await this.fileService.storeCompanyLogo(file, adjustedEmail);
|
||||||
|
}
|
||||||
|
@Delete('logo/:email/')
|
||||||
|
async deleteLogoImagesById(@Param('email') adjustedEmail: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/logo/${adjustedEmail}.avif`);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
bizmatch-server/src/image/image.module.ts
Normal file
14
bizmatch-server/src/image/image.module.ts
Normal 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 {}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountService {}
|
export class ImageService {}
|
||||||
18
bizmatch-server/src/jwt-auth/jwt-auth.guard.ts
Normal file
18
bizmatch-server/src/jwt-auth/jwt-auth.guard.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') implements CanActivate {
|
||||||
|
canActivate(context: ExecutionContext) {
|
||||||
|
// Add your custom authentication logic here
|
||||||
|
// for example, call super.logIn(request) to establish a session.
|
||||||
|
return super.canActivate(context);
|
||||||
|
}
|
||||||
|
handleRequest(err, user, info) {
|
||||||
|
// You can throw an exception based on either "info" or "err" arguments
|
||||||
|
if (err || !user) {
|
||||||
|
throw err || new UnauthorizedException(info);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
bizmatch-server/src/jwt-auth/optional-jwt-auth.guard.ts
Normal file
13
bizmatch-server/src/jwt-auth/optional-jwt-auth.guard.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OptionalJwtAuthGuard extends AuthGuard('jwt') {
|
||||||
|
handleRequest(err, user, info) {
|
||||||
|
// Wenn der Benutzer nicht authentifiziert ist, aber kein Fehler vorliegt, geben Sie null zurück
|
||||||
|
if (err || !user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
bizmatch-server/src/jwt.strategy.ts
Normal file
45
bizmatch-server/src/jwt.strategy.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { passportJwtSecret } from 'jwks-rsa';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { JwtPayload, JwtUser } from './models/main.model';
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor(
|
||||||
|
configService: ConfigService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
) {
|
||||||
|
const realm = configService.get<string>('REALM');
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKeyProvider: passportJwtSecret({
|
||||||
|
cache: true,
|
||||||
|
rateLimit: true,
|
||||||
|
jwksRequestsPerMinute: 5,
|
||||||
|
jwksUri: `https://auth.bizmatch.net/realms/${realm}/protocol/openid-connect/certs`,
|
||||||
|
}),
|
||||||
|
audience: 'account', // Keycloak Client ID
|
||||||
|
authorize: '',
|
||||||
|
issuer: `https://auth.bizmatch.net/realms/${realm}`,
|
||||||
|
algorithms: ['RS256'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: JwtPayload): Promise<JwtUser> {
|
||||||
|
if (!payload) {
|
||||||
|
this.logger.error('Invalid payload');
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
if (!payload.sub || !payload.preferred_username) {
|
||||||
|
this.logger.error('Missing required claims');
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
const result = { userId: payload.sub, username: payload.preferred_username, roles: payload.realm_access?.roles };
|
||||||
|
this.logger.info(`JWT User: ${JSON.stringify(result)}`); // Debugging: JWT Payload anzeigen
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
bizmatch-server/src/listings/broker-listings.controller.ts
Normal file
21
bizmatch-server/src/listings/broker-listings.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
52
bizmatch-server/src/listings/business-listings.controller.ts
Normal file
52
bizmatch-server/src/listings/business-listings.controller.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGuards } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { businesses } from '../drizzle/schema.js';
|
||||||
|
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
|
||||||
|
import { JwtUser, 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,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Get(':id')
|
||||||
|
findById(@Request() req, @Param('id') id: string): any {
|
||||||
|
return this.listingsService.findBusinessesById(id, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Get('user/:userid')
|
||||||
|
findByUserId(@Request() req, @Param('userid') userid: string): any {
|
||||||
|
return this.listingsService.findBusinessesByEmail(userid, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Post('search')
|
||||||
|
find(@Request() req, @Body() criteria: ListingCriteria): any {
|
||||||
|
return this.listingsService.findBusinessListings(criteria, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.updateBusinessListing(listing.id, listing);
|
||||||
|
}
|
||||||
|
@Delete(':id')
|
||||||
|
deleteById(@Param('id') id: string) {
|
||||||
|
this.listingsService.deleteListing(id, businesses);
|
||||||
|
}
|
||||||
|
@Get('states/all')
|
||||||
|
getStates(): any {
|
||||||
|
return this.listingsService.getStates(businesses);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGuards } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { CommercialPropertyListing } from 'src/models/db.model.js';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { commercials } from '../drizzle/schema.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
|
||||||
|
import { JwtUser, 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,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Get(':id')
|
||||||
|
findById(@Request() req, @Param('id') id: string): any {
|
||||||
|
return this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Get('user/:email')
|
||||||
|
findByEmail(@Request() req, @Param('email') email: string): Promise<CommercialPropertyListing[]> {
|
||||||
|
return this.listingsService.findCommercialPropertiesByEmail(email, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Post('search')
|
||||||
|
async find(@Request() req, @Body() criteria: ListingCriteria): Promise<any> {
|
||||||
|
return await this.listingsService.findCommercialPropertyListings(criteria, req.user as JwtUser);
|
||||||
|
}
|
||||||
|
@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.updateCommercialPropertyListing(listing.id, listing);
|
||||||
|
}
|
||||||
|
@Delete(':id/:imagePath')
|
||||||
|
deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) {
|
||||||
|
this.listingsService.deleteListing(id, commercials);
|
||||||
|
this.fileService.deleteDirectoryIfExists(imagePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
18
bizmatch-server/src/listings/listings.module.ts
Normal file
18
bizmatch-server/src/listings/listings.module.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AuthModule } from '../auth/auth.module.js';
|
||||||
|
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, AuthModule],
|
||||||
|
controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController],
|
||||||
|
providers: [ListingsService, FileService, UserService],
|
||||||
|
exports: [ListingsService],
|
||||||
|
})
|
||||||
|
export class ListingsModule {}
|
||||||
@@ -1,86 +1,185 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import {
|
import { and, eq, gte, ilike, lte, ne, or, 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 { FileService } from '../file/file.service.js';
|
||||||
|
import { JwtUser, ListingCriteria, emailToDirName } 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(
|
private fileService: FileService,
|
||||||
value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing,
|
) {}
|
||||||
id?: string,
|
private getConditions(criteria: ListingCriteria, table: typeof businesses | typeof commercials, user: JwtUser): any[] {
|
||||||
) {
|
const conditions = [];
|
||||||
if (!id) {
|
if (criteria.type) {
|
||||||
id = await this.redisService.getId(LISTINGS);
|
conditions.push(eq(table.type, criteria.type));
|
||||||
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 findCommercialPropertyListings(criteria: ListingCriteria, user: JwtUser): Promise<any> {
|
||||||
|
const start = criteria.start ? criteria.start : 0;
|
||||||
|
const length = criteria.length ? criteria.length : 12;
|
||||||
|
const conditions = this.getConditions(criteria, commercials, user);
|
||||||
|
if (!user || (!user?.roles?.includes('ADMIN') ?? false)) {
|
||||||
|
conditions.push(or(eq(commercials.draft, false), eq(commercials.imagePath, emailToDirName(user?.username))));
|
||||||
|
}
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
this.conn
|
||||||
|
.select()
|
||||||
|
.from(commercials)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.offset(start)
|
||||||
|
.limit(length),
|
||||||
|
this.conn
|
||||||
|
.select({ count: sql`count(*)` })
|
||||||
|
.from(commercials)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.then(result => Number(result[0].count)),
|
||||||
|
]);
|
||||||
|
return { total, data };
|
||||||
|
}
|
||||||
|
async findBusinessListings(criteria: ListingCriteria, user: JwtUser): Promise<any> {
|
||||||
|
const start = criteria.start ? criteria.start : 0;
|
||||||
|
const length = criteria.length ? criteria.length : 12;
|
||||||
|
const conditions = this.getConditions(criteria, businesses, user);
|
||||||
|
if (!user || (!user?.roles?.includes('ADMIN') ?? false)) {
|
||||||
|
conditions.push(or(eq(businesses.draft, false), eq(businesses.imageName, emailToDirName(user?.username))));
|
||||||
|
}
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
this.conn
|
||||||
|
.select()
|
||||||
|
.from(businesses)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.offset(start)
|
||||||
|
.limit(length),
|
||||||
|
this.conn
|
||||||
|
.select({ count: sql`count(*)` })
|
||||||
|
.from(businesses)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.then(result => Number(result[0].count)),
|
||||||
|
]);
|
||||||
|
return { total, data };
|
||||||
|
}
|
||||||
|
async findCommercialPropertiesById(id: string, user: JwtUser): Promise<CommercialPropertyListing> {
|
||||||
|
let result = await this.conn
|
||||||
|
.select()
|
||||||
|
.from(commercials)
|
||||||
|
.where(and(sql`${commercials.id} = ${id}`));
|
||||||
|
result = result.filter(r => !r.draft || r.imagePath === emailToDirName(user?.username) || user?.roles.includes('ADMIN'));
|
||||||
|
return result[0] as CommercialPropertyListing;
|
||||||
|
}
|
||||||
|
async findBusinessesById(id: string, user: JwtUser): Promise<CommercialPropertyListing> {
|
||||||
|
let result = await this.conn
|
||||||
|
.select()
|
||||||
|
.from(businesses)
|
||||||
|
.where(and(sql`${businesses.id} = ${id}`));
|
||||||
|
result = result.filter(r => !r.draft || r.imageName === emailToDirName(user?.username) || user?.roles.includes('ADMIN'));
|
||||||
|
return result[0] as BusinessListing;
|
||||||
|
}
|
||||||
|
async findCommercialPropertiesByEmail(email: string, user: JwtUser): Promise<CommercialPropertyListing[]> {
|
||||||
|
const conditions = [];
|
||||||
|
conditions.push(eq(commercials.imagePath, emailToDirName(email)));
|
||||||
|
if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) {
|
||||||
|
conditions.push(ne(commercials.draft, true));
|
||||||
|
}
|
||||||
|
return (await this.conn
|
||||||
|
.select()
|
||||||
|
.from(commercials)
|
||||||
|
.where(and(...conditions))) as CommercialPropertyListing[];
|
||||||
|
}
|
||||||
|
async findBusinessesByEmail(email: string, user: JwtUser): Promise<BusinessListing[]> {
|
||||||
|
const conditions = [];
|
||||||
|
conditions.push(eq(businesses.imageName, emailToDirName(email)));
|
||||||
|
if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) {
|
||||||
|
conditions.push(ne(businesses.draft, true));
|
||||||
|
}
|
||||||
|
return (await this.conn
|
||||||
|
.select()
|
||||||
|
.from(businesses)
|
||||||
|
.where(and(...conditions))) as CommercialPropertyListing[];
|
||||||
|
}
|
||||||
|
async findByImagePath(imagePath: string, serial: string): Promise<CommercialPropertyListing> {
|
||||||
|
const result = await this.conn
|
||||||
|
.select()
|
||||||
|
.from(commercials)
|
||||||
|
.where(and(sql`${commercials.imagePath} = ${imagePath}`, sql`${commercials.serialId} = ${serial}`));
|
||||||
|
return result[0] as CommercialPropertyListing;
|
||||||
|
}
|
||||||
|
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
|
data.created = new Date();
|
||||||
|
data.updated = new Date();
|
||||||
|
const [createdListing] = await this.conn.insert(table).values(data).returning();
|
||||||
|
return createdListing as BusinessListing | CommercialPropertyListing;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getListingById(id: string) {
|
async updateCommercialPropertyListing(id: string, data: CommercialPropertyListing): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
return await this.redisService.getJson(id, LISTINGS);
|
data.updated = new Date();
|
||||||
|
data.created = new Date(data.created);
|
||||||
|
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
||||||
|
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
||||||
|
if (difference.length > 0) {
|
||||||
|
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
||||||
|
data.imageOrder = imageOrder;
|
||||||
}
|
}
|
||||||
deleteListing(id: string){
|
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
||||||
this.redisService.delete(id);
|
return updateListing as BusinessListing | CommercialPropertyListing;
|
||||||
this.logger.info(`delete listing with ID:${id}`)
|
|
||||||
}
|
}
|
||||||
async getAllListings(start?: number, end?: number) {
|
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
const searchResult = await this.redisService.search(LISTINGS, '*');
|
data.updated = new Date();
|
||||||
const listings = searchResult.slice(1).reduce((acc, item, index, array) => {
|
data.created = new Date(data.created);
|
||||||
// Jedes zweite Element (beginnend mit dem ersten) ist ein JSON-String des Listings
|
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
||||||
if (index % 2 === 1) {
|
return updateListing as BusinessListing | CommercialPropertyListing;
|
||||||
try {
|
}
|
||||||
const listing = JSON.parse(item[1]); // Parsen des JSON-Strings
|
async deleteListing(id: string, table: typeof businesses | typeof commercials): Promise<void> {
|
||||||
acc.push(listing);
|
await this.conn.delete(table).where(eq(table.id, id));
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Fehler beim Parsen des JSON-Strings: ', error);
|
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 deleteImage(imagePath: string, serial: string, name: string) {
|
||||||
|
const listing = (await this.findByImagePath(imagePath, serial)) as unknown as CommercialPropertyListing;
|
||||||
|
const index = listing.imageOrder.findIndex(im => im === name);
|
||||||
|
if (index > -1) {
|
||||||
|
listing.imageOrder.splice(index, 1);
|
||||||
|
await this.updateCommercialPropertyListing(listing.id, listing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
async addImage(imagePath: string, serial: string, imagename: string) {
|
||||||
}, []);
|
const listing = (await this.findByImagePath(imagePath, serial)) as unknown as CommercialPropertyListing;
|
||||||
return listings;
|
listing.imageOrder.push(imagename);
|
||||||
}
|
await this.updateCommercialPropertyListing(listing.id, listing);
|
||||||
//criteria.type,criteria.location,criteria.minPrice,criteria.maxPrice,criteria.realEstateChecked,criteria.listingsCategory
|
|
||||||
//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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
bizmatch-server/src/listings/unknown-listings.controller.ts
Normal file
22
bizmatch-server/src/listings/unknown-listings.controller.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Controller, Inject } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { ListingsService } from './listings.service.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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import { ErrorResponse, 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<void | ErrorResponse> {
|
||||||
|
if (mailInfo.listing) {
|
||||||
|
return this.mailService.sendInquiry(mailInfo);
|
||||||
|
} else {
|
||||||
|
return this.mailService.sendRequest(mailInfo);
|
||||||
}
|
}
|
||||||
@Post(':id')
|
|
||||||
sendEMail(@Param('id') id:string,@Body() mailInfo: MailInfo): any {
|
|
||||||
return this.mailService.sendInquiry(id,mailInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -1,24 +1,63 @@
|
|||||||
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 { ErrorResponse, MailInfo, isEmpty } 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<void | ErrorResponse> {
|
||||||
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));
|
||||||
|
if (isEmpty(mailInfo.sender.name)) {
|
||||||
|
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||||
|
}
|
||||||
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,
|
||||||
|
id: mailInfo.listing.id,
|
||||||
|
url: mailInfo.url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
|
if (isEmpty(mailInfo.sender.name)) {
|
||||||
|
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||||
}
|
}
|
||||||
|
await this.mailerService.sendMail({
|
||||||
|
to: 'support@bizmatch.net',
|
||||||
|
from: `"Bizmatch Support Team" <info@bizmatch.net>`,
|
||||||
|
subject: `Support Request from ${mailInfo.sender.name}`,
|
||||||
|
//template: './inquiry', // `.hbs` extension is appended automatically
|
||||||
|
template: join(__dirname, '../..', 'mail/templates/request.hbs'),
|
||||||
|
context: {
|
||||||
|
// ✏️ filling curly brackets with content
|
||||||
|
request: mailInfo.sender.comments,
|
||||||
|
iname: mailInfo.sender.name,
|
||||||
|
phone: mailInfo.sender.phoneNumber,
|
||||||
|
email: mailInfo.sender.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,104 @@
|
|||||||
<p>Hey {{ name }},</p>
|
<!DOCTYPE html>
|
||||||
<p>You got an inquiry a</p>
|
<html lang="en">
|
||||||
<p>
|
<head>
|
||||||
{{inquiry}}
|
<meta charset="UTF-8">
|
||||||
</p>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notification: New Buyer Lead</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.subheader {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #1E90FF;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #1E90FF;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.info:nth-child(even) {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.info-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">Notification: New buyer lead from the Bizmatch Network</div>
|
||||||
|
<div class="subheader">Dear {{name}},</div>
|
||||||
|
<p>You've received a message regarding your "{{title}}" listing.</p>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Buyer Information</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Name:</span>
|
||||||
|
<span class="info-value">{{iname}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Email:</span>
|
||||||
|
<span class="info-value">{{email}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Phone:</span>
|
||||||
|
<span class="info-value">{{phone}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Comments:</span>
|
||||||
|
<span class="info-value">{{inquiry}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Listing Information</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Headline:</span>
|
||||||
|
<span class="info-value">{{title}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Listing ID:</span>
|
||||||
|
<span class="info-value"><a href="{{url}}/listing/{{id}}">{{id}}</a></span>
|
||||||
|
</div>
|
||||||
|
{{#if internalListingNumber}}
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Ref ID:</span>
|
||||||
|
<span class="info-value">{{internalListingNumber}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
bizmatch-server/src/mail/templates/request.hbs
Normal file
83
bizmatch-server/src/mail/templates/request.hbs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notification: New User Request</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.subheader {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #1E90FF;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #1E90FF;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.info:nth-child(even) {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.info-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">Notification: New request from a user of the Bizmatch Network</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Requester Information</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Name:</span>
|
||||||
|
<span class="info-value">{{iname}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Email:</span>
|
||||||
|
<span class="info-value">{{email}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Phone:</span>
|
||||||
|
<span class="info-value">{{phone}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Question:</span>
|
||||||
|
<span class="info-value">{{request}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { AppModule } from './app.module.js';
|
import { AppModule } from './app.module.js';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
const server = express();
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.setGlobalPrefix('bizmatch');
|
app.setGlobalPrefix('bizmatch');
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: '*',
|
origin: '*',
|
||||||
|
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
allowedHeaders: 'Content-Type, Accept',
|
allowedHeaders: 'Content-Type, Accept, Authorization',
|
||||||
});
|
});
|
||||||
//origin: 'http://localhost:4200',
|
//origin: 'http://localhost:4200',
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
|
|||||||
107
bizmatch-server/src/models/db.model.ts
Normal file
107
bizmatch-server/src/models/db.model.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
export interface User {
|
||||||
|
id?: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
description?: string;
|
||||||
|
companyName?: string;
|
||||||
|
companyOverview?: string;
|
||||||
|
companyWebsite?: string;
|
||||||
|
companyLocation?: string;
|
||||||
|
offeredServices?: string;
|
||||||
|
areasServed?: AreasServed[];
|
||||||
|
hasProfile?: boolean;
|
||||||
|
hasCompanyLogo?: boolean;
|
||||||
|
licensedIn?: LicensedIn[];
|
||||||
|
gender?: 'male' | 'female';
|
||||||
|
customerType?: 'buyer' | 'broker' | 'professional';
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
}
|
||||||
|
export interface UserData {
|
||||||
|
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[];
|
||||||
|
gender?: 'male' | 'female';
|
||||||
|
customerType?: 'buyer' | 'broker' | 'professional';
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
imageName?: string;
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
visits?: number;
|
||||||
|
lastVisit?: Date;
|
||||||
|
listingsCategory?: 'commercialProperty' | 'business';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommercialPropertyListing {
|
||||||
|
id: string;
|
||||||
|
serialId?: number;
|
||||||
|
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?: 'commercialProperty' | 'business';
|
||||||
|
}
|
||||||
|
export interface AreasServed {
|
||||||
|
county: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
export interface LicensedIn {
|
||||||
|
registerNo: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../common-models/src/main.model.ts
|
|
||||||
222
bizmatch-server/src/models/main.model.ts
Normal file
222
bizmatch-server/src/models/main.model.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
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 JwtUser {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
export interface JwtPayload {
|
||||||
|
sub: string;
|
||||||
|
preferred_username: string;
|
||||||
|
realm_access?: {
|
||||||
|
roles?: string[];
|
||||||
|
};
|
||||||
|
[key: string]: any; // für andere optionale Felder im JWT-Payload
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
url: 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;
|
||||||
|
}
|
||||||
|
export interface ErrorResponse {
|
||||||
|
fields?: FieldError[];
|
||||||
|
general?: string[];
|
||||||
|
}
|
||||||
|
export interface FieldError {
|
||||||
|
fieldname: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
export function isEmpty(value: any): boolean {
|
||||||
|
// Check for undefined or null
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for empty string or string with only whitespace
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.trim().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for number and NaN
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return isNaN(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a string or number, it's not considered empty by this function
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export function emailToDirName(email: string): string {
|
||||||
|
if (email === undefined || email === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Entferne ungültige Zeichen und ersetze sie durch Unterstriche
|
||||||
|
const sanitizedEmail = email.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||||
|
|
||||||
|
// Entferne führende und nachfolgende Unterstriche
|
||||||
|
const trimmedEmail = sanitizedEmail.replace(/^_+|_+$/g, '');
|
||||||
|
|
||||||
|
// Ersetze mehrfache aufeinanderfolgende Unterstriche durch einen einzelnen Unterstrich
|
||||||
|
const normalizedEmail = trimmedEmail.replace(/_+/g, '_');
|
||||||
|
|
||||||
|
return normalizedEmail;
|
||||||
|
}
|
||||||
@@ -1,11 +1,64 @@
|
|||||||
export interface MailInfo {
|
import { Entity } from "redis-om";
|
||||||
sender:Sender;
|
export interface Geo {
|
||||||
userId:string;
|
id: number;
|
||||||
}
|
name: string;
|
||||||
export interface Sender {
|
iso3: string;
|
||||||
name:string;
|
iso2: string;
|
||||||
email:string;
|
numeric_code: string;
|
||||||
phoneNumber:string;
|
phone_code: string;
|
||||||
state:string;
|
capital: string;
|
||||||
comments: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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -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';
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||||
|
import { NextFunction, Request, Response } 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`);
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
let logMessage = `${req.method} ${req.url} - ${duration}ms`;
|
||||||
|
|
||||||
|
if (req.method === 'POST' || req.method === 'PUT') {
|
||||||
|
const body = JSON.stringify(req.body);
|
||||||
|
logMessage += ` - Body: ${body}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(logMessage);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,16 @@ import { SelectOptionsService } from './select-options.service.js';
|
|||||||
|
|
||||||
@Controller('select-options')
|
@Controller('select-options')
|
||||||
export class SelectOptionsController {
|
export class SelectOptionsController {
|
||||||
constructor(private selectOptionsService:SelectOptionsService){}
|
constructor(private selectOptionsService: SelectOptionsService) {}
|
||||||
@Get()
|
@Get()
|
||||||
getSelectOption():any{
|
getSelectOption(): any {
|
||||||
return {
|
return {
|
||||||
typesOfBusiness:this.selectOptionsService.typesOfBusiness,
|
typesOfBusiness: this.selectOptionsService.typesOfBusiness,
|
||||||
prices:this.selectOptionsService.prices,
|
prices: this.selectOptionsService.prices,
|
||||||
listingCategories:this.selectOptionsService.listingCategories,
|
listingCategories: this.selectOptionsService.listingCategories,
|
||||||
categories:this.selectOptionsService.categories,
|
customerTypes: this.selectOptionsService.customerTypes,
|
||||||
locations:this.selectOptionsService.locations,
|
locations: this.selectOptionsService.locations,
|
||||||
}
|
typesOfCommercialProperty: this.selectOptionsService.typesOfCommercialProperty,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
@@ -1,24 +1,32 @@
|
|||||||
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' },
|
||||||
{ name: 'Industrial Services', value: '2', icon:'fa-solid fa-industry',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600'},
|
{ name: 'Industrial Services', value: '2', icon: 'fa-solid fa-industry', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
|
||||||
{ name: 'Real Estate', value: '3' , icon:'pi pi-building',bgColorClass:'bg-blue-100',textColorClass:'text-blue-600'},
|
{ name: 'Real Estate', value: '3', icon: 'pi pi-building', bgColorClass: 'bg-blue-100', textColorClass: 'text-blue-600' },
|
||||||
{ name: 'Uncategorized', value: '4' , icon:'pi pi-question',bgColorClass:'bg-cyan-100',textColorClass:'text-cyan-600'},
|
{ name: 'Uncategorized', value: '4', icon: 'pi pi-question', bgColorClass: 'bg-cyan-100', textColorClass: 'text-cyan-600' },
|
||||||
{ name: 'Retail', value: '5' , icon:'fa-solid fa-money-bill-wave',bgColorClass:'bg-pink-100',textColorClass:'text-pink-600'},
|
{ name: 'Retail', value: '5', icon: 'fa-solid fa-money-bill-wave', bgColorClass: 'bg-pink-100', textColorClass: 'text-pink-600' },
|
||||||
{ name: 'Oilfield SVE and MFG.', value: '6' , icon:'fa-solid fa-oil-well',bgColorClass:'bg-indigo-100',textColorClass:'text-indigo-600'},
|
{ name: 'Oilfield SVE and MFG.', value: '6', icon: 'fa-solid fa-oil-well', bgColorClass: 'bg-indigo-100', textColorClass: 'text-indigo-600' },
|
||||||
{ name: 'Service', value: '7' , icon:'fa-solid fa-umbrella',bgColorClass:'bg-teal-100',textColorClass:'text-teal-600'},
|
{ name: 'Service', value: '7', icon: 'fa-solid fa-umbrella', bgColorClass: 'bg-teal-100', textColorClass: 'text-teal-600' },
|
||||||
{ name: 'Advertising', value: '8' , icon:'fa-solid fa-rectangle-ad',bgColorClass:'bg-orange-100',textColorClass:'text-orange-600'},
|
{ name: 'Advertising', value: '8', icon: 'fa-solid fa-rectangle-ad', bgColorClass: 'bg-orange-100', textColorClass: 'text-orange-600' },
|
||||||
{ name: 'Agriculture', value: '9' , icon:'fa-solid fa-wheat-awn',bgColorClass:'bg-bluegray-100',textColorClass:'text-bluegray-600'},
|
{ name: 'Agriculture', value: '9', icon: 'fa-solid fa-wheat-awn', bgColorClass: 'bg-bluegray-100', textColorClass: 'text-bluegray-600' },
|
||||||
{ name: 'Franchise', value: '10' , icon:'pi pi-star',bgColorClass:'bg-purple-100',textColorClass:'text-purple-600'},
|
{ name: 'Franchise', value: '10', icon: 'pi pi-star', bgColorClass: 'bg-purple-100', textColorClass: 'text-purple-600' },
|
||||||
{ name: 'Professional', value: '11' , icon:'fa-solid fa-user-gear',bgColorClass:'bg-gray-100',textColorClass:'text-gray-600'},
|
{ name: 'Professional', value: '11', icon: 'fa-solid fa-user-gear', bgColorClass: 'bg-gray-100', textColorClass: 'text-gray-600' },
|
||||||
{ 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' },
|
||||||
@@ -29,74 +37,76 @@ 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 customerTypes: Array<KeyValue> = [
|
||||||
public categories: Array<KeyValueStyle> = [
|
{ name: 'Buyer', value: 'buyer' },
|
||||||
{ name: 'Broker', value: 'broker', icon:'pi-image',bgColorClass:'bg-green-100',textColorClass:'text-green-600' },
|
{ name: 'Broker', value: 'broker' },
|
||||||
{ name: 'Professional', value: 'professional', icon:'pi-globe',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600' },
|
{ name: 'Professional', value: 'professional' },
|
||||||
]
|
];
|
||||||
|
public gender: Array<KeyValue> = [
|
||||||
|
{ name: 'Male', value: 'male' },
|
||||||
|
{ name: 'Female', value: 'female' },
|
||||||
|
];
|
||||||
|
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'},
|
{ name: 'COLORADO', abbreviation: 'CO' },
|
||||||
{ name: 'COLORADO', abbreviation: 'CO'},
|
{ 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: 'FLORIDA', abbreviation: 'FL' },
|
||||||
{ name: 'FEDERATED STATES OF MICRONESIA', abbreviation: 'FM'},
|
{ name: 'GEORGIA', abbreviation: 'GA' },
|
||||||
{ name: 'FLORIDA', abbreviation: 'FL'},
|
{ name: 'GUAM', abbreviation: 'GU' },
|
||||||
{ name: 'GEORGIA', abbreviation: 'GA'},
|
{ name: 'HAWAII', abbreviation: 'HI' },
|
||||||
{ name: 'GUAM', abbreviation: 'GU'},
|
{ name: 'IDAHO', abbreviation: 'ID' },
|
||||||
{ name: 'HAWAII', abbreviation: 'HI'},
|
{ name: 'ILLINOIS', abbreviation: 'IL' },
|
||||||
{ name: 'IDAHO', abbreviation: 'ID'},
|
{ name: 'INDIANA', abbreviation: 'IN' },
|
||||||
{ name: 'ILLINOIS', abbreviation: 'IL'},
|
{ name: 'IOWA', abbreviation: 'IA' },
|
||||||
{ name: 'INDIANA', abbreviation: 'IN'},
|
{ name: 'KANSAS', abbreviation: 'KS' },
|
||||||
{ name: 'IOWA', abbreviation: 'IA'},
|
{ name: 'KENTUCKY', abbreviation: 'KY' },
|
||||||
{ name: 'KANSAS', abbreviation: 'KS'},
|
{ name: 'LOUISIANA', abbreviation: 'LA' },
|
||||||
{ name: 'KENTUCKY', abbreviation: 'KY'},
|
{ name: 'MAINE', abbreviation: 'ME' },
|
||||||
{ name: 'LOUISIANA', abbreviation: 'LA'},
|
{ name: 'MARYLAND', abbreviation: 'MD' },
|
||||||
{ name: 'MAINE', abbreviation: 'ME'},
|
{ name: 'MASSACHUSETTS', abbreviation: 'MA' },
|
||||||
{ name: 'MARSHALL ISLANDS', abbreviation: 'MH'},
|
{ name: 'MICHIGAN', abbreviation: 'MI' },
|
||||||
{ name: 'MARYLAND', abbreviation: 'MD'},
|
{ name: 'MINNESOTA', abbreviation: 'MN' },
|
||||||
{ name: 'MASSACHUSETTS', abbreviation: 'MA'},
|
{ name: 'MISSISSIPPI', abbreviation: 'MS' },
|
||||||
{ name: 'MICHIGAN', abbreviation: 'MI'},
|
{ name: 'MISSOURI', abbreviation: 'MO' },
|
||||||
{ name: 'MINNESOTA', abbreviation: 'MN'},
|
{ name: 'MONTANA', abbreviation: 'MT' },
|
||||||
{ name: 'MISSISSIPPI', abbreviation: 'MS'},
|
{ name: 'NEBRASKA', abbreviation: 'NE' },
|
||||||
{ name: 'MISSOURI', abbreviation: 'MO'},
|
{ name: 'NEVADA', abbreviation: 'NV' },
|
||||||
{ name: 'MONTANA', abbreviation: 'MT'},
|
{ name: 'NEW HAMPSHIRE', abbreviation: 'NH' },
|
||||||
{ name: 'NEBRASKA', abbreviation: 'NE'},
|
{ name: 'NEW JERSEY', abbreviation: 'NJ' },
|
||||||
{ name: 'NEVADA', abbreviation: 'NV'},
|
{ name: 'NEW MEXICO', abbreviation: 'NM' },
|
||||||
{ name: 'NEW HAMPSHIRE', abbreviation: 'NH'},
|
{ name: 'NEW YORK', abbreviation: 'NY' },
|
||||||
{ name: 'NEW JERSEY', abbreviation: 'NJ'},
|
{ name: 'NORTH CAROLINA', abbreviation: 'NC' },
|
||||||
{ name: 'NEW MEXICO', abbreviation: 'NM'},
|
{ name: 'NORTH DAKOTA', abbreviation: 'ND' },
|
||||||
{ name: 'NEW YORK', abbreviation: 'NY'},
|
{ name: 'OHIO', abbreviation: 'OH' },
|
||||||
{ name: 'NORTH CAROLINA', abbreviation: 'NC'},
|
{ name: 'OKLAHOMA', abbreviation: 'OK' },
|
||||||
{ name: 'NORTH DAKOTA', abbreviation: 'ND'},
|
{ name: 'OREGON', abbreviation: 'OR' },
|
||||||
{ name: 'NORTHERN MARIANA ISLANDS', abbreviation: 'MP'},
|
{ name: 'PALAU', abbreviation: 'PW' },
|
||||||
{ name: 'OHIO', abbreviation: 'OH'},
|
{ name: 'PENNSYLVANIA', abbreviation: 'PA' },
|
||||||
{ name: 'OKLAHOMA', abbreviation: 'OK'},
|
{ name: 'RHODE ISLAND', abbreviation: 'RI' },
|
||||||
{ name: 'OREGON', abbreviation: 'OR'},
|
{ name: 'SOUTH CAROLINA', abbreviation: 'SC' },
|
||||||
{ name: 'PALAU', abbreviation: 'PW'},
|
{ name: 'SOUTH DAKOTA', abbreviation: 'SD' },
|
||||||
{ name: 'PENNSYLVANIA', abbreviation: 'PA'},
|
{ name: 'TENNESSEE', abbreviation: 'TN' },
|
||||||
{ name: 'PUERTO RICO', abbreviation: 'PR'},
|
{ name: 'TEXAS', abbreviation: 'TX' },
|
||||||
{ name: 'RHODE ISLAND', abbreviation: 'RI'},
|
{ name: 'UTAH', abbreviation: 'UT' },
|
||||||
{ name: 'SOUTH CAROLINA', abbreviation: 'SC'},
|
{ name: 'VERMONT', abbreviation: 'VT' },
|
||||||
{ name: 'SOUTH DAKOTA', abbreviation: 'SD'},
|
{ name: 'VIRGINIA', abbreviation: 'VA' },
|
||||||
{ name: 'TENNESSEE', abbreviation: 'TN'},
|
{ name: 'WASHINGTON', abbreviation: 'WA' },
|
||||||
{ name: 'TEXAS', abbreviation: 'TX'},
|
{ name: 'WEST VIRGINIA', abbreviation: 'WV' },
|
||||||
{ name: 'UTAH', abbreviation: 'UT'},
|
{ name: 'WISCONSIN', abbreviation: 'WI' },
|
||||||
{ name: 'VERMONT', abbreviation: 'VT'},
|
{ name: 'WYOMING', abbreviation: 'WY' },
|
||||||
{ name: 'VIRGIN ISLANDS', abbreviation: 'VI'},
|
];
|
||||||
{ name: 'VIRGINIA', abbreviation: 'VA'},
|
public locations: Array<any> = [...this.usStates.map(state => ({ name: state.name, value: state.abbreviation }))].concat({ name: 'CANADA', value: 'CA' });
|
||||||
{ name: 'WASHINGTON', abbreviation: 'WA'},
|
|
||||||
{ name: 'WEST VIRGINIA', abbreviation: 'WV'},
|
|
||||||
{ name: 'WISCONSIN', abbreviation: 'WI'},
|
|
||||||
{ name: 'WYOMING', abbreviation: 'WY' }
|
|
||||||
]
|
|
||||||
public locations:Array<any> = [...this.usStates.map(state=>({name:state.name, value:state.abbreviation}))].concat({name:'CANADA',value:"CA"});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
|
|
||||||
@Controller('subscriptions')
|
|
||||||
export class SubscriptionsController {
|
|
||||||
constructor(private readonly fileService: FileService){}
|
|
||||||
@Get()
|
|
||||||
findAll(): any {
|
|
||||||
return this.fileService.getSubscriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
65
bizmatch-server/src/user/user.controller.ts
Normal file
65
bizmatch-server/src/user/user.controller.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Body, Controller, Get, Inject, Param, Post, Query } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { User } from 'src/models/db.model.js';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { Subscription } from '../models/main.model.js';
|
||||||
|
import { UserService } from './user.service.js';
|
||||||
|
|
||||||
|
@Controller('user')
|
||||||
|
export class UserController {
|
||||||
|
constructor(
|
||||||
|
private userService: UserService,
|
||||||
|
private fileService: FileService,
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
@Get('states/all')
|
||||||
|
async getStates(): Promise<any[]> {
|
||||||
|
this.logger.info(`Getting all states for users`);
|
||||||
|
const result = await this.userService.getStates();
|
||||||
|
this.logger.info(`Found ${result.length} entries`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('subscriptions/:id')
|
||||||
|
async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> {
|
||||||
|
const subscriptions = this.fileService.getSubscriptions();
|
||||||
|
const user = await this.userService.getUserById(id);
|
||||||
|
subscriptions.forEach(s => {
|
||||||
|
s.userId = user.id;
|
||||||
|
s.start = user.created;
|
||||||
|
s.modified = user.created;
|
||||||
|
});
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
bizmatch-server/src/user/user.module.ts
Normal file
12
bizmatch-server/src/user/user.module.ts
Normal 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 {}
|
||||||
87
bizmatch-server/src/user/user.service.ts
Normal file
87
bizmatch-server/src/user/user.service.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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 { Logger } from 'winston';
|
||||||
|
import * as schema from '../drizzle/schema.js';
|
||||||
|
import { PG_CONNECTION } from '../drizzle/schema.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { User } from '../models/db.model.js';
|
||||||
|
import { ListingCriteria, emailToDirName } 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} || '%')`);
|
||||||
|
conditions.push(sql`${schema.users.areasServed} @> ${JSON.stringify([{ state: 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(emailToDirName(user.email));
|
||||||
|
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
async getUserById(id: string) {
|
||||||
|
const users = (await this.conn
|
||||||
|
.select()
|
||||||
|
.from(schema.users)
|
||||||
|
.where(sql`id = ${id}`)) as User[];
|
||||||
|
const user = users[0];
|
||||||
|
user.hasCompanyLogo = this.fileService.hasCompanyLogo(emailToDirName(user.email));
|
||||||
|
user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email));
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
async saveUser(user: any): Promise<User> {
|
||||||
|
if (user.id) {
|
||||||
|
user.created = new Date(user.created);
|
||||||
|
user.updated = new Date();
|
||||||
|
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
|
||||||
|
return updateUser as User;
|
||||||
|
} else {
|
||||||
|
user.created = new Date();
|
||||||
|
user.updated = new Date();
|
||||||
|
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
|
||||||
|
return newUser as User;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
async getStates(): Promise<any[]> {
|
||||||
|
const query = sql`SELECT jsonb_array_elements(${schema.users.areasServed}) ->> 'state' AS state, COUNT(DISTINCT ${schema.users.id}) AS count FROM ${schema.users} GROUP BY state ORDER BY count DESC`;
|
||||||
|
const result = await this.conn.execute(query);
|
||||||
|
return result.rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user