Moderierte Kommentare zu allem: Gast oder Konto, Antworten, Bewertung 1–5, Sperren, DSGVO-Löschung, sicheres HTML
Installieren
genpm add @core/commentsWas du bekommst
- Quellcode in src/lib/comments/, 9 Dateien. (23 kB)
- KI-Regeln in src/lib/comments/AGENTS.md, dazu Regeldateien für die IDE.
- Umgebungsvariablen in .env.example ergänzt: COMMENTS_NOTIFY_TO.
- Löst @core/antispam, @core/auth, @core/contracts, @core/db, @core/email, @core/jobs für dich auf.
README
Dieses Paket hat keine README.
Genau das liest deine KI, wenn sie in src/lib/comments arbeitet. Sonst wird ihrem Kontext nichts hinzugefügt.
@core/comments — rules for AI agents
Purpose
Comments on anything (entityType + entityId: posts, products, pages): guests (name + email, stored only as a hash)
or signed-in users, one level of replies, optional 1–5 rating (reused by @core/reviews), moderation queue with email
alerts, bans, GDPR erasure and safe HTML rendering. Anti-spam via @core/antispam. Tables: comments, comment_bans.
Map
index.ts— public API:postComment,listComments,moderate,ban,eraseComments,ratingSummary,commentHtml,commentsAdminResource.comments.ts— logic and thecomments.notifyjob.admin.ts— moderation queue.schema.ts— tables.adapters/hono.ts—commentRoutes({ currentUser }).adapters/next.ts—commentRoute({ currentUser })→{ GET, POST }.
Integration
- Env: optional
COMMENTS_NOTIFY_TO(moderators, comma-separated) andEMAIL_FROM. Migrations as insrc/lib/db/AGENTS.md; the @core/jobs cron must run. - Mount the endpoints at
/api/comments/:type/:idwithcurrentUserfrom @core/auth (getUser/getUserFromCookieHeader). - The form posts
body(andname,emailfor guests,parentIdfor replies) plus the @core/antispam fields (honeypotProps,_ts). Show "your comment awaits moderation" when the response status ispending. - Render the list from
GETorlistComments(type, id); render each body withcommentHtml(body)(escaped; the only HTML allowed). - Add
commentsAdminResourcetosrc/genpm/admin.ts(permissionscomments:read,comments:moderate). - Verify: a guest comment is pending until approved in the admin, then it is listed.
Conventions
- Default moderation is
guests: guests and users without an approved comment wait for approval. Usenoneonly on trusted, private sites. - Erase on request with
eraseComments({ email })or({ userId }); delete user comments when the account is deleted. - Show ratings only from
ratingSummary(approved comments).
Don't
- Don't store or show guest emails; don't render comment bodies as Markdown/HTML.
- Don't auto-approve guests to "increase engagement".
- Don't expose
pending,spamordeletedcomments in public endpoints.
# @core/comments — rules for AI agents
## Purpose
Comments on anything (`entityType` + `entityId`: posts, products, pages): guests (name + email, stored only as a hash)
or signed-in users, one level of replies, optional 1–5 rating (reused by @core/reviews), moderation queue with email
alerts, bans, GDPR erasure and safe HTML rendering. Anti-spam via @core/antispam. Tables: `comments`, `comment_bans`.
## Map
- `index.ts` — public API: `postComment`, `listComments`, `moderate`, `ban`, `eraseComments`, `ratingSummary`, `commentHtml`, `commentsAdminResource`.
- `comments.ts` — logic and the `comments.notify` job. `admin.ts` — moderation queue. `schema.ts` — tables.
- `adapters/hono.ts` — `commentRoutes({ currentUser })`. `adapters/next.ts` — `commentRoute({ currentUser })` → `{ GET, POST }`.
## Integration
1. Env: optional `COMMENTS_NOTIFY_TO` (moderators, comma-separated) and `EMAIL_FROM`. Migrations as in `src/lib/db/AGENTS.md`; the @core/jobs cron must run.
2. Mount the endpoints at `/api/comments/:type/:id` with `currentUser` from @core/auth (`getUser`/`getUserFromCookieHeader`).
3. The form posts `body` (and `name`, `email` for guests, `parentId` for replies) plus the @core/antispam fields (`honeypotProps`, `_ts`).
Show "your comment awaits moderation" when the response status is `pending`.
4. Render the list from `GET` or `listComments(type, id)`; render each body with `commentHtml(body)` (escaped; the only HTML allowed).
5. Add `commentsAdminResource` to `src/genpm/admin.ts` (permissions `comments:read`, `comments:moderate`).
6. Verify: a guest comment is pending until approved in the admin, then it is listed.
## Conventions
- Default moderation is `guests`: guests and users without an approved comment wait for approval. Use `none` only on trusted, private sites.
- Erase on request with `eraseComments({ email })` or `({ userId })`; delete user comments when the account is deleted.
- Show ratings only from `ratingSummary` (approved comments).
## Don't
- Don't store or show guest emails; don't render comment bodies as Markdown/HTML.
- Don't auto-approve guests to "increase engagement".
- Don't expose `pending`, `spam` or `deleted` comments in public endpoints.
Der genaue Baum, der nach .genpmignore eingebunden wird. Gepinnt an
// Handlers comunes: GET lista aprobada, POST nuevo comentario (form-data o JSON).
import { CommentError, listComments, type Moderation, postComment } from '../index.ts';
export type CurrentUser = (req: Request) => Promise<{ id: string; name: string | null } | null>;
const STATUS = { spam: 400, rate_limited: 429, invalid: 422, banned: 403, not_found: 404, forbidden: 403 } as const;
/** Envío HTML clásico (sin JavaScript) desde una página del mismo sitio: URL a la que volver, o null. */
function backUrl(req: Request, isJson: boolean): URL | null {
if (isJson || !(req.headers.get('accept') ?? '').includes('text/html')) return null;
const ref = req.headers.get('referer');
if (!ref) return null;
try {
const u = new URL(ref);
return u.host === new URL(req.url).host ? u : null;
} catch {
return null;
}
}
export async function handleList(req: Request, entityType: string, entityId: string): Promise<Response> {
const page = Number(new URL(req.url).searchParams.get('page') ?? 1) || 1;
return Response.json(await listComments(entityType, entityId, { page }), { headers: { 'cache-control': 'public, max-age=30' } });
}
export async function handlePost(req: Request, entityType: string, entityId: string, opts: { currentUser: CurrentUser; moderation?: Moderation }): Promise<Response> {
const isJson = (req.headers.get('content-type') ?? '').includes('application/json');
const fields: Record<string, unknown> = {};
if (isJson) Object.assign(fields, await req.json().catch(() => ({})));
else (await req.formData()).forEach((v, k) => typeof v === 'string' && (fields[k] = v));
const user = await opts.currentUser(req);
try {
const c = await postComment(
{
entityType,
entityId,
body: String(fields.body ?? ''),
...(fields.parentId ? { parentId: String(fields.parentId) } : {}),
...(fields.rating ? { rating: Number(fields.rating) } : {}),
user,
...(!user && { guest: { name: String(fields.name ?? ''), email: String(fields.email ?? '') } }),
},
{ headers: req.headers, fields, moderation: opts.moderation },
);
const back = backUrl(req, isJson);
if (back) return seeOther(back, c.status);
return Response.json({ id: c.id, status: c.status }, { status: 201 });
} catch (e) {
if (e instanceof CommentError) {
const back = backUrl(req, isJson);
return back ? seeOther(back, e.code) : Response.json({ error: e.code }, { status: STATUS[e.code] });
}
throw e;
}
}
/** Vuelve a la página con `?comment=<estado|error>#comments` (la plantilla muestra el aviso). */
function seeOther(back: URL, result: string): Response {
back.searchParams.set('comment', result);
back.hash = 'comments';
return new Response(null, { status: 303, headers: { location: back.toString() } });
}
Dieses Paket deklariert keine MCP-Server.
| Version | Commit | Veröffentlicht | Prüfung |
|---|---|---|---|
| 1.0.1 | abfbbe8 | vor 3 Stunden | Prüfung bestanden |
- genpm
- @core/antispam ^1.0.0@core/auth ^1.1.0@core/contracts ^1.0.0@core/db ^1.0.0@core/email ^1.0.1@core/jobs ^1.0.0
- npm
- zod ^4.0.0
- vorgeschlagen
- GenPM schlägt den npm-Befehl vor und führt ihn nur aus, wenn du zustimmst.
- Verwendet von (2)
- @core/kit-blog ^1.0.0@core/reviews ^1.0.0
- Prüfung
- Prüfung bestanden · 0 Befunde
- Commit
- v1.0.1 → abfbbe8ddb3706a9640b94d9dea43a45e70da1a9 · nach dem Abruf verifiziert
- Skripte
- Keine. GenPM führt niemals Paketcode aus.
- Lizenz
- MIT
- Qualität
- 100/100
- Anerkannte Lizenzerfüllt
- AGENTS.md erklärt den Zweckerfüllt
- AGENTS.md enthält Integrationsschritteerfüllt
- AGENTS.md nennt Konventionen oder Verboteerfüllt
- Enthält Testserfüllt
- Sicherheitsscan bestandenerfüllt
- In den letzten 6 Monaten veröffentlichterfüllt
- Verifizierter Herausgebererfüllt
- Zusammenfassung und Schlagwörtererfüllt
- Meldung
- Stimmt etwas nicht?