Function bodies 269 total
login function · typescript · L6-L17 (12 LOC)e2e/helpers.ts
export async function login(page: Page) {
await page.goto("/auth/login");
await page.fill('input[id="email"]', "[email protected]");
await page.fill('input[id="password"]', "password123");
// Use Promise.all to avoid race between click and navigation
await Promise.all([
page.waitForURL("**/dashboard", { timeout: 30_000 }),
page.click('button[type="submit"]'),
]);
await expect(page.getByText("Welcome back")).toBeVisible({ timeout: 15_000 });
}loginAndSelectMaya function · typescript · L22-L47 (26 LOC)e2e/helpers.ts
export async function loginAndSelectMaya(page: Page) {
await login(page);
// Wait for children data to fully load (async streak/badge fetches cause card instability)
await page.waitForTimeout(1000);
// Click Maya's card — sets activeChild in React context + localStorage
const mayaCard = page.locator("div.cursor-pointer", { hasText: "Maya" }).first();
await mayaCard.click({ force: true });
// Wait for student dashboard (child view lives at /home; first load may need compilation)
await page.waitForURL("**/home", { timeout: 30_000 });
await page.waitForFunction(
() => {
const text = document.body.innerText;
return (
text.includes("Up Next") ||
text.includes("Continue lesson") ||
text.includes("This Week") ||
text.includes("Lessons done") ||
text.includes("My Writing")
);
},
{ timeout: 20_000 }
);
}navigateToLesson function · typescript · L52-L76 (25 LOC)e2e/helpers.ts
export async function navigateToLesson(page: Page, lessonId: string) {
const lessonLink = page.locator(`a[href="/lesson/${lessonId}"]`);
if (await lessonLink.first().isVisible({ timeout: 3_000 }).catch(() => false)) {
await lessonLink.first().click();
} else {
// Fall back to primary CTA
const primaryCta = page.locator('a[href^="/lesson/"]').first();
await primaryCta.click();
}
// Wait for lesson page URL first, then lesson-specific content
await page.waitForURL("**/lesson/**", { timeout: 30_000 });
await page.waitForFunction(
() => {
const text = document.body.innerText;
return (
/Step \d of 3/.test(text) ||
text.includes("Start writing") ||
text.includes("Time to write")
);
},
{ timeout: 60_000 }
);
}navigateToReport function · typescript · L81-L87 (7 LOC)e2e/helpers.ts
export async function navigateToReport(page: Page, childId: string) {
await page.goto(`/dashboard/children/${childId}/report`);
await page.waitForFunction(
() => document.body.innerText.includes("Progress Report"),
{ timeout: 15_000 }
);
}waitForAIResponse function · typescript · L92-L105 (14 LOC)e2e/helpers.ts
export async function waitForAIResponse(page: Page) {
const typing = page.locator('[data-testid="typing-indicator"]');
try {
await expect(typing).toBeVisible({ timeout: 15_000 });
} catch {
// Typing indicator may have already disappeared
await page.waitForTimeout(1000);
return;
}
await expect(typing).not.toBeVisible({ timeout: 90_000 });
await page.waitForTimeout(500);
}interactWithStep function · typescript · L111-L174 (64 LOC)e2e/helpers.ts
export async function interactWithStep(page: Page) {
await page.waitForTimeout(500);
const quickAnswer = page.getByPlaceholder("Type your answer...");
const continueBtn = page.getByRole("button", { name: "Continue" });
if (await quickAnswer.isVisible().catch(() => false)) {
// Text input is visible — type an answer and submit
const answers = [
"First, the cat climbed the tall tree. Next, it saw a beautiful bird!",
"I think the answer is the first one because it makes the most sense.",
"The character felt happy because they solved the problem by being brave.",
"First, I would introduce the character. Then, I would describe the setting.",
"I think it helps the reader understand what is happening in the story.",
];
const randomAnswer = answers[Math.floor(Math.random() * answers.length)];
await quickAnswer.fill(randomAnswer);
const doneBtn = page.getByRole("button", { name: /done/i });
if (await doneBtn.isVisible().catch(getCurrentStep function · typescript · L179-L187 (9 LOC)e2e/helpers.ts
export async function getCurrentStep(page: Page): Promise<number> {
for (let step = 3; step >= 1; step--) {
const stepText = page.getByText(`Step ${step} of 3`);
if (await stepText.isVisible().catch(() => false)) {
return step;
}
}
return 1;
}All rows scored by the Repobility analyzer (https://repobility.com)
main function · typescript · L9-L74 (66 LOC)prisma/migrate-assessments.ts
async function main() {
const assessments = await prisma.assessment.findMany();
console.log(`Found ${assessments.length} assessments to migrate`);
let migrated = 0;
let skipped = 0;
for (const assessment of assessments) {
// Check if already migrated (matching sessionId + submissionText)
const existing = await prisma.writingSubmission.findFirst({
where: {
sessionId: assessment.sessionId,
submissionText: assessment.submissionText,
},
});
if (existing) {
skipped++;
continue;
}
// Parse feedback JSON
let strength = "";
let growthArea = "";
let encouragement = "";
try {
const feedback = JSON.parse(assessment.feedback);
strength = feedback.strength || "";
growthArea = feedback.growth || feedback.growthArea || "";
encouragement = feedback.encouragement || "";
} catch {
console.warn(` Could not parse feedback for assessment ${assessment.id}`);
}
// CalculatseedBase function · typescript · L15-L60 (46 LOC)prisma/seed-e2e.ts
async function seedBase() {
console.log("── Base seed ──");
const parent = await prisma.user.upsert({
where: { email: "[email protected]" },
update: {},
create: {
id: "parent-001",
email: "[email protected]",
passwordHash: await bcryptjs.hash("password123", 12),
name: "Demo Parent",
role: "PARENT",
},
});
console.log(` Parent: ${parent.name} (${parent.id})`);
const maya = await prisma.childProfile.upsert({
where: { id: "child-maya-001" },
update: {},
create: {
id: "child-maya-001",
parentId: parent.id,
name: "Maya",
age: 8,
tier: 1,
avatarEmoji: "\u{1F989}",
},
});
console.log(` Child: ${maya.name} (${maya.id})`);
const ethan = await prisma.childProfile.upsert({
where: { id: "child-ethan-001" },
update: {},
create: {
id: "child-ethan-001",
parentId: parent.id,
name: "Ethan",
age: 11,
tier: 2,
avatarEmoji: "\u{1F98A}",
seedLessonProgress function · typescript · L62-L108 (47 LOC)prisma/seed-e2e.ts
async function seedLessonProgress(childId: string) {
console.log("── Lesson progress ──");
// N1.1.1 — completed, high score
await prisma.lessonProgress.upsert({
where: { childId_lessonId: { childId, lessonId: "N1.1.1" } },
update: { status: "completed", currentPhase: "feedback" },
create: {
childId,
lessonId: "N1.1.1",
status: "completed",
currentPhase: "feedback",
startedAt: new Date("2026-02-01T10:00:00Z"),
completedAt: new Date("2026-02-01T10:30:00Z"),
},
});
console.log(" N1.1.1: completed (high score)");
// N1.1.2 — needs_improvement, low score
await prisma.lessonProgress.upsert({
where: { childId_lessonId: { childId, lessonId: "N1.1.2" } },
update: { status: "needs_improvement", currentPhase: "feedback" },
create: {
childId,
lessonId: "N1.1.2",
status: "needs_improvement",
currentPhase: "feedback",
startedAt: new Date("2026-02-03T10:00:00Z"),
completedAt: new DateseedAssessmentData function · typescript · L110-L285 (176 LOC)prisma/seed-e2e.ts
async function seedAssessmentData(childId: string) {
console.log("── Assessment data ──");
// Clean up existing assessment-related data for idempotency
await prisma.aIFeedback.deleteMany({
where: { submission: { childId } },
});
await prisma.writingSubmission.deleteMany({ where: { childId } });
await prisma.assessment.deleteMany({ where: { childId } });
await prisma.session.deleteMany({ where: { childId } });
// ── N1.1.1: Completed, HIGH score (3.2) ──
const session1 = await prisma.session.create({
data: {
id: "e2e-session-n111",
childId,
lessonId: "N1.1.1",
phase: "feedback",
phaseState: JSON.stringify({
instructionCompleted: true,
comprehensionCheckPassed: true,
guidedAttempts: 3,
hintsGiven: 1,
guidedComplete: true,
}),
conversationHistory: JSON.stringify([
{ role: "assistant", content: "Welcome! Today we're learning about story beginnings." },
{ role: "useseedSkillProgress function · typescript · L287-L317 (31 LOC)prisma/seed-e2e.ts
async function seedSkillProgress(childId: string) {
console.log("── Skill progress ──");
const skillData = [
{ skillCategory: "narrative", skillName: "story_structure", score: 3.0, level: "PROFICIENT" as const },
{ skillCategory: "narrative", skillName: "setting_description", score: 2.5, level: "DEVELOPING" as const },
{ skillCategory: "narrative", skillName: "voice_style", score: 3.2, level: "PROFICIENT" as const },
{ skillCategory: "narrative", skillName: "character_development", score: 2.0, level: "DEVELOPING" as const },
{ skillCategory: "narrative", skillName: "plot_pacing", score: 1.5, level: "EMERGING" as const },
];
for (const skill of skillData) {
await prisma.skillProgress.upsert({
where: {
childId_skillCategory_skillName: {
childId,
skillCategory: skill.skillCategory,
skillName: skill.skillName,
},
},
update: { score: skill.score, level: skill.level, totalAttempts: 2 },
crseedStreak function · typescript · L319-L342 (24 LOC)prisma/seed-e2e.ts
async function seedStreak(childId: string) {
console.log("── Streak ──");
await prisma.streak.upsert({
where: { childId },
update: {
currentStreak: 2,
longestStreak: 5,
weeklyGoal: 3,
weeklyCompleted: 2,
lastActiveDate: new Date("2026-02-13T10:00:00Z"),
},
create: {
childId,
currentStreak: 2,
longestStreak: 5,
weeklyGoal: 3,
weeklyCompleted: 2,
lastActiveDate: new Date("2026-02-13T10:00:00Z"),
weekStartDate: new Date("2026-02-10T00:00:00Z"),
},
});
console.log(" Streak: 2 days current, 5 longest, 2/3 weekly");
}seedAchievement function · typescript · L344-L358 (15 LOC)prisma/seed-e2e.ts
async function seedAchievement(childId: string) {
console.log("── Achievement ──");
await prisma.achievement.upsert({
where: { childId_badgeId: { childId, badgeId: "first_lesson" } },
update: {},
create: {
childId,
badgeId: "first_lesson",
unlockedAt: new Date("2026-02-01T10:30:00Z"),
seen: true,
},
});
console.log(" Badge: first_lesson");
}seedPlacementAndCurriculum function · typescript · L360-L428 (69 LOC)prisma/seed-e2e.ts
async function seedPlacementAndCurriculum(childId: string) {
console.log("── Placement & Curriculum ──");
// Clean up existing
await prisma.curriculumWeek.deleteMany({
where: { curriculum: { childId } },
});
await prisma.curriculumRevision.deleteMany({
where: { curriculum: { childId } },
});
await prisma.curriculum.deleteMany({ where: { childId } });
await prisma.placementResult.deleteMany({ where: { childId } });
await prisma.placementResult.create({
data: {
childId,
prompts: JSON.stringify([
"Write a short story about an animal who goes on an adventure.",
"Describe your favorite place using all five senses.",
"Convince your teacher to let the class have a pet.",
]),
responses: JSON.stringify([
"Once upon a time there was a bunny named Flop...",
"My favorite place is grandma's kitchen...",
"I think our class should get a hamster...",
]),
aiAnalysis: JSON.stringify({
Same scanner, your repo: https://repobility.com — Repobility
main function · typescript · L430-L442 (13 LOC)prisma/seed-e2e.ts
async function main() {
console.log("=== E2E Seed Start ===\n");
const { maya } = await seedBase();
await seedLessonProgress(maya.id);
await seedAssessmentData(maya.id);
await seedSkillProgress(maya.id);
await seedStreak(maya.id);
await seedAchievement(maya.id);
await seedPlacementAndCurriculum(maya.id);
console.log("\n=== E2E Seed Complete ===");
}loadFile function · typescript · L45-L47 (3 LOC)scripts/run-evals.ts
function loadFile(rel: string): string {
return fs.readFileSync(path.join(CONTENT_DIR, rel), "utf-8");
}extractSection function · typescript · L55-L64 (10 LOC)scripts/run-evals.ts
function extractSection(content: string, startMarker: string): string {
const startIndex = content.indexOf(startMarker);
if (startIndex === -1) return "";
const afterMarker = content.slice(startIndex + startMarker.length);
const nextHeadingMatch = afterMarker.match(/\n## /);
const endIndex = nextHeadingMatch
? startIndex + startMarker.length + (nextHeadingMatch.index ?? 0)
: content.length;
return content.slice(startIndex, endIndex).trim();
}buildEvalPrompt function · typescript · L117-L171 (55 LOC)scripts/run-evals.ts
function buildEvalPrompt(evalCase: EvalCase): string {
const ctx = evalCase.context;
const parts: string[] = [];
// 1. Core skill instructions
parts.push(skillContent.core);
// 2. Tier adaptation
parts.push(TIER_INSERTS[ctx.student_tier] ?? "");
// 3. Phase behavior
parts.push(PHASE_PROMPTS[ctx.current_phase] ?? "");
// 4. Session context — match the real buildPrompt() format from prompt-builder.ts
let sessionContext = `## Current Session Context\n\n`;
// Student line (matches buildPrompt)
const ageStr = ctx.student_age
? ` (age ${ctx.student_age}, Tier ${ctx.student_tier})`
: ` (Tier ${ctx.student_tier})`;
sessionContext += `Student: ${ctx.student_name}${ageStr}\n`;
sessionContext += `Lesson: ${ctx.lesson_id}\n`;
sessionContext += `Current Phase: ${ctx.current_phase.toUpperCase()}\n`;
// Learning objectives (real buildPrompt always includes these)
sessionContext += `\nLEARNING OBJECTIVES:\n1. Learn and apply the writing techniques for twithRetry function · typescript · L176-L190 (15 LOC)scripts/run-evals.ts
async function withRetry<T>(fn: () => Promise<T>, retries = 3, baseDelay = 5000): Promise<T> {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await fn();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
const isRateLimit = msg.includes("429") || msg.toLowerCase().includes("rate") || msg.includes("RESOURCE_EXHAUSTED");
if (!isRateLimit || attempt === retries) throw err;
const delay = baseDelay * Math.pow(2, attempt);
console.log(` Rate limited, retrying in ${(delay / 1000).toFixed(0)}s...`);
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error("Unreachable");
}runEval function · typescript · L213-L302 (90 LOC)scripts/run-evals.ts
async function runEval(evalCase: EvalCase): Promise<MarkerResult> {
const systemPrompt = buildEvalPrompt(evalCase);
const messages: LLMMessage[] = [];
// For step-specific evals (step > 1), inject synthetic prior turns
// so the LLM sees that earlier steps actually happened (mimics real
// multi-turn conversation history from buildPrompt)
const ctx = evalCase.context;
const currentStep = ctx.current_step ?? (ctx.phase_state?.phase1Step as number | undefined) ?? undefined;
if (ctx.current_phase === "instruction" && currentStep && currentStep > 1) {
for (let s = 1; s < currentStep; s++) {
messages.push({
role: "assistant",
content: `[STEP: ${s}]\n(Coach taught step ${s} content for this lesson)`,
});
messages.push({
role: "user",
content: "(Student responded to step " + s + ")",
});
}
}
// If there's a prior_context, simulate it as an earlier exchange
if (ctx.prior_context) {
messages.push({
judgeResponse function · typescript · L313-L383 (71 LOC)scripts/run-evals.ts
async function judgeResponse(
evalCase: EvalCase,
response: string
): Promise<JudgeResult[]> {
const judgeSystemPrompt = `You are an eval judge for an AI writing coach for children. Given the coach's response to a student, evaluate whether each expectation is met.
For each expectation, respond with a JSON array. Each element must have:
- "index": the 1-based expectation number
- "pass": true or false
- "reason": a brief (1 sentence) explanation
Respond with ONLY valid JSON array, no markdown fences, no other text.`;
const judgeUserContent = `CONTEXT:
- Student: ${evalCase.context.student_name} (Tier ${evalCase.context.student_tier})
- Phase: ${evalCase.context.current_phase}
- Lesson: ${evalCase.context.lesson_id}
${evalCase.context.prior_context ? `- Prior context: ${evalCase.context.prior_context}` : ""}
${evalCase.context.current_step ? `- Current step: ${evalCase.context.current_step}` : ""}
STUDENT SAID: "${evalCase.prompt}"
COACH RESPONSE:
"""
${response}
"""
WORD CwordCount function · typescript · L388-L390 (3 LOC)scripts/run-evals.ts
function wordCount(text: string): number {
return text.trim().split(/\s+/).length;
}Repobility · MCP-ready · https://repobility.com
runWithConcurrency function · typescript · L392-L412 (21 LOC)scripts/run-evals.ts
async function runWithConcurrency<T>(
tasks: (() => Promise<T>)[],
concurrency: number
): Promise<T[]> {
const results: T[] = new Array(tasks.length);
let nextIndex = 0;
async function worker() {
while (nextIndex < tasks.length) {
const index = nextIndex++;
results[index] = await tasks[index]();
// Delay between evals to respect rate limits
if (nextIndex < tasks.length && concurrency <= 2) {
await new Promise((r) => setTimeout(r, DELAY_BETWEEN_EVALS_MS));
}
}
}
await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker()));
return results;
}worker function · typescript · L399-L408 (10 LOC)scripts/run-evals.ts
async function worker() {
while (nextIndex < tasks.length) {
const index = nextIndex++;
results[index] = await tasks[index]();
// Delay between evals to respect rate limits
if (nextIndex < tasks.length && concurrency <= 2) {
await new Promise((r) => setTimeout(r, DELAY_BETWEEN_EVALS_MS));
}
}
}main function · typescript · L442-L757 (316 LOC)scripts/run-evals.ts
async function main() {
const { provider, model } = getLLMConfig();
const judgeModel = process.env.LLM_JUDGE_MODEL || model;
if (provider === "anthropic" && !process.env.ANTHROPIC_API_KEY) {
console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
process.exit(1);
}
if (provider === "google" && !process.env.GOOGLE_AI_API_KEY) {
console.error("Error: GOOGLE_AI_API_KEY environment variable is not set.");
process.exit(1);
}
if (provider === "groq" && !process.env.GROQ_API_KEY) {
console.error("Error: GROQ_API_KEY environment variable is not set.");
process.exit(1);
}
// Parse args
const args = process.argv.slice(2);
const filterIdx = args.indexOf("--id");
const filterId = filterIdx !== -1 ? args[filterIdx + 1] : null;
const concurrencyIdx = args.indexOf("--concurrency");
const concurrency = concurrencyIdx !== -1 ? parseInt(args[concurrencyIdx + 1], 10) : DEFAULT_CONCURRENCY;
let evals = evalsData.evals;
if (filtPOST function · typescript · L5-L72 (68 LOC)src/app/api/auth/signup/route.ts
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { name, email, password } = body;
// Validate required fields
if (!name || typeof name !== "string" || name.trim().length === 0) {
return NextResponse.json(
{ error: "Name is required" },
{ status: 400 }
);
}
if (!email || typeof email !== "string") {
return NextResponse.json(
{ error: "Valid email is required" },
{ status: 400 }
);
}
// Basic email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return NextResponse.json(
{ error: "Invalid email format" },
{ status: 400 }
);
}
if (!password || typeof password !== "string" || password.length < 8) {
return NextResponse.json(
{ error: "Password must be at least 8 characters" },
{ status: 400 }
);
}
// Check email uniqueneGET function · typescript · L6-L60 (55 LOC)src/app/api/children/[id]/badges/route.ts
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const achievements = await prisma.achievement.findMany({
where: { childId },
orderBy: { unlockedAt: "desc" },
});
const badges = achievements
.map((a) => {
const def = getBadgeById(a.badgeId);
if (!def) return null;
return {
id: def.id,
name: def.name,
emoji: def.emoji,
description: def.description,
category: def.category,
unlockedAt: a.unlockedAPOST function · typescript · L5-L51 (47 LOC)src/app/api/children/[id]/badges/seen/route.ts
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const body = await request.json();
const { badgeIds } = body;
if (!Array.isArray(badgeIds) || badgeIds.length === 0) {
return NextResponse.json(
{ error: "badgeIds must be a non-empty array" },
{ status: 400 }
);
}
const result = await prisma.achievement.updateMany({
where: {
childId,
badgeId: { in: badgeIds },
seen: false,
},
data: { seen: true },
});
return GET function · typescript · L6-L66 (61 LOC)src/app/api/children/[id]/portfolio/export/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const submissions = await prisma.writingSubmission.findMany({
where: { childId },
include: { feedback: true },
orderBy: { createdAt: "asc" },
});
const rows = submissions.map((s) => {
const lesson = getLessonById(s.lessonId);
return [
new Date(s.createdAt).toLocaleDateString(),
lesson?.title ?? "Unknown Lesson",
lesson?.type ?? "unknown",
s.wordCount,
s.feedback?.overallScore ??GET function · typescript · L13-L119 (107 LOC)src/app/api/children/[id]/portfolio/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const { searchParams } = request.nextUrl;
const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10));
const limit = Math.min(50, Math.max(1, parseInt(searchParams.get("limit") || "10", 10)));
const type = searchParams.get("type");
const sort = searchParams.get("sort") || "newest";
const includeRevisions = searchParams.get("includeRevisions") === "true";
// Build where clause
const where: Record<string, unknown> = {Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
formatSubmission function · typescript · L121-L157 (37 LOC)src/app/api/children/[id]/portfolio/route.ts
function formatSubmission(s: {
id: string;
lessonId: string;
submissionText: string;
wordCount: number;
revisionNumber: number;
createdAt: Date;
feedback: {
scores: string;
overallScore: number;
strength: string;
growthArea: string;
encouragement: string;
} | null;
}) {
const lesson = getLessonById(s.lessonId);
return {
id: s.id,
lessonId: s.lessonId,
lessonTitle: lesson?.title ?? "Unknown Lesson",
lessonType: lesson?.type ?? "unknown",
lessonUnit: lesson?.unit ?? "",
submissionText: s.submissionText,
wordCount: s.wordCount,
revisionNumber: s.revisionNumber,
createdAt: s.createdAt.toISOString(),
feedback: s.feedback
? {
scores: JSON.parse(s.feedback.scores) as Record<string, number>,
overallScore: s.feedback.overallScore,
strength: s.feedback.strength,
growthArea: s.feedback.growthArea,
encouragement: s.feedback.encouragement,
}
: null,
GET function · typescript · L7-L201 (195 LOC)src/app/api/children/[id]/progress/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
// Verify child exists and belongs to this parent
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json(
{ error: "Child not found or access denied" },
{ status: 403 }
);
}
// Get all lesson progress for this child
const progressRecords = await prisma.lessonProgress.findMany({
where: { childId },
orderBy: { startedAt: "desc" },
});
// Get recent assessments
const assessments = await prisma.assessment.findMany({
where: { childId },
orderBy: { createdAt: "desc" },
take: 10,
});
// CategoescapeCsvValue function · typescript · L6-L13 (8 LOC)src/app/api/children/[id]/report/export/route.ts
function escapeCsvValue(value: string | number | null | undefined): string {
const str = String(value ?? "");
// If the value contains commas, quotes, or newlines, wrap in quotes and escape internal quotes
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}GET function · typescript · L15-L91 (77 LOC)src/app/api/children/[id]/report/export/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
// Verify child exists and belongs to this parent
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json(
{ error: "Child not found or access denied" },
{ status: 403 }
);
}
// Query all writing submissions with their AI feedback
const submissions = await prisma.writingSubmission.findMany({
where: { childId },
include: { feedback: true },
orderBy: { createdAt: "desc" },
});
// Build CSV header
const headers = [
"Date",
"Lesson",
"Type",
"Score",
"Word Count",
"StrGET function · typescript · L8-L151 (144 LOC)src/app/api/children/[id]/report/[lessonId]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string; lessonId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId, lessonId } = await params;
// Verify child belongs to this parent
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json(
{ error: "Child not found or access denied" },
{ status: 403 }
);
}
// Get lesson metadata from catalog
const lesson = getLessonById(lessonId);
if (!lesson) {
return NextResponse.json(
{ error: "Lesson not found" },
{ status: 404 }
);
}
// Get lesson progress
const progress = await prisma.lessonProgress.findUnique({
where: {
childId_lessonId: { childIGET function · typescript · L9-L309 (301 LOC)src/app/api/children/[id]/report/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const url = new URL(request.url);
const generateSummary = url.searchParams.get("generateSummary") === "true";
// Verify child exists and belongs to this parent
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json(
{ error: "Child not found or access denied" },
{ status: 403 }
);
}
// --- Summary aggregations ---
const lessonProgressRecords = await prisma.lessonProgress.findMany({
where: { childId },
});
const totalLessons = lessonProgressRecords.length;
const completedLessons = lessonProgressRecords.fcomputeTier function · typescript · L5-L9 (5 LOC)src/app/api/children/[id]/route.ts
function computeTier(age: number): number {
if (age <= 9) return 1;
if (age <= 12) return 2;
return 3;
}GET function · typescript · L11-L39 (29 LOC)src/app/api/children/[id]/route.ts
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const child = await prisma.childProfile.findUnique({
where: { id },
});
if (!child || child.parentId !== session.user.userId) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
return NextResponse.json({ child });
} catch (error) {
console.error("GET /api/children/[id] error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}All rows scored by the Repobility analyzer (https://repobility.com)
PATCH function · typescript · L41-L112 (72 LOC)src/app/api/children/[id]/route.ts
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const existing = await prisma.childProfile.findUnique({
where: { id },
});
if (!existing || existing.parentId !== session.user.userId) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const body = await request.json();
const { name, age, gradeLevel, interests, avatarEmoji } = body;
const updateData: Record<string, unknown> = {};
if (name !== undefined) {
if (typeof name !== "string" || name.trim().length === 0) {
return NextResponse.json(
{ error: "Name cannot be empty" },
{ status: 400 }
);
}
updateData.name = name.trim();
}
if (age !== undefined) {
DELETE function · typescript · L114-L146 (33 LOC)src/app/api/children/[id]/route.ts
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id } = await params;
const existing = await prisma.childProfile.findUnique({
where: { id },
});
if (!existing || existing.parentId !== session.user.userId) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
await prisma.childProfile.delete({
where: { id },
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("DELETE /api/children/[id] error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}GET function · typescript · L6-L89 (84 LOC)src/app/api/children/[id]/skills/route.ts
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const skillRecords = await prisma.skillProgress.findMany({
where: { childId },
orderBy: { skillCategory: "asc" },
});
// Group by category with display names from SKILL_DEFINITIONS
const categoryMap: Record<
string,
{
name: string;
displayName: string;
avgScore: number;
skills: {
name: string;
displayName: string;
score: number;
level: string;
POST function · typescript · L5-L61 (57 LOC)src/app/api/children/[id]/streak/goal/route.ts
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const body = await request.json();
const { weeklyGoal } = body;
if (
typeof weeklyGoal !== "number" ||
!Number.isInteger(weeklyGoal) ||
weeklyGoal < 1 ||
weeklyGoal > 7
) {
return NextResponse.json(
{ error: "weeklyGoal must be an integer between 1 and 7" },
{ status: 400 }
);
}
const streak = await prisma.streak.upsert({
where: { childId },
update: { weeklyGoal },
GET function · typescript · L5-L50 (46 LOC)src/app/api/children/[id]/streak/route.ts
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: childId } = await params;
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const streak = await prisma.streak.findUnique({ where: { childId } });
if (!streak) {
return NextResponse.json({
currentStreak: 0,
longestStreak: 0,
lastActiveDate: null,
weeklyGoal: 3,
weeklyCompleted: 0,
});
}
return NextResponse.json({
currentStreak: streak.currentStreak,
longestStreak: streak.longestStreak,
lastActiveDate: streak.lastActiveDate,
weeklyGoal: streacomputeTier function · typescript · L5-L9 (5 LOC)src/app/api/children/route.ts
function computeTier(age: number): number {
if (age <= 9) return 1;
if (age <= 12) return 2;
return 3;
}GET function · typescript · L11-L31 (21 LOC)src/app/api/children/route.ts
export async function GET() {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const children = await prisma.childProfile.findMany({
where: { parentId: session.user.userId },
orderBy: { createdAt: "asc" },
});
return NextResponse.json({ children });
} catch (error) {
console.error("GET /api/children error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}POST function · typescript · L33-L79 (47 LOC)src/app/api/children/route.ts
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { name, age, gradeLevel, interests, avatarEmoji } = body;
if (!name || typeof name !== "string" || name.trim().length === 0) {
return NextResponse.json(
{ error: "Name is required" },
{ status: 400 }
);
}
if (!age || typeof age !== "number" || age < 7 || age > 15) {
return NextResponse.json(
{ error: "Age must be between 7 and 15" },
{ status: 400 }
);
}
const tier = computeTier(age);
const child = await prisma.childProfile.create({
data: {
parentId: session.user.userId,
name: name.trim(),
age,
tier,
gradeLevel: gradeLevel || null,
interests: interests || null,
avatarEmoji: avatarEmoji ||Same scanner, your repo: https://repobility.com — Repobility
POST function · typescript · L9-L248 (240 LOC)src/app/api/curriculum/[childId]/revise/route.ts
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ childId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { childId } = await params;
// Verify ownership
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const body = await request.json();
const { reason, description } = body;
if (!reason || typeof reason !== "string") {
return NextResponse.json(
{ error: "reason is required" },
{ status: 400 }
);
}
if (!description || typeof description !== "string") {
return NextResponse.json(
{ error: "description is required" },
{ status: 400 }
);
}
// Get currentGET function · typescript · L6-L82 (77 LOC)src/app/api/curriculum/[childId]/route.ts
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ childId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { childId } = await params;
// Verify ownership
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const curriculum = await prisma.curriculum.findUnique({
where: { childId },
include: { weeks: { orderBy: { weekNumber: "asc" } } },
});
if (!curriculum) {
return NextResponse.json(
{ error: "No curriculum found" },
{ status: 404 }
);
}
// Get completed lesson IDs for this child
const completedProgress = await prisma.lessonProgress.findMany({
where: { childId, statPATCH function · typescript · L84-L172 (89 LOC)src/app/api/curriculum/[childId]/route.ts
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ childId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { childId } = await params;
// Verify ownership
const child = await prisma.childProfile.findFirst({
where: { id: childId, parentId: session.user.userId },
});
if (!child) {
return NextResponse.json({ error: "Child not found" }, { status: 404 });
}
const curriculum = await prisma.curriculum.findUnique({
where: { childId },
});
if (!curriculum) {
return NextResponse.json(
{ error: "No curriculum found" },
{ status: 404 }
);
}
const body = await request.json();
const updateData: Record<string, unknown> = {};
if (body.lessonsPerWeek !== undefined) {
if (
typeof body.lessonsPerWeek !== "number" ||
page 1 / 6next ›