Adds optional `gameName` column to community_posts so game-share posts
can render with the game-banner above the post-content (Snake/Tetris/
Memory/TTT visual indicator).
- prisma/schema.prisma: CommunityPost.gameName String? @map("game_name")
- migration: ALTER TABLE rebreak.community_posts ADD COLUMN game_name
- db/community.ts: createPost() accepts gameName param
- api/community/post.post.ts: extracts gameName from body
- api/community/posts.get.ts: returns gameName, prefers DB over content-parse
Frontend (already in flight on upgrade/sdk-54): PostCard.tsx renders
GameShareBanner when post.category === 'game_share' && post.gameName.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
import { awardPoints } from "../../utils/scoring";
|
||
import { createPost } from "../../db/community";
|
||
|
||
const MODERATION_PROMPT = `Du moderierst Beiträge in einer anonymen Selbsthilfe-Community für Menschen mit Glücksspielsucht.
|
||
Entferne Beiträge die:
|
||
- Casino-Werbung oder Links zu Glücksspielseiten enthalten
|
||
- Tipps zum Umgehen von Blockern geben
|
||
- Andere Nutzer manipulieren oder angreifen
|
||
- Offensichtlichen Spam darstellen
|
||
Erlaube: persönliche Erfahrungen, Hilferufe, Erfolgsgeschichten, Fragen.
|
||
Antworte NUR mit JSON: {"approved": boolean, "reason": string}`;
|
||
|
||
export default defineEventHandler(async (event) => {
|
||
const user = await requireUser(event);
|
||
|
||
const body = await readBody(event);
|
||
const { category, content, imageUrl, gameName } = body as {
|
||
category: string;
|
||
content: string;
|
||
imageUrl?: string;
|
||
gameName?: string | null;
|
||
};
|
||
|
||
if (!content?.trim() || !category) {
|
||
throw createError({
|
||
statusCode: 400,
|
||
message: "category und content erforderlich",
|
||
});
|
||
}
|
||
|
||
const config = useRuntimeConfig();
|
||
|
||
// Moderate via OpenRouter (optional – skip if no API key)
|
||
if (config.openrouterApiKey) {
|
||
try {
|
||
const modResponse = await $fetch<{
|
||
choices: { message: { content: string } }[];
|
||
}>("https://openrouter.ai/api/v1/chat/completions", {
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${config.openrouterApiKey}`,
|
||
"Content-Type": "application/json",
|
||
"HTTP-Referer": "https://rebreak.org",
|
||
"X-Title": "ReBreak Moderation",
|
||
},
|
||
body: {
|
||
model: "meta-llama/llama-3.2-3b-instruct:free",
|
||
max_tokens: 100,
|
||
messages: [
|
||
{ role: "system", content: MODERATION_PROMPT },
|
||
{
|
||
role: "user",
|
||
content: `Kategorie: ${category}\nInhalt: ${content}`,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
|
||
const raw = modResponse.choices?.[0]?.message?.content ?? "";
|
||
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
||
const modResult: { approved: boolean; reason: string } = jsonMatch
|
||
? JSON.parse(jsonMatch[0])
|
||
: { approved: true, reason: "" };
|
||
|
||
if (!modResult.approved) {
|
||
throw createError({
|
||
statusCode: 422,
|
||
message: `Beitrag abgelehnt: ${modResult.reason}`,
|
||
});
|
||
}
|
||
} catch (err: any) {
|
||
if (err.statusCode === 422) throw err;
|
||
}
|
||
}
|
||
|
||
const data = await createPost(user.id, category, content.trim(), imageUrl, gameName ?? null);
|
||
|
||
// Punkte vergeben
|
||
await awardPoints(user.id, "post_created", { post_id: data.id });
|
||
|
||
return data;
|
||
});
|