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",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.0",
|
||||
"exports": {
|
||||
"./models": "./src/models/mod.ts",
|
||||
"./db": "./src/db/mod.ts",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import type { Db, Resource } from '../../../mod.ts'
|
||||
import type { UUID } from '../../../types.ts'
|
||||
import type { ResourceBuilder, ResourceType } from '../types.ts'
|
||||
import { respondJson } from '../utils.ts'
|
||||
import type { Db } from '../db/mod.ts'
|
||||
import type { Resource } from '../models/mod.ts'
|
||||
import type { UUID } from '../../types.ts'
|
||||
import type { ResourceBuilder, ResourceType } from './types.ts'
|
||||
import { respondJson } from './utils.ts'
|
||||
|
||||
export async function resourceHandler<T extends Resource>(
|
||||
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