mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Phase 6: sync jobs, inquiry and order forms
- Sync scheduler in the content service with a per-job cache (sync_cache), sync_runs reporting and Healthchecks pings. Jobs: Archidekt, BoardGameGeek, cover art (Open Library, TMDB, AniList), Suwayomi and Kavita reading progress, Bambuddy filament and Homebox material stock. A note's own value always wins over a synced one. - PersonalReader: updates only Library notes with a matching anilistId, changes to Greg's notes are held for review; Reading series with no note show as suggestions on the admin Overview. - /api/inquiries and /api/orders: Turnstile, rate limits, a saved copy in the content service (intake token) plus email over SMTP. Orders are re-checked against the live notes: accepted only when the category's ordersOpen is true and the line has capacity. - Admin Inbox for saved submissions. Options' inStock is optional so synced stock can fill it. All settings from process.env; .env.example per service. - Sources could not be reached from the build environment; parsers are tested against fixtures labelled as not live. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* POST /api/inquiries: the Inquiries form (multipart, works without script).
|
||||
* Checks the post, the rate limit and Turnstile, saves a copy through the
|
||||
* content service, emails Greg, and redirects (303) to /inquiries/sent with
|
||||
* the gk-inquiry cookie. A post that is turned down goes back to /inquiries
|
||||
* with the reason and the visitor's answers.
|
||||
*/
|
||||
import type { APIRoute } from "astro";
|
||||
import {
|
||||
clientIp,
|
||||
encodeDraft,
|
||||
formValues,
|
||||
inquiryEmail,
|
||||
inquiryFields,
|
||||
inquirySent,
|
||||
isBot,
|
||||
saveAndSend,
|
||||
TURNSTILE_FIELD,
|
||||
validateInquiry,
|
||||
type ErrorCode,
|
||||
type FieldErrors,
|
||||
} from "../../lib/forms.ts";
|
||||
import {
|
||||
contactTo,
|
||||
flashCookie,
|
||||
inquiryLimiter,
|
||||
maxAttachmentBytes,
|
||||
reportDelivery,
|
||||
saveSubmission,
|
||||
sendMail,
|
||||
verifyTurnstile,
|
||||
} from "../../lib/forms-server.ts";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const POST: APIRoute = async ({ request, cookies, redirect, url, clientAddress }) => {
|
||||
let form: FormData;
|
||||
try {
|
||||
form = await request.formData();
|
||||
} catch {
|
||||
return redirect("/inquiries?error=invalid", 303);
|
||||
}
|
||||
const type = String(form.get("type") ?? "");
|
||||
const back = (code: ErrorCode, errors: FieldErrors = {}) => {
|
||||
cookies.set("gk-inquiry-draft", encodeDraft({ code, errors, values: formValues(form) }), flashCookie(url));
|
||||
const q = new URLSearchParams({ error: code });
|
||||
if (/^[a-z]{1,20}$/.test(type)) q.set("type", type);
|
||||
return redirect(`/inquiries?${q}`, 303);
|
||||
};
|
||||
|
||||
// A bot filled the hidden field: answer as if it worked, keep nothing.
|
||||
if (isBot(form)) return redirect("/inquiries/sent", 303);
|
||||
|
||||
const checked = validateInquiry(form, { maxBytes: maxAttachmentBytes() });
|
||||
if (!checked.ok) return back(checked.code, checked.errors);
|
||||
const q = checked.value;
|
||||
|
||||
let ip = "unknown";
|
||||
try {
|
||||
ip = clientIp(request.headers, clientAddress);
|
||||
} catch {
|
||||
ip = clientIp(request.headers);
|
||||
}
|
||||
if (inquiryLimiter.hit(ip)) return back("rate");
|
||||
if (!(await verifyTurnstile(String(form.get(TURNSTILE_FIELD) ?? ""), ip))) return back("bot");
|
||||
|
||||
const result = await saveAndSend({
|
||||
save: () => saveSubmission({ kind: "inquiry", type: q.type, name: q.name, email: q.email, fields: inquiryFields(q), attachment: q.attachment }),
|
||||
email: (savedId) => sendMail({ to: contactTo(), replyTo: { name: q.name, address: q.email }, ...inquiryEmail(q, savedId), attachment: q.attachment }),
|
||||
report: reportDelivery,
|
||||
});
|
||||
if (!result.ok) return back("failed");
|
||||
|
||||
cookies.set("gk-inquiry", JSON.stringify(inquirySent(q)), flashCookie(url));
|
||||
return redirect("/inquiries/sent", 303);
|
||||
};
|
||||
Reference in New Issue
Block a user