showInDirectory, loggingInterceptor, conditional Views props & profs

This commit is contained in:
Andreas Knuth 2025-03-03 19:53:43 +01:00
parent d8c48bf58a
commit e37613ffa0
11 changed files with 52 additions and 20 deletions

View File

@ -33,6 +33,7 @@ export const users = pgTable(
subscriptionId: text('subscriptionId'), subscriptionId: text('subscriptionId'),
subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'), subscriptionPlan: subscriptionTypeEnum('subscriptionPlan'),
location: jsonb('location'), location: jsonb('location'),
showInDirectory: boolean('showInDirectory').default(true),
// city: varchar('city', { length: 255 }), // city: varchar('city', { length: 255 }),
// state: char('state', { length: 2 }), // state: char('state', { length: 2 }),
// latitude: doublePrecision('latitude'), // latitude: doublePrecision('latitude'),

View File

@ -15,7 +15,7 @@ export class LoggingInterceptor implements NestInterceptor {
const ip = this.cls.get('ip') || 'unknown'; const ip = this.cls.get('ip') || 'unknown';
const countryCode = this.cls.get('countryCode') || 'unknown'; const countryCode = this.cls.get('countryCode') || 'unknown';
const username = this.cls.get('username') || 'unknown'; const username = this.cls.get('email') || 'unknown';
const method = request.method; const method = request.method;
const url = request.originalUrl; const url = request.originalUrl;

View File

@ -13,12 +13,12 @@ export class UserInterceptor implements NestInterceptor {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
// Überprüfe, ob der Benutzer authentifiziert ist // Überprüfe, ob der Benutzer authentifiziert ist
if (request.user && request.user.username) { if (request.user && request.user.email) {
try { try {
this.cls.set('username', request.user.username); this.cls.set('email', request.user.email);
this.logger.log(`CLS context gesetzt: Username=${request.user.username}`); this.logger.log(`CLS context gesetzt: EMail=${request.user.email}`);
} catch (error) { } catch (error) {
this.logger.error('Fehler beim Setzen der Username im CLS-Kontext', error); this.logger.error('Fehler beim Setzen der EMail im CLS-Kontext', error);
} }
} else { } else {
this.logger.log('Kein authentifizierter Benutzer gefunden'); this.logger.log('Kein authentifizierter Benutzer gefunden');

View File

@ -186,6 +186,7 @@ export const UserSchema = z
updated: z.date().optional().nullable(), updated: z.date().optional().nullable(),
subscriptionId: z.string().optional().nullable(), subscriptionId: z.string().optional().nullable(),
subscriptionPlan: SubscriptionTypeEnum.optional().nullable(), subscriptionPlan: SubscriptionTypeEnum.optional().nullable(),
showInDirectory: z.boolean(),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
if (data.customerType === 'professional') { if (data.customerType === 'professional') {
@ -233,7 +234,7 @@ export const UserSchema = z
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: 'Company location is required for professional customers', message: 'Company location is required for professional customers',
path: ['companyLocation'], path: ['location'],
}); });
} }

View File

@ -52,6 +52,10 @@ export class UserService {
if (criteria.state) { if (criteria.state) {
whereConditions.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'state' = ${criteria.state})`); whereConditions.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'state' = ${criteria.state})`);
} }
//never show user which denied
whereConditions.push(eq(schema.users.showInDirectory, true))
return whereConditions; return whereConditions;
} }
async searchUserListings(criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { async searchUserListings(criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> {
@ -59,7 +63,7 @@ export class UserService {
const length = criteria.length ? criteria.length : 12; const length = criteria.length ? criteria.length : 12;
const query = this.conn.select().from(schema.users); const query = this.conn.select().from(schema.users);
const whereConditions = this.getWhereConditions(criteria); const whereConditions = this.getWhereConditions(criteria);
if (whereConditions.length > 0) { if (whereConditions.length > 0) {
const whereClause = and(...whereConditions); const whereClause = and(...whereConditions);
query.where(whereClause); query.where(whereClause);

View File

@ -1,9 +1,9 @@
<!-- <div class="container"> --> <!-- <div class="container"> -->
<div class="wrapper" [ngClass]="{ 'bg-slate-100 print:bg-white': actualRoute !== 'home' }"> <div class="wrapper" [ngClass]="{ 'print:bg-white': actualRoute !== 'home' }">
@if (actualRoute !=='home' && actualRoute !=='login' && actualRoute!=='emailVerification' && actualRoute!=='email-authorized'){ @if (actualRoute !=='home' && actualRoute !=='login' && actualRoute!=='emailVerification' && actualRoute!=='email-authorized'){
<header></header> <header></header>
} }
<main class="flex-1"> <main class="flex-1 bg-slate-100">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</main> </main>

View File

@ -111,6 +111,7 @@
>Businesses</a >Businesses</a
> >
</li> </li>
@if ((numberOfCommercial$ | async) > 0) {
<li> <li>
<a <a
routerLink="/commercialPropertyListings" routerLink="/commercialPropertyListings"
@ -120,6 +121,8 @@
>Properties</a >Properties</a
> >
</li> </li>
}
@if ((numberOfBroker$ | async) > 0) {
<li> <li>
<a <a
routerLink="/brokerListings" routerLink="/brokerListings"
@ -129,6 +132,7 @@
>Professionals</a >Professionals</a
> >
</li> </li>
}
</ul> </ul>
</div> </div>
} @else { } @else {
@ -151,6 +155,7 @@
>Businesses</a >Businesses</a
> >
</li> </li>
@if ((numberOfCommercial$ | async) > 0) {
<li> <li>
<a <a
routerLink="/commercialPropertyListings" routerLink="/commercialPropertyListings"
@ -160,6 +165,8 @@
>Properties</a >Properties</a
> >
</li> </li>
}
@if ((numberOfBroker$ | async) > 0) {
<li> <li>
<a <a
routerLink="/brokerListings" routerLink="/brokerListings"
@ -169,6 +176,7 @@
>Professionals</a >Professionals</a
> >
</li> </li>
}
</ul> </ul>
</div> </div>
} }
@ -200,6 +208,7 @@
>Businesses</a >Businesses</a
> >
</li> </li>
@if ((numberOfCommercial$ | async) > 0) {
<li> <li>
<a <a
routerLinkActive="active-link" routerLinkActive="active-link"
@ -210,6 +219,8 @@
>Properties</a >Properties</a
> >
</li> </li>
}
@if ((numberOfBroker$ | async) > 0) {
<li> <li>
<a <a
routerLinkActive="active-link" routerLinkActive="active-link"
@ -220,6 +231,7 @@
>Professionals</a >Professionals</a
> >
</li> </li>
}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -12,6 +12,7 @@ import { BusinessListingCriteria, CommercialPropertyListingCriteria, emailToDirN
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { CriteriaChangeService } from '../../services/criteria-change.service'; import { CriteriaChangeService } from '../../services/criteria-change.service';
import { ListingsService } from '../../services/listings.service';
import { SearchService } from '../../services/search.service'; import { SearchService } from '../../services/search.service';
import { SelectOptionsService } from '../../services/select-options.service'; import { SelectOptionsService } from '../../services/select-options.service';
import { SharedService } from '../../services/shared.service'; import { SharedService } from '../../services/shared.service';
@ -46,6 +47,8 @@ export class HeaderComponent {
baseRoute: string; baseRoute: string;
sortDropdownVisible: boolean; sortDropdownVisible: boolean;
sortByOptions: KeyValueAsSortBy[] = []; sortByOptions: KeyValueAsSortBy[] = [];
numberOfBroker$: Observable<number>;
numberOfCommercial$: Observable<number>;
constructor( constructor(
private router: Router, private router: Router,
private userService: UserService, private userService: UserService,
@ -56,6 +59,7 @@ export class HeaderComponent {
private criteriaChangeService: CriteriaChangeService, private criteriaChangeService: CriteriaChangeService,
public selectOptions: SelectOptionsService, public selectOptions: SelectOptionsService,
private authService: AuthService, private authService: AuthService,
private listingService: ListingsService,
) {} ) {}
@HostListener('document:click', ['$event']) @HostListener('document:click', ['$event'])
handleGlobalClick(event: Event) { handleGlobalClick(event: Event) {
@ -71,7 +75,8 @@ export class HeaderComponent {
this.user = await this.userService.getByMail(this.keycloakUser?.email); this.user = await this.userService.getByMail(this.keycloakUser?.email);
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`; this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
} }
this.numberOfBroker$ = this.userService.getNumberOfBroker(createEmptyUserListingCriteria());
this.numberOfCommercial$ = this.listingService.getNumberOfListings(createEmptyCommercialPropertyListingCriteria(), 'commercialProperty');
setTimeout(() => { setTimeout(() => {
initFlowbite(); initFlowbite();
}, 10); }, 10);

View File

@ -56,6 +56,7 @@
>Businesses</a >Businesses</a
> >
</li> </li>
@if ((numberOfCommercial$ | async) > 0) {
<li class="me-2"> <li class="me-2">
<a <a
(click)="changeTab('commercialProperty')" (click)="changeTab('commercialProperty')"
@ -68,6 +69,8 @@
>Properties</a >Properties</a
> >
</li> </li>
}
@if ((numberOfBroker$ | async) > 0) {
<li class="me-2"> <li class="me-2">
<a <a
(click)="changeTab('broker')" (click)="changeTab('broker')"
@ -80,6 +83,7 @@
>Professionals</a >Professionals</a
> >
</li> </li>
}
</ul> </ul>
</div> </div>
} @if(aiSearch){ } @if(aiSearch){

View File

@ -52,7 +52,8 @@ export class HomeComponent {
cityOrState = undefined; cityOrState = undefined;
private criteriaChangeSubscription: Subscription; private criteriaChangeSubscription: Subscription;
numberOfResults$: Observable<number>; numberOfResults$: Observable<number>;
numberOfBroker$: Observable<number>;
numberOfCommercial$: Observable<number>;
aiSearch = false; aiSearch = false;
aiSearchText = ''; aiSearchText = '';
aiSearchFailed = false; aiSearchFailed = false;
@ -84,7 +85,8 @@ export class HomeComponent {
setTimeout(() => { setTimeout(() => {
initFlowbite(); initFlowbite();
}, 0); }, 0);
this.numberOfBroker$ = this.userService.getNumberOfBroker(createEmptyUserListingCriteria());
this.numberOfCommercial$ = this.listingService.getNumberOfListings(createEmptyCommercialPropertyListingCriteria(), 'commercialProperty');
const token = await this.authService.getToken(); const token = await this.authService.getToken();
sessionStorage.removeItem('businessListings'); sessionStorage.removeItem('businessListings');
sessionStorage.removeItem('commercialPropertyListings'); sessionStorage.removeItem('commercialPropertyListings');
@ -114,14 +116,7 @@ export class HomeComponent {
private setupCriteriaChangeListener() { private setupCriteriaChangeListener() {
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(untilDestroyed(this), debounceTime(400)).subscribe(() => this.setTotalNumberOfResults()); this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(untilDestroyed(this), debounceTime(400)).subscribe(() => this.setTotalNumberOfResults());
} }
// login() {
// this.keycloakService.login({
// redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`,
// });
// }
// register() {
// this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
// }
toggleMenu() { toggleMenu() {
this.isMenuOpen = !this.isMenuOpen; this.isMenuOpen = !this.isMenuOpen;
} }

View File

@ -220,6 +220,16 @@
</div> </div>
</div> </div>
} }
<div class="flex items-center !my-8">
<label class="flex items-center cursor-pointer">
<div class="relative">
<input type="checkbox" [(ngModel)]="user.showInDirectory" name="showInDirectory" class="hidden" />
<div class="toggle-bg block w-12 h-6 rounded-full bg-gray-600 transition"></div>
</div>
<div class="ml-3 text-gray-700 font-medium">Show your profile in Professional Directory</div>
</label>
</div>
<div class="flex justify-start"> <div class="flex justify-start">
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" (click)="updateProfile(user)"> <button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" (click)="updateProfile(user)">
Update Profile Update Profile