fix: gate onboarding on ledger snapshot
This commit is contained in:
+65
-2
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user