chore(docker): .dockerignore angepasst; lokale Build-Schritte in Rebuild-Skripten; Doku/README zu production vs production-prebuilt aktualisiert

This commit is contained in:
2025-10-06 18:59:17 +02:00
parent 7a84130aec
commit 1124b1f40b
24 changed files with 1149 additions and 270 deletions

View File

@@ -1,7 +1,8 @@
import { call, os } from "@orpc/server";
import { z } from "zod";
import { createKV } from "../lib/create-kv.js";
import { assertOwner, sessionsKV } from "../lib/auth.js";
import { assertOwner, getSessionFromCookies } from "../lib/auth.js";
import { checkAdminRateLimit, getClientIP } from "../lib/rate-limiter.js";
// Schema Definition
const ReviewSchema = z.object({
id: z.string(),
@@ -75,20 +76,29 @@ const submitReview = os
});
// Admin Endpoint: approveReview
const approveReview = os
.input(z.object({ sessionId: z.string(), id: z.string() }))
.handler(async ({ input }) => {
.input(z.object({ id: z.string() }))
.handler(async ({ input, context }) => {
try {
await assertOwner(input.sessionId);
await assertOwner(context);
// Admin Rate Limiting
const ip = getClientIP(context.req.raw.headers);
const session = await getSessionFromCookies(context);
if (session) {
const result = checkAdminRateLimit({ ip, userId: session.userId });
if (!result.allowed) {
throw new Error(`Zu viele Admin-Anfragen. Bitte versuche es in ${result.retryAfterSeconds} Sekunden erneut.`);
}
}
const review = await reviewsKV.getItem(input.id);
if (!review) {
throw new Error("Bewertung nicht gefunden");
}
const session = await sessionsKV.getItem(input.sessionId).catch(() => undefined);
const session2 = await getSessionFromCookies(context);
const updatedReview = {
...review,
status: "approved",
reviewedAt: new Date().toISOString(),
reviewedBy: session?.userId || review.reviewedBy,
reviewedBy: session2?.userId || review.reviewedBy,
};
await reviewsKV.setItem(input.id, updatedReview);
return updatedReview;
@@ -100,20 +110,29 @@ const approveReview = os
});
// Admin Endpoint: rejectReview
const rejectReview = os
.input(z.object({ sessionId: z.string(), id: z.string() }))
.handler(async ({ input }) => {
.input(z.object({ id: z.string() }))
.handler(async ({ input, context }) => {
try {
await assertOwner(input.sessionId);
await assertOwner(context);
// Admin Rate Limiting
const ip = getClientIP(context.req.raw.headers);
const session = await getSessionFromCookies(context);
if (session) {
const result = checkAdminRateLimit({ ip, userId: session.userId });
if (!result.allowed) {
throw new Error(`Zu viele Admin-Anfragen. Bitte versuche es in ${result.retryAfterSeconds} Sekunden erneut.`);
}
}
const review = await reviewsKV.getItem(input.id);
if (!review) {
throw new Error("Bewertung nicht gefunden");
}
const session = await sessionsKV.getItem(input.sessionId).catch(() => undefined);
const session2 = await getSessionFromCookies(context);
const updatedReview = {
...review,
status: "rejected",
reviewedAt: new Date().toISOString(),
reviewedBy: session?.userId || review.reviewedBy,
reviewedBy: session2?.userId || review.reviewedBy,
};
await reviewsKV.setItem(input.id, updatedReview);
return updatedReview;
@@ -125,10 +144,19 @@ const rejectReview = os
});
// Admin Endpoint: deleteReview
const deleteReview = os
.input(z.object({ sessionId: z.string(), id: z.string() }))
.handler(async ({ input }) => {
.input(z.object({ id: z.string() }))
.handler(async ({ input, context }) => {
try {
await assertOwner(input.sessionId);
await assertOwner(context);
// Admin Rate Limiting
const ip = getClientIP(context.req.raw.headers);
const session = await getSessionFromCookies(context);
if (session) {
const result = checkAdminRateLimit({ ip, userId: session.userId });
if (!result.allowed) {
throw new Error(`Zu viele Admin-Anfragen. Bitte versuche es in ${result.retryAfterSeconds} Sekunden erneut.`);
}
}
await reviewsKV.removeItem(input.id);
}
catch (err) {
@@ -160,12 +188,11 @@ const listPublishedReviews = os.handler(async () => {
// Admin Endpoint: adminListReviews
const adminListReviews = os
.input(z.object({
sessionId: z.string(),
statusFilter: z.enum(["all", "pending", "approved", "rejected"]).optional().default("all"),
}))
.handler(async ({ input }) => {
.handler(async ({ input, context }) => {
try {
await assertOwner(input.sessionId);
await assertOwner(context);
const allReviews = await reviewsKV.getAllItems();
const filtered = input.statusFilter === "all"
? allReviews
@@ -188,11 +215,10 @@ const live = {
}),
adminListReviews: os
.input(z.object({
sessionId: z.string(),
statusFilter: z.enum(["all", "pending", "approved", "rejected"]).optional().default("all"),
}))
.handler(async function* ({ input, signal }) {
await assertOwner(input.sessionId);
.handler(async function* ({ input, context, signal }) {
await assertOwner(context);
const allReviews = await reviewsKV.getAllItems();
const filtered = input.statusFilter === "all"
? allReviews