export DB, Event creation, broker with city/state

This commit is contained in:
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
/node_modules
/build
/data
# Logs
logs

View File

@@ -22,6 +22,7 @@ import { PaymentModule } from './payment/payment.module';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
import { SelectOptionsModule } from './select-options/select-options.module';
import { UserModule } from './user/user.module';
import { EventModule } from './event/event.module';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
@@ -85,6 +86,7 @@ loadEnvFiles();
AiModule,
LogModule,
PaymentModule,
EventModule,
],
controllers: [AppController, LogController],
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', {
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
eventType: varchar('event_type', { length: 50 }).notNull(), // 'view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact'
eventTimestamp: timestamp('event_timestamp').defaultNow().notNull(),
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
userAgent: varchar('user_agent', { length: 255 }), // Store User-Agent as string
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 { GeoService } from './geo.service';
@@ -24,4 +24,8 @@ export class GeoController {
findByPrefixAndStates(@Body() countyRequest: CountyRequest): any {
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;
}
findCitiesAndStatesStartingWith(prefix: string, state?: string): Array<CityAndStateResult> {
findCitiesAndStatesStartingWith(prefix: string): Array<CityAndStateResult> {
const results: Array<CityAndStateResult> = [];
const lowercasePrefix = prefix.toLowerCase();
@@ -100,4 +100,14 @@ export class GeoService {
getCityWithCoords(state: string, city: string): 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> {
try {
const validatedSender = SenderSchema.parse(mailInfo.sender);
SenderSchema.parse(mailInfo.sender);
} catch (error) {
if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({
@@ -54,7 +54,7 @@ export class MailService {
}
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
try {
const validatedSender = SenderSchema.parse(mailInfo.sender);
SenderSchema.parse(mailInfo.sender);
} catch (error) {
if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({
@@ -97,7 +97,7 @@ export class MailService {
}
async send2Friend(shareByEMail: ShareByEMail): Promise<void | ErrorResponse> {
try {
const validatedSender = ShareByEMailSchema.parse(shareByEMail);
ShareByEMailSchema.parse(shareByEMail);
} catch (error) {
if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err => ({

View File

@@ -316,3 +316,20 @@ export const ShareByEMailSchema = z.object({
type: ListingsCategoryEnum,
});
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>;