export DB, Event creation, broker with city/state

This commit is contained in:
Andreas Knuth 2024-09-12 15:13:56 +02:00
parent 60866473f7
commit 68d2615f0f
21 changed files with 242 additions and 40 deletions

View File

@ -2,6 +2,7 @@
/dist /dist
/node_modules /node_modules
/build /build
/data
# Logs # Logs
logs logs

View File

@ -22,6 +22,7 @@ import { PaymentModule } from './payment/payment.module';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
import { SelectOptionsModule } from './select-options/select-options.module'; import { SelectOptionsModule } from './select-options/select-options.module';
import { UserModule } from './user/user.module'; import { UserModule } from './user/user.module';
import { EventModule } from './event/event.module';
// const __filename = fileURLToPath(import.meta.url); // const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename); // const __dirname = path.dirname(__filename);
@ -85,6 +86,7 @@ loadEnvFiles();
AiModule, AiModule,
LogModule, LogModule,
PaymentModule, PaymentModule,
EventModule,
], ],
controllers: [AppController, LogController], controllers: [AppController, LogController],
providers: [AppService, FileService, JwtStrategy], providers: [AppService, FileService, JwtStrategy],

View File

@ -0,0 +1,35 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { promises as fs } from 'fs';
import { Pool } from 'pg';
import * as schema from './schema';
// Drizzle-Tabellen-Definitionen (hier hast du bereits die Tabellen definiert, wir nehmen an, sie werden hier importiert)
import { businesses, commercials, users } from './schema'; // Anpassen je nach tatsächlicher Struktur
const connectionString = process.env.DATABASE_URL;
console.log(connectionString);
const client = new Pool({ connectionString });
const db = drizzle(client, { schema, logger: true });
(async () => {
try {
// Abfrage der Daten für jede Tabelle
const usersData = await db.select().from(users).execute();
const businessesData = await db.select().from(businesses).execute();
const commercialsData = await db.select().from(commercials).execute();
// Speichern der Daten in JSON-Dateien
await fs.writeFile('./data/users_export.json', JSON.stringify(usersData, null, 2));
console.log('Users exportiert in users.json');
await fs.writeFile('./data/businesses_export.json', JSON.stringify(businessesData, null, 2));
console.log('Businesses exportiert in businesses.json');
await fs.writeFile('./data/commercials_export.json', JSON.stringify(commercialsData, null, 2));
console.log('Commercials exportiert in commercials.json');
} catch (error) {
console.error('Fehler beim Exportieren der Tabellen:', error);
} finally {
await client.end();
}
})();

View File

