feat: initial commit — Curio mastery tutor

Full Next.js App Router application with:
- AI-graded lesson checkpoints (BKT/Elo mastery tracking)
- Auth.js v5: credentials, Google, GitHub, generic OIDC
- Anonymous-session-first with migrate-on-signin
- Admin panel: users, blueprints, reports, site settings
- Password reset + email verification (nodemailer/SMTP)
- Site config: require_auth + signups_enabled flags
- server-only guards on all DB/generation/verification modules
- PostgreSQL 16 + pgvector, Redis cache, Drizzle ORM
- 271 unit tests (Vitest), golden-eval harness, Playwright e2e stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 22:25:43 +02:00
commit 4e38b5a791
194 changed files with 30789 additions and 0 deletions
@@ -0,0 +1,62 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { NextRequest } from 'next/server';
vi.mock('@/lib/db/queries', () => ({
getReviewCheckpoints: vi.fn(),
}));
import { GET } from '../route';
import { getReviewCheckpoints } from '@/lib/db/queries';
const mockGet = vi.mocked(getReviewCheckpoints);
const REVIEW_ITEMS = [
{
conceptId: 'c1',
conceptName: 'Closures',
score: 0.65,
checkpointId: 'cp1',
checkpointPrompt: 'Explain what a closure is.',
checkpointKind: 'explain' as const,
},
];
function makeRequest(userId?: string) {
const url = userId
? `http://localhost:3000/api/review?userId=${userId}`
: 'http://localhost:3000/api/review';
return new NextRequest(url);
}
describe('GET /api/review', () => {
beforeEach(() => vi.clearAllMocks());
it('returns 400 when userId is missing', async () => {
const res = await GET(makeRequest());
expect(res.status).toBe(400);
});
it('returns 200 with empty list when nothing due', async () => {
mockGet.mockResolvedValue([]);
const res = await GET(makeRequest('user-1'));
expect(res.status).toBe(200);
const body = await res.json();
expect(body.checkpoints).toHaveLength(0);
});
it('returns 200 with due checkpoints', async () => {
mockGet.mockResolvedValue(REVIEW_ITEMS);
const res = await GET(makeRequest('user-1'));
expect(res.status).toBe(200);
const body = await res.json();
expect(body.checkpoints).toHaveLength(1);
expect(body.checkpoints[0].conceptName).toBe('Closures');
expect(body.checkpoints[0].score).toBe(0.65);
});
it('passes userId to getReviewCheckpoints', async () => {
mockGet.mockResolvedValue([]);
await GET(makeRequest('user-abc'));
expect(mockGet).toHaveBeenCalledWith('user-abc');
});
});