worldwide todo list: public todo list of 68 real, sourced problems (planet to street) at /projects/worldwide-todo. worldtodo schema (categories, tasks, change_requests), idempotent seed on deploy, public add/edit/delete requests with Turnstile + salted daily-rotating IP-hash rate limit, owner-only review queue (approve, edit & approve, reject) with live updates over /ws/worldtodo(-owner), JSON API, detail pages with OSM map and history; listed in projects, header, /start and sitemap
Some checks failed
Deploy production / deploy (push) Failing after 9s
Some checks failed
Deploy production / deploy (push) Failing after 9s
This commit is contained in:
12
src/routes/projects/worldwide-todo/api/queue/+server.ts
Normal file
12
src/routes/projects/worldwide-todo/api/queue/+server.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { requireOwner } from '#lib/server/owner.js';
|
||||
import { queue } from '#lib/server/worldtodo/store.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Owner: full requests with the current task (for diffs), stale flags and similar titles.
|
||||
export const GET: RequestHandler = async (event) => {
|
||||
requireOwner(event);
|
||||
const raw = event.url.searchParams.get('status') ?? 'pending';
|
||||
const status = (['pending', 'approved', 'rejected'] as const).find((s) => s === raw) ?? 'pending';
|
||||
return json(await queue(status), { headers: { 'cache-control': 'private, no-store' } });
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { requireOwner } from '#lib/server/owner.js';
|
||||
import { approveRequest } from '#lib/server/worldtodo/decide.js';
|
||||
import { DecisionError } from '#lib/server/worldtodo/store.js';
|
||||
import { fieldsSchema } from '#lib/server/worldtodo/validate.js';
|
||||
import type { TaskFields } from '#lib/worldtodo/shared.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Owner: body { note?, payload? } where payload is the full tweaked field set.
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireOwner(event);
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isSafeInteger(id) || id <= 0) error(404, 'Not Found');
|
||||
const body = ((await event.request.json().catch(() => ({}))) ?? {}) as {
|
||||
note?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
};
|
||||
let tweaked: TaskFields | null = null;
|
||||
if (body.payload) {
|
||||
const parsed = fieldsSchema.safeParse(body.payload);
|
||||
if (!parsed.success)
|
||||
return json({ error: parsed.error.issues.map((i) => i.message) }, { status: 400 });
|
||||
tweaked = parsed.data as TaskFields;
|
||||
}
|
||||
try {
|
||||
const applied = await approveRequest(id, body.note, tweaked);
|
||||
return json({ ok: true, ...applied });
|
||||
} catch (e) {
|
||||
if (e instanceof DecisionError) return json({ error: e.message }, { status: e.status });
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { requireOwner } from '#lib/server/owner.js';
|
||||
import { rejectRequest } from '#lib/server/worldtodo/decide.js';
|
||||
import { DecisionError } from '#lib/server/worldtodo/store.js';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
// Owner: body { note? }.
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireOwner(event);
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isSafeInteger(id) || id <= 0) error(404, 'Not Found');
|
||||
const body = ((await event.request.json().catch(() => ({}))) ?? {}) as { note?: string };
|
||||
try {
|
||||
await rejectRequest(id, body.note);
|
||||
return json({ ok: true });
|
||||
} catch (e) {
|
||||
if (e instanceof DecisionError) return json({ error: e.message }, { status: e.status });
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user