Внутренний метод /api/internal/access-check: есть ли у почты доступ к курсу

Для бота выдачи SB Access (payment-router): старый покупатель Second Brain
Vault оставляет почту на /tg/link, ключ входа в бота выдаётся, только если
доступ к курсу действительно есть. Тот же общий секрет, что у grant;
ответ только да/нет, почта без учёта регистра, истёкший доступ не считается.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-13 15:03:24 +05:00
co-authored by Claude Opus 5.5
parent 0a5368577c
commit 83f34ea679
3 changed files with 94 additions and 0 deletions
@@ -0,0 +1,30 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { accessWhere, isAuthorized, parseAccessCheck } from "@/lib/access-check";
/**
* Internal: есть ли у почты действующий доступ к курсу.
* Auth: тот же общий секрет, что у /api/internal/grant (`x-internal-secret`).
*
* Body: { email, course_slug } → { ok: true, has_access: boolean }
*/
export async function POST(request: NextRequest) {
if (!isAuthorized(request.headers.get("x-internal-secret"), process.env.INTERNAL_GRANT_SECRET)) {
return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
}
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json({ ok: false, error: "bad_json" }, { status: 400 });
}
const input = parseAccessCheck(body);
if (!input) {
return NextResponse.json({ ok: false, error: "missing_fields", need: ["email", "course_slug"] }, { status: 400 });
}
const found = await prisma.courseEnrollment.findFirst({
where: accessWhere(input, new Date()),
select: { userId: true },
});
return NextResponse.json({ ok: true, has_access: Boolean(found) });
}