Skip to content

Commit

Permalink
chore: openapi, contract, nextjs, expressjs playgrounds
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Nov 19, 2024
1 parent b3304ea commit f8abece
Show file tree
Hide file tree
Showing 80 changed files with 2,988 additions and 0 deletions.
4 changes: 4 additions & 0 deletions playgrounds/contract-openapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
pnpm-lock.yaml
package-lock.json
yarn.lock
3 changes: 3 additions & 0 deletions playgrounds/contract-openapi/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true,
}
28 changes: 28 additions & 0 deletions playgrounds/contract-openapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "orpc-openapi-playground",
"version": "0.0.0",
"description": "oRPC OpenAPI Playground",
"type": "module",
"scripts": {
"dev": "tsx --watch src/main.ts",
"start": "tsx src/main.ts"
},
"keywords": ["orpc", "openapi", "playground", "unnoq"],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.9.0",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
},
"dependencies": {
"@orpc/client": "latest",
"@orpc/contract": "latest",
"@orpc/openapi": "latest",
"@orpc/react": "latest",
"@orpc/server": "latest",
"@orpc/zod": "latest",
"@whatwg-node/server": "^0.9.55",
"zod": "^3.23.8"
}
}
46 changes: 46 additions & 0 deletions playgrounds/contract-openapi/src/contract/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { oc } from '@orpc/contract'
import { CredentialSchema, TokenSchema } from '../schemas/auth'
import { NewUserSchema, UserSchema } from '../schemas/user'

export const signup = oc
.route({
method: 'POST',
path: '/signup',
summary: 'Sign up a new user',
})
.input(NewUserSchema)
.output(UserSchema)

export const signin = oc
.route({
method: 'POST',
path: '/signin',
summary: 'Sign in a user',
})
.input(CredentialSchema)
.output(TokenSchema)

export const refresh = oc
.route({
method: 'POST',
path: '/refresh',
summary: 'Refresh a token',
})
.input(TokenSchema)
.output(TokenSchema)

export const revoke = oc
.route({
method: 'DELETE',
path: '/revoke',
summary: 'Revoke a token',
})
.input(TokenSchema)

export const me = oc
.route({
method: 'GET',
path: '/me',
summary: 'Get the current user',
})
.output(UserSchema)
29 changes: 29 additions & 0 deletions playgrounds/contract-openapi/src/contract/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { oc } from '@orpc/contract'
import { me, refresh, revoke, signin, signup } from './auth'
import {
createPlanet,
deletePlanet,
findPlanet,
listPlanets,
updatePlanet,
updatePlanetImage,
} from './planet'

export const contract = oc.router({
auth: oc.tags('Authentication').prefix('/auth').router({
signup,
signin,
refresh,
revoke,
me,
}),

planet: oc.tags('Planets').prefix('/planets').router({
list: listPlanets,
create: createPlanet,
find: findPlanet,
update: updatePlanet,
updateImage: updatePlanetImage,
delete: deletePlanet,
}),
})
81 changes: 81 additions & 0 deletions playgrounds/contract-openapi/src/contract/planet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { oc } from '@orpc/contract'
import { oz } from '@orpc/zod'
import { z } from 'zod'
import { planets } from '../data/planet'
import {
NewPlanetSchema,
PlanetSchema,
UpdatePlanetSchema,
} from '../schemas/planet'

export const listPlanets = oc
.route({
method: 'GET',
path: '/',
summary: 'List all planets',
})
.input(
z.object({
limit: z.number().int().min(1).max(100).optional(),
cursor: z.number().int().min(0).default(0),
}),
)
.output(oz.openapi(z.array(PlanetSchema), { examples: [planets] }))

export const createPlanet = oc
.route({
method: 'POST',
path: '/',
summary: 'Create a planet',
})
.input(NewPlanetSchema)
.output(PlanetSchema)

export const findPlanet = oc
.route({
method: 'GET',
path: '/{id}',
summary: 'Find a planet',
})
.input(
z.object({
id: z.number().int().min(1),
}),
)
.output(PlanetSchema)

export const updatePlanet = oc
.route({
method: 'PUT',
path: '/{id}',
summary: 'Update a planet',
})
.input(UpdatePlanetSchema)
.output(PlanetSchema)

