import { NextResponse } from "next/server"; import { mergeSettings, getSettings } from "@/lib/store"; import { mapAssignmentToCanvas } from "@/lib/canvas/map"; import { normalizeBaseUrl, quizPayload, questionPayloads } from "@/lib/canvas/api"; export const dynamic = "force-dynamic"; // Browser→Canvas is blocked by CORS, so all Canvas REST calls go through this server route. // Token + base URL come from the saved local settings (data/db.json), like the AI keys. function creds(settings) { const c = settings?.canvas || {}; const baseUrl = normalizeBaseUrl(c.baseUrl); const token = String(c.token || "").trim(); if (!baseUrl) throw new Error("Add your Canvas web address in Settings first."); if (!token) throw new Error("Add your Canvas access token in Settings first."); return { baseUrl, token }; } async function canvas(baseUrl, token, path, init = {}) { let res; try { res = await fetch(baseUrl + path, { ...init, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...(init.headers || {}) }, }); } catch { throw new Error("Couldn't reach Canvas — check the web address and your network."); } const text = await res.text(); let data; try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; } if (!res.ok) { if (res.status === 401) throw new Error("Canvas rejected the access token (401). Check or regenerate it."); const msg = data?.errors?.[0]?.message || data?.message || (Array.isArray(data?.errors) ? JSON.stringify(data.errors) : "") || `Canvas returned ${res.status}.`; throw new Error(typeof msg === "string" ? msg : `Canvas error ${res.status}.`); } return data; } export async function POST(request) { try { const body = await request.json(); // Accept settings from the client (test-before-save) or fall back to what's saved. const settings = mergeSettings(body.settings || getSettings()); const { baseUrl, token } = creds(settings); const action = body.action; if (action === "test") { const me = await canvas(baseUrl, token, "/api/v1/users/self"); return NextResponse.json({ ok: true, message: `Connected as ${me.name || me.short_name || "Canvas user"}` }); } if (action === "courses") { const list = await canvas(baseUrl, token, "/api/v1/courses?enrollment_type=teacher&enrollment_state=active&per_page=100"); const courses = (Array.isArray(list) ? list : []) .filter((c) => c?.id && c?.name && !c.access_restricted_by_date) .map((c) => ({ id: c.id, name: c.name })); return NextResponse.json({ courses }); } if (action === "push") { const { assignment, opts, courseId } = body; if (!courseId) throw new Error("Pick a course first."); const ir = mapAssignmentToCanvas(assignment, opts || {}); if (ir.gradedCount === 0) throw new Error("This assignment has no Canvas-compatible questions."); const quiz = await canvas(baseUrl, token, `/api/v1/courses/${courseId}/quizzes`, { method: "POST", body: JSON.stringify(quizPayload(ir)), }); const quizId = quiz.id; let added = 0; for (const q of questionPayloads(ir)) { await canvas(baseUrl, token, `/api/v1/courses/${courseId}/quizzes/${quizId}/questions`, { method: "POST", body: JSON.stringify(q), }); added++; } const url = quiz.html_url || `${baseUrl}/courses/${courseId}/quizzes/${quizId}`; return NextResponse.json({ ok: true, url, quizId, added }); } throw new Error("Unknown action."); } catch (e) { return NextResponse.json({ error: String(e.message || e) }, { status: 400 }); } }