Skip to content

Commit d298301

Browse files
committed
feat: implement PostHog analytics for canvas events and user identification
1 parent 6310c76 commit d298301

File tree

4 files changed

+71
-6
lines changed

4 files changed

+71
-6
lines changed

src/backend/main.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@
66
from fastapi.responses import FileResponse
77
from fastapi.middleware.cors import CORSMiddleware
88
from fastapi.staticfiles import StaticFiles
9-
from fastapi.templating import Jinja2Templates
9+
from api_analytics.fastapi import Analytics
10+
from dotenv import load_dotenv
11+
import posthog
12+
13+
load_dotenv()
14+
15+
POSTHOG_API_KEY = os.environ.get("VITE_PUBLIC_POSTHOG_KEY")
16+
POSTHOG_HOST = os.environ.get("VITE_PUBLIC_POSTHOG_HOST")
17+
18+
if POSTHOG_API_KEY:
19+
posthog.project_api_key = POSTHOG_API_KEY
20+
posthog.host = POSTHOG_HOST
1021

1122
from db import init_db
1223
from config import STATIC_DIR, ASSETS_DIR
@@ -24,6 +35,9 @@ async def lifespan(_: FastAPI):
2435

2536
app = FastAPI(lifespan=lifespan)
2637

38+
# Add analytics middleware
39+
app.add_middleware(Analytics, api_key="ea6d92e3-51d7-48f0-a327-8a38869ade13")
40+
2741
# CORS middleware setup
2842
app.add_middleware(
2943
CORSMiddleware,
@@ -33,7 +47,6 @@ async def lifespan(_: FastAPI):
3347
allow_headers=["*"],
3448
)
3549

36-
print("ASSETS_DIR", ASSETS_DIR)
3750
app.mount("/assets", StaticFiles(directory=ASSETS_DIR), name="assets")
3851

3952
@app.get("/")

src/backend/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ asyncpg
66
python-dotenv
77
PyJWT
88
requests
9-
sqlalchemy
9+
api-analytics[fastapi]
10+
sqlalchemy
11+
posthog

src/backend/routers/canvas.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import json
22
import jwt
33
from typing import Dict, Any
4-
from fastapi import APIRouter, HTTPException, Depends
4+
from fastapi import APIRouter, HTTPException, Depends, Request
55
from fastapi.responses import JSONResponse
66

77
from dependencies import SessionData, require_auth
88
from db import store_canvas_data, get_canvas_data
9+
import posthog
910

1011
canvas_router = APIRouter()
1112

@@ -19,14 +20,49 @@ def get_default_canvas_data():
1920
detail=f"Failed to load default canvas: {str(e)}"
2021
)
2122

23+
@canvas_router.get("/default")
24+
async def get_default_canvas(auth: SessionData = Depends(require_auth)):
25+
try:
26+
with open("default_canvas.json", "r") as f:
27+
canvas_data = json.load(f)
28+
return canvas_data
29+
except Exception as e:
30+
return JSONResponse(
31+
status_code=500,
32+
content={"error": f"Failed to load default canvas: {str(e)}"}
33+
)
34+
2235
@canvas_router.post("")
23-
async def save_canvas(data: Dict[str, Any], auth: SessionData = Depends(require_auth)):
36+
async def save_canvas(data: Dict[str, Any], auth: SessionData = Depends(require_auth), request: Request = None):
2437
access_token = auth.token_data.get("access_token")
2538
decoded = jwt.decode(access_token, options={"verify_signature": False})
2639
user_id = decoded["sub"]
2740
success = await store_canvas_data(user_id, data)
2841
if not success:
2942
raise HTTPException(status_code=500, detail="Failed to save canvas data")
43+
# PostHog analytics: capture canvas_saved event
44+
try:
45+
app_state = data.get("appState", {})
46+
width = app_state.get("width")
47+
height = app_state.get("height")
48+
zoom = app_state.get("zoom", {}).get("value")
49+
api_path = str(request.url.path) if request else None
50+
full_url = None
51+
if request:
52+
full_url = str(request.base_url).rstrip("/") + str(request.url.path)
53+
posthog.capture(
54+
distinct_id=user_id,
55+
event="canvas_saved",
56+
properties={
57+
"pad_width": width,
58+
"pad_height": height,
59+
"pad_zoom": zoom,
60+
"$current_url": full_url,
61+
}
62+
)
63+
except Exception as e:
64+
print(f"Error capturing canvas_saved event: {str(e)}")
65+
pass
3066
return {"status": "success"}
3167

3268
@canvas_router.get("")

src/backend/routers/user.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import jwt
22
from fastapi import APIRouter, Depends
3+
import posthog
34

45
from dependencies import SessionData, require_auth
56

@@ -11,6 +12,19 @@ async def get_user_info(auth: SessionData = Depends(require_auth)):
1112
access_token = token_data.get("access_token")
1213

1314
decoded = jwt.decode(access_token, options={"verify_signature": False})
15+
16+
# Identify user in PostHog (mirrors frontend identify)
17+
posthog.identify(
18+
distinct_id=decoded["sub"],
19+
properties={
20+
"email": decoded.get("email", ""),
21+
"username": decoded.get("preferred_username", ""),
22+
"name": decoded.get("name", ""),
23+
"given_name": decoded.get("given_name", ""),
24+
"family_name": decoded.get("family_name", ""),
25+
"email_verified": decoded.get("email_verified", False)
26+
}
27+
)
1428

1529
return {
1630
"id": decoded["sub"], # Unique user ID
@@ -20,4 +34,4 @@ async def get_user_info(auth: SessionData = Depends(require_auth)):
2034
"given_name": decoded.get("given_name", ""),
2135
"family_name": decoded.get("family_name", ""),
2236
"email_verified": decoded.get("email_verified", False)
23-
}
37+
}

0 commit comments

Comments
 (0)