export const updatePlanetImage = oc
.route({
method: 'PATCH',
path: '/{id}/image',
summary: 'Update a planet image',
})
.input(
z.object({
id: z.number().int().min(1),
image: oz.file().type('image/*').optional(),
}),
)
.output(PlanetSchema)

export const deletePlanet = oc
.route({
method: 'DELETE',
path: '/{id}',
summary: 'Delete a planet',
deprecated: true,
})
.input(
z.object({
id: z.number().int().min(1),
}),
)
38 changes: 38 additions & 0 deletions playgrounds/contract-openapi/src/data/planet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { z } from 'zod'
import type { PlanetSchema } from '../schemas/planet'

export const planets: z.infer<typeof PlanetSchema>[] = [
{
id: 1,
name: 'Earth',
description: 'The planet Earth',
imageUrl: 'https://picsum.photos/200/300',
creator: {
id: '1',
name: 'John Doe',
email: 'john@doe.com',
},
},
{
id: 2,
name: 'Mars',
description: 'The planet Mars',
imageUrl: 'https://picsum.photos/200/300',
creator: {
id: '1',
name: 'John Doe',
email: 'john@doe.com',
},
},
{
id: 3,
name: 'Jupiter',
description: 'The planet Jupiter',
imageUrl: 'https://picsum.photos/200/300',
creator: {
id: '1',
name: 'John Doe',
email: 'john@doe.com',
},
},
]
112 changes: 112 additions & 0 deletions playgrounds/contract-openapi/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createServer } from 'node:http'
import { generateOpenAPI } from '@orpc/openapi'
import { createFetchHandler } from '@orpc/server/fetch'
import { createServerAdapter } from '@whatwg-node/server'
import { contract } from './contract'
import { router } from './router'

const orpcHandler = createFetchHandler({
router,
hooks(context, meta) {
meta.onError((e) => console.error(e))
},
})

const server = createServer(
createServerAdapter((request: Request) => {
const url = new URL(request.url)

const context = request.headers.get('Authorization')
? { user: { id: 'test', name: 'John Doe', email: 'john@doe.com' } }
: {}

if (url.pathname.startsWith('/api')) {
return orpcHandler({
request,
prefix: '/api',
context,
})
}

if (url.pathname === '/spec.json') {
const spec = generateOpenAPI({
router: contract,
info: {
title: 'ORPC Playground',
version: '1.0.0',
description: `
The example OpenAPI Playground for ORPC.
## Resources
* [Github](https://github.com/unnoq/orpc)
* [Documentation](https://orpc.unnoq.com)
`,
},
servers: [
{ url: '/api' /** Should use absolute URLs in production */ },
],
security: [{ bearerAuth: [] }],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
},
},
},
})

return new Response(JSON.stringify(spec), {
headers: {
'Content-Type': 'application/json',
},
})
}

return new Response(
`
<!doctype html>
<html>
<head>
<title>ORPC Playground</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="https://orpc.unnoq.com/icon.svg" />
</head>
<body>
<script
id="api-reference"
data-url="/spec.json"
data-configuration="${JSON.stringify({
authentication: {
preferredSecurityScheme: 'bearerAuth',
http: {
bearer: {
token: 'default-token',
},
},
},
}).replaceAll('"', '&quot;')}"
></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
`,
{
headers: {
'Content-Type': 'text/html',
},
},
)
}),
)

server.listen(2026, () => {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.log('Playground is available at http://localhost:2026')
})
32 changes: 32 additions & 0 deletions playgrounds/contract-openapi/src/orpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { os, ORPCError } from '@orpc/server'
import type { z } from 'zod'
import { contract } from './contract'
import type { UserSchema } from './schemas/user'

export type ORPCContext = { user?: z.infer<typeof UserSchema>; db: any }

const osBase = os.context<ORPCContext>().use((input, context, meta) => {
const start = Date.now()

meta.onFinish(() => {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.log(`[${meta.path.join('/')}] ${Date.now() - start}ms`)
})
})

const authedBase = osBase.use((input, context, meta) => {
if (!context.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
})
}

return {
context: {
user: context.user,
},
}
})

export const osw = osBase.contract(contract)
export const authed = authedBase.contract(contract)
Loading

0 comments on commit f8abece

Please sign in to comment.