From c22bc68a8b77106f82693e1a7fa5b66004d59602 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 4 Jul 2026 15:04:16 +0200 Subject: [PATCH] feat: add created_at field to CreatedApiKeyResponse and update related handlers --- crates/api-types/src/responses.rs | 1 + crates/presentation/src/handlers/api_keys.rs | 1 + .../app/settings/api-keys/page.tsx | 6 ++---- .../app/users/[username]/page.tsx | 6 +++--- .../components/follow-button.tsx | 10 ++++++++-- thoughts-frontend/lib/api.ts | 2 +- thoughts-frontend/middleware.ts | 20 ++++++++++++++++++- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/api-types/src/responses.rs b/crates/api-types/src/responses.rs index bfa3fb9..04bbf2c 100644 --- a/crates/api-types/src/responses.rs +++ b/crates/api-types/src/responses.rs @@ -105,6 +105,7 @@ pub struct ErrorResponse { pub struct CreatedApiKeyResponse { pub id: Uuid, pub name: String, + pub created_at: DateTime, /// Raw API key — shown only once at creation pub key: String, } diff --git a/crates/presentation/src/handlers/api_keys.rs b/crates/presentation/src/handlers/api_keys.rs index 622828c..49c8005 100644 --- a/crates/presentation/src/handlers/api_keys.rs +++ b/crates/presentation/src/handlers/api_keys.rs @@ -42,6 +42,7 @@ pub async fn post_api_key( Ok(Json(CreatedApiKeyResponse { id: key.id.as_uuid(), name: key.name, + created_at: key.created_at, key: raw, })) } diff --git a/thoughts-frontend/app/settings/api-keys/page.tsx b/thoughts-frontend/app/settings/api-keys/page.tsx index 3f1d198..83164dc 100644 --- a/thoughts-frontend/app/settings/api-keys/page.tsx +++ b/thoughts-frontend/app/settings/api-keys/page.tsx @@ -9,9 +9,7 @@ export default async function ApiKeysPage() { redirect("/login"); } - const initialApiKeys = await getApiKeys(token).catch(() => ({ - keys: [], - })); + const initialApiKeys = await getApiKeys(token).catch(() => []); return (
@@ -21,7 +19,7 @@ export default async function ApiKeysPage() { Manage API keys for third-party applications.

- + ); } diff --git a/thoughts-frontend/app/users/[username]/page.tsx b/thoughts-frontend/app/users/[username]/page.tsx index e698943..d557478 100644 --- a/thoughts-frontend/app/users/[username]/page.tsx +++ b/thoughts-frontend/app/users/[username]/page.tsx @@ -190,13 +190,13 @@ export default async function ProfilePage({ params }: ProfilePageProps) { -
-

+
+

{user.displayName || user.username}

@{user.username}

diff --git a/thoughts-frontend/components/follow-button.tsx b/thoughts-frontend/components/follow-button.tsx index dbbebe2..313003c 100644 --- a/thoughts-frontend/components/follow-button.tsx +++ b/thoughts-frontend/components/follow-button.tsx @@ -1,7 +1,9 @@ "use client" import { useOptimistic, useRef } from "react" -import { followUser, unfollowUser } from "@/app/actions/social" +import { useRouter } from "next/navigation" +import { followUser, unfollowUser } from "@/lib/api" +import { useAuth } from "@/hooks/use-auth" import { Button } from "@/components/ui/button" import { toast } from "sonner" import { UserPlus, UserMinus } from "lucide-react" @@ -68,8 +70,11 @@ function burstParticles(canvas: HTMLCanvasElement) { export function FollowButton({ username, isInitiallyFollowing }: FollowButtonProps) { const [optimisticFollowing, setOptimisticFollowing] = useOptimistic(isInitiallyFollowing) const canvasRef = useRef(null) + const { token } = useAuth() + const router = useRouter() async function handleClick() { + if (!token) return const next = !optimisticFollowing setOptimisticFollowing(next) @@ -78,7 +83,8 @@ export function FollowButton({ username, isInitiallyFollowing }: FollowButtonPro } try { - await (next ? followUser(username) : unfollowUser(username)) + await (next ? followUser(username, token) : unfollowUser(username, token)) + router.refresh() } catch { setOptimisticFollowing(!next) toast.error(`Failed to ${next ? "follow" : "unfollow"} user.`) diff --git a/thoughts-frontend/lib/api.ts b/thoughts-frontend/lib/api.ts index fdc6f60..de608dd 100644 --- a/thoughts-frontend/lib/api.ts +++ b/thoughts-frontend/lib/api.ts @@ -455,7 +455,7 @@ export const search = (query: string, token: string | null) => // ── API Keys ────────────────────────────────────────────────────────────── export const getApiKeys = (token: string) => - apiFetch("/api-keys", { next: { tags: ['api-keys'] } }, z.object({ keys: z.array(ApiKeySchema) }), token); + apiFetch("/api-keys", { next: { tags: ['api-keys'] } }, z.array(ApiKeySchema), token); export const createApiKey = (data: z.infer, token: string) => apiFetch("/api-keys", { method: "POST", body: JSON.stringify(data) }, ApiKeyResponseSchema, token); diff --git a/thoughts-frontend/middleware.ts b/thoughts-frontend/middleware.ts index d2dedac..4dc68ea 100644 --- a/thoughts-frontend/middleware.ts +++ b/thoughts-frontend/middleware.ts @@ -15,6 +15,24 @@ export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const parts = pathname.split("/"); + if (parts[1] === "media") { + const apiBase = + process.env.NEXT_PUBLIC_SERVER_SIDE_API_URL ?? "http://api:8000"; + const forwardHeaders: Record = {}; + for (const [key, value] of request.headers.entries()) { + if (key.toLowerCase() !== "host") forwardHeaders[key] = value; + } + const res = await fetch(`${apiBase}${pathname}`, { headers: forwardHeaders }); + const body = await res.arrayBuffer(); + return new NextResponse(body, { + status: res.status, + headers: { + "content-type": res.headers.get("content-type") ?? "application/octet-stream", + "cache-control": res.headers.get("cache-control") ?? "public, max-age=31536000, immutable", + }, + }); + } + if (parts.length >= 3 && parts[1] === "users") { const segment = decodeURIComponent(parts[2]); const accept = request.headers.get("accept") ?? ""; @@ -71,5 +89,5 @@ export async function middleware(request: NextRequest) { } export const config = { - matcher: "/users/:path*", + matcher: ["/users/:path*", "/media/:path*"], };