Skip to content

Commit 2891064

Browse files
committed
feat: API page
1 parent 5a2c495 commit 2891064

File tree

8 files changed

+981
-8
lines changed

8 files changed

+981
-8
lines changed

apps/api/src/lib/api-key.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,17 @@ const getCachedAccessEntries = cacheable(
5050
export async function getApiKeyFromHeader(
5151
headers: Headers
5252
): Promise<ApiKeyRow | null> {
53-
const xApiKey = headers.get('x-api-key');
53+
const xApiKey = headers.get('x-api-key')?.trim();
5454
const auth = headers.get('authorization');
55-
const bearer = auth?.toLowerCase().startsWith('bearer ')
55+
const bearer = auth?.toLowerCase()?.startsWith('bearer ')
5656
? auth.slice(7).trim()
5757
: null;
58-
const secret = xApiKey ?? bearer ?? null;
58+
const secret =
59+
xApiKey && xApiKey.length > 0
60+
? xApiKey
61+
: bearer && bearer.length > 0
62+
? bearer
63+
: null;
5964
if (!secret) {
6065
return null;
6166
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use server';
2+
3+
interface QueryConfig {
4+
allowedFilters: string[];
5+
customizable: boolean;
6+
defaultLimit: number;
7+
}
8+
9+
interface QueryTypesResponse {
10+
success: boolean;
11+
types: string[];
12+
configs: Record<string, QueryConfig>;
13+
}
14+
15+
export async function getQueryTypes(): Promise<QueryTypesResponse> {
16+
try {
17+
const response = await fetch('https://api.databuddy.cc/v1/query/types', {
18+
method: 'GET',
19+
headers: {
20+
'Content-Type': 'application/json',
21+
'X-Api-Key': process.env.DATABUDDY_API_KEY as string,
22+
},
23+
cache: 'force-cache',
24+
});
25+
26+
if (!response.ok) {
27+
throw new Error(`API responded with status: ${response.status}`);
28+
}
29+
30+
const data = await response.json();
31+
return data;
32+
} catch (error) {
33+
console.error('Failed to fetch query types:', error);
34+
return {
35+
success: false,
36+
types: [],
37+
configs: {},
38+
};
39+
}
40+
}

apps/docs/app/(home)/api/page.tsx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { Footer } from '@/components/footer';
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardHeader,
7+
CardTitle,
8+
} from '@/components/ui/card';
9+
import { getQueryTypes } from './actions';
10+
import { QueryDemo } from './query-demo';
11+
12+
export default async function ApiPlaygroundPage() {
13+
const queryTypesData = await getQueryTypes();
14+
15+
return (
16+
<div className="px-4 pt-10 sm:px-6 lg:px-8">
17+
<div className="mx-auto w-full max-w-7xl">
18+
<div className="mb-8">
19+
<h1 className="font-semibold text-2xl">Databuddy Query API</h1>
20+
<p className="mt-2 text-muted-foreground text-sm">
21+
Interactive API explorer - select query types and see real responses
22+
</p>
23+
</div>
24+
25+
{/* Interactive Demo */}
26+
<Card className="mb-8 rounded">
27+
<QueryDemo />
28+
</Card>
29+
30+
<Card className="rounded">
31+
<CardHeader>
32+
<CardTitle>API Documentation</CardTitle>
33+
<CardDescription>
34+
Endpoint details and authentication requirements
35+
</CardDescription>
36+
</CardHeader>
37+
<CardContent className="space-y-8">
38+
{/* Query Types */}
39+
<div className="space-y-6">
40+
<div>
41+
<h3 className="font-semibold text-lg">Available Query Types</h3>
42+
<p className="text-muted-foreground text-sm">
43+
Use these parameter names in your queries to get specific
44+
analytics data.
45+
</p>
46+
</div>
47+
48+
{queryTypesData.success ? (
49+
queryTypesData.types.length === 0 ? (
50+
<div className="rounded border border-gray-200 bg-gray-50 p-6 text-center">
51+
<div className="space-y-2">
52+
<h4 className="font-medium text-gray-800">
53+
No Query Types Found
54+
</h4>
55+
<p className="text-gray-600 text-sm">
56+
No query types are currently available.
57+
</p>
58+
</div>
59+
</div>
60+
) : (
61+
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
62+
{queryTypesData.types.sort().map((type) => (
63+
<div className="rounded border p-4" key={type}>
64+
<div className="space-y-3">
65+
<div className="flex items-start justify-between">
66+
<code className="font-medium font-mono text-sm">
67+
{type}
68+
</code>
69+
{queryTypesData.configs[type]?.customizable && (
70+
<span className="rounded bg-blue-100 px-2 py-1 text-blue-800 text-xs">
71+
Customizable
72+
</span>
73+
)}
74+
</div>
75+
76+
{queryTypesData.configs[type] && (
77+
<div className="space-y-2">
78+
{queryTypesData.configs[type].defaultLimit && (
79+
<div className="text-muted-foreground text-xs">
80+
Default limit:{' '}
81+
{queryTypesData.configs[type].defaultLimit}
82+
</div>
83+
)}
84+
85+
{queryTypesData.configs[type].allowedFilters
86+
?.length > 0 && (
87+
<div className="text-muted-foreground text-xs">
88+
<span className="font-medium">Filters:</span>{' '}
89+
{queryTypesData.configs[
90+
type
91+
].allowedFilters.join(', ')}
92+
</div>
93+
)}
94+
</div>
95+
)}
96+
</div>
97+
</div>
98+
))}
99+
</div>
100+
)
101+
) : (
102+
<div className="rounded border border-red-200 bg-red-50 p-6 text-center">
103+
<div className="space-y-2">
104+
<h4 className="font-medium text-red-800">
105+
Query Types Unavailable
106+
</h4>
107+
<p className="text-red-600 text-sm">
108+
Could not fetch query types from the API. Please check
109+
your connection and try again.
110+
</p>
111+
</div>
112+
</div>
113+
)}
114+
</div>
115+
116+
{/* Authentication */}
117+
<div className="space-y-4">
118+
<div>
119+
<h3 className="font-semibold text-lg">Authentication</h3>
120+
<p className="text-muted-foreground text-sm">
121+
Include your API key in the X-Api-Key header.
122+
</p>
123+
</div>
124+
125+
<div className="rounded border bg-muted p-4">
126+
<code className="text-xs">X-Api-Key: YOUR_API_KEY</code>
127+
</div>
128+
</div>
129+
130+
{/* Parameters */}
131+
<div className="space-y-4">
132+
<div>
133+
<h3 className="font-semibold text-lg">Required Parameters</h3>
134+
<p className="text-muted-foreground text-sm">
135+
Include these query parameters with every request.
136+
</p>
137+
</div>
138+
139+
<div className="grid gap-4 md:grid-cols-3">
140+
<div className="rounded border p-4">
141+
<div className="font-medium text-sm">website_id</div>
142+
<div className="text-muted-foreground text-xs">
143+
Your website identifier (e.g., web_abc123)
144+
</div>
145+
</div>
146+
<div className="rounded border p-4">
147+
<div className="font-medium text-sm">start_date</div>
148+
<div className="text-muted-foreground text-xs">
149+
Start date (YYYY-MM-DD format)
150+
</div>
151+
</div>
152+
<div className="rounded border p-4">
153+
<div className="font-medium text-sm">end_date</div>
154+
<div className="text-muted-foreground text-xs">
155+
End date (YYYY-MM-DD format)
156+
</div>
157+
</div>
158+
</div>
159+
</div>
160+
161+
{/* Rate Limits */}
162+
<div className="space-y-4">
163+
<div>
164+
<h3 className="font-semibold text-lg">Rate Limits</h3>
165+
<p className="text-muted-foreground text-sm">
166+
API requests are limited to ensure fair usage across all
167+
users.
168+
</p>
169+
</div>
170+
171+
<div className="grid gap-4 md:grid-cols-3">
172+
<div className="rounded border p-4 text-center">
173+
<div className="font-semibold text-2xl">1,000</div>
174+
<div className="text-muted-foreground text-sm">
175+
requests per hour
176+
</div>
177+
</div>
178+
<div className="rounded border p-4 text-center">
179+
<div className="font-semibold text-2xl">10,000</div>
180+
<div className="text-muted-foreground text-sm">
181+
requests per day
182+
</div>
183+
</div>
184+
<div className="rounded border p-4 text-center">
185+
<div className="font-semibold text-2xl">200,000</div>
186+
<div className="text-muted-foreground text-sm">
187+
requests per month
188+
</div>
189+
</div>
190+
</div>
191+
</div>
192+
</CardContent>
193+
</Card>
194+
195+
<div className="mt-10">
196+
<Footer />
197+
</div>
198+
</div>
199+
</div>
200+
);
201+
}

0 commit comments

Comments
 (0)