Skip to content

Commit 63348cf

Browse files
authored
Add auditor read-only governance endpoints (#14)
1 parent 48f06e0 commit 63348cf

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

app/api/audit/flags/route.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { listModerationFlagsBySeverity } from "@/lib/general-chat/store";
4+
import type { ModerationFlagDetail } from "@/lib/general-chat/store";
5+
import type { ModerationSeverity } from "@/lib/general-chat/models";
6+
7+
const VALID_SEVERITIES: ModerationSeverity[] = ["low", "medium", "high"];
8+
9+
export async function GET(req: Request) {
10+
const { searchParams } = new URL(req.url);
11+
const severity = (searchParams.get("severity") ?? "high") as ModerationSeverity;
12+
13+
if (!VALID_SEVERITIES.includes(severity)) {
14+
return NextResponse.json(
15+
{ error: "Invalid severity. Use low, medium, or high." },
16+
{ status: 400 }
17+
);
18+
}
19+
20+
try {
21+
const flags: ModerationFlagDetail[] = await listModerationFlagsBySeverity(
22+
severity
23+
);
24+
return NextResponse.json({ flags });
25+
} catch (error) {
26+
console.error("Failed to load audit moderation flags", error);
27+
return NextResponse.json(
28+
{ error: "Unable to load moderation flags." },
29+
{ status: 500 }
30+
);
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { augmentThread } from "@/lib/general-chat/store";
4+
5+
interface RouteParams {
6+
params: {
7+
threadId: string;
8+
};
9+
}
10+
11+
export async function GET(_req: Request, { params }: RouteParams) {
12+
const threadId = params.threadId;
13+
if (!threadId) {
14+
return NextResponse.json(
15+
{ error: "threadId is required." },
16+
{ status: 400 }
17+
);
18+
}
19+
20+
try {
21+
const thread = await augmentThread(threadId);
22+
return NextResponse.json({ thread });
23+
} catch (error) {
24+
if (error instanceof Error && error.message === "Thread not found") {
25+
return NextResponse.json({ error: "Thread not found." }, { status: 404 });
26+
}
27+
console.error("Failed to load audit thread", error);
28+
return NextResponse.json(
29+
{ error: "Unable to load audit thread." },
30+
{ status: 500 }
31+
);
32+
}
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { listVerificationsByResult } from "@/lib/general-chat/store";
4+
import type { VerificationDetail } from "@/lib/general-chat/store";
5+
import type { VerificationResult } from "@/lib/general-chat/models";
6+
7+
const STATUS_MAP: Record<string, VerificationResult> = {
8+
confirmed: "confirmed",
9+
partial: "partially_correct",
10+
incorrect: "incorrect",
11+
};
12+
13+
export async function GET(req: Request) {
14+
const { searchParams } = new URL(req.url);
15+
const statusParam = (searchParams.get("status") ?? "partial").toLowerCase();
16+
const verificationResult = STATUS_MAP[statusParam];
17+
18+
if (!verificationResult) {
19+
return NextResponse.json(
20+
{ error: "Invalid status. Use confirmed, partial, or incorrect." },
21+
{ status: 400 }
22+
);
23+
}
24+
25+
try {
26+
const verifications: VerificationDetail[] =
27+
await listVerificationsByResult(verificationResult);
28+
return NextResponse.json({ verifications });
29+
} catch (error) {
30+
console.error("Failed to load audit verifications", error);
31+
return NextResponse.json(
32+
{ error: "Unable to load verifications." },
33+
{ status: 500 }
34+
);
35+
}
36+
}

lib/general-chat/store.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
ChatMessageMetadata,
88
ChatThread,
99
ModerationFlag,
10+
ModerationSeverity,
1011
OfficialRule,
1112
User,
1213
VerifiedStatus,
14+
VerificationResult,
1315
} from "./models";
1416
import {
1517
createAcsChatThread,
@@ -391,6 +393,55 @@ export async function listModerationFlags(
391393
return resources.map(stripCosmosFields);
392394
}
393395

396+
export type ModerationFlagDetail = ModerationFlag & { message?: ChatMessage };
397+
export type VerificationDetail = AIVerification & { message?: ChatMessage };
398+
399+
export async function listModerationFlagsBySeverity(
400+
severity: ModerationSeverity
401+
): Promise<ModerationFlagDetail[]> {
402+
const container = await moderationContainerPromise;
403+
const { resources } = await container.items
404+
.query<WithId<ModerationFlag>>({
405+
query:
406+
"SELECT * FROM c WHERE c.severity = @severity ORDER BY c.createdAt DESC",
407+
parameters: [{ name: "@severity", value: severity }],
408+
})
409+
.fetchAll();
410+
411+
const flags = resources.map(stripCosmosFields);
412+
const messages = await Promise.all(
413+
flags.map((flag) => findMessage(flag.messageId))
414+
);
415+
416+
return flags.map((flag, index) => ({
417+
...flag,
418+
message: messages[index],
419+
}));
420+
}
421+
422+
export async function listVerificationsByResult(
423+
verificationResult: VerificationResult
424+
): Promise<VerificationDetail[]> {
425+
const container = await verificationsContainerPromise;
426+
const { resources } = await container.items
427+
.query<WithId<AIVerification>>({
428+
query:
429+
"SELECT * FROM c WHERE c.verificationResult = @verificationResult ORDER BY c.createdAt DESC",
430+
parameters: [{ name: "@verificationResult", value: verificationResult }],
431+
})
432+
.fetchAll();
433+
434+
const verifications = resources.map(stripCosmosFields);
435+
const messages = await Promise.all(
436+
verifications.map((verification) => findMessage(verification.messageId))
437+
);
438+
439+
return verifications.map((verification, index) => ({
440+
...verification,
441+
message: messages[index],
442+
}));
443+
}
444+
394445
export function getOfficialRules(schoolId: string, language: string) {
395446
return store.rules.filter(
396447
(rule) => rule.schoolId === schoolId && rule.language === language

0 commit comments

Comments
 (0)