Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/api/mock/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1660,7 +1660,8 @@ async function handle(req: NextRequest, path: string[]) {

/* ---------- api keys ----------
Wire shape mirrors the REAL backend's ApiKeyResponse exactly: envelope
key `keys`, Unix-second timestamps, NO last_used_at (not served yet).
key `keys`, Unix-second timestamps, last_used_at nullable (null until
the key first authenticates; server debounces updates to ~hourly).
The frontend normalizes; keeping the mock honest prevents drift. */
const keyToWire = (k: MockKey) => ({
id: k.id,
Expand All @@ -1673,6 +1674,9 @@ async function handle(req: NextRequest, path: string[]) {
expires_at: k.expires_at
? Math.floor(new Date(k.expires_at).getTime() / 1000)
: null,
last_used_at: k.last_used_at
? Math.floor(new Date(k.last_used_at).getTime() / 1000)
: null,
revoked: k.revoked,
token_prefix: k.token_prefix,
})
Expand Down
13 changes: 13 additions & 0 deletions app/api/mock/[...path]/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,19 @@ export function buildKeys(): MockKey[] {
last_used_at: isoDaysAgo(61, rand),
revoked: true,
},
// Never-used key: legal on the wire (last_used_at is null until the
// key first authenticates), so the walkthrough exercises it.
{
id: "key_fresh",
name: "local testing",
description: null,
token_prefix: "spk_live_e51b",
scopes: ["shorten:create", "stats:read"],
created_at: isoDaysAgo(2, rand),
expires_at: null,
last_used_at: null,
revoked: false,
},
]
}

Expand Down
6 changes: 4 additions & 2 deletions app/dashboard/developer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ function KeyRow({ apiKey }: { apiKey: ApiKey }) {
</TooltipContent>
</Tooltip>{" "}
<span className="font-sans">
· created {formatWhen(apiKey.created_at)} · last used{" "}
{formatWhen(apiKey.last_used_at)}
· created {formatWhen(apiKey.created_at)} ·{" "}
{apiKey.last_used_at
? `last used ${formatWhen(apiKey.last_used_at)}`
: "never used"}
{apiKey.expires_at && (
<>
{" "}
Expand Down
7 changes: 4 additions & 3 deletions lib/api/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ export type ApiKeyCreated = ApiKey & {
}

/** Wire shape of /api/v1/keys (backend ApiKeyResponse): the list envelope
key is `keys`, timestamps are Unix SECONDS, and last_used_at is not
served yet (optional so it lights up if the backend adds it). Normalized
here so the rest of the app keeps ISO strings. */
key is `keys`, timestamps are Unix SECONDS, and last_used_at is null
until the key first authenticates a request (the server debounces
updates to at most once an hour). Normalized here so the rest of the
app keeps ISO strings. */
type ApiKeyWire = {
id: string
name: string
Expand Down