fix: gate onboarding on ledger snapshot

This commit is contained in:
-Puter
2026-06-24 19:34:36 +05:30
parent a442f1f53a
commit eaba7f95e3
+65 -2
View File
@@ -1,8 +1,8 @@
import { Hono } from "hono";
import { requireUser, type AuthContext } from "../auth/clerk.js";
import { db } from "../db/client.js";
import { users, userStacks, type UserStack } from "../db/schema.js";
import { eq } from "drizzle-orm";
import { growEvents, users, userStacks, type UserStack } from "../db/schema.js";
import { and, desc, eq, inArray } from "drizzle-orm";
import { provisionUserStack } from "../docker/manager.js";
import { log } from "../log.js";
import { config } from "../config.js";
@@ -62,6 +62,36 @@ async function ensureUserServiceUser(req: Request) {
return res.json() as Promise<Record<string, unknown>>;
}
function completedAtFromUserServiceProfile(userProfile: Record<string, unknown>) {
const preferences = userProfile.preferences;
if (!preferences || typeof preferences !== "object" || Array.isArray(preferences)) return null;
const onboarding = (preferences as Record<string, unknown>).onboarding;
if (!onboarding || typeof onboarding !== "object" || Array.isArray(onboarding)) return null;
const completedAt = (onboarding as Record<string, unknown>).completed_at;
return typeof completedAt === "string" && completedAt.trim() ? completedAt : null;
}
async function getOnboardingEventStatus(userId: string) {
const [row] = await db
.select({
id: growEvents.id,
type: growEvents.type,
occurredAt: growEvents.occurredAt,
processingStatus: growEvents.processingStatus,
})
.from(growEvents)
.where(
and(
eq(growEvents.userId, userId),
inArray(growEvents.type, ["onboarding.snapshot.saved", "onboarding.completed"]),
),
)
.orderBy(desc(growEvents.occurredAt))
.limit(1);
return row ?? null;
}
export function userRoutes() {
const app = new Hono<AuthContext>();
app.use("*", requireUser);
@@ -103,6 +133,39 @@ export function userRoutes() {
});
});
app.get("/onboarding-status", async (c) => {
const userId = c.get("userId");
let userServiceUser: Record<string, unknown>;
try {
userServiceUser = await ensureUserServiceUser(c.req.raw);
} catch (err) {
log.error({ err, userId }, "failed to load user-service user for onboarding status");
return c.json(
{ error: "user_service_onboarding_status_failed", message: "Failed to load onboarding status" },
502,
);
}
const event = await getOnboardingEventStatus(userId);
const profileCompletedAt = completedAtFromUserServiceProfile(userServiceUser);
return c.json({
userId,
profileCompleted: Boolean(profileCompletedAt),
profileCompletedAt,
hasOnboardingEvent: Boolean(event),
onboardingEvent: event
? {
id: event.id,
type: event.type,
occurredAt: event.occurredAt.toISOString(),
processingStatus: event.processingStatus,
}
: null,
needsOnboarding: !profileCompletedAt || !event,
});
});
app.get("/me", async (c) => proxyUserService(c.req.raw, "/me"));
app.patch("/me", async (c) => {
const res = await fetchUserService(c.req.raw, "/me");