Same pattern as touchLastSeen: getLastSeenBatch, setPresenceVisible, getFollowingIds wurden im db/profile.ts implementiert aber nicht in den Endpoints importiert. Alle 3 warfen 500 ReferenceError → grüner Dot zeigte sich nie + Toggle silently failed. Nitro's auto-import covered nur defineEventHandler/getQuery etc., NICHT unsere eigenen db-layer helper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
/**
|
|
* GET /api/presence/last-seen?userIds=uuid1,uuid2,...
|
|
*
|
|
* Batch-fetch lastSeenAt for up to 50 users.
|
|
* Auth required — callers must be logged in (prevents anonymous enumeration).
|
|
*
|
|
* Query param:
|
|
* userIds — comma-separated UUIDs (max 50)
|
|
*
|
|
* Response:
|
|
* { success: true, data: { [userId: string]: string | null } }
|
|
* Value is lastSeenAt as ISO-8601 string, or null if never seen / user not found.
|
|
*
|
|
* Privacy filters (both must pass):
|
|
* 1. Requester follows the target (UserFollow row exists).
|
|
* 2. Target has presenceVisible = true (opt-out respected).
|
|
* Targets failing either filter → null in response map.
|
|
* Frontend: null = no indicator shown.
|
|
*
|
|
* Privacy note: only lastSeenAt is exposed — no nickname, avatar, email or any
|
|
* other profile field (feedback_anonymity_nickname.md).
|
|
*/
|
|
import { requireUser } from "../../utils/auth";
|
|
import { getLastSeenBatch } from "../../db/profile";
|
|
|
|
const MAX_USER_IDS = 50;
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await requireUser(event);
|
|
|
|
const query = getQuery(event);
|
|
const raw = typeof query.userIds === "string" ? query.userIds.trim() : "";
|
|
|
|
if (!raw) {
|
|
throw createError({ statusCode: 400, message: "MISSING_USER_IDS" });
|
|
}
|
|
|
|
const userIds = raw
|
|
.split(",")
|
|
.map((id) => id.trim())
|
|
.filter(Boolean);
|
|
|
|
if (userIds.length === 0) {
|
|
throw createError({ statusCode: 400, message: "MISSING_USER_IDS" });
|
|
}
|
|
|
|
if (userIds.length > MAX_USER_IDS) {
|
|
throw createError({ statusCode: 400, message: "TOO_MANY_USER_IDS" });
|
|
}
|
|
|
|
const rows = await getLastSeenBatch(user.id, userIds);
|
|
|
|
// Build map — guarantee every requested ID has an entry (null for filtered/missing)
|
|
const result: Record<string, string | null> = {};
|
|
for (const id of userIds) {
|
|
result[id] = null;
|
|
}
|
|
for (const row of rows) {
|
|
result[row.id] = row.lastSeenAt ? row.lastSeenAt.toISOString() : null;
|
|
}
|
|
|
|
return { success: true, data: result };
|
|
});
|