mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
feat(types): type-register-rest-routes (#10171)
This commit is contained in:
@@ -32,7 +32,8 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
"typescript": "~5.6.3",
|
||||
"typescript-eslint": "^8.61.0"
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"@types/express": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from './xo-app.mjs'
|
||||
export * from './lib/xen-orchestra-xapi.mjs'
|
||||
export * from './lib/xen-orchestra-acl.mjs'
|
||||
export * from './lib/complex-matcher.mjs'
|
||||
export * from './lib/rest-api.mjs'
|
||||
|
||||
102
@vates/types/src/lib/rest-api.mts
Normal file
102
@vates/types/src/lib/rest-api.mts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import type { VatesTask } from './vates-task.mjs'
|
||||
import type { XoApp } from '../xo-app.mjs'
|
||||
|
||||
export type SecurityName = '*' | 'token' | 'basic' | 'none'
|
||||
|
||||
export type FieldDefinition =
|
||||
| {
|
||||
type: 'string'
|
||||
example?: string
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'boolean'
|
||||
example?: boolean
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'number'
|
||||
example?: number
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'enum'
|
||||
enum: string[]
|
||||
example?: string
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'object'
|
||||
fields: Record<string, FieldDefinition>
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'array'
|
||||
items: FieldDefinition
|
||||
example?: unknown[]
|
||||
optional?: boolean
|
||||
}
|
||||
|
||||
export type ParamFieldDefinition = Exclude<
|
||||
FieldDefinition,
|
||||
{ type: 'boolean' } | { type: 'object' } | { type: 'array' }
|
||||
>
|
||||
|
||||
export type QueryFieldDefinition = Exclude<FieldDefinition, { type: 'object' } | { type: 'array' }>
|
||||
type RestApi = object & { xoApp: XoApp }
|
||||
export type CreateAction = <Result>(
|
||||
callback: (task: VatesTask) => MaybePromise<Result>,
|
||||
opts: {
|
||||
sync?: boolean
|
||||
statusCode?: number
|
||||
taskProperties: { name: string; [key: string]: unknown }
|
||||
}
|
||||
) => Promise<Result | undefined>
|
||||
|
||||
export interface BaseRouteDefinition<Middleware> {
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
|
||||
endpoint: string
|
||||
description?: string
|
||||
tags?: string[]
|
||||
params?: Record<string, ParamFieldDefinition>
|
||||
query?: Record<string, QueryFieldDefinition>
|
||||
body?: Record<string, FieldDefinition>
|
||||
responses?: Array<{
|
||||
status: number
|
||||
description: string
|
||||
schema?: Record<string, FieldDefinition>
|
||||
}>
|
||||
middlewares?: Middleware[]
|
||||
scope?: 'acl'
|
||||
callback: (params: {
|
||||
req: Request
|
||||
res: Response
|
||||
next: NextFunction
|
||||
restApi: RestApi
|
||||
createAction: CreateAction
|
||||
}) => MaybePromise<unknown>
|
||||
security?: SecurityName
|
||||
}
|
||||
|
||||
export type MiddlewareDescriptor =
|
||||
| { name: 'json' | 'urlencoded' | 'text' | 'raw'; options?: Record<string, unknown> }
|
||||
| { name: 'acl'; acls: LooseAclEntry | LooseAclEntry[] }
|
||||
|
||||
type MaybePromise<T> = T | Promise<T>
|
||||
/**
|
||||
* Loosely-typed mirror of {@link https://github.com/vatesfr/xen-orchestra/blob/master/@xen-orchestra/rest-api/src/middlewares/acl.middleware.mts | AclEntry}
|
||||
* from `@xen-orchestra/rest-api/src/middlewares/acl.middleware.mts`.
|
||||
*/
|
||||
export type LooseAclEntry = {
|
||||
resource: string
|
||||
action?: string | ((opts: { req: object; restApi: RestApi }) => string | undefined)
|
||||
actions?: string[] | ((opts: { req: object; restApi: RestApi }) => string[])
|
||||
objectId?: string | ((opts: { req: object; restApi: RestApi }) => string)
|
||||
objectIds?: string[] | ((opts: { req: object; restApi: RestApi }) => string[])
|
||||
object?: object | ((opts: { req: object; restApi: RestApi }) => MaybePromise<object> | undefined)
|
||||
objects?: object[] | ((opts: { req: object; restApi: RestApi }) => MaybePromise<object[]> | undefined)
|
||||
getObject?: (opts: { restApi: RestApi }) => (id: string) => MaybePromise<object>
|
||||
}
|
||||
|
||||
export type PluginRestRouteDefinition = BaseRouteDefinition<MiddlewareDescriptor>
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EventEmitter } from 'node:stream'
|
||||
import type {
|
||||
AnyXoBackupJob,
|
||||
AnyXoJob,
|
||||
AnyXoLog,
|
||||
XapiXoRecord,
|
||||
@@ -21,6 +20,7 @@ import type {
|
||||
XoVmBackupArchive,
|
||||
} from './xo.mjs'
|
||||
import { VatesTask } from './lib/vates-task.mjs'
|
||||
import type { PluginRestRouteDefinition } from './lib/rest-api.mjs'
|
||||
import {
|
||||
Xapi,
|
||||
XapiHostStats,
|
||||
@@ -195,6 +195,7 @@ export type XoApp = {
|
||||
params?: any
|
||||
}
|
||||
) => () => void // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
registerRestRoutes: (routes: PluginRestRouteDefinition[], base?: string) => () => void
|
||||
authenticateUser: (
|
||||
credentials: { token?: string; username?: string; password?: string },
|
||||
userData?: { ip?: string },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Readable } from 'node:stream'
|
||||
import type { Request, Response } from 'express'
|
||||
import type { SecurityName } from '../middlewares/authentication.middleware.mjs'
|
||||
import type { SecurityName } from '@vates/types'
|
||||
|
||||
export type MaybePromise<T> = T | Promise<T>
|
||||
|
||||
|
||||
@@ -9,11 +9,9 @@ import type { AuthenticatedRequest } from '../helpers/helper.type.mjs'
|
||||
import { iocContainer } from '../ioc/ioc.mjs'
|
||||
import { RestApi } from '../rest-api/rest-api.mjs'
|
||||
import { ACL_MIDDLEWARE_NAME } from './acl.middleware.mjs'
|
||||
|
||||
import type { SecurityName } from '@vates/types'
|
||||
const log = createLogger('xo:rest-api:authentication')
|
||||
|
||||
export type SecurityName = '*' | 'token' | 'basic' | 'none'
|
||||
|
||||
// TSOA spec require this function to be async
|
||||
export async function expressAuthentication(req: AuthenticatedRequest, securityName: SecurityName, scopes: 'acl'[]) {
|
||||
if (securityName === 'none') {
|
||||
|
||||
@@ -17,11 +17,11 @@ import { iocContainer } from '../ioc/ioc.mjs'
|
||||
import { RestApi } from '../rest-api/rest-api.mjs'
|
||||
import {
|
||||
CONTENT_TYPE_BY_MIDDLEWARE_NAME,
|
||||
type CreateAction,
|
||||
type FieldDefinition,
|
||||
type MiddlewareDescriptor,
|
||||
type RouteDefinition,
|
||||
} from './types.mjs'
|
||||
import type { CreateAction } from '@vates/types'
|
||||
|
||||
const log = createLogger('xo:rest-api:external-router')
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import type { SecurityName } from '../middlewares/authentication.middleware.mjs'
|
||||
import type { AclEntry } from '../middlewares/acl.middleware.mjs'
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import type { MaybePromise } from '../helpers/helper.type.mjs'
|
||||
import type { RestApi } from '../rest-api/rest-api.mjs'
|
||||
import type { VatesTask } from '@vates/types/lib/vates/task'
|
||||
import type {
|
||||
BaseRouteDefinition,
|
||||
FieldDefinition,
|
||||
ParamFieldDefinition,
|
||||
QueryFieldDefinition,
|
||||
CreateAction,
|
||||
} from '@vates/types'
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
|
||||
export type CreateAction = <CbType>(
|
||||
cb: (task: VatesTask) => MaybePromise<CbType>,
|
||||
options: {
|
||||
sync?: boolean
|
||||
statusCode?: number
|
||||
taskProperties: { name: string; [key: string]: unknown }
|
||||
}
|
||||
) => Promise<CbType | undefined>
|
||||
export type { FieldDefinition, ParamFieldDefinition, QueryFieldDefinition }
|
||||
|
||||
// Maps middleware descriptor names to their OpenAPI content type
|
||||
export const CONTENT_TYPE_BY_MIDDLEWARE_NAME: Record<string, string> = {
|
||||
@@ -26,62 +24,7 @@ export type MiddlewareDescriptor =
|
||||
| { name: 'json' | 'urlencoded' | 'text' | 'raw'; options?: Record<string, unknown> }
|
||||
| { name: 'acl'; acls: AclEntry | AclEntry[] }
|
||||
|
||||
export type FieldDefinition =
|
||||
| {
|
||||
type: 'string'
|
||||
example?: string
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'boolean'
|
||||
example?: boolean
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'number'
|
||||
example?: number
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'enum'
|
||||
enum: string[]
|
||||
example?: string
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'object'
|
||||
fields: Record<string, FieldDefinition>
|
||||
optional?: boolean
|
||||
}
|
||||
| {
|
||||
type: 'array'
|
||||
items: FieldDefinition
|
||||
example?: unknown[]
|
||||
optional?: boolean
|
||||
}
|
||||
|
||||
export type ParamFieldDefinition = Exclude<
|
||||
FieldDefinition,
|
||||
{ type: 'boolean' } | { type: 'object' } | { type: 'array' }
|
||||
>
|
||||
|
||||
export type QueryFieldDefinition = Exclude<FieldDefinition, { type: 'object' } | { type: 'array' }>
|
||||
|
||||
export interface RouteDefinition {
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
|
||||
endpoint: string
|
||||
description?: string
|
||||
tags?: string[]
|
||||
params?: Record<string, ParamFieldDefinition>
|
||||
query?: Record<string, QueryFieldDefinition>
|
||||
body?: Record<string, FieldDefinition>
|
||||
responses?: Array<{
|
||||
status: number
|
||||
description: string
|
||||
schema?: Record<string, FieldDefinition>
|
||||
}>
|
||||
middlewares?: MiddlewareDescriptor[]
|
||||
scope?: 'acl'
|
||||
export type RouteDefinition = Omit<BaseRouteDefinition<MiddlewareDescriptor>, 'callback'> & {
|
||||
callback: (params: {
|
||||
req: Request
|
||||
res: Response
|
||||
@@ -89,5 +32,4 @@ export interface RouteDefinition {
|
||||
restApi: RestApi
|
||||
createAction: CreateAction
|
||||
}) => MaybePromise<unknown>
|
||||
security?: SecurityName
|
||||
}
|
||||
|
||||
@@ -125,7 +125,8 @@ export function defineRemoteResource<
|
||||
string,
|
||||
{
|
||||
count: number
|
||||
evictionTimeout?: number
|
||||
evictionTimeout?: NodeJS.Timeout // TODO : Fixme , the actual type in browser is number, but NodeJS.Timeout in nodejs.
|
||||
// Due to transitive types of vates/types, the type is NodeJS.Timeout.
|
||||
pause: VoidFunction
|
||||
resume: VoidFunction
|
||||
isPaused: boolean
|
||||
|
||||
Reference in New Issue
Block a user