feat(notifications): add account-scoped inbox and persistent preferences
This commit is contained in:
1 parent
8f55ff2d17
commit
1eee6661f9
39 files changed
+1559
-3
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,67 @@
|
||||
import { z } from "zod";
|
||||
import { notifications } from "@/features/notifications/server";
|
||||
import { apiError, apiJson, apiUnavailable } from "@/lib/api";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { sessionUserId } from "@/lib/auth/session-user";
|
||||
import { rateLimit } from "@/lib/rate-limit";
|
||||
|
||||
const mutation = z.discriminatedUnion("action", [
|
||||
z
|
||||
.object({ action: z.literal("read"), key: z.string().min(1).max(128) })
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
action: z.literal("preferences"),
|
||||
preferences: z
|
||||
.object({
|
||||
support: z.boolean(),
|
||||
friends: z.boolean(),
|
||||
events: z.boolean(),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
.strict(),
|
||||
]);
|
||||
export async function GET(req: Request) {
|
||||
const uid = sessionUserId((await auth())?.user?.id);
|
||||
if (!uid) return apiError("Unauthorized", 401);
|
||||
try {
|
||||
return apiJson(
|
||||
await notifications.feed(
|
||||
uid,
|
||||
Number(new URL(req.url).searchParams.get("page") ?? 1),
|
||||
),
|
||||
);
|
||||
} catch {
|
||||
return apiUnavailable();
|
||||
}
|
||||
}
|
||||
export async function POST(req: Request) {
|
||||
const uid = sessionUserId((await auth())?.user?.id);
|
||||
if (!uid) return apiError("Unauthorized", 401);
|
||||
if (req.headers.get("origin") !== new URL(req.url).origin)
|
||||
return apiError("Invalid origin", 403);
|
||||
if (!req.headers.get("content-type")?.startsWith("application/json"))
|
||||
return apiError("Invalid content type", 415);
|
||||
if (!(await rateLimit(`notifications:${uid}`, 60, 60_000)).ok)
|
||||
return apiError("Too many requests", 429);
|
||||
const body = await req.text();
|
||||
if (body.length > 2048) return apiError("Request too large", 413);
|
||||
let value: unknown;
|
||||
try {
|
||||
value = JSON.parse(body);
|
||||
} catch {
|
||||
return apiError("Invalid request");
|
||||
}
|
||||
const parsed = mutation.safeParse(value);
|
||||
if (!parsed.success) return apiError("Invalid request");
|
||||
try {
|
||||
if (parsed.data.action === "read") {
|
||||
if (!(await notifications.mark(uid, parsed.data.key)))
|
||||
return apiError("Notification not found", 404);
|
||||
} else await notifications.preferences(uid, parsed.data.preferences);
|
||||
return apiJson({ ok: true });
|
||||
} catch {
|
||||
return apiUnavailable();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user