@ -134,10 +134,10 @@ export const commercials = pgTable(
// }); // });
export const listingEvents = pgTable('listing_events', { export const listingEvents = pgTable('listing_events', {
id: uuid('id').primaryKey().defaultRandom().notNull(), id: uuid('id').primaryKey().defaultRandom().notNull(),
listingId: uuid('listing_id').notNull(), // Assuming listings are referenced by UUID, adjust as necessary 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 userId: uuid('user_id'), // Nullable, if user is logged in, otherwise null
eventType: varchar('event_type', { length: 50 }).notNull(), // 'view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact' eventType: varchar('event_type', { length: 50 }), // 'view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact'
eventTimestamp: timestamp('event_timestamp').defaultNow().notNull(), eventTimestamp: timestamp('event_timestamp').defaultNow(),
userIp: varchar('user_ip', { length: 45 }), // Optional if you choose to track IP in frontend or backend userIp: varchar('user_ip', { length: 45 }), // Optional if you choose to track IP in frontend or backend
userAgent: varchar('user_agent', { length: 255 }), // Store User-Agent as string userAgent: varchar('user_agent', { length: 255 }), // Store User-Agent as string
locationCountry: varchar('location_country', { length: 100 }), // Country from IP locationCountry: varchar('location_country', { length: 100 }), // Country from IP

View File

@ -0,0 +1,20 @@
import { Body, Controller, Headers, Ip, Post } from '@nestjs/common';
import { ListingEvent } from 'src/models/db.model';
import { EventService } from './event.service';
@Controller('event')
export class EventController {
constructor(private eventService: EventService) {}
@Post()
async createEvent(
@Body() event: ListingEvent, // Struktur des Body-Objekts entsprechend anpassen
@Ip() userIp: string, // IP Adresse des Clients
@Headers('user-agent') userAgent: string, // User-Agent des Clients
) {
//const { listingId, userId, eventType, locationCountry, locationCity, locationLat, locationLng, referrer, additionalData } = body;
event.userIp = userIp;
event.userAgent = userAgent;
this.eventService.createEvent(event);
return { message: 'Event gespeichert' };
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { DrizzleModule } from 'src/drizzle/drizzle.module';
import { EventController } from './event.controller';
import { EventService } from './event.service';
@Module({
imports: [DrizzleModule],
controllers: [EventController],
providers: [EventService],
})
export class EventModule {}

View File

@ -0,0 +1,17 @@
import { Inject, Injectable } from '@nestjs/common';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { ListingEvent } from 'src/models/db.model';
import * as schema from '../drizzle/schema';
import { listingEvents, PG_CONNECTION } from '../drizzle/schema';
@Injectable()
export class EventService {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
) {}
async createEvent(event: ListingEvent) {
// Speichere das Event in der Datenbank
await this.conn.insert(listingEvents).values(event).execute();
}
}

View File

@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { Body, Controller, Get, Ip, Param, Post } from '@nestjs/common';
import { CountyRequest } from 'src/models/server.model'; import { CountyRequest } from 'src/models/server.model';
import { GeoService } from './geo.service'; import { GeoService } from './geo.service';
@ -24,4 +24,8 @@ export class GeoController {
findByPrefixAndStates(@Body() countyRequest: CountyRequest): any { findByPrefixAndStates(@Body() countyRequest: CountyRequest): any {
return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states); return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states);
} }
@Get('ipinfo/georesult/wysiwyg')
fetchIpAndGeoLocation(@Ip() userIp: string): any {
return this.geoService.fetchIpAndGeoLocation(userIp);
}
} }

View File

@ -62,7 +62,7 @@ export class GeoService {
}); });
return state ? result.filter(e => e.state.toLowerCase() === state.toLowerCase()) : result; return state ? result.filter(e => e.state.toLowerCase() === state.toLowerCase()) : result;
} }
findCitiesAndStatesStartingWith(prefix: string, state?: string): Array<CityAndStateResult> { findCitiesAndStatesStartingWith(prefix: string): Array<CityAndStateResult> {
const results: Array<CityAndStateResult> = []; const results: Array<CityAndStateResult> = [];
const lowercasePrefix = prefix.toLowerCase(); const lowercasePrefix = prefix.toLowerCase();
@ -100,4 +100,14 @@ export class GeoService {
getCityWithCoords(state: string, city: string): City { getCityWithCoords(state: string, city: string): City {
return this.geo.states.find(s => s.state_code === state).cities.find(c => c.name === city); return this.geo.states.find(s => s.state_code === state).cities.find(c => c.name === city);
} }
async fetchIpAndGeoLocation(ip: string): Promise<any> {
const response = await fetch(`${process.env.IP_INFO_URL}/${ip}/geo?token=${process.env.IP_INFO_TOKEN}`, {
method: 'GET',
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
}
} }

View File

@ -17,7 +17,7 @@ export class MailService {
async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> { async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> {
try { try {
const validatedSender = SenderSchema.parse(mailInfo.sender); SenderSchema.parse(mailInfo.sender);
} catch (error) { } catch (error) {
if (error instanceof ZodError) { if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({ const formattedErrors = error.errors.map(err => ({
@ -54,7 +54,7 @@ export class MailService {
} }
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> { async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
try { try {
const validatedSender = SenderSchema.parse(mailInfo.sender); SenderSchema.parse(mailInfo.sender);
} catch (error) { } catch (error) {
if (error instanceof ZodError) { if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({ const formattedErrors = error.errors.map(err => ({
@ -97,7 +97,7 @@ export class MailService {
} }
async send2Friend(shareByEMail: ShareByEMail): Promise<void | ErrorResponse> { async send2Friend(shareByEMail: ShareByEMail): Promise<void | ErrorResponse> {
try { try {
const validatedSender = ShareByEMailSchema.parse(shareByEMail); ShareByEMailSchema.parse(shareByEMail);
} catch (error) { } catch (error) {
if (error instanceof ZodError) { if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({ const formattedErrors = error.errors.map(err => ({

View File

@ -316,3 +316,20 @@ export const ShareByEMailSchema = z.object({
type: ListingsCategoryEnum, type: ListingsCategoryEnum,
}); });
export type ShareByEMail = z.infer<typeof ShareByEMailSchema>; export type ShareByEMail = z.infer<typeof ShareByEMailSchema>;
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
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
locationCountry: z.string().max(100).optional().nullable(), // Land, optional
locationCity: z.string().max(100).optional().nullable(), // Stadt, optional
locationLat: z.string().max(20).optional().nullable(), // Latitude, als String
locationLng: z.string().max(20).optional().nullable(), // Longitude, als String
referrer: z.string().max(255).optional().nullable(), // Referrer URL, optional
additionalData: z.record(z.any()).optional().nullable(), // JSON für zusätzliche Daten, z.B. soziale Medien, optional
});
export type ListingEvent = z.infer<typeof ListingEventSchema>;

View File

@ -1,10 +1,28 @@
{ {
"/api": { "/api": {
"target": "http://localhost:3000", "target": "http://localhost:3000",
"secure": false "secure": false,
"changeOrigin": true,
"logLevel": "debug"
}, },
"/pictures": { "/pictures": {
"target": "http://localhost:8080", "target": "http://localhost:8080",
"secure": false "secure": false
},
"/ipify": {
"target": "https://api.ipify.org",
"secure": true,
"changeOrigin": true,
"pathRewrite": {
"^/ipify": ""
}
},
"/ipinfo": {
"target": "https://ipinfo.io",
"secure": true,
"changeOrigin": true,
"pathRewrite": {
"^/ipinfo": ""
}
} }
} }

View File

@ -12,6 +12,8 @@ import { FooterComponent } from './components/footer/footer.component';
import { HeaderComponent } from './components/header/header.component'; import { HeaderComponent } from './components/header/header.component';
import { MessageContainerComponent } from './components/message/message-container.component'; import { MessageContainerComponent } from './components/message/message-container.component';
import { SearchModalComponent } from './components/search-modal/search-modal.component'; import { SearchModalComponent } from './components/search-modal/search-modal.component';
import { AuditService } from './services/audit.service';
import { GeoService } from './services/geo.service';
import { LoadingService } from './services/loading.service'; import { LoadingService } from './services/loading.service';
import { UserService } from './services/user.service'; import { UserService } from './services/user.service';
@ -35,6 +37,8 @@ export class AppComponent {
private keycloakService: KeycloakService, private keycloakService: KeycloakService,
private userService: UserService, private userService: UserService,
private confirmationService: ConfirmationService, private confirmationService: ConfirmationService,
private auditService: AuditService,
private geoService: GeoService,
) { ) {
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => { this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
let currentRoute = this.activatedRoute.root; let currentRoute = this.activatedRoute.root;
@ -55,6 +59,11 @@ export class AppComponent {
} }
}, },
}); });
// Ensure the service fetches the IP and Geolocation only once
// this.auditService.fetchIpAndGeoLocation();
this.geoService.fetchIpAndGeoLocation().subscribe(data => {
console.log(JSON.stringify(data));
});
} }
private async handleTokenExpiration(): Promise<void> { private async handleTokenExpiration(): Promise<void> {
try { try {

View File

@ -13,9 +13,9 @@ import { ValidatedInputComponent } from '../../../components/validated-input/val
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component'; import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component'; import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
import { ValidationMessagesService } from '../../../components/validation-messages.service'; import { ValidationMessagesService } from '../../../components/validation-messages.service';
import { AuditService } from '../../../services/audit.service';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { LogService } from '../../../services/log.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
@ -71,7 +71,7 @@ export class DetailsBusinessListingComponent {
public keycloakService: KeycloakService, public keycloakService: KeycloakService,
private validationMessagesService: ValidationMessagesService, private validationMessagesService: ValidationMessagesService,
private messageService: MessageService, private messageService: MessageService,
private logService: LogService, private auditService: AuditService,
public emailService: EMailService, public emailService: EMailService,
) { ) {
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
@ -89,12 +89,13 @@ export class DetailsBusinessListingComponent {
this.user = await this.userService.getByMail(this.keycloakUser.email); this.user = await this.userService.getByMail(this.keycloakUser.email);
this.mailinfo = createMailInfo(this.user); 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 { try {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business')); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
this.listingUser = await this.userService.getByMail(this.listing.email); this.listingUser = await this.userService.getByMail(this.listing.email);
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
} catch (error) { } catch (error) {
this.logService.log({ severity: 'error', text: error.error.message }); this.auditService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']); this.router.navigate(['notfound']);
} }
} }

View File

@ -15,10 +15,10 @@ import { ValidatedInputComponent } from '../../../components/validated-input/val
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component'; import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component'; import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
import { ValidationMessagesService } from '../../../components/validation-messages.service'; import { ValidationMessagesService } from '../../../components/validation-messages.service';
import { AuditService } from '../../../services/audit.service';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { LogService } from '../../../services/log.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
@ -80,7 +80,7 @@ export class DetailsCommercialPropertyListingComponent {
private ngZone: NgZone, private ngZone: NgZone,
private validationMessagesService: ValidationMessagesService, private validationMessagesService: ValidationMessagesService,
private messageService: MessageService, private messageService: MessageService,
private logService: LogService, private auditService: AuditService,
private emailService: EMailService, private emailService: EMailService,
) { ) {
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl }; this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
@ -116,7 +116,7 @@ export class DetailsCommercialPropertyListingComponent {
this.images.push(new ImageItem({ src: imageURL, thumb: imageURL })); this.images.push(new ImageItem({ src: imageURL, thumb: imageURL }));
}); });
} catch (error) { } catch (error) {
this.logService.log({ severity: 'error', text: error.error.message }); this.auditService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']); this.router.navigate(['notfound']);
} }

View File

@ -4,7 +4,6 @@
@for (user of users; track user) { @for (user of users; track user) {
<div class="bg-white rounded-lg shadow-md p-6 flex flex-col justify-between"> <div class="bg-white rounded-lg shadow-md p-6 flex flex-col justify-between">
<div class="flex items-start space-x-4"> <div class="flex items-start space-x-4">
<!-- <img src="https://placehold.co/80x105" alt="Portrait of Amanda Taylor, a professional woman with long dark hair" class="rounded-md w-20 h-26 object-cover" /> -->
@if(user.hasProfile){ @if(user.hasProfile){
<img src="{{ env.imageBaseUrl }}/pictures/profile/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="rounded-md w-20 h-26 object-cover" /> <img src="{{ env.imageBaseUrl }}/pictures/profile/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="rounded-md w-20 h-26 object-cover" />
} @else { } @else {
@ -12,13 +11,17 @@
} }
<div class="flex-1"> <div class="flex-1">
<p class="text-sm text-gray-800 mb-2">{{ user.description }}</p> <p class="text-sm text-gray-800 mb-2">{{ user.description }}</p>
<h3 class="text-lg font-semibold">{{ user.firstname }} {{ user.lastname }}</h3> <h3 class="text-lg font-semibold">
<app-customer-sub-type [customerSubType]="user.customerSubType"></app-customer-sub-type> {{ user.firstname }} {{ user.lastname }}<span class="bg-gray-200 text-gray-700 text-xs font-semibold px-2 py-1 rounded ml-4">{{ user.location?.name }} - {{ user.location?.state }}</span>
<p class="text-sm text-gray-600 mt-1">{{ user.companyName }}</p> </h3>
<div class="flex items-center space-x-2 mt-2">
<app-customer-sub-type [customerSubType]="user.customerSubType"></app-customer-sub-type>
<p class="text-sm text-gray-600">{{ user.companyName }}</p>
</div>
<div class="flex items-center justify-between my-2"></div>
</div> </div>
</div> </div>
<div class="mt-4 flex justify-between items-center"> <div class="mt-4 flex justify-between items-center">
<!-- <img src="https://placehold.co/35x45" alt="Lone Star Business Brokers logo" class="w-8 h-10 object-contain" /> -->
@if(user.hasCompanyLogo){ @if(user.hasCompanyLogo){
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="w-8 h-10 object-contain" /> <img src="{{ env.imageBaseUrl }}/pictures/logo/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="w-8 h-10 object-contain" />
} @else { } @else {
@ -30,6 +33,7 @@
</button> </button>
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import { Component } from '@angular/core';
import { Router, RouterModule } from '@angular/router'; import { Router, RouterModule } from '@angular/router';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { User } from '../../../../../bizmatch-server/src/models/db.model'; import { User } from '../../../../../bizmatch-server/src/models/db.model';
import { LogService } from '../../services/log.service'; import { AuditService } from '../../services/audit.service';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
import { map2User } from '../../utils/utils'; import { map2User } from '../../utils/utils';
@ -18,7 +18,7 @@ export class SuccessComponent {
user: User; user: User;
maxAttemptsReached: boolean = false; // Neue Variable hinzufügen maxAttemptsReached: boolean = false; // Neue Variable hinzufügen
constructor(private keycloakService: KeycloakService, private userService: UserService, private logService: LogService, private router: Router) {} constructor(private keycloakService: KeycloakService, private userService: UserService, private auditService: AuditService, private router: Router) {}
async ngOnInit() { async ngOnInit() {
let email = null; let email = null;
@ -35,7 +35,7 @@ export class SuccessComponent {
async checkSubscriptionPlan(email: string, error?: string) { async checkSubscriptionPlan(email: string, error?: string) {
if (!email) { if (!email) {
this.logService.log({ severity: 'error', text: `Unauthorized Access to Success Page ${error}` }); this.auditService.log({ severity: 'error', text: `Unauthorized Access to Success Page ${error}` });
this.router.navigate(['home']); this.router.navigate(['home']);
return; return;
} }

View File

@ -0,0 +1,66 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, lastValueFrom, Observable } from 'rxjs';
import { ListingEvent } from '../../../../bizmatch-server/src/models/db.model';
import { LogMessage } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class AuditService {
private apiBaseUrl = environment.apiBaseUrl;
private apiKey = environment.ipinfo_token;
// private ipifyUrl = 'https://api.ipify.org?format=json';
// private ipInfoUrl = 'https://ipinfo.io';
private ipifyUrl = 'https://api.ipify.org?format=json';
private ipInfoUrl = 'https://ipinfo.io';
// BehaviorSubject to store the geolocation data
private geoLocationSubject = new BehaviorSubject<any>(null);
public geoLocation$: Observable<any> = this.geoLocationSubject.asObservable();
constructor(private http: HttpClient) {}
async log(message: LogMessage): Promise<void> {
lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/log`, message));
}
async createEvent(event: ListingEvent): Promise<void> {
lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/event`, event));
}
// Function to get the IP address
private getIpAddress(): Observable<{ ip: string }> {
return this.http.get<{ ip: string }>(`/ipinfo?format=json`);
}
// Function to get geolocation using IP address
private getGeolocation(ip: string): Observable<any> {
return this.http.get(`/ipinfo/${ip}?token=${this.apiKey}`);
}
// Fetch IP and Geolocation only once, if not already fetched
fetchIpAndGeoLocation(): void {
if (!this.geoLocationSubject.getValue()) {
this.getIpAddress().subscribe({
next: response => {
this.getGeolocation(response.ip).subscribe({
next: geoData => {
this.geoLocationSubject.next(geoData); // Store the geolocation data
},
error: geoError => {
console.error('Error fetching geolocation:', geoError);
},
});
},
error: ipError => {
console.error('Error fetching IP address:', ipError);
},
});
}
}
// Method to provide the stored geolocation data
getGeoLocationData(): Observable<any> {
return this.geoLocation$; // Returns the observable for other components to subscribe
}
}

View File

@ -27,4 +27,7 @@ export class GeoService {
let headers = new HttpHeaders().set('X-Hide-Loading', 'true').set('Accept-Language', 'en-US'); let headers = new HttpHeaders().set('X-Hide-Loading', 'true').set('Accept-Language', 'en-US');
return this.http.get(`${this.baseUrl}?q=${prefix},US&format=json&addressdetails=1&limit=5`, { headers }) as Observable<Place[]>; return this.http.get(`${this.baseUrl}?q=${prefix},US&format=json&addressdetails=1&limit=5`, { headers }) as Observable<Place[]>;
} }
fetchIpAndGeoLocation(): Observable<any> {
return this.http.get(`${this.apiBaseUrl}/bizmatch/geo/ipinfo/georesult/wysiwyg`);
}
} }

View File

@ -1,17 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { LogMessage } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class LogService {
private apiBaseUrl = environment.apiBaseUrl;
constructor(private http: HttpClient) {}
async log(message: LogMessage): Promise<void> {
lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/log`, message));
}
}

View File

@ -11,4 +11,5 @@ export const environment_base = {
clientId: 'bizmatch-dev', clientId: 'bizmatch-dev',
redirectUri: 'https://dev.bizmatch.net', redirectUri: 'https://dev.bizmatch.net',
}, },
ipinfo_token: '7029590fb91214',
}; };