From 40ba402c7045e3626e820de2b98eec7746256f07 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Thu, 12 Sep 2024 19:48:29 +0200 Subject: [PATCH] =?UTF-8?q?Fixes=20f=C3=BCr=20input=20fields,=20#60=20->?= =?UTF-8?q?=20AuditService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bizmatch-server/src/drizzle/schema.ts | 2 +- bizmatch-server/src/event/event.service.ts | 1 + bizmatch-server/src/models/db.model.ts | 68 +++++++++++-------- .../validated-input.component.html | 2 +- .../validated-input.component.ts | 16 ++++- .../details-business-listing.component.html | 10 +-- .../details-business-listing.component.ts | 13 +++- ...commercial-property-listing.component.html | 10 +-- ...s-commercial-property-listing.component.ts | 9 ++- .../edit-business-listing.component.html | 19 +----- .../email-us/email-us.component.html | 2 +- bizmatch/src/app/services/audit.service.ts | 58 ++++++---------- bizmatch/src/app/services/geo.service.ts | 49 ++++++++++++- 13 files changed, 155 insertions(+), 104 deletions(-) diff --git a/bizmatch-server/src/drizzle/schema.ts b/bizmatch-server/src/drizzle/schema.ts index 1322ff3..2fa48e5 100644 --- a/bizmatch-server/src/drizzle/schema.ts +++ b/bizmatch-server/src/drizzle/schema.ts @@ -135,7 +135,7 @@ export const commercials = pgTable( export const listingEvents = pgTable('listing_events', { id: uuid('id').primaryKey().defaultRandom().notNull(), listingId: uuid('listing_id'), // Assuming listings are referenced by UUID, adjust as necessary - userId: uuid('user_id'), // Nullable, if user is logged in, otherwise null + email: varchar('email', { length: 255 }).references(() => users.email), eventType: varchar('event_type', { length: 50 }), // 'view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact' eventTimestamp: timestamp('event_timestamp').defaultNow(), userIp: varchar('user_ip', { length: 45 }), // Optional if you choose to track IP in frontend or backend diff --git a/bizmatch-server/src/event/event.service.ts b/bizmatch-server/src/event/event.service.ts index 331d98e..c344db6 100644 --- a/bizmatch-server/src/event/event.service.ts +++ b/bizmatch-server/src/event/event.service.ts @@ -12,6 +12,7 @@ export class EventService { ) {} async createEvent(event: ListingEvent) { // Speichere das Event in der Datenbank + event.eventTimestamp = new Date(); await this.conn.insert(listingEvents).values(event).execute(); } } diff --git a/bizmatch-server/src/models/db.model.ts b/bizmatch-server/src/models/db.model.ts index a4ce090..cef031a 100644 --- a/bizmatch-server/src/models/db.model.ts +++ b/bizmatch-server/src/models/db.model.ts @@ -34,6 +34,8 @@ export const CustomerTypeEnum = z.enum(['buyer', 'seller', 'professional']); export const SubscriptionTypeEnum = z.enum(['free', 'professional', 'broker']); export const CustomerSubTypeEnum = z.enum(['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']); export const ListingsCategoryEnum = z.enum(['commercialProperty', 'business']); +export const ZodEventTypeEnum = z.enum(['view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact', 'favorite']); +export type EventTypeEnum = z.infer; const PropertyTypeEnum = z.enum(['retail', 'land', 'industrial', 'office', 'mixedUse', 'multifamily', 'uncategorized']); const TypeEnum = z.enum([ 'automotive', @@ -123,32 +125,42 @@ export const LicensedInSchema = z.object({ }), registerNo: z.string().nonempty('License number is required'), }); -export const GeoSchema = z.object({ - name: z.string(), - state: z.string().refine(val => USStates.safeParse(val).success, { - message: 'Invalid state. Must be a valid 2-letter US state code.', - }), - latitude: z.number().refine( - value => { - return value >= -90 && value <= 90; - }, - { - message: 'Latitude muss zwischen -90 und 90 liegen', - }, - ), - longitude: z.number().refine( - value => { - return value >= -180 && value <= 180; - }, - { - message: 'Longitude muss zwischen -180 und 180 liegen', - }, - ), - county: z.string().optional().nullable(), - housenumber: z.string().optional().nullable(), - street: z.string().optional().nullable(), - zipCode: z.number().optional().nullable(), -}); +export const GeoSchema = z + .object({ + name: z.string().optional().nullable(), + state: z.string().refine(val => USStates.safeParse(val).success, { + message: 'Invalid state. Must be a valid 2-letter US state code.', + }), + latitude: z.number().refine( + value => { + return value >= -90 && value <= 90; + }, + { + message: 'Latitude muss zwischen -90 und 90 liegen', + }, + ), + longitude: z.number().refine( + value => { + return value >= -180 && value <= 180; + }, + { + message: 'Longitude muss zwischen -180 und 180 liegen', + }, + ), + county: z.string().optional().nullable(), + housenumber: z.string().optional().nullable(), + street: z.string().optional().nullable(), + zipCode: z.number().optional().nullable(), + }) + .superRefine((data, ctx) => { + if (!data.name && !data.county) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'You need to select either a city or a county', + path: ['name'], + }); + } + }); const phoneRegex = /^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/; export const UserSchema = z .object({ @@ -320,8 +332,8 @@ export type ShareByEMail = z.infer; export const ListingEventSchema = z.object({ id: z.string().uuid(), // UUID für das Event listingId: z.string().uuid(), // UUID für das Listing - userId: z.string().uuid().optional().nullable(), // UUID für den Benutzer, optional, wenn kein Benutzer eingeloggt ist - eventType: z.enum(['view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact']), // Die Event-Typen + email: z.string().email().optional().nullable(), // EMail des den Benutzer, optional, wenn kein Benutzer eingeloggt ist + eventType: ZodEventTypeEnum, // Die Event-Typen eventTimestamp: z.string().datetime().or(z.date()), // Der Zeitstempel des Events, kann ein String im ISO-Format oder ein Date-Objekt sein userIp: z.string().max(45).optional().nullable(), // IP-Adresse des Benutzers, optional userAgent: z.string().max(255).optional().nullable(), // User-Agent des Benutzers, optional diff --git a/bizmatch/src/app/components/validated-input/validated-input.component.html b/bizmatch/src/app/components/validated-input/validated-input.component.html index 8157e76..568cc75 100644 --- a/bizmatch/src/app/components/validated-input/validated-input.component.html +++ b/bizmatch/src/app/components/validated-input/validated-input.component.html @@ -14,7 +14,7 @@ } 0 ? event : null; + onInputChange(event: string | number): void { + if (this.kind === 'number') { + if (typeof event === 'number') { + this.value = event; + } else { + this.value = parseFloat(event); + } + } else { + const text = event as string; + this.value = text?.length > 0 ? event : null; + } + // this.value = event?.length > 0 ? (this.kind === 'number' ? parseFloat(event) : event) : null; this.onChange(this.value); } } diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html index 94747cf..77f0cd2 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html @@ -43,7 +43,7 @@ } - +
- - - + + + @@ -70,7 +70,7 @@
- +
diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts index 0dcaf14..0ec7edf 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { KeycloakService } from 'keycloak-angular'; import { ShareButton } from 'ngx-sharebuttons/button'; import { lastValueFrom } from 'rxjs'; -import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; +import { BusinessListing, EventTypeEnum, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { EMailService } from '../../../components/email/email.service'; @@ -14,6 +14,7 @@ import { ValidatedNgSelectComponent } from '../../../components/validated-ng-sel import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component'; import { ValidationMessagesService } from '../../../components/validation-messages.service'; import { AuditService } from '../../../services/audit.service'; +import { GeoService } from '../../../services/geo.service'; import { HistoryService } from '../../../services/history.service'; import { ListingsService } from '../../../services/listings.service'; import { MailService } from '../../../services/mail.service'; @@ -73,6 +74,7 @@ export class DetailsBusinessListingComponent { private messageService: MessageService, private auditService: AuditService, public emailService: EMailService, + private geoService: GeoService, ) { this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { @@ -89,9 +91,9 @@ export class DetailsBusinessListingComponent { this.user = await this.userService.getByMail(this.keycloakUser.email); this.mailinfo = createMailInfo(this.user); } - this.auditService.createEvent({ listingId: this.listing.id, eventType: 'view', eventTimestamp: new Date(), userAgent: navigator.userAgent, userId: this.user?.email }); try { this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business')); + this.auditService.createEvent(this.listing.id, 'view', this.user?.email); this.listingUser = await this.userService.getByMail(this.listing.email); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); } catch (error) { @@ -110,6 +112,7 @@ export class DetailsBusinessListingComponent { this.mailinfo.email = this.listingUser.email; this.mailinfo.listing = this.listing; await this.mailService.mail(this.mailinfo); + this.auditService.createEvent(this.listing.id, 'contact', this.user?.email); this.messageService.addMessage({ severity: 'success', text: 'Your message has been sent to the creator of the listing', duration: 3000 }); } catch (error) { this.messageService.addMessage({ @@ -155,6 +158,7 @@ export class DetailsBusinessListingComponent { save() { this.listing.favoritesForUser.push(this.user.email); this.listingsService.save(this.listing, 'business'); + this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); } isAlreadyFavorite() { return this.listing.favoritesForUser.includes(this.user.email); @@ -169,6 +173,7 @@ export class DetailsBusinessListingComponent { type: 'business', }); if (result) { + this.auditService.createEvent(this.listing.id, 'email', this.user?.email); this.messageService.addMessage({ severity: 'success', text: 'Your Email has beend sent', @@ -176,4 +181,8 @@ export class DetailsBusinessListingComponent { }); } } + + createEvent(eventType: EventTypeEnum) { + this.auditService.createEvent(this.listing.id, eventType, this.user?.email); + } } diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html index d31ba20..b126409 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html @@ -39,7 +39,7 @@ } - +
- - - + + + @@ -73,7 +73,7 @@
- +
diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts index 043a16a..44abc5e 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts @@ -6,7 +6,7 @@ import { KeycloakService } from 'keycloak-angular'; import { GalleryModule, ImageItem } from 'ng-gallery'; import { ShareButton } from 'ngx-sharebuttons/button'; import { lastValueFrom } from 'rxjs'; -import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; +import { CommercialPropertyListing, EventTypeEnum, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { CommercialPropertyListingCriteria, ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { EMailService } from '../../../components/email/email.service'; @@ -97,6 +97,7 @@ export class DetailsCommercialPropertyListingComponent { } try { this.listing = (await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'))) as CommercialPropertyListing; + this.auditService.createEvent(this.listing.id, 'view', this.user?.email); this.listingUser = await this.userService.getByMail(this.listing.email); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); import('flowbite').then(flowbite => { @@ -142,6 +143,7 @@ export class DetailsCommercialPropertyListingComponent { this.mailinfo.email = this.listingUser.email; this.mailinfo.listing = this.listing; await this.mailService.mail(this.mailinfo); + this.auditService.createEvent(this.listing.id, 'contact', this.user?.email); this.messageService.addMessage({ severity: 'success', text: 'Your message has been sent to the creator of the listing', duration: 3000 }); } catch (error) { this.messageService.addMessage({ @@ -166,6 +168,7 @@ export class DetailsCommercialPropertyListingComponent { save() { this.listing.favoritesForUser.push(this.user.email); this.listingsService.save(this.listing, 'commercialProperty'); + this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); } isAlreadyFavorite() { return this.listing.favoritesForUser.includes(this.user.email); @@ -180,6 +183,7 @@ export class DetailsCommercialPropertyListingComponent { type: 'commercialProperty', }); if (result) { + this.auditService.createEvent(this.listing.id, 'email', this.user?.email); this.messageService.addMessage({ severity: 'success', text: 'Your Email has beend sent', @@ -187,4 +191,7 @@ export class DetailsCommercialPropertyListingComponent { }); } } + createEvent(eventType: EventTypeEnum) { + this.auditService.createEvent(this.listing.id, eventType, this.user?.email); + } } diff --git a/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.html b/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.html index 674ce8c..1831c36 100644 --- a/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.html +++ b/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.html @@ -117,15 +117,8 @@ -->
- - + +
@@ -199,13 +192,7 @@
- +