diff --git a/lib/api/user.ts b/lib/api/user.ts index 328be19..4447a12 100644 --- a/lib/api/user.ts +++ b/lib/api/user.ts @@ -1,7 +1,7 @@ import { Elysia, t } from "elysia" import { auth } from "@/lib/auth" import { db } from "@/lib/db/index" -import { user, performanceEntries } from "@/lib/db/schema" +import { user, performanceEntries, games, gameVersions, hardware } from "@/lib/db/schema" import { eq, sql } from "drizzle-orm" export const userRoutes = new Elysia({ prefix: "/user" }) @@ -32,6 +32,14 @@ export const userRoutes = new Elysia({ prefix: "/user" }) .from(performanceEntries) .where(eq(performanceEntries.userId, params.id)) + // Count verified entries + const [{ count: verifiedEntries }] = await db + .select({ count: sql`count(*)::int` }) + .from(performanceEntries) + .where( + sql`${performanceEntries.userId} = ${params.id} AND ${performanceEntries.verifiedAt} IS NOT NULL` + ) + // Reputation = contributions * 10 (simple formula for now) const reputation = contributions * 10 @@ -39,6 +47,7 @@ export const userRoutes = new Elysia({ prefix: "/user" }) return { ...publicProfile, contributions, + verifiedEntries, reputation, verified: emailVerified, } @@ -85,10 +94,20 @@ export const userRoutes = new Elysia({ prefix: "/user" }) .from(performanceEntries) .where(eq(performanceEntries.userId, session.user.id)) + // Count verified entries + const [{ count: verifiedEntries }] = await db + .select({ count: sql`count(*)::int` }) + .from(performanceEntries) + .where( + sql`${performanceEntries.userId} = ${session.user.id} AND ${performanceEntries.verifiedAt} IS NOT NULL` + ) + return { ...profile, contributions, + verifiedEntries, reputation: contributions * 10, + verified: profile.emailVerified, } }, ) @@ -111,3 +130,77 @@ export const userRoutes = new Elysia({ prefix: "/user" }) return sessions }, ) + .get( + "/profile/:id/contributions", + async ({ params, query, set }) => { + const [profile] = await db + .select({ id: user.id }) + .from(user) + .where(eq(user.id, params.id)) + .limit(1) + + if (!profile) { + set.status = 404 + return { error: "User not found" } + } + + const limit = Math.min(Number(query.limit) || 10, 50) + const offset = Number(query.offset) || 0 + + const entries = await db + .select({ + id: performanceEntries.id, + fpsAvg: performanceEntries.fpsAvg, + fpsLow: performanceEntries.fpsLow, + fpsHigh: performanceEntries.fpsHigh, + hardwareSlug: performanceEntries.hardwareSlug, + hardwareName: hardware.name, + fsrVersion: performanceEntries.fsrVersion, + frameGenMethod: performanceEntries.frameGenMethod, + verifiedAt: performanceEntries.verifiedAt, + createdAt: performanceEntries.createdAt, + gameTitle: games.title, + gameId: games.id, + gameHeaderImage: games.headerImage, + }) + .from(performanceEntries) + .innerJoin( + gameVersions, + eq(performanceEntries.versionId, gameVersions.id), + ) + .innerJoin(games, eq(gameVersions.gameId, games.id)) + .innerJoin( + hardware, + eq(performanceEntries.hardwareSlug, hardware.slug), + ) + .where(eq(performanceEntries.userId, params.id)) + .orderBy(sql`${performanceEntries.createdAt} DESC`) + .limit(limit) + .offset(offset) + + const [{ count: total }] = await db + .select({ count: sql`count(*)::int` }) + .from(performanceEntries) + .where(eq(performanceEntries.userId, params.id)) + + return { + data: entries.map((e) => ({ + ...e, + createdAt: e.createdAt.toISOString(), + verifiedAt: e.verifiedAt?.toISOString() ?? null, + })), + total, + limit, + offset, + } + }, + { + params: t.Object({ + id: t.String(), + }), + query: t.Object({ + limit: t.Optional(t.String()), + offset: t.Optional(t.String()), + }), + }, + ) diff --git a/types/api.ts b/types/api.ts index 45a86ee..e4a5421 100644 --- a/types/api.ts +++ b/types/api.ts @@ -4,6 +4,22 @@ export type ApiError = { status: number } +export type ContributionEntry = { + id: string + fpsAvg: number + fpsLow: number | null + fpsHigh: number | null + hardwareSlug: string + hardwareName: string + fsrVersion: string + frameGenMethod: string + verifiedAt: string | null + createdAt: string + gameTitle: string + gameId: string + gameHeaderImage: string | null +} + export type UserProfile = { id: string name: string @@ -12,6 +28,7 @@ export type UserProfile = { role: string | null createdAt: string contributions: number + verifiedEntries: number reputation: number verified: boolean }