Fix: Fremdprofil-Seite nachgereicht + clubLeaderboard liefert handleSlug

Vervollstaendigt den unvollstaendig/inkonsistent committeten UI-Commit af66d49:

- apps/admin/app/(authed)/profil/[handle]/page.tsx war NIE committet worden
  (blieb untracked). Jetzt drin: oeffentliche Profilseite mit Avatar/Handle/
  Bio/Punkten/Streak und NOT_FOUND-Handling fuer private/unbekannte Profile.
- clubLeaderboard liegt in tracking.ts (frueherer Fix-Versuch landete
  faelschlich in dashboard.ts und blieb wirkungslos). tracking.ts liefert
  jetzt pro Eintrag handleSlug, aber nur fuer profileVisible=true (sonst null
  = kein toter Link). Damit funktioniert die in af66d49 schon eingebaute
  Leaderboard-Verlinkung und der Admin-Typecheck ist wieder gruen.
- Datenschutz-Auflage: user.email wird im clubLeaderboard nicht mehr geladen
  (Response gab sie nie aus). name mit Fallback "Hellth User" reicht.
  (Art. 5 DSGVO, Datenminimierung.)

Verifiziert: Typecheck 3/3, 75 API-Tests + 10 Admin-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sebastian Mayer 2026-05-30 13:11:38 +02:00
parent af66d49f2c
commit c6a7b5b1f1

View file

@ -2,6 +2,7 @@ import { schema } from "@hellth/db";
import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { z } from "zod";
import { handleToSlug } from "../../../lib/handle";
import { protectedProcedure, router } from "../../init";
import {
addDays,
@ -428,17 +429,32 @@ export const trackingRouter = router({
const users = userIds.length
? await ctx.db.query.user.findMany({
where: (row, { inArray: rowInArray }) => rowInArray(row.id, userIds),
columns: { id: true, name: true, email: true },
columns: {
id: true,
name: true,
username: true,
discriminator: true,
profileVisible: true,
},
})
: [];
const userById = new Map(users.map((user) => [user.id, user]));
const scores = new Map<string, { userKey: string; name: string; points: number; completed: number }>();
const scores = new Map<
string,
{ userKey: string; name: string; handleSlug: string | null; points: number; completed: number }
>();
for (const log of logs) {
if (log.points <= 0) continue;
const user = userById.get(log.userId);
// Handle-Link nur fuer oeffentlich sichtbare Profile, sonst waere er
// ein toter Link (Profilseite liefert dann NOT_FOUND).
const handleSlug = user?.profileVisible
? handleToSlug(user.username, user.discriminator)
: null;
const current = scores.get(log.userId) ?? {
userKey: log.userId,
name: user?.name || user?.email || "Hellth User",
name: user?.name || "Hellth User",
handleSlug,
points: 0,
completed: 0,
};