mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Create an example app for OpenAPI
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Change output path logic
|
||||
+2
-2
@@ -13,8 +13,8 @@ apps/docs/shaders/*
|
||||
apps/docs/content/docs/ui/museum/*.mdx
|
||||
!apps/docs/content/docs/ui/museum/index.mdx
|
||||
|
||||
apps/docs/content/docs/unkey/**/*.mdx
|
||||
!apps/docs/content/docs/unkey/index.mdx
|
||||
examples/openapi/content/docs/(api)/**/*.mdx
|
||||
!examples/openapi/content/docs/(api)/index.mdx
|
||||
|
||||
apps/docs/content/docs/ui/typescript.mdx
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
out
|
||||
content/docs/ui/museum/*.mdx
|
||||
content/docs/ui/museum/**/*.mdx
|
||||
!content/docs/ui/museum/index.mdx
|
||||
|
||||
content/docs/unkey/**/*.mdx
|
||||
!content/docs/unkey/index.mdx
|
||||
|
||||
content/docs/ui/(integrations)/typescript.mdx
|
||||
@@ -94,7 +94,7 @@ function Item(
|
||||
className="rounded-2xl border border-transparent p-6 shadow-lg transition-all hover:shadow-fd-primary/20"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(to right bottom, hsl(var(--background)) 10%, hsl(var(--fd-accent)), hsl(var(--background)) 60%),' +
|
||||
'linear-gradient(to right bottom, hsl(var(--background)) 10%, hsl(var(--accent)), hsl(var(--background)) 60%),' +
|
||||
'linear-gradient(to right bottom, rgb(40,40,40) 10%, rgb(180,180,180), rgb(30,30,30) 60%)',
|
||||
backgroundOrigin: 'border-box',
|
||||
backgroundClip: 'padding-box, border-box',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Unkey
|
||||
description: Experimenting OpenAPI
|
||||
index: true
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "Unkey",
|
||||
"root": true
|
||||
}
|
||||
@@ -3,12 +3,6 @@ import * as Typescript from 'fumadocs-typescript';
|
||||
import * as path from 'node:path';
|
||||
import { rimrafSync } from 'rimraf';
|
||||
|
||||
rimrafSync('./content/docs/ui/unkey', {
|
||||
filter(v) {
|
||||
return !v.endsWith('index.mdx') && !v.endsWith('meta.json');
|
||||
},
|
||||
});
|
||||
|
||||
rimrafSync('./content/docs/ui/museum', {
|
||||
filter(v) {
|
||||
return !v.endsWith('index.mdx') && !v.endsWith('meta.json');
|
||||
@@ -17,17 +11,10 @@ rimrafSync('./content/docs/ui/museum', {
|
||||
|
||||
void OpenAPI.generateFiles({
|
||||
input: ['./museum.yaml'],
|
||||
output: './content/docs/ui',
|
||||
output: './content/docs/ui/museum',
|
||||
per: 'operation',
|
||||
});
|
||||
|
||||
void OpenAPI.generateFiles({
|
||||
input: ['./unkey.json'],
|
||||
output: './content/docs',
|
||||
per: 'operation',
|
||||
groupBy: 'tag',
|
||||
});
|
||||
|
||||
const demoRegex = /^---type-table-demo---\r?\n(?<content>.+)\r?\n---end---$/gm;
|
||||
void Typescript.generateFiles({
|
||||
input: ['./content/docs/**/*.model.mdx'],
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
LibraryIcon,
|
||||
PaperclipIcon,
|
||||
type LucideIcon,
|
||||
KeyIcon,
|
||||
} from 'lucide-react';
|
||||
import mdx from '../../../packages/mdx/package.json';
|
||||
import ui from '../../../packages/ui/package.json';
|
||||
@@ -44,12 +43,4 @@ export const modes: Mode[] = [
|
||||
version: mdx.version,
|
||||
icon: PaperclipIcon,
|
||||
},
|
||||
{
|
||||
param: 'unkey',
|
||||
name: 'Unkey',
|
||||
package: 'test',
|
||||
description: 'Experimental OpenAPI',
|
||||
version: '0',
|
||||
icon: KeyIcon,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
content/docs/(api)/**/*.mdx
|
||||
@@ -0,0 +1,3 @@
|
||||
# Unkey API Docs
|
||||
|
||||
This is an example API documentation based on Unkey's [API documentation](https://github.com/unkeyed/unkey), using Fumadocs OpenAPI.
|
||||
@@ -0,0 +1,11 @@
|
||||
import { getPages } from '@/app/source';
|
||||
import { createSearchAPI } from 'fumadocs-core/search/server';
|
||||
|
||||
export const { GET } = createSearchAPI('advanced', {
|
||||
indexes: getPages().map((page) => ({
|
||||
title: page.data.title,
|
||||
structuredData: page.data.exports.structuredData,
|
||||
id: page.url,
|
||||
url: page.url,
|
||||
})),
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { getPage, getPages } from '@/app/source';
|
||||
import type { Metadata } from 'next';
|
||||
import { DocsPage, DocsBody } from 'fumadocs-ui/page';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: { slug?: string[] };
|
||||
}) {
|
||||
const page = getPage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const MDX = page.data.exports.default;
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.exports.toc} full={page.data.full}>
|
||||
<h1 className="text-3xl font-bold text-fd-foreground sm:text-4xl">
|
||||
{page.data.title}
|
||||
</h1>
|
||||
<p className="text-fd-muted-foreground">{page.data.description}</p>
|
||||
<DocsBody>
|
||||
<MDX />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return getPages().map((page) => ({
|
||||
slug: page.slugs,
|
||||
}));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug?: string[] } }) {
|
||||
const page = getPage(params.slug);
|
||||
|
||||
if (page == null) notFound();
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
} satisfies Metadata;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { pageTree } from '../source';
|
||||
import { DocsLayout } from 'fumadocs-ui/layout';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function RootDocsLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout
|
||||
tree={pageTree}
|
||||
nav={{
|
||||
title: <span className="text-lg font-bold py-2">Unkey Docs</span>,
|
||||
}}
|
||||
sidebar={{ defaultOpenLevel: 0 }}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
background-image: radial-gradient(
|
||||
800px 500px at 90% 0%,
|
||||
theme('colors.fd-primary.DEFAULT / 10%'),
|
||||
transparent
|
||||
),
|
||||
radial-gradient(
|
||||
800px 500px at 50% 0%,
|
||||
theme('colors.blue.300 / 5%'),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.prose {
|
||||
--tw-prose-body: hsl(var(--muted-foreground));
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { RootProvider } from 'fumadocs-ui/provider';
|
||||
import './global.css';
|
||||
import { Inter } from 'next/font/google';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={inter.className}>
|
||||
<body>
|
||||
<RootProvider>{children}</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main
|
||||
style={{
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
}}
|
||||
>
|
||||
Hello World
|
||||
</h1>
|
||||
<p>
|
||||
You can open{' '}
|
||||
<Link
|
||||
href="/docs"
|
||||
style={{
|
||||
fontWeight: '600',
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
>
|
||||
/docs
|
||||
</Link>{' '}
|
||||
and see the documentation.
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { map } from '@/.map';
|
||||
import { createMDXSource, defaultSchemas } from 'fumadocs-mdx';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { attachFile } from 'fumadocs-openapi/source';
|
||||
import { z } from 'zod';
|
||||
|
||||
const frontmatter = defaultSchemas.frontmatter.extend({
|
||||
method: z.string().optional(),
|
||||
});
|
||||
|
||||
export const { getPage, getPages, pageTree } = loader({
|
||||
baseUrl: '/docs',
|
||||
rootDir: 'docs',
|
||||
source: createMDXSource(map, {
|
||||
schema: {
|
||||
frontmatter,
|
||||
},
|
||||
}),
|
||||
pageTree: {
|
||||
attachFile,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: 'Authentication'
|
||||
description: "How to authenticate your requests to Unkey's API"
|
||||
---
|
||||
|
||||
You'll need to authenticate your requests to access some of the endpoints in the Unkey API. In this guide, we'll look at how authentication works.
|
||||
|
||||
## Bearer Token
|
||||
|
||||
When requesting resources, you will need your root key — you will find it in the [Dashboard](https://app.unkey.com/settings/root-keys). Here's how to add the root key to the request header using cURL:
|
||||
|
||||
```bash
|
||||
curl https://api.unkey.dev/v1/... \
|
||||
-H "Authorization: Bearer unkey_xxx"
|
||||
```
|
||||
|
||||
Always keep your root key safe and reset it if you suspect it has been compromised.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Overview
|
||||
description: General information about the API.
|
||||
---
|
||||
|
||||
The Unkey API uses HTTP RPC-style methods and generally follow the schema:
|
||||
|
||||
```
|
||||
https://api.unkey.dev/{version}/{service}.{method}
|
||||
```
|
||||
|
||||
For example `GET https://api.unkey.dev/v1/apis.listKeys` to list all keys for an API.
|
||||
|
||||
## HTTP Methods
|
||||
|
||||
We strictly use only `GET` and `POST` methods. `PUT` and `DELETE` are not used.
|
||||
|
||||
### `GET`
|
||||
|
||||
`GET` methods are used for reading data. Filtering, sorting, or pagination is done via query parameters.
|
||||
|
||||
```http
|
||||
curl "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \
|
||||
-H "Authorization: Bearer <ROOT_KEY>"
|
||||
```
|
||||
|
||||
### `POST`
|
||||
|
||||
`POST` methods are used for creating, updating, and deleting data. Data is passed as `application/json` in the request body.
|
||||
|
||||
```http
|
||||
curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \
|
||||
-H "Authorization: Bearer <ROOT_KEY>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"apiId": "api_123", "name": "My Key"}'
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"pages": [
|
||||
"---API Documentation---",
|
||||
"index",
|
||||
"...",
|
||||
"---References---",
|
||||
"...(api)"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
import defaultComponents from 'fumadocs-ui/mdx';
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...defaultComponents,
|
||||
...components,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import createMDX from 'fumadocs-mdx/config';
|
||||
|
||||
const withMDX = createMDX();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default withMDX(config);
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "example-openapi",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"build:docs": "node ./scripts/generate-docs.mjs",
|
||||
"dev": "next dev",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"fumadocs-core": "workspace:*",
|
||||
"fumadocs-mdx": "workspace:*",
|
||||
"fumadocs-openapi": "workspace:*",
|
||||
"fumadocs-ui": "workspace:*",
|
||||
"next": "^14.2.5",
|
||||
"postcss": "^8.4.39",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as OpenAPI from 'fumadocs-openapi';
|
||||
import { rimrafSync } from 'rimraf';
|
||||
|
||||
rimrafSync('./content/docs/(api)', {
|
||||
filter(v) {
|
||||
return !v.endsWith('index.mdx') && !v.endsWith('meta.json');
|
||||
},
|
||||
});
|
||||
|
||||
void OpenAPI.generateFiles({
|
||||
input: ['./unkey.json'],
|
||||
output: './content/docs/(api)',
|
||||
per: 'operation',
|
||||
groupBy: 'tag',
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// @ts-check
|
||||
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./content/**/*.mdx',
|
||||
'./mdx-components.tsx',
|
||||
'./node_modules/fumadocs-ui/dist/**/*.js',
|
||||
'./node_modules/fumadocs-openapi/dist/**/*.js',
|
||||
],
|
||||
presets: [
|
||||
createPreset({
|
||||
preset: 'dusk',
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "ESNext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -61,10 +61,10 @@ export async function generateFiles({
|
||||
|
||||
await Promise.all(
|
||||
resolvedInputs.map(async (path) => {
|
||||
let filename = parse(path).name;
|
||||
filename = nameFn?.('file', filename) ?? filename;
|
||||
|
||||
if (per === 'file') {
|
||||
let filename = parse(path).name;
|
||||
if (nameFn) filename = nameFn('file', filename);
|
||||
|
||||
const outPath = join(outputDir, `${filename}.mdx`);
|
||||
|
||||
const result = await generate(path, options);
|
||||
@@ -97,14 +97,12 @@ export async function generateFiles({
|
||||
|
||||
outPath = join(
|
||||
outputDir,
|
||||
filename,
|
||||
getFilename(result.method.tags[0]),
|
||||
`${getFilename(id)}.mdx`,
|
||||
);
|
||||
} else if (groupBy === 'route') {
|
||||
outPath = join(
|
||||
outputDir,
|
||||
filename,
|
||||
result.route.summary
|
||||
? getFilename(result.route.summary)
|
||||
: getFilenameFromRoute(result.route.path),
|
||||
@@ -124,7 +122,7 @@ export async function generateFiles({
|
||||
console.log(`Generated Meta: ${metaFile}`);
|
||||
}
|
||||
} else {
|
||||
outPath = join(outputDir, filename, `${getFilename(id)}.mdx`);
|
||||
outPath = join(outputDir, `${getFilename(id)}.mdx`);
|
||||
}
|
||||
|
||||
await write(outPath, result.content);
|
||||
@@ -140,7 +138,7 @@ export async function generateFiles({
|
||||
let tagName = result.tag;
|
||||
tagName = nameFn?.('tag', tagName) ?? getFilename(tagName);
|
||||
|
||||
const outPath = join(outputDir, filename, `${tagName}.mdx`);
|
||||
const outPath = join(outputDir, `${tagName}.mdx`);
|
||||
await write(outPath, result.content);
|
||||
console.log(`Generated: ${outPath}`);
|
||||
}
|
||||
|
||||
Generated
+50
-11
@@ -292,6 +292,55 @@ importers:
|
||||
specifier: ^5.5.4
|
||||
version: 5.5.4
|
||||
|
||||
examples/openapi:
|
||||
dependencies:
|
||||
fumadocs-core:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core
|
||||
fumadocs-mdx:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/mdx
|
||||
fumadocs-openapi:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/openapi
|
||||
fumadocs-ui:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
next:
|
||||
specifier: ^14.2.5
|
||||
version: 14.2.5(@babel/[email protected])(@opentelemetry/[email protected])([email protected]([email protected]))([email protected])
|
||||
postcss:
|
||||
specifier: ^8.4.39
|
||||
version: 8.4.39
|
||||
react:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1([email protected])
|
||||
tailwindcss:
|
||||
specifier: ^3.4.6
|
||||
version: 3.4.6
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.23.8
|
||||
devDependencies:
|
||||
'@types/mdx':
|
||||
specifier: ^2.0.13
|
||||
version: 2.0.13
|
||||
'@types/react':
|
||||
specifier: ^18.3.3
|
||||
version: 18.3.3
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.0
|
||||
version: 18.3.0
|
||||
rimraf:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
typescript:
|
||||
specifier: ^5.5.4
|
||||
version: 5.5.4
|
||||
|
||||
examples/remote-mdx:
|
||||
dependencies:
|
||||
'@fumadocs/mdx-remote':
|
||||
@@ -5411,10 +5460,6 @@ packages:
|
||||
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -9645,7 +9690,7 @@ snapshots:
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
fast-glob: 3.3.2
|
||||
postcss: 8.4.38
|
||||
postcss: 8.4.39
|
||||
tailwindcss: 3.4.6
|
||||
|
||||
[email protected]([email protected])([email protected]):
|
||||
@@ -11711,12 +11756,6 @@ snapshots:
|
||||
picocolors: 1.0.1
|
||||
source-map-js: 1.2.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
picocolors: 1.0.1
|
||||
source-map-js: 1.2.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
nanoid: 3.3.7
|
||||
|
||||
Reference in New Issue
Block a user