diff --git a/.gitignore b/.gitignore index 845959d..f4056c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -**/.env \ No newline at end of file +**/.env +**/.venv \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5d04139..abfb77d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,20 +17,34 @@ services: kafka: container_name: "PHANTOM-kafka" - image: confluentinc/cp-kafka:latest + image: confluentinc/cp-kafka:7.5.0 depends_on: - zookeeper environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:29092,PLAINTEXT_HOST://0.0.0.0:9092 + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - ALLOW_PLAINTEXT_LISTENER: yes + KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" ports: - "${KAFKA_PORT:-9092}:9092" volumes: - phantom_kafka_data:/var/lib/kafka/data + redpanda-console: + container_name: "PHANTOM-redpanda-console" + image: docker.redpanda.com/redpandadata/console:latest + depends_on: + - kafka + environment: + KAFKA_BROKERS: kafka:29092 + ports: + - "${REDPANDA_CONSOLE_PORT:-8080}:8080" + restart: unless-stopped + volumes: phantom_kafka_data: phantom_redis_data: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..627da29 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +kafka-python +dotenv diff --git a/web/src/app/api/track/route.ts b/web/src/app/api/track/route.ts new file mode 100644 index 0000000..eaa4f57 --- /dev/null +++ b/web/src/app/api/track/route.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { sendInteractionEvent } from '@/lib/kafka'; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { sessionId, eventType, targetEl, targetUrl, metadata } = body; + + if (!sessionId || !eventType) { + return NextResponse.json( + { error: 'sessionId and eventType required' }, + { status: 400 } + ); + } + + await sendInteractionEvent({ + sessionId, + eventType, + targetEl, + targetUrl, + metadata, + ts: Date.now(), + }); + + return NextResponse.json({ success: true }); + } catch (err: any) { + console.error('track error:', err); + return NextResponse.json( + { error: err.message || 'unknown error' }, + { status: 500 } + ); + } +} diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index f7fa87e..2cef36e 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { TrackingProvider } from "@/components/TrackingProvider"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,7 +28,7 @@ export default function RootLayout({
- {children} +