export DB, Event creation, broker with city/state
This commit is contained in:
1
bizmatch-server/.gitignore
vendored
1
bizmatch-server/.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
/data
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
||||
@@ -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],
|
||||
|
||||
35
bizmatch-server/src/drizzle/export.ts
Normal file
35
bizmatch-server/src/drizzle/export.ts
Normal 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();
|
||||
}
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
20
bizmatch-server/src/event/event.controller.ts
Normal file
20
bizmatch-server/src/event/event.controller.ts
Normal 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' };
|
||||
}
|
||||
}
|
||||
11
bizmatch-server/src/event/event.module.ts
Normal file
11
bizmatch-server/src/event/event.module.ts
Normal 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 {}
|
||||
17
bizmatch-server/src/event/event.service.ts
Normal file
17
bizmatch-server/src/event/event.service.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => ({
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user