protected
This commit is contained in:
@@ -1,6 +1,18 @@
|
||||
import { createSignal, For, Show } from "solid-js";
|
||||
import { query, createAsync } from "@solidjs/router";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { getPrivilegeLevel } from "~/server/utils";
|
||||
import { api } from "~/lib/api";
|
||||
|
||||
const getAuthState = query(async () => {
|
||||
"use server";
|
||||
|
||||
const event = getRequestEvent()!;
|
||||
const privilegeLevel = await getPrivilegeLevel(event.nativeEvent);
|
||||
|
||||
return { privilegeLevel };
|
||||
}, "test-auth-state");
|
||||
|
||||
type EndpointTest = {
|
||||
name: string;
|
||||
router: string;
|
||||
@@ -846,6 +858,8 @@ const routerSections: RouterSection[] = [
|
||||
];
|
||||
|
||||
export default function TestPage() {
|
||||
const authState = createAsync(() => getAuthState());
|
||||
|
||||
const [expandedSections, setExpandedSections] = createSignal<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
@@ -919,282 +933,297 @@ export default function TestPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<main class="min-h-screen p-8">
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<div class="bg-surface0 mb-6 rounded-lg p-6 shadow-lg">
|
||||
<h1 class="mb-2 text-3xl font-bold">tRPC API Testing Dashboard</h1>
|
||||
<p class="text-text mb-4">
|
||||
Complete API coverage: Example, Auth, Database, User, Misc, and
|
||||
Lineage routers
|
||||
</p>
|
||||
|
||||
<div class="border-lavender bg-mauve rounded border p-4">
|
||||
<p class="text-base text-sm">
|
||||
<strong>Quick Start:</strong> Expand any section below to test
|
||||
endpoints. Public endpoints work immediately. Auth-required
|
||||
endpoints need valid tokens.
|
||||
<Show
|
||||
when={authState()?.privilegeLevel === "admin"}
|
||||
fallback={
|
||||
<div class="w-full pt-[30vh] text-center">
|
||||
<div class="text-text text-2xl">Unauthorized</div>
|
||||
<div class="text-subtext0 mt-4">
|
||||
You must be an admin to access this page.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<main class="min-h-screen p-8">
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<div class="bg-surface0 mb-6 rounded-lg p-6 shadow-lg">
|
||||
<h1 class="mb-2 text-3xl font-bold">tRPC API Testing Dashboard</h1>
|
||||
<p class="text-text mb-4">
|
||||
Complete API coverage: Example, Auth, Database, User, Misc, and
|
||||
Lineage routers
|
||||
</p>
|
||||
|
||||
<div class="border-lavender bg-mauve rounded border p-4">
|
||||
<p class="text-base text-sm">
|
||||
<strong>Quick Start:</strong> Expand any section below to test
|
||||
endpoints. Public endpoints work immediately. Auth-required
|
||||
endpoints need valid tokens.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<For each={routerSections}>
|
||||
{(section) => {
|
||||
const isExpanded = () => expandedSections().has(section.name);
|
||||
<div class="space-y-4">
|
||||
<For each={routerSections}>
|
||||
{(section) => {
|
||||
const isExpanded = () => expandedSections().has(section.name);
|
||||
|
||||
return (
|
||||
<div class="bg-surface0 rounded-lg shadow">
|
||||
{/* Section Header */}
|
||||
<button
|
||||
onClick={() => toggleSection(section.name)}
|
||||
class="flex w-full items-center justify-between px-6 py-4 transition"
|
||||
>
|
||||
<div class="text-left">
|
||||
<h2 class="text-xl font-bold">{section.name}</h2>
|
||||
<p class="text-subtext0 text-sm">{section.description}</p>
|
||||
<p class="text-subtext1 mt-1 text-xs">
|
||||
{section.endpoints.length} endpoint
|
||||
{section.endpoints.length !== 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-subtext1 text-2xl">
|
||||
{isExpanded() ? "−" : "+"}
|
||||
</div>
|
||||
</button>
|
||||
return (
|
||||
<div class="bg-surface0 rounded-lg shadow">
|
||||
{/* Section Header */}
|
||||
<button
|
||||
onClick={() => toggleSection(section.name)}
|
||||
class="flex w-full items-center justify-between px-6 py-4 transition"
|
||||
>
|
||||
<div class="text-left">
|
||||
<h2 class="text-xl font-bold">{section.name}</h2>
|
||||
<p class="text-subtext0 text-sm">
|
||||
{section.description}
|
||||
</p>
|
||||
<p class="text-subtext1 mt-1 text-xs">
|
||||
{section.endpoints.length} endpoint
|
||||
{section.endpoints.length !== 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-subtext1 text-2xl">
|
||||
{isExpanded() ? "−" : "+"}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Section Content */}
|
||||
<Show when={isExpanded()}>
|
||||
<div class="border-base space-y-4 border-t p-6">
|
||||
<For each={section.endpoints}>
|
||||
{(endpoint) => {
|
||||
const key = `${endpoint.router}.${endpoint.procedure}`;
|
||||
const hasInput = endpoint.sampleInput !== undefined;
|
||||
const displayInput = () => {
|
||||
if (inputEdits()[key]) {
|
||||
return inputEdits()[key];
|
||||
}
|
||||
// Handle primitive values (string, number, boolean)
|
||||
if (typeof endpoint.sampleInput === "string") {
|
||||
return `"${endpoint.sampleInput}"`;
|
||||
}
|
||||
if (
|
||||
typeof endpoint.sampleInput === "number" ||
|
||||
typeof endpoint.sampleInput === "boolean"
|
||||
) {
|
||||
return String(endpoint.sampleInput);
|
||||
}
|
||||
// Handle objects and arrays
|
||||
return JSON.stringify(
|
||||
endpoint.sampleInput,
|
||||
null,
|
||||
2
|
||||
);
|
||||
};
|
||||
{/* Section Content */}
|
||||
<Show when={isExpanded()}>
|
||||
<div class="border-base space-y-4 border-t p-6">
|
||||
<For each={section.endpoints}>
|
||||
{(endpoint) => {
|
||||
const key = `${endpoint.router}.${endpoint.procedure}`;
|
||||
const hasInput = endpoint.sampleInput !== undefined;
|
||||
const displayInput = () => {
|
||||
if (inputEdits()[key]) {
|
||||
return inputEdits()[key];
|
||||
}
|
||||
// Handle primitive values (string, number, boolean)
|
||||
if (typeof endpoint.sampleInput === "string") {
|
||||
return `"${endpoint.sampleInput}"`;
|
||||
}
|
||||
if (
|
||||
typeof endpoint.sampleInput === "number" ||
|
||||
typeof endpoint.sampleInput === "boolean"
|
||||
) {
|
||||
return String(endpoint.sampleInput);
|
||||
}
|
||||
// Handle objects and arrays
|
||||
return JSON.stringify(
|
||||
endpoint.sampleInput,
|
||||
null,
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="bg-surface2 border-surface1 rounded-lg border p-4">
|
||||
{/* Endpoint Header */}
|
||||
<div class="mb-3 flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-subtext0 text-lg font-semibold">
|
||||
{endpoint.name}
|
||||
</h3>
|
||||
<Show when={endpoint.requiresAuth}>
|
||||
<span class="bg-surface1 text-yellow rounded px-2 py-1 text-xs">
|
||||
🔒 Auth Required
|
||||
return (
|
||||
<div class="bg-surface2 border-surface1 rounded-lg border p-4">
|
||||
{/* Endpoint Header */}
|
||||
<div class="mb-3 flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-subtext0 text-lg font-semibold">
|
||||
{endpoint.name}
|
||||
</h3>
|
||||
<Show when={endpoint.requiresAuth}>
|
||||
<span class="bg-surface1 text-yellow rounded px-2 py-1 text-xs">
|
||||
🔒 Auth Required
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={endpoint.requiresAdmin}>
|
||||
<span class="bg-maroon rounded px-2 py-1 text-base text-xs">
|
||||
👑 Admin Only
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{endpoint.description}
|
||||
</p>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<code class="bg-surface0 rounded px-2 py-1 text-xs">
|
||||
{key}
|
||||
</code>
|
||||
<span class="bg-blue text-text rounded px-2 py-1 text-xs">
|
||||
{endpoint.method === "query"
|
||||
? "GET"
|
||||
: "POST"}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={endpoint.requiresAdmin}>
|
||||
<span class="bg-maroon rounded px-2 py-1 text-base text-xs">
|
||||
👑 Admin Only
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{endpoint.description}
|
||||
</p>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<code class="bg-surface0 rounded px-2 py-1 text-xs">
|
||||
{key}
|
||||
</code>
|
||||
<span class="bg-blue text-text rounded px-2 py-1 text-xs">
|
||||
{endpoint.method === "query"
|
||||
? "GET"
|
||||
: "POST"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => testEndpoint(endpoint)}
|
||||
disabled={loading()[key]}
|
||||
class="bg-green ml-4 rounded px-4 py-2 text-base font-semibold whitespace-nowrap transition hover:brightness-125 disabled:brightness-50"
|
||||
>
|
||||
{loading()[key] ? "Testing..." : "Test"}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => testEndpoint(endpoint)}
|
||||
disabled={loading()[key]}
|
||||
class="bg-green ml-4 rounded px-4 py-2 text-base font-semibold whitespace-nowrap transition hover:brightness-125 disabled:brightness-50"
|
||||
>
|
||||
{loading()[key] ? "Testing..." : "Test"}
|
||||
</button>
|
||||
|
||||
{/* Input Editor */}
|
||||
<Show when={hasInput}>
|
||||
<div class="mb-3">
|
||||
<label class="text-text mb-1 block text-xs font-semibold">
|
||||
Request Body (edit JSON):
|
||||
</label>
|
||||
<textarea
|
||||
value={displayInput()}
|
||||
onInput={(e) =>
|
||||
updateInput(key, e.currentTarget.value)
|
||||
}
|
||||
class="border-lavender bg-crust min-h-[100px] w-full rounded border p-2 font-mono text-xs"
|
||||
spellcheck={false}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Error Display */}
|
||||
<Show when={errors()[key]}>
|
||||
<div class="mb-3 rounded border border-red-200 bg-red-50 p-3">
|
||||
<p class="text-sm font-semibold text-red-800">
|
||||
Error:
|
||||
</p>
|
||||
<p class="font-mono text-sm text-red-600">
|
||||
{errors()[key]}
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Results Display */}
|
||||
<Show when={results()[key]}>
|
||||
<div class="rounded bg-gray-900 p-3">
|
||||
<p class="mb-2 text-xs font-semibold text-green-400">
|
||||
✓ Response:
|
||||
</p>
|
||||
<pre class="max-h-60 overflow-auto text-xs text-green-400">
|
||||
{JSON.stringify(results()[key], null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
{/* Input Editor */}
|
||||
<Show when={hasInput}>
|
||||
<div class="mb-3">
|
||||
<label class="text-text mb-1 block text-xs font-semibold">
|
||||
Request Body (edit JSON):
|
||||
</label>
|
||||
<textarea
|
||||
value={displayInput()}
|
||||
onInput={(e) =>
|
||||
updateInput(key, e.currentTarget.value)
|
||||
}
|
||||
class="border-lavender bg-crust min-h-[100px] w-full rounded border p-2 font-mono text-xs"
|
||||
spellcheck={false}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
{/* Footer Instructions */}
|
||||
<div class="bg-overlay2 mt-6 rounded-lg p-6 shadow-lg">
|
||||
<h2 class="text-crust mb-4 text-2xl font-bold">Testing Guide</h2>
|
||||
|
||||
{/* Error Display */}
|
||||
<Show when={errors()[key]}>
|
||||
<div class="mb-3 rounded border border-red-200 bg-red-50 p-3">
|
||||
<p class="text-sm font-semibold text-red-800">
|
||||
Error:
|
||||
</p>
|
||||
<p class="font-mono text-sm text-red-600">
|
||||
{errors()[key]}
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="space-y-4 text-base">
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🟢 No Auth Required</h3>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Hello endpoint
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage JSON Service</strong> - All 6 endpoints work
|
||||
immediately
|
||||
</li>
|
||||
<li>
|
||||
<strong>Database</strong> - All endpoints (comments, posts,
|
||||
users, reactions, likes)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Misc</strong> - Downloads, S3 operations, password
|
||||
utilities
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Misc</strong> - Offline Secret, Get
|
||||
Opponents
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage PvP</strong> - Get Opponents
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Results Display */}
|
||||
<Show when={results()[key]}>
|
||||
<div class="rounded bg-gray-900 p-3">
|
||||
<p class="mb-2 text-xs font-semibold text-green-400">
|
||||
✓ Response:
|
||||
</p>
|
||||
<pre class="max-h-60 overflow-auto text-xs text-green-400">
|
||||
{JSON.stringify(results()[key], null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🟡 Auth Required</h3>
|
||||
<p class="mb-2 text-sm">
|
||||
These need valid JWT tokens from login/registration:
|
||||
</p>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Get Profile
|
||||
</li>
|
||||
<li>
|
||||
<strong>User Router</strong> - All endpoints (profile
|
||||
updates, password, account deletion)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Auth</strong> - Email Login, Refresh Token
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Database</strong> - Get Credentials,
|
||||
Deletion endpoints
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Footer Instructions */}
|
||||
<div class="bg-overlay2 mt-6 rounded-lg p-6 shadow-lg">
|
||||
<h2 class="text-crust mb-4 text-2xl font-bold">Testing Guide</h2>
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🔴 Admin Required</h3>
|
||||
<p class="mb-2 text-sm">
|
||||
Maintenance endpoints require admin privileges (userIDToken
|
||||
cookie with ADMIN_ID).
|
||||
</p>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Admin Dashboard
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Maintenance</strong> - Find Loose Databases,
|
||||
Cleanup Expired
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 text-base">
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🟢 No Auth Required</h3>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Hello endpoint
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage JSON Service</strong> - All 6 endpoints work
|
||||
immediately
|
||||
</li>
|
||||
<li>
|
||||
<strong>Database</strong> - All endpoints (comments, posts,
|
||||
users, reactions, likes)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Misc</strong> - Downloads, S3 operations, password
|
||||
utilities
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Misc</strong> - Offline Secret, Get Opponents
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage PvP</strong> - Get Opponents
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">📝 Typical Workflows</h3>
|
||||
<ol class="ml-6 list-decimal space-y-2 text-sm">
|
||||
<li>
|
||||
<strong>Test public endpoints:</strong> Start with Example
|
||||
Hello, Lineage JSON Service, or Database queries
|
||||
</li>
|
||||
<li>
|
||||
<strong>OAuth flow:</strong> Use Auth router callbacks with
|
||||
OAuth codes from GitHub/Google
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email auth flow:</strong> Register → verify email →
|
||||
login → use JWT
|
||||
</li>
|
||||
<li>
|
||||
<strong>Blog/Project management:</strong> Create posts → add
|
||||
comments/likes → upload images via S3
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage game data:</strong> Fetch JSON data →
|
||||
register character → find PvP opponents
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🟡 Auth Required</h3>
|
||||
<p class="mb-2 text-sm">
|
||||
These need valid JWT tokens from login/registration:
|
||||
</p>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Get Profile
|
||||
</li>
|
||||
<li>
|
||||
<strong>User Router</strong> - All endpoints (profile updates,
|
||||
password, account deletion)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Auth</strong> - Email Login, Refresh Token
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Database</strong> - Get Credentials, Deletion
|
||||
endpoints
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">🔴 Admin Required</h3>
|
||||
<p class="mb-2 text-sm">
|
||||
Maintenance endpoints require admin privileges (userIDToken
|
||||
cookie with ADMIN_ID).
|
||||
</p>
|
||||
<ul class="ml-6 list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
<strong>Example Router</strong> - Admin Dashboard
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage Maintenance</strong> - Find Loose Databases,
|
||||
Cleanup Expired
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mb-2 text-lg font-semibold">📝 Typical Workflows</h3>
|
||||
<ol class="ml-6 list-decimal space-y-2 text-sm">
|
||||
<li>
|
||||
<strong>Test public endpoints:</strong> Start with Example
|
||||
Hello, Lineage JSON Service, or Database queries
|
||||
</li>
|
||||
<li>
|
||||
<strong>OAuth flow:</strong> Use Auth router callbacks with
|
||||
OAuth codes from GitHub/Google
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email auth flow:</strong> Register → verify email →
|
||||
login → use JWT
|
||||
</li>
|
||||
<li>
|
||||
<strong>Blog/Project management:</strong> Create posts → add
|
||||
comments/likes → upload images via S3
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lineage game data:</strong> Fetch JSON data → register
|
||||
character → find PvP opponents
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="border-rosewater bg-rosewater mt-4 rounded border p-4">
|
||||
<p class="text-crust text-sm">
|
||||
<strong>Note:</strong> Some endpoints require specific setup
|
||||
(e.g., OAuth codes, existing database records, valid S3 keys).
|
||||
Check the sample input to understand what data each endpoint
|
||||
expects.
|
||||
</p>
|
||||
<div class="border-rosewater bg-rosewater mt-4 rounded border p-4">
|
||||
<p class="text-crust text-sm">
|
||||
<strong>Note:</strong> Some endpoints require specific setup
|
||||
(e.g., OAuth codes, existing database records, valid S3 keys).
|
||||
Check the sample input to understand what data each endpoint
|
||||
expects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user