mirror of
https://github.com/tanweai/pua.git
synced 2026-09-01 15:39:55 +08:00
fe1977fbcb
Task 2: Landing page logo
- Replace 💢 emoji favicon with pua-skill-logo.svg
- Logo SVG copied to dist/pua-logo.svg
Task 3b: /api/feedback Cloudflare Function
- POST: save rating, task_summary, pua_level, pua_count, flavor, session_data
- GET: aggregate stats (total feedback, breakdown by rating)
- D1 migration: 0002_create_feedback.sql (feedback table + indexes)
Eval补全:
- evals/test-helpers.sh — reusable assertions (run_pua, assert_skill_triggered,
assert_contains, assert_not_contains, count_matches)
- evals/test-behavior.sh — 4 behavior verification tests
(红线 knowledge, 阿里味旁白, [PUA生效] markers, pressure escalation)
Deploy note: /tmp/pua-deploy-final/ is ready for wrangler pages deploy.
D1 migration needs: wrangler d1 execute pua-uploads-db --file=migrations/0002_create_feedback.sql
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
interface Env {
|
|
DB: D1Database
|
|
UPLOADS: R2Bucket
|
|
}
|
|
|
|
function getSession(request: Request) {
|
|
const cookie = request.headers.get("Cookie") || ""
|
|
const match = cookie.match(/pua_session=([^;]+)/)
|
|
if (!match) return null
|
|
try {
|
|
return JSON.parse(atob(match[1])) as {
|
|
id: string
|
|
login: string
|
|
avatar: string
|
|
token: string
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
|
|
const session = getSession(request)
|
|
if (!session) {
|
|
return Response.json({ error: "Unauthorized" }, { status: 401 })
|
|
}
|
|
|
|
const formData = await request.formData()
|
|
const file = formData.get("file") as File | null
|
|
const wechatId = formData.get("wechat_id") as string | null
|
|
|
|
if (!file) {
|
|
return Response.json({ error: "No file provided" }, { status: 400 })
|
|
}
|
|
if (!wechatId?.trim()) {
|
|
return Response.json({ error: "WeChat ID is required" }, { status: 400 })
|
|
}
|
|
if (!file.name.endsWith(".jsonl")) {
|
|
return Response.json({ error: "Only .jsonl files are accepted" }, { status: 400 })
|
|
}
|
|
if (file.size > 50 * 1024 * 1024) {
|
|
return Response.json({ error: "File too large (max 50MB)" }, { status: 400 })
|
|
}
|
|
|
|
// Upload to R2
|
|
const key = `${session.login}/${Date.now()}-${file.name}`
|
|
await env.UPLOADS.put(key, file.stream(), {
|
|
httpMetadata: { contentType: "application/jsonl" },
|
|
customMetadata: {
|
|
github_id: session.id,
|
|
github_login: session.login,
|
|
wechat_id: wechatId.trim(),
|
|
},
|
|
})
|
|
|
|
// Record in D1
|
|
await env.DB.prepare(
|
|
"INSERT INTO uploads (github_id, github_login, wechat_id, file_key, file_name, file_size) VALUES (?, ?, ?, ?, ?, ?)"
|
|
).bind(session.id, session.login, wechatId.trim(), key, file.name, file.size).run()
|
|
|
|
// Send email notification (fire-and-forget)
|
|
const sizeMB = (file.size / 1024 / 1024).toFixed(2)
|
|
const emailBody = [
|
|
`New PUA Skill data upload:`,
|
|
``,
|
|
`GitHub: ${session.login} (${session.id})`,
|
|
`WeChat: ${wechatId.trim()}`,
|
|
`File: ${file.name} (${sizeMB} MB)`,
|
|
`R2 Key: ${key}`,
|
|
`Time: ${new Date().toISOString()}`,
|
|
].join("\n")
|
|
|
|
fetch("https://api.mailchannels.net/tx/v1/send", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
personalizations: [{ to: [{ email: "xsser.w@gmail.com", name: "PUA Admin" }] }],
|
|
from: { email: "noreply@pua-skill.pages.dev", name: "PUA Skill Upload" },
|
|
subject: `[PUA Upload] ${session.login} uploaded ${file.name}`,
|
|
content: [{ type: "text/plain", value: emailBody }],
|
|
}),
|
|
}).catch(() => {})
|
|
|
|
return Response.json({ ok: true, key, file_name: file.name, file_size: file.size })
|
|
}
|
|
|
|
// GET: list user's uploads
|
|
export const onRequestGet: PagesFunction<Env> = async ({ request, env }) => {
|
|
const session = getSession(request)
|
|
if (!session) {
|
|
return Response.json({ error: "Unauthorized" }, { status: 401 })
|
|
}
|
|
|
|
const { results } = await env.DB.prepare(
|
|
"SELECT file_name, file_size, created_at FROM uploads WHERE github_id = ? ORDER BY created_at DESC LIMIT 50"
|
|
).bind(session.id).all()
|
|
|
|
return Response.json({ uploads: results })
|
|
}
|