feat: add created_at field to CreatedApiKeyResponse and update related handlers
Some checks failed
lint / lint (push) Successful in 16m47s
test / unit (push) Has been cancelled

This commit is contained in:
2026-07-04 15:04:16 +02:00
parent 8f69cfb011
commit c22bc68a8b
7 changed files with 35 additions and 11 deletions

View File

@@ -105,6 +105,7 @@ pub struct ErrorResponse {
pub struct CreatedApiKeyResponse {
pub id: Uuid,
pub name: String,
pub created_at: DateTime<Utc>,
/// Raw API key — shown only once at creation
pub key: String,
}

View File

@@ -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,
}))
}

View File

@@ -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 (
<div className="space-y-6">
@@ -21,7 +19,7 @@ export default async function ApiKeysPage() {
Manage API keys for third-party applications.
</p>
</div>
<ApiKeyList initialApiKeys={initialApiKeys.keys} />
<ApiKeyList initialApiKeys={initialApiKeys} />
</div>
);
}

View File

@@ -190,13 +190,13 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
</div>
</div>
<div id="profile-card__info" className="mt-4">
<h1 id="profile-card__name" className="text-2xl font-bold">
<div id="profile-card__info" className="mt-4 min-w-0">
<h1 id="profile-card__name" className="text-2xl font-bold break-words">
{user.displayName || user.username}
</h1>
<p
id="profile-card__username"
className="text-sm text-muted-foreground"
className="text-sm text-muted-foreground break-all"
>
@{user.username}
</p>

View File

@@ -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<HTMLCanvasElement>(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.`)

View File

@@ -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<typeof CreateApiKeySchema>, token: string) =>
apiFetch("/api-keys", { method: "POST", body: JSON.stringify(data) }, ApiKeyResponseSchema, token);

View File

@@ -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<string, string> = {};
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*"],
};