2026-03-06 20:14:50 +07:00
|
|
|
from datetime import timedelta
|
|
|
|
|
|
|
|
|
|
import httpx
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
2026-03-06 20:14:50 +07:00
|
|
|
from fastapi.responses import RedirectResponse
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
from app.core.config import settings
|
|
|
|
|
from app.core.dependencies import get_current_user, require_auth
|
|
|
|
|
from app.core.security import create_access_token, hash_password, verify_password
|
|
|
|
|
from app.database.session import get_db
|
|
|
|
|
from app.models.user import User
|
|
|
|
|
from app.schemas.auth import LoginRequest, RegisterRequest, UserResponse
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
from app.utils.geo import country_from_request
|
2026-03-06 20:14:50 +07:00
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_auth_cookie(response: Response, token: str) -> None:
|
|
|
|
|
response.set_cookie(
|
|
|
|
|
key="access_token",
|
|
|
|
|
value=token,
|
|
|
|
|
httponly=True,
|
|
|
|
|
samesite="lax",
|
|
|
|
|
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
2026-03-07 07:03:51 +07:00
|
|
|
secure=settings.COOKIE_SECURE,
|
2026-03-06 20:14:50 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
async def register(
|
|
|
|
|
body: RegisterRequest,
|
|
|
|
|
request: Request,
|
|
|
|
|
response: Response,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
2026-03-06 20:14:50 +07:00
|
|
|
# Check uniqueness
|
|
|
|
|
existing = await db.execute(
|
|
|
|
|
select(User).where((User.email == body.email) | (User.username == body.username))
|
|
|
|
|
)
|
|
|
|
|
if existing.scalar_one_or_none():
|
|
|
|
|
raise HTTPException(status_code=400, detail="Email or username already taken.")
|
|
|
|
|
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
country = country_from_request(request)
|
2026-03-06 20:14:50 +07:00
|
|
|
user = User(
|
|
|
|
|
username=body.username,
|
|
|
|
|
email=body.email,
|
|
|
|
|
hashed_password=hash_password(body.password),
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
signup_country=country,
|
|
|
|
|
last_country=country,
|
2026-03-06 20:14:50 +07:00
|
|
|
)
|
|
|
|
|
db.add(user)
|
|
|
|
|
await db.commit()
|
|
|
|
|
await db.refresh(user)
|
|
|
|
|
|
|
|
|
|
token = create_access_token({"sub": user.id})
|
|
|
|
|
_set_auth_cookie(response, token)
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/login", response_model=UserResponse)
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
async def login(
|
|
|
|
|
body: LoginRequest,
|
|
|
|
|
request: Request,
|
|
|
|
|
response: Response,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
2026-03-06 20:14:50 +07:00
|
|
|
result = await db.execute(select(User).where(User.email == body.email))
|
|
|
|
|
user = result.scalar_one_or_none()
|
|
|
|
|
if not user or not user.hashed_password or not verify_password(body.password, user.hashed_password):
|
|
|
|
|
raise HTTPException(status_code=401, detail="Invalid credentials.")
|
|
|
|
|
if not user.is_active:
|
|
|
|
|
raise HTTPException(status_code=403, detail="Account is disabled.")
|
|
|
|
|
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
country = country_from_request(request)
|
|
|
|
|
if country:
|
|
|
|
|
user.last_country = country
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
2026-03-06 20:14:50 +07:00
|
|
|
token = create_access_token({"sub": user.id})
|
|
|
|
|
_set_auth_cookie(response, token)
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/me", response_model=UserResponse)
|
|
|
|
|
async def me(user: User = Depends(get_current_user)):
|
|
|
|
|
if user is None:
|
|
|
|
|
raise HTTPException(status_code=401, detail="Not authenticated.")
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/logout")
|
|
|
|
|
async def logout(response: Response, _user: User = Depends(require_auth)):
|
|
|
|
|
response.delete_cookie("access_token")
|
|
|
|
|
return {"message": "Logged out."}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Google OAuth ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
|
|
|
|
|
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
|
|
|
|
|
GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/google")
|
|
|
|
|
async def google_login():
|
|
|
|
|
if not settings.GOOGLE_CLIENT_ID:
|
|
|
|
|
raise HTTPException(status_code=501, detail="Google OAuth not configured.")
|
|
|
|
|
params = {
|
|
|
|
|
"client_id": settings.GOOGLE_CLIENT_ID,
|
2026-03-07 06:53:54 +07:00
|
|
|
"redirect_uri": settings.GOOGLE_REDIRECT_URI,
|
2026-03-06 20:14:50 +07:00
|
|
|
"response_type": "code",
|
|
|
|
|
"scope": "openid email profile",
|
|
|
|
|
"access_type": "offline",
|
|
|
|
|
}
|
|
|
|
|
from urllib.parse import urlencode
|
|
|
|
|
url = f"{GOOGLE_AUTH_URL}?{urlencode(params)}"
|
|
|
|
|
return RedirectResponse(url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/google/callback")
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
async def google_callback(
|
|
|
|
|
code: str,
|
|
|
|
|
request: Request,
|
|
|
|
|
response: Response,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
2026-03-06 20:14:50 +07:00
|
|
|
if not settings.GOOGLE_CLIENT_ID:
|
|
|
|
|
raise HTTPException(status_code=501, detail="Google OAuth not configured.")
|
|
|
|
|
|
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
|
|
|
token_resp = await client.post(
|
|
|
|
|
GOOGLE_TOKEN_URL,
|
|
|
|
|
data={
|
|
|
|
|
"code": code,
|
|
|
|
|
"client_id": settings.GOOGLE_CLIENT_ID,
|
|
|
|
|
"client_secret": settings.GOOGLE_CLIENT_SECRET,
|
2026-03-07 06:53:54 +07:00
|
|
|
"redirect_uri": settings.GOOGLE_REDIRECT_URI,
|
2026-03-06 20:14:50 +07:00
|
|
|
"grant_type": "authorization_code",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
token_resp.raise_for_status()
|
|
|
|
|
access_token = token_resp.json()["access_token"]
|
|
|
|
|
|
|
|
|
|
userinfo_resp = await client.get(
|
|
|
|
|
GOOGLE_USERINFO_URL,
|
|
|
|
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
|
|
|
)
|
|
|
|
|
userinfo_resp.raise_for_status()
|
|
|
|
|
userinfo = userinfo_resp.json()
|
|
|
|
|
|
|
|
|
|
google_id: str = userinfo["sub"]
|
|
|
|
|
email: str = userinfo.get("email", "")
|
|
|
|
|
avatar_url: str | None = userinfo.get("picture")
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
country = country_from_request(request)
|
2026-03-06 20:14:50 +07:00
|
|
|
|
|
|
|
|
# Upsert user by google_id
|
|
|
|
|
result = await db.execute(select(User).where(User.google_id == google_id))
|
|
|
|
|
user = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
|
# Try to find by email (link accounts)
|
|
|
|
|
result2 = await db.execute(select(User).where(User.email == email))
|
|
|
|
|
user = result2.scalar_one_or_none()
|
|
|
|
|
if user:
|
|
|
|
|
user.google_id = google_id
|
|
|
|
|
if avatar_url and not user.avatar_url:
|
|
|
|
|
user.avatar_url = avatar_url
|
|
|
|
|
else:
|
|
|
|
|
# Generate username from email prefix
|
|
|
|
|
base_username = email.split("@")[0].lower()
|
|
|
|
|
import re
|
|
|
|
|
base_username = re.sub(r"[^a-z0-9_-]", "-", base_username)[:28]
|
|
|
|
|
username = base_username
|
|
|
|
|
counter = 1
|
|
|
|
|
while True:
|
|
|
|
|
existing = await db.execute(select(User).where(User.username == username))
|
|
|
|
|
if not existing.scalar_one_or_none():
|
|
|
|
|
break
|
|
|
|
|
username = f"{base_username}{counter}"
|
|
|
|
|
counter += 1
|
|
|
|
|
|
|
|
|
|
user = User(
|
|
|
|
|
username=username,
|
|
|
|
|
email=email,
|
|
|
|
|
google_id=google_id,
|
|
|
|
|
avatar_url=avatar_url,
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
signup_country=country,
|
|
|
|
|
last_country=country,
|
2026-03-06 20:14:50 +07:00
|
|
|
)
|
|
|
|
|
db.add(user)
|
|
|
|
|
|
feat(metrics): add usage analytics dashboard with country tracking
Track per-user, per-project, and per-board usage to inform pricing tier
decisions. Adds an admin dashboard with KPIs (DAU/WAU/MAU, totals,
success rate), time-series charts for compiles/runs, board family +
FQBN breakdowns, "board diversity" pie chart (key freemium signal),
top users/projects, and per-country breakdown via Cloudflare's
CF-IPCountry header. Admin can now also view private projects.
Backend:
- New UsageEvent table (append-only event log with user_id, project_id,
event_type, board_fqbn/family, country, error_kind, duration_ms)
- Aggregate counters on User (total_compiles/runs/errors, last_active,
signup_country, last_country) and Project (compile/run/update counts,
last_compiled/run timestamps) kept in sync by MetricsService for O(1)
dashboard reads
- 10 admin endpoints under /api/admin/metrics/{overview, timeseries,
boards, board-diversity, top-users, top-projects, countries,
users/{id}, projects/{id}}
- POST /api/metrics/run for client-side run telemetry
- Country detection via cf-ipcountry header (no DB / no API calls)
- Auto-migrations in lifespan for legacy DBs
Frontend:
- recharts-powered Dashboard tab with KPI cards and 4 charts
- New Boards tab with per-family + per-FQBN breakdown
- Country column with flag emoji on Users tab
- Top countries card on Dashboard
- compileCode now forwards project_id; Run button reports via WS
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 05:46:52 +07:00
|
|
|
if country:
|
|
|
|
|
user.last_country = country
|
|
|
|
|
|
2026-03-06 20:14:50 +07:00
|
|
|
await db.commit()
|
|
|
|
|
await db.refresh(user)
|
|
|
|
|
|
|
|
|
|
jwt_token = create_access_token({"sub": user.id}, expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
|
2026-03-07 07:50:35 +07:00
|
|
|
# Send the user straight to the editor after OAuth login
|
|
|
|
|
redirect = RedirectResponse(url=f"{settings.FRONTEND_URL}/editor")
|
2026-03-06 20:14:50 +07:00
|
|
|
_set_auth_cookie(redirect, jwt_token)
|
|
|
|
|
return redirect
|