* track core product analytics flows Track auth, search, export, audit, and credit-consumption events with canonical route IDs so PostHog funnels and usage dashboards stay low-noise and privacy-safe. * fix: keep auth actions usable after session loss * refactor: simplify analytics and auth helpers - Replace isRecord/getActiveOrganizationId type guards with simple cast - Refactor getAnalyticsRouteContext from if/return chain to route tables - Replace toVerificationIssueType switch with zod enum - Merge duplicate credits_consume events into single event per API call - Merge two PostHogBootstrap useEffects into one * refactor: add projectId to middleware context to reduce boilerplate The requireProjectContext middleware now includes projectId directly, eliminating repeated manual construction of BillingCustomerContext objects across all server function handlers. * remove unused BILLING_* env var fallbacks from cost profile script * remove before_send event enrichment to preserve native PostHog URL tracking The before_send hook was stripping $pathname, $current_url, $referrer and other URL properties, which breaks PostHog web analytics dashboards, paths analysis, session replay, and attribution. The route_id/route_group injection it provided is unnecessary since PostHog already captures $pathname natively. * remove route mapping layer, pass raw redirect paths to analytics events The route ID registry (STATIC_ROUTES, PROJECT_ROUTES, getAnalyticsRouteContext, getRedirectRouteId) duplicated what PostHog already captures via $pathname. Replace redirect_route_id with redirect_to containing the raw path, and remove ~80 lines of route mapping infrastructure. * clean up analytics events: drop redundant submit events and derived properties - Remove search_submit events for keywords, domain overview, and backlinks (the search_complete events capture the meaningful outcome data) - Remove target_type from backlinks events (derived 1:1 from search_scope) - Remove result_limit from keyword research (requested limit, not useful alongside actual result_count) - Remove export_format from data:export events (always "csv") * refactor: inline wrappers, colocate helpers, deduplicate getActiveOrganizationId - Inline toVerificationIssueType into verify-email.tsx (single-use wrapper) - Move mapDataforseoPathToCreditFeature into dataforseoClient.ts (only consumer) - Extract shared getActiveOrganizationId into lib/auth-session.ts (was duplicated in __root.tsx and middleware/ensure-user/hosted.ts) - Rename shared/analytics.ts → shared/internal-user.ts (only email helpers remain after removing route mapping, verification, and dataforseo helpers) * remove internal user tracking and email domain properties Drop is_internal_user super property, email_domain person property, and all supporting code (shared/internal-user.ts, getEmailDomain, isInternalUserEmail). Simplifies initPostHog and identifyAnalyticsUser signatures. * remove backlinks:search_complete effect-based tracking The reactive useEffect + useRef dedup pattern added ~30 lines of plumbing inside a data hook for a single analytics event. Not worth the complexity. * simplify: replace manual type guards with zod, deduplicate posthog and sign-out helpers - Replace hand-rolled typeof checks in getActiveOrganizationId and isAuthenticatedServerFunctionContext with zod safeParse - Extract withPostHogClient helper to deduplicate client posthog wrapper - Move apiKey guard into getServerPostHogClient factory - Extract signOutAndRedirect to avoid duplicated sign-out logic - Drop derivable has_results from analytics events - Remove unnecessary path normalization in mapDataforseoPathToCreditFeature * fix: strip email from pageview URLs, restore sign-out guard, harden server posthog, fix path mapper - Sanitize $current_url on pageviews to remove email query param (PII) - Restore onSuccess for sign-out redirect to avoid bounce-back on failure - Swallow shutdown() errors so PostHog outages can't fail billed work - Rewrite mapDataforseoPathToCreditFeature to match real API path structure (path[1] = module, path[3] = endpoint) instead of scanning all segments * simplify: remove redundant refs in verify-email, infer middleware context type - Remove unnecessary useRef guards in verify-email effects (deps already prevent re-firing) - Use z.ZodType<EnsuredUserContext> annotation to infer return type instead of casting - Add comment explaining one-shot PostHog client on Workers * fix: reset PostHog identity on sign-out before redirect * fix: require POSTHOG_HOST env var instead of defaulting to us.i.posthog.com * fix: annotate url as unknown to satisfy no-unsafe-assignment * format
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { createMiddleware } from "@tanstack/react-start";
|
|
import { z } from "zod";
|
|
import { AppError } from "@/server/lib/errors";
|
|
import { errorHandlingMiddleware } from "@/middleware/errorHandling";
|
|
import type { EnsuredUserContext } from "@/middleware/ensure-user/types";
|
|
import { ensureUserMiddleware } from "@/middleware/ensureUser";
|
|
import { requireManagedServiceAccess } from "@/server/billing/subscription";
|
|
|
|
const ensuredUserContextSchema: z.ZodType<EnsuredUserContext> = z.object({
|
|
userId: z.string(),
|
|
userEmail: z.string(),
|
|
organizationId: z.string(),
|
|
project: z.any().optional(),
|
|
});
|
|
|
|
function getAuthenticatedContext(context: unknown): EnsuredUserContext {
|
|
const result = ensuredUserContextSchema.safeParse(context);
|
|
if (!result.success) {
|
|
throw new AppError(
|
|
"INTERNAL_ERROR",
|
|
"Authenticated server function context missing",
|
|
);
|
|
}
|
|
return result.data;
|
|
}
|
|
|
|
export const globalServerFunctionMiddleware = [
|
|
errorHandlingMiddleware,
|
|
ensureUserMiddleware,
|
|
] as const;
|
|
|
|
export const requireAuthenticatedContext = [
|
|
createMiddleware({ type: "function" }).server(async ({ next, context }) => {
|
|
const authenticatedContext = getAuthenticatedContext(context);
|
|
await requireManagedServiceAccess(authenticatedContext);
|
|
|
|
return next({
|
|
context: authenticatedContext,
|
|
});
|
|
}),
|
|
] as const;
|
|
|
|
export const requireProjectContext = [
|
|
createMiddleware({ type: "function" }).server(async ({ next, context }) => {
|
|
const authenticatedContext = getAuthenticatedContext(context);
|
|
|
|
await requireManagedServiceAccess(authenticatedContext);
|
|
|
|
if (!authenticatedContext.project) {
|
|
throw new AppError(
|
|
"INTERNAL_ERROR",
|
|
"Project context missing from authenticated server function",
|
|
);
|
|
}
|
|
|
|
return next({
|
|
context: {
|
|
...authenticatedContext,
|
|
project: authenticatedContext.project,
|
|
projectId: authenticatedContext.project.id,
|
|
},
|
|
});
|
|
}),
|
|
] as const;
|