feat(api): ✨ implement basic rest api draft
This commit is contained in:
parent
90cfd257aa
commit
e652336485
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"conventionalCommits.scopes": ["model", "chore", "db", "task"]
|
"conventionalCommits.scopes": ["model", "chore", "db", "task", "api"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@cohabit/resources-manager",
|
"name": "@cohabit/resources-manager",
|
||||||
"version": "0.1.2",
|
"version": "0.2.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./models": "./src/models/mod.ts",
|
"./models": "./src/models/mod.ts",
|
||||||
"./db": "./src/db/mod.ts",
|
"./db": "./src/db/mod.ts",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { Db, Resource } from '../../../mod.ts'
|
import type { Db } from '../db/mod.ts'
|
||||||
import type { UUID } from '../../../types.ts'
|
import type { Resource } from '../models/mod.ts'
|
||||||
import type { ResourceBuilder, ResourceType } from '../types.ts'
|
import type { UUID } from '../../types.ts'
|
||||||
import { respondJson } from '../utils.ts'
|
import type { ResourceBuilder, ResourceType } from './types.ts'
|
||||||
|
import { respondJson } from './utils.ts'
|
||||||
|
|
||||||
export async function resourceHandler<T extends Resource>(
|
export async function resourceHandler<T extends Resource>(
|
||||||
type: ResourceType<T>,
|
type: ResourceType<T>,
|
81
src/api/server.ts
Normal file
81
src/api/server.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// /(users|groups|machines)/:?id
|
||||||
|
import { stripTypeS } from '../api/utils.ts'
|
||||||
|
import type { Db } from '../db/mod.ts'
|
||||||
|
import { Credential, Group, Machine, Service, User } from '../models/mod.ts'
|
||||||
|
import type { CredentialCategory } from '../models/src/credential.ts'
|
||||||
|
import { resourceHandler } from './hander.ts'
|
||||||
|
|
||||||
|
export class Api {
|
||||||
|
static init(db: Db, { base = '/' }: { base: `/${string}` }) {
|
||||||
|
return new Api({ db, base })
|
||||||
|
}
|
||||||
|
|
||||||
|
#db: Db
|
||||||
|
#base: `/${string}`
|
||||||
|
|
||||||
|
private constructor({ db, base }: { db: Db; base: `/${string}` }) {
|
||||||
|
this.#db = db
|
||||||
|
this.#base = base
|
||||||
|
}
|
||||||
|
|
||||||
|
serve() {
|
||||||
|
return Deno.serve((req) => {
|
||||||
|
const url = new URL(req.url)
|
||||||
|
|
||||||
|
if (url.pathname.startsWith(this.#base)) {
|
||||||
|
const base = new URL(this.#base, req.url)
|
||||||
|
return router(req, base, this.#db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(null, {
|
||||||
|
status: 301,
|
||||||
|
statusText: 'Not allowed base url',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function router(req: Request, base: URL, db: Db): Promise<Response> {
|
||||||
|
const pattern = new URLPattern({ pathname: '/:type/:id?' }, base.href)
|
||||||
|
const match = pattern.exec(req.url)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return Promise.resolve(
|
||||||
|
new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad request',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, id } = match.pathname.groups as {
|
||||||
|
type: string
|
||||||
|
id: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'credentials':
|
||||||
|
return resourceHandler<Credential<CredentialCategory>>(
|
||||||
|
stripTypeS(type),
|
||||||
|
Credential,
|
||||||
|
db,
|
||||||
|
req,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
case 'groups':
|
||||||
|
return resourceHandler<Group>(stripTypeS(type), Group, db, req, id)
|
||||||
|
case 'machines':
|
||||||
|
return resourceHandler<Machine>(stripTypeS(type), Machine, db, req, id)
|
||||||
|
case 'services':
|
||||||
|
return resourceHandler<Service>(stripTypeS(type), Service, db, req, id)
|
||||||
|
case 'users':
|
||||||
|
return resourceHandler<User>(stripTypeS(type), User, db, req, id)
|
||||||
|
default:
|
||||||
|
return Promise.resolve(
|
||||||
|
new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad request',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
29
src/api/types.ts
Normal file
29
src/api/types.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import type {
|
||||||
|
Credential,
|
||||||
|
Group,
|
||||||
|
Machine,
|
||||||
|
Resource,
|
||||||
|
Service,
|
||||||
|
User,
|
||||||
|
} from '../../src/models/mod.ts'
|
||||||
|
import type { JsonValue } from '@std/json'
|
||||||
|
import type { CredentialCategory } from '../models/src/credential.ts'
|
||||||
|
|
||||||
|
export type ResourceBuilder<T extends Resource> = T extends
|
||||||
|
Credential<CredentialCategory> ? typeof Credential
|
||||||
|
: T extends Group ? typeof Group
|
||||||
|
: T extends Machine ? typeof Machine
|
||||||
|
: T extends Service ? typeof Service
|
||||||
|
: T extends User ? typeof User
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ResourceType<T extends Resource> = T extends
|
||||||
|
Credential<CredentialCategory> ? 'credential'
|
||||||
|
: T extends Group ? 'group'
|
||||||
|
: T extends Machine ? 'machine'
|
||||||
|
: T extends Service ? 'service'
|
||||||
|
: T extends User ? 'user'
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type JsonObject = Record<string, JsonValue>
|
||||||
|
export type JsonStringifiable = { toJSON(): JsonObject } | JsonObject
|
38
src/api/utils.ts
Normal file
38
src/api/utils.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import type { JsonStringifiable } from '../api/types.ts'
|
||||||
|
|
||||||
|
export function respondJson<T extends JsonStringifiable | Error>(
|
||||||
|
valueOrError: T,
|
||||||
|
): Response {
|
||||||
|
if (valueOrError instanceof Error) {
|
||||||
|
const { name, cause, message } = valueOrError
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
result: 'error',
|
||||||
|
error: { name, cause, message },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
statusText: valueOrError.message,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
result: 'ok',
|
||||||
|
value: valueOrError,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripTypeS<T extends string>(typeS: `${T}s`): T {
|
||||||
|
return typeS.slice(0, -1) as T
|
||||||
|
}
|
Loading…
Reference in a new issue