feat(api): add user profile routes (public profile, me, sessions)
This commit is contained in:
@@ -2,6 +2,7 @@ import { Elysia } from "elysia"
|
||||
import { auth } from "@/lib/auth"
|
||||
import { rateLimit } from "@/lib/auth/rate-limit"
|
||||
import { healthRoutes } from "@/lib/api/health"
|
||||
import { userRoutes } from "@/lib/api/user"
|
||||
|
||||
const betterAuth = new Elysia({ name: "better-auth" })
|
||||
.mount(auth.handler)
|
||||
@@ -35,6 +36,7 @@ export const app = new Elysia({ prefix: "/api" })
|
||||
.use(rateLimit(60, 100))
|
||||
.use(betterAuth)
|
||||
.use(healthRoutes)
|
||||
.use(userRoutes)
|
||||
.get("/", () => ({
|
||||
name: "DeckyVault API",
|
||||
version: "2026.0.1",
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import { Elysia, t } from "elysia"
|
||||
import { auth } from "@/lib/auth"
|
||||
import { db } from "@/lib/db/index"
|
||||
import { user, performanceEntries } from "@/lib/db/schema"
|
||||
import { eq, sql } from "drizzle-orm"
|
||||
export const userRoutes = new Elysia({ prefix: "/user" })
|
||||
.get(
|
||||
"/profile/:id",
|
||||
async ({ params, set }) => {
|
||||
const [profile] = await db
|
||||
.select({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
role: user.role,
|
||||
createdAt: user.createdAt,
|
||||
emailVerified: user.emailVerified,
|
||||
})
|
||||
.from(user)
|
||||
.where(eq(user.id, params.id))
|
||||
.limit(1)
|
||||
|
||||
if (!profile) {
|
||||
set.status = 404
|
||||
return { error: "User not found" }
|
||||
}
|
||||
|
||||
// Count contributions (performance entries)
|
||||
const [{ count: contributions }] = await db
|
||||
.select({ count: sql<number>`count(*)::int` })
|
||||
.from(performanceEntries)
|
||||
.where(eq(performanceEntries.userId, params.id))
|
||||
|
||||
// Reputation = contributions * 10 (simple formula for now)
|
||||
const reputation = contributions * 10
|
||||
|
||||
return {
|
||||
...profile,
|
||||
contributions,
|
||||
reputation,
|
||||
verified: profile.emailVerified,
|
||||
// Hide email from public profiles
|
||||
email: undefined,
|
||||
emailVerified: undefined,
|
||||
}
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/me",
|
||||
async ({ request, set }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
set.status = 401
|
||||
return { error: "Unauthorized" }
|
||||
}
|
||||
|
||||
const [profile] = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.id, session.user.id))
|
||||
.limit(1)
|
||||
|
||||
if (!profile) {
|
||||
set.status = 404
|
||||
return { error: "User not found" }
|
||||
}
|
||||
|
||||
const [{ count: contributions }] = await db
|
||||
.select({ count: sql<number>`count(*)::int` })
|
||||
.from(performanceEntries)
|
||||
.where(eq(performanceEntries.userId, session.user.id))
|
||||
|
||||
return {
|
||||
...profile,
|
||||
contributions,
|
||||
reputation: contributions * 10,
|
||||
}
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/me/sessions",
|
||||
async ({ request, set }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
set.status = 401
|
||||
return { error: "Unauthorized" }
|
||||
}
|
||||
|
||||
const sessions = await auth.api.listSessions({
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
return sessions
|
||||
},
|
||||
)
|
||||
@@ -69,10 +69,7 @@ export const rateLimit = (
|
||||
}
|
||||
|
||||
// These headers are informational — clients can use them to throttle
|
||||
set.headers = {
|
||||
...set.headers,
|
||||
"X-RateLimit-Limit": String(max),
|
||||
"X-RateLimit-Remaining": String(result.remaining),
|
||||
"X-RateLimit-Reset": String(Math.ceil(result.resetAt / 1000)),
|
||||
}
|
||||
set.headers["X-RateLimit-Limit"] = String(max)
|
||||
set.headers["X-RateLimit-Remaining"] = String(result.remaining)
|
||||
set.headers["X-RateLimit-Reset"] = String(Math.ceil(result.resetAt / 1000))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export type ApiError = {
|
||||
error: string
|
||||
code?: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export type UserProfile = {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
image: string | null
|
||||
role: string | null
|
||||
createdAt: string
|
||||
contributions: number
|
||||
reputation: number
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
export type PaginatedResponse<T> = {
|
||||
data: T[]
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
Reference in New Issue
Block a user