From 64294688ad70ef0c3016a41c27e9fafa2385f036 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Fri, 23 Sep 2022 16:13:17 +0200 Subject: [PATCH] [Files] Refactor how types are shared between server and client (#141580) * migrated uploads component and added new way of typing and creating an endpoint * updated create endpoint types * convert delete endpoint * convert download endpoint * convert get by id endpoint * convert list endpoint * convert update endpoint * convert public facing download * convert share get * convert list shares * missed a spot * remove unused import * convert share endpoint * convert unshare endpoint * convert find find endpoint, fix types and fix use of empty value check * convert metrics --- x-pack/plugins/files/common/api_routes.ts | 208 +++--------------- x-pack/plugins/files/public/types.ts | 16 +- .../files/server/routes/file_kind/create.ts | 39 ++-- .../files/server/routes/file_kind/delete.ts | 28 +-- .../files/server/routes/file_kind/download.ts | 30 +-- .../server/routes/file_kind/get_by_id.ts | 31 ++- .../files/server/routes/file_kind/index.ts | 1 + .../files/server/routes/file_kind/list.ts | 36 ++- .../server/routes/file_kind/share/get.ts | 35 ++- .../server/routes/file_kind/share/list.ts | 37 ++-- .../server/routes/file_kind/share/share.ts | 58 ++--- .../server/routes/file_kind/share/unshare.ts | 31 +-- .../files/server/routes/file_kind/types.ts | 7 + .../files/server/routes/file_kind/update.ts | 48 ++-- .../files/server/routes/file_kind/upload.ts | 49 ++--- x-pack/plugins/files/server/routes/find.ts | 56 +++-- x-pack/plugins/files/server/routes/metrics.ts | 7 +- .../server/routes/public_facing/download.ts | 43 ++-- x-pack/plugins/files/server/routes/types.ts | 7 + 19 files changed, 284 insertions(+), 483 deletions(-) diff --git a/x-pack/plugins/files/common/api_routes.ts b/x-pack/plugins/files/common/api_routes.ts index 523fe2b2e7c8f..2f522e7c77150 100644 --- a/x-pack/plugins/files/common/api_routes.ts +++ b/x-pack/plugins/files/common/api_routes.ts @@ -5,14 +5,8 @@ * 2.0. */ +import type { TypeOf, Type } from '@kbn/config-schema'; import { PLUGIN_ID } from './constants'; -import type { - FileJSON, - Pagination, - FilesMetrics, - FileShareJSON, - FileShareJSONWithToken, -} from './types'; export const API_BASE_PATH = `/api/${PLUGIN_ID}`; @@ -22,6 +16,27 @@ export const FILES_SHARE_API_BASE_PATH = `${API_BASE_PATH}/shares`; export const FILES_PUBLIC_API_BASE_PATH = `${API_BASE_PATH}/public`; +export interface EndpointInputs< + P extends Type = Type, + Q extends Type = Type, + B extends Type = Type +> { + params?: P; + query?: Q; + body?: B; +} + +export interface CreateRouteDefinition { + inputs: { + params: TypeOf>; + query: TypeOf>; + body: TypeOf>; + }; + output: R; +} + +export type AnyEndpoint = CreateRouteDefinition; + /** * Abstract type definition for API route inputs and outputs. * @@ -42,168 +57,17 @@ export interface HttpApiInterfaceEntryDefinition< output: R; } -export type CreateFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - unknown, - unknown, - { - name: string; - alt?: string; - meta?: Record; - mimeType?: string; - }, - { file: FileJSON } ->; - -export type DeleteFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - id: string; - }, - unknown, - unknown, - { ok: true } ->; - -export type DownloadFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - id: string; - fileName?: string; - }, - unknown, - unknown, - any ->; - -export type GetByIdFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - id: string; - }, - unknown, - unknown, - { file: FileJSON } ->; - -export type ListFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - unknown, - Pagination, - unknown, - { files: FileJSON[] } ->; - -export type UpdateFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - { id: string }, - unknown, - { name?: string; alt?: string; meta?: Record }, - { file: FileJSON } ->; - -export type UploadFileKindHttpEndpoint = HttpApiInterfaceEntryDefinition< - { id: string }, - { selfDestructOnAbort?: boolean }, - { body: unknown }, - { - ok: true; - size: number; - } ->; - -export type FindFilesHttpEndpoint = HttpApiInterfaceEntryDefinition< - unknown, - Pagination, - { - /** - * Filter for set of file-kinds - */ - kind?: string[]; - - /** - * Filter for match on names - */ - name?: string[]; - - /** - * Filter for set of meta attributes matching this object - */ - meta?: {}; - - /** - * Filter for match on extensions - */ - extension?: string[]; - - /** - * Filter for match on extensions - */ - status?: string[]; - }, - { files: FileJSON[] } ->; - -export type FilesMetricsHttpEndpoint = HttpApiInterfaceEntryDefinition< - unknown, - unknown, - unknown, - FilesMetrics ->; - -export type FileShareHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - fileId: string; - }, - unknown, - { - /** - * Unix timestamp of when the share will expire. - */ - validUntil?: number; - /** - * Optional name to uniquely identify this share instance. - */ - name?: string; - }, - FileShareJSONWithToken ->; - -export type FileUnshareHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - /** - * Share token id - */ - id: string; - }, - unknown, - unknown, - { - ok: true; - } ->; - -export type FileGetShareHttpEndpoint = HttpApiInterfaceEntryDefinition< - { - /** - * ID of the share object - */ - id: string; - }, - unknown, - unknown, - { - share: FileShareJSON; - } ->; - -export type FileListSharesHttpEndpoint = HttpApiInterfaceEntryDefinition< - unknown, - Pagination & { forFileId?: string }, - unknown, - { - shares: FileShareJSON[]; - } ->; - -export type FilePublicDownloadHttpEndpoint = HttpApiInterfaceEntryDefinition< - { fileName?: string }, - { token: string }, - unknown, - // Should be a readable stream - any ->; +export type { Endpoint as CreateFileKindHttpEndpoint } from '../server/routes/file_kind/create'; +export type { Endpoint as DeleteFileKindHttpEndpoint } from '../server/routes/file_kind/delete'; +export type { Endpoint as DownloadFileKindHttpEndpoint } from '../server/routes/file_kind/download'; +export type { Endpoint as GetByIdFileKindHttpEndpoint } from '../server/routes/file_kind/get_by_id'; +export type { Endpoint as ListFileKindHttpEndpoint } from '../server/routes/file_kind/list'; +export type { Endpoint as UpdateFileKindHttpEndpoint } from '../server/routes/file_kind/update'; +export type { Endpoint as UploadFileKindHttpEndpoint } from '../server/routes/file_kind/upload'; +export type { Endpoint as FindFilesHttpEndpoint } from '../server/routes/find'; +export type { Endpoint as FilesMetricsHttpEndpoint } from '../server/routes/metrics'; +export type { Endpoint as FileShareHttpEndpoint } from '../server/routes/file_kind/share/share'; +export type { Endpoint as FileUnshareHttpEndpoint } from '../server/routes/file_kind/share/unshare'; +export type { Endpoint as FileGetShareHttpEndpoint } from '../server/routes/file_kind/share/get'; +export type { Endpoint as FileListSharesHttpEndpoint } from '../server/routes/file_kind/share/list'; +export type { Endpoint as FilePublicDownloadHttpEndpoint } from '../server/routes/public_facing/download'; diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index 78cce57ace087..ac5ec40c2c252 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -96,10 +96,18 @@ export interface FilesClient extends GlobalEndpoints { * * @param args - upload file args */ - upload: ClientMethodFrom< - UploadFileKindHttpEndpoint, - { abortSignal?: AbortSignal; contentType?: string } - >; + upload: ( + args: UploadFileKindHttpEndpoint['inputs']['params'] & + UploadFileKindHttpEndpoint['inputs']['query'] & { + /** + * Should be blob or ReadableStream of some kind. + */ + body: unknown; + kind: string; + abortSignal?: AbortSignal; + contentType?: string; + } + ) => Promise; /** * Stream a download of the file object's content. * diff --git a/x-pack/plugins/files/server/routes/file_kind/create.ts b/x-pack/plugins/files/server/routes/file_kind/create.ts index 985b1e8b05a54..a134bdd292e98 100644 --- a/x-pack/plugins/files/server/routes/file_kind/create.ts +++ b/x-pack/plugins/files/server/routes/file_kind/create.ts @@ -5,32 +5,27 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { Ensure } from '@kbn/utility-types'; -import type { CreateFileKindHttpEndpoint } from '../../../common/api_routes'; -import type { FileKind } from '../../../common/types'; -import { FILES_API_ROUTES } from '../api_routes'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; +import { schema } from '@kbn/config-schema'; +import type { FileJSON, FileKind } from '../../../common/types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; +import type { FileKindRouter } from './types'; import * as commonSchemas from '../common_schemas'; +import { CreateHandler } from './types'; export const method = 'post' as const; -export const bodySchema = schema.object({ - name: commonSchemas.fileName, - alt: commonSchemas.fileAlt, - meta: commonSchemas.fileMeta, - mimeType: schema.maybe(schema.string()), -}); - -type Body = Ensure>; +const rt = { + body: schema.object({ + name: commonSchemas.fileName, + alt: commonSchemas.fileAlt, + meta: commonSchemas.fileMeta, + mimeType: schema.maybe(schema.string()), + }), +}; -type Response = CreateFileKindHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { fileKind, files }, - req, - res -) => { +export const handler: CreateHandler = async ({ fileKind, files }, req, res) => { const { fileService } = await files; const { body: { name, alt, meta, mimeType }, @@ -38,7 +33,7 @@ export const handler: FileKindsRequestHandler = async ( const file = await fileService .asCurrentUser() .create({ fileKind, name, alt, meta, mime: mimeType }); - const body: Response = { + const body: Endpoint['output'] = { file: file.toJSON(), }; return res.ok({ body }); @@ -50,7 +45,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { { path: FILES_API_ROUTES.fileKind.getCreateFileRoute(fileKind.id), validate: { - body: bodySchema, + ...rt, }, options: { tags: fileKind.http.create.tags, diff --git a/x-pack/plugins/files/server/routes/file_kind/delete.ts b/x-pack/plugins/files/server/routes/file_kind/delete.ts index 154ae7efe448a..ec23525f2686a 100644 --- a/x-pack/plugins/files/server/routes/file_kind/delete.ts +++ b/x-pack/plugins/files/server/routes/file_kind/delete.ts @@ -5,27 +5,25 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; -import type { DeleteFileKindHttpEndpoint } from '../../../common/api_routes'; +import { schema } from '@kbn/config-schema'; import type { FileKind } from '../../../common/types'; import { fileErrors } from '../../file'; -import { FILES_API_ROUTES } from '../api_routes'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; +import type { CreateHandler, FileKindRouter } from './types'; import { getById } from './helpers'; export const method = 'delete' as const; -export const paramsSchema = schema.object({ - id: schema.string(), -}); - -type Params = Ensure>; +const rt = { + params: schema.object({ + id: schema.string(), + }), +}; -type Response = DeleteFileKindHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ({ files, fileKind }, req, res) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { params: { id }, } = req; @@ -43,7 +41,7 @@ export const handler: FileKindsRequestHandler = async ({ files, fileKind } throw e; } - const body: Response = { + const body: Endpoint['output'] = { ok: true, }; return res.ok({ body }); @@ -54,9 +52,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getDeleteRoute(fileKind.id), - validate: { - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.delete.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/download.ts b/x-pack/plugins/files/server/routes/file_kind/download.ts index d1ff70d17355f..85f8b5bd0a2d6 100644 --- a/x-pack/plugins/files/server/routes/file_kind/download.ts +++ b/x-pack/plugins/files/server/routes/file_kind/download.ts @@ -5,35 +5,31 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import { Ensure } from '@kbn/utility-types'; +import { schema } from '@kbn/config-schema'; import { Readable } from 'stream'; -import type { DownloadFileKindHttpEndpoint } from '../../../common/api_routes'; import type { FileKind } from '../../../common/types'; import { fileNameWithExt } from '../common_schemas'; import { fileErrors } from '../../file'; import { getDownloadHeadersForFile } from '../common'; import { getById } from './helpers'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; -import { FILES_API_ROUTES } from '../api_routes'; +import type { CreateHandler, FileKindRouter } from './types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; export const method = 'get' as const; -export const paramsSchema = schema.object({ - id: schema.string(), - fileName: schema.maybe(fileNameWithExt), -}); +const rt = { + params: schema.object({ + id: schema.string(), + fileName: schema.maybe(fileNameWithExt), + }), +}; -type Params = Ensure>; +export type Endpoint = CreateRouteDefinition; type Response = Readable; -export const handler: FileKindsRequestHandler = async ( - { files, fileKind }, - req, - res -) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; const { params: { id, fileName }, @@ -59,9 +55,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getDownloadRoute(fileKind.id), - validate: { - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.download.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts index 02c4df0685aea..00c5bd2312f89 100644 --- a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts +++ b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts @@ -5,31 +5,30 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; -import type { GetByIdFileKindHttpEndpoint } from '../../../common/api_routes'; -import type { FileKind } from '../../../common/types'; -import { FILES_API_ROUTES } from '../api_routes'; +import { schema } from '@kbn/config-schema'; +import type { FileJSON, FileKind } from '../../../common/types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; import { getById } from './helpers'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; - -type Response = GetByIdFileKindHttpEndpoint['output']; +import type { CreateHandler, FileKindRouter } from './types'; export const method = 'get' as const; -export const paramsSchema = schema.object({ - id: schema.string(), -}); -type Params = Ensure>; +const rt = { + params: schema.object({ + id: schema.string(), + }), +}; + +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ({ files, fileKind }, req, res) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; const { params: { id }, } = req; const { error, result: file } = await getById(fileService.asCurrentUser(), id, fileKind); if (error) return error; - const body: Response = { + const body: Endpoint['output'] = { file: file.toJSON(), }; return res.ok({ body }); @@ -40,9 +39,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getByIdRoute(fileKind.id), - validate: { - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.getById.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/index.ts b/x-pack/plugins/files/server/routes/file_kind/index.ts index 2a85b289f4ea6..1bd61b8fb2f2e 100644 --- a/x-pack/plugins/files/server/routes/file_kind/index.ts +++ b/x-pack/plugins/files/server/routes/file_kind/index.ts @@ -9,6 +9,7 @@ import { FileKind } from '../../../common/types'; import { FilesRouter } from '../types'; import { enhanceRouter } from './enhance_router'; + import * as create from './create'; import * as upload from './upload'; import * as update from './update'; diff --git a/x-pack/plugins/files/server/routes/file_kind/list.ts b/x-pack/plugins/files/server/routes/file_kind/list.ts index 3eaa183869b6c..3f1e36913bdc9 100644 --- a/x-pack/plugins/files/server/routes/file_kind/list.ts +++ b/x-pack/plugins/files/server/routes/file_kind/list.ts @@ -4,35 +4,29 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; -import type { ListFileKindHttpEndpoint } from '../../../common/api_routes'; -import type { FileKind } from '../../../common/types'; -import { FILES_API_ROUTES } from '../api_routes'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; +import { schema } from '@kbn/config-schema'; +import type { FileJSON, FileKind } from '../../../common/types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; +import type { CreateHandler, FileKindRouter } from './types'; export const method = 'get' as const; -export const querySchema = schema.object({ - page: schema.maybe(schema.number({ defaultValue: 1 })), - perPage: schema.maybe(schema.number({ defaultValue: 100 })), -}); - -type Query = Ensure>; +const rt = { + query: schema.object({ + page: schema.maybe(schema.number({ defaultValue: 1 })), + perPage: schema.maybe(schema.number({ defaultValue: 100 })), + }), +}; -type Response = ListFileKindHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files, fileKind }, - req, - res -) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { query: { page, perPage }, } = req; const { fileService } = await files; const response = await fileService.asCurrentUser().list({ fileKind, page, perPage }); - const body: Response = { + const body: Endpoint['output'] = { files: response.map((result) => result.toJSON()), }; return res.ok({ body }); @@ -43,9 +37,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getListRoute(fileKind.id), - validate: { - query: querySchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.list.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/share/get.ts b/x-pack/plugins/files/server/routes/file_kind/share/get.ts index 7ed01ff444a1f..b39e7c2ccbc92 100644 --- a/x-pack/plugins/files/server/routes/file_kind/share/get.ts +++ b/x-pack/plugins/files/server/routes/file_kind/share/get.ts @@ -4,37 +4,34 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Ensure } from '@kbn/utility-types'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { FileShareNotFoundError } from '../../../file_share_service/errors'; -import { FileGetShareHttpEndpoint, FILES_API_ROUTES } from '../../api_routes'; -import type { FileKind } from '../../../../common/types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../../api_routes'; +import type { FileKind, FileShareJSON } from '../../../../common/types'; -import { FileKindRouter, FileKindsRequestHandler } from '../types'; +import { CreateHandler, FileKindRouter } from '../types'; export const method = 'get' as const; -export const paramsSchema = schema.object({ - id: schema.string(), -}); - -type Params = Ensure>; +const rt = { + params: schema.object({ + id: schema.string(), + }), +}; -type Response = FileGetShareHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files }, - req, - res -) => { +export const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { params: { id }, } = req; try { - const body: Response = { share: await fileService.asCurrentUser().getShareObject({ id }) }; + const body: Endpoint['output'] = { + share: await fileService.asCurrentUser().getShareObject({ id }), + }; return res.ok({ body, }); @@ -51,9 +48,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getGetShareRoute(fileKind.id), - validate: { - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.share.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/share/list.ts b/x-pack/plugins/files/server/routes/file_kind/share/list.ts index 017f99db9b2e7..edd58dbed7b6e 100644 --- a/x-pack/plugins/files/server/routes/file_kind/share/list.ts +++ b/x-pack/plugins/files/server/routes/file_kind/share/list.ts @@ -4,30 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; +import { schema } from '@kbn/config-schema'; -import { FileListSharesHttpEndpoint, FILES_API_ROUTES } from '../../api_routes'; -import type { FileKind } from '../../../../common/types'; -import { FileKindRouter, FileKindsRequestHandler } from '../types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../../api_routes'; +import type { FileKind, FileShareJSON } from '../../../../common/types'; +import { CreateHandler, FileKindRouter } from '../types'; export const method = 'get' as const; -export const querySchema = schema.object({ - page: schema.maybe(schema.number()), - perPage: schema.maybe(schema.number()), - forFileId: schema.maybe(schema.string()), -}); - -type Query = Ensure>; +const rt = { + query: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + forFileId: schema.maybe(schema.string()), + }), +}; -type Response = FileListSharesHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files }, - req, - res -) => { +export const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { query: { forFileId, page, perPage }, @@ -37,7 +32,7 @@ export const handler: FileKindsRequestHandler = async ( .asCurrentUser() .listShareObjects({ fileId: forFileId, page, perPage }); - const body: Response = result; + const body: Endpoint['output'] = result; return res.ok({ body, }); @@ -48,9 +43,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getListShareRoute(fileKind.id), - validate: { - query: querySchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.share.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/share/share.ts b/x-pack/plugins/files/server/routes/file_kind/share/share.ts index 0ac43abdb0353..f4c8f660e080b 100644 --- a/x-pack/plugins/files/server/routes/file_kind/share/share.ts +++ b/x-pack/plugins/files/server/routes/file_kind/share/share.ts @@ -4,46 +4,37 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; - +import { schema } from '@kbn/config-schema'; import { ExpiryDateInThePastError } from '../../../file_share_service/errors'; -import { FileKindRouter, FileKindsRequestHandler } from '../types'; +import { CreateHandler, FileKindRouter } from '../types'; -import { FileShareHttpEndpoint, FILES_API_ROUTES } from '../../api_routes'; -import type { FileKind } from '../../../../common/types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../../api_routes'; +import type { FileKind, FileShareJSONWithToken } from '../../../../common/types'; import { getById } from '../helpers'; export const method = 'post' as const; -export const paramsSchema = schema.object({ - fileId: schema.string(), -}); - const nameRegex = /^[a-z0-9-_]+$/i; -export const bodySchema = schema.object({ - validUntil: schema.maybe(schema.number()), - name: schema.maybe( - schema.string({ - maxLength: 256, - validate: (v) => - nameRegex.test(v) ? undefined : 'Only alphanumeric, "-" and "_" characters are allowed.', - }) - ), -}); - -type Body = Ensure>; - -type Params = Ensure>; +const rt = { + params: schema.object({ + fileId: schema.string(), + }), + body: schema.object({ + validUntil: schema.maybe(schema.number()), + name: schema.maybe( + schema.string({ + maxLength: 256, + validate: (v) => + nameRegex.test(v) ? undefined : 'Only alphanumeric, "-" and "_" characters are allowed.', + }) + ), + }), +}; -type Response = FileShareHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files, fileKind }, - req, - res -) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; const { params: { fileId }, @@ -55,7 +46,7 @@ export const handler: FileKindsRequestHandler = async ( try { const share = await file.share({ name, validUntil }); - const body: Response = { + const body: Endpoint['output'] = { id: share.id, created: share.created, fileId: share.fileId, @@ -81,10 +72,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getShareRoute(fileKind.id), - validate: { - params: paramsSchema, - body: bodySchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.share.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/share/unshare.ts b/x-pack/plugins/files/server/routes/file_kind/share/unshare.ts index ec7cbfb09213e..49e59898433b1 100644 --- a/x-pack/plugins/files/server/routes/file_kind/share/unshare.ts +++ b/x-pack/plugins/files/server/routes/file_kind/share/unshare.ts @@ -4,29 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; +import { schema } from '@kbn/config-schema'; -import { FILES_API_ROUTES, FileUnshareHttpEndpoint } from '../../api_routes'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../../api_routes'; import type { FileKind } from '../../../../common/types'; -import { FileKindRouter, FileKindsRequestHandler } from '../types'; +import { CreateHandler, FileKindRouter } from '../types'; import { FileShareNotFoundError } from '../../../file_share_service/errors'; export const method = 'delete' as const; -export const paramsSchema = schema.object({ - id: schema.string(), -}); - -type Params = Ensure>; +const rt = { + params: schema.object({ + id: schema.string(), + }), +}; -type Response = FileUnshareHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files }, - req, - res -) => { +export const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { params: { id }, @@ -41,7 +36,7 @@ export const handler: FileKindsRequestHandler = async ( throw e; } - const body: Response = { + const body: Endpoint['output'] = { ok: true, }; return res.ok({ @@ -54,9 +49,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getUnshareRoute(fileKind.id), - validate: { - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.share.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/types.ts b/x-pack/plugins/files/server/routes/file_kind/types.ts index 19e00663a1d39..148767f27a285 100644 --- a/x-pack/plugins/files/server/routes/file_kind/types.ts +++ b/x-pack/plugins/files/server/routes/file_kind/types.ts @@ -6,6 +6,7 @@ */ import type { IRouter, RequestHandler } from '@kbn/core/server'; +import { AnyEndpoint } from '../api_routes'; import type { FilesRequestHandlerContext } from '../types'; export type FileKindRouter = IRouter; @@ -20,3 +21,9 @@ export type FileKindsRequestHandler

= Req B, FileKindsRequestHandlerContext >; + +export type CreateHandler = FileKindsRequestHandler< + E['inputs']['params'], + E['inputs']['query'], + E['inputs']['body'] +>; diff --git a/x-pack/plugins/files/server/routes/file_kind/update.ts b/x-pack/plugins/files/server/routes/file_kind/update.ts index 91a4bcb63d751..733f9c9ce78c2 100644 --- a/x-pack/plugins/files/server/routes/file_kind/update.ts +++ b/x-pack/plugins/files/server/routes/file_kind/update.ts @@ -5,39 +5,30 @@ * 2.0. */ -import type { Ensure } from '@kbn/utility-types'; -import { schema, TypeOf } from '@kbn/config-schema'; -import type { FileKind } from '../../../common/types'; -import type { UpdateFileKindHttpEndpoint } from '../../../common/api_routes'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; -import { FILES_API_ROUTES } from '../api_routes'; +import { schema } from '@kbn/config-schema'; +import type { FileJSON, FileKind } from '../../../common/types'; +import type { CreateHandler, FileKindRouter } from './types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; import { getById } from './helpers'; import * as commonSchemas from '../common_schemas'; export const method = 'patch' as const; -export const bodySchema = schema.object({ - name: schema.maybe(commonSchemas.fileName), - alt: schema.maybe(commonSchemas.fileAlt), - meta: schema.maybe(commonSchemas.fileMeta), -}); - -type Body = Ensure>; - -export const paramsSchema = schema.object({ - id: schema.string(), -}); - -type Params = Ensure>; +const rt = { + body: schema.object({ + name: schema.maybe(commonSchemas.fileName), + alt: schema.maybe(commonSchemas.fileAlt), + meta: schema.maybe(commonSchemas.fileMeta), + }), + params: schema.object({ + id: schema.string(), + }), +}; -type Response = UpdateFileKindHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -export const handler: FileKindsRequestHandler = async ( - { files, fileKind }, - req, - res -) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; const { params: { id }, @@ -46,7 +37,7 @@ export const handler: FileKindsRequestHandler = async ( const { error, result: file } = await getById(fileService.asCurrentUser(), id, fileKind); if (error) return error; await file.update(attrs); - const body: Response = { + const body: Endpoint['output'] = { file: file.toJSON(), }; return res.ok({ body }); @@ -57,10 +48,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { fileKindRouter[method]( { path: FILES_API_ROUTES.fileKind.getUpdateRoute(fileKind.id), - validate: { - body: bodySchema, - params: paramsSchema, - }, + validate: { ...rt }, options: { tags: fileKind.http.update.tags, }, diff --git a/x-pack/plugins/files/server/routes/file_kind/upload.ts b/x-pack/plugins/files/server/routes/file_kind/upload.ts index f04cb43e17b01..10c230de469b9 100644 --- a/x-pack/plugins/files/server/routes/file_kind/upload.ts +++ b/x-pack/plugins/files/server/routes/file_kind/upload.ts @@ -5,39 +5,38 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { ReplaySubject } from 'rxjs'; -import type { Ensure } from '@kbn/utility-types'; import { Readable } from 'stream'; import type { FileKind } from '../../../common/types'; -import type { UploadFileKindHttpEndpoint } from '../../../common/api_routes'; +import type { CreateRouteDefinition } from '../../../common/api_routes'; import { FILES_API_ROUTES } from '../api_routes'; import { fileErrors } from '../../file'; import { getById } from './helpers'; -import type { FileKindRouter, FileKindsRequestHandler } from './types'; +import type { FileKindRouter } from './types'; +import { CreateHandler } from './types'; export const method = 'put' as const; -export const bodySchema = schema.stream(); -type Body = TypeOf; - -export const querySchema = schema.object({ - selfDestructOnAbort: schema.maybe(schema.boolean()), -}); -type Query = Ensure>; - -export const paramsSchema = schema.object({ - id: schema.string(), -}); -type Params = Ensure>; +const rt = { + params: schema.object({ + id: schema.string(), + }), + body: schema.stream(), + query: schema.object({ + selfDestructOnAbort: schema.maybe(schema.boolean()), + }), +}; -type Response = UploadFileKindHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition< + typeof rt, + { + ok: true; + size: number; + } +>; -export const handler: FileKindsRequestHandler = async ( - { files, fileKind }, - req, - res -) => { +export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { // Ensure that we are listening to the abort stream as early as possible. // In local testing I found that there is a chance for us to miss the abort event // if we subscribe too late. @@ -75,7 +74,7 @@ export const handler: FileKindsRequestHandler = async ( } finally { sub.unsubscribe(); } - const body: Response = { ok: true, size: file.data.size! }; + const body: Endpoint['output'] = { ok: true, size: file.data.size! }; return res.ok({ body }); }; @@ -87,9 +86,7 @@ export function register(fileKindRouter: FileKindRouter, fileKind: FileKind) { { path: FILES_API_ROUTES.fileKind.getUploadRoute(fileKind.id), validate: { - body: bodySchema, - params: paramsSchema, - query: querySchema, + ...rt, }, options: { tags: fileKind.http.create.tags, diff --git a/x-pack/plugins/files/server/routes/find.ts b/x-pack/plugins/files/server/routes/find.ts index 6841e4a019841..43348491b77b5 100644 --- a/x-pack/plugins/files/server/routes/find.ts +++ b/x-pack/plugins/files/server/routes/find.ts @@ -4,12 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; -import type { Ensure } from '@kbn/utility-types'; -import type { FilesRouter } from './types'; - -import { FindFilesHttpEndpoint, FILES_API_ROUTES } from './api_routes'; -import type { FilesRequestHandler } from './types'; +import { schema } from '@kbn/config-schema'; +import type { CreateHandler, FilesRouter } from './types'; +import { FileJSON } from '../../common'; +import { FILES_API_ROUTES, CreateRouteDefinition } from './api_routes'; const method = 'post' as const; @@ -19,41 +17,39 @@ const string256 = schema.string({ maxLength: 256 }); const stringOrArrayOfStrings = schema.oneOf([string64, schema.arrayOf(string64)]); const nameStringOrArrayOfNameStrings = schema.oneOf([string256, schema.arrayOf(string256)]); -const bodySchema = schema.object({ - kind: schema.maybe(stringOrArrayOfStrings), - status: schema.maybe(stringOrArrayOfStrings), - name: schema.maybe(nameStringOrArrayOfNameStrings), - meta: schema.maybe(schema.object({}, { unknowns: 'allow' })), -}); - -const querySchema = schema.object({ - page: schema.maybe(schema.number()), - perPage: schema.maybe(schema.number({ defaultValue: 100 })), -}); - -type Body = Ensure>; - -type Query = Ensure>; +const rt = { + body: schema.object({ + kind: schema.maybe(stringOrArrayOfStrings), + status: schema.maybe(stringOrArrayOfStrings), + extension: schema.maybe(stringOrArrayOfStrings), + name: schema.maybe(nameStringOrArrayOfNameStrings), + meta: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), + query: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number({ defaultValue: 100 })), + }), +}; -type Response = FindFilesHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; function toArray(val: string | string[]) { return Array.isArray(val) ? val : [val]; } -const handler: FilesRequestHandler = async ({ files }, req, res) => { +const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { body: { meta, extension, kind, name, status }, query, } = req; - const body: Response = { + const body: Endpoint['output'] = { files: await fileService.asCurrentUser().find({ - kind: kind && toArray(kind), - name: name && toArray(name), - status: status && toArray(status), - extension: extension && toArray(extension), + kind: kind ? toArray(kind) : undefined, + name: name ? toArray(name) : undefined, + status: status ? toArray(status) : undefined, + extension: extension ? toArray(extension) : undefined, meta, ...query, }), @@ -72,9 +68,7 @@ export function register(router: FilesRouter) { router[method]( { path: FILES_API_ROUTES.find, - validate: { - body: bodySchema, - }, + validate: { ...rt }, }, handler ); diff --git a/x-pack/plugins/files/server/routes/metrics.ts b/x-pack/plugins/files/server/routes/metrics.ts index 5d7e08773c04e..eb1d0ae39b9a1 100644 --- a/x-pack/plugins/files/server/routes/metrics.ts +++ b/x-pack/plugins/files/server/routes/metrics.ts @@ -6,16 +6,17 @@ */ import type { FilesRouter } from './types'; -import { FilesMetricsHttpEndpoint, FILES_API_ROUTES } from './api_routes'; +import { FilesMetrics } from '../../common'; +import { CreateRouteDefinition, FILES_API_ROUTES } from './api_routes'; import type { FilesRequestHandler } from './types'; const method = 'get' as const; -type Response = FilesMetricsHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition<{}, FilesMetrics>; const handler: FilesRequestHandler = async ({ files }, req, res) => { const { fileService } = await files; - const body: Response = await fileService.asCurrentUser().getUsageMetrics(); + const body: Endpoint['output'] = await fileService.asCurrentUser().getUsageMetrics(); return res.ok({ body, }); diff --git a/x-pack/plugins/files/server/routes/public_facing/download.ts b/x-pack/plugins/files/server/routes/public_facing/download.ts index fd908c2ca8abd..bd739f93077fe 100644 --- a/x-pack/plugins/files/server/routes/public_facing/download.ts +++ b/x-pack/plugins/files/server/routes/public_facing/download.ts @@ -4,40 +4,34 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Ensure } from '@kbn/utility-types'; -import { schema, TypeOf } from '@kbn/config-schema'; - +import { schema } from '@kbn/config-schema'; +import { Readable } from 'stream'; import { NoDownloadAvailableError } from '../../file/errors'; import { FileNotFoundError } from '../../file_service/errors'; import { FileShareNotFoundError, FileShareTokenInvalidError, } from '../../file_share_service/errors'; -import type { FilesRouter, FilesRequestHandler } from '../types'; -import { FilePublicDownloadHttpEndpoint, FILES_API_ROUTES } from '../api_routes'; +import type { FilesRouter } from '../types'; +import { CreateRouteDefinition, FILES_API_ROUTES } from '../api_routes'; import { getDownloadHeadersForFile } from '../common'; import { fileNameWithExt } from '../common_schemas'; +import { CreateHandler } from '../types'; const method = 'get' as const; -const querySchema = schema.object({ - token: schema.string(), -}); - -export const paramsSchema = schema.object({ - fileName: schema.maybe(fileNameWithExt), -}); - -type Query = Ensure>; - -type Params = Ensure< - FilePublicDownloadHttpEndpoint['inputs']['params'], - TypeOf ->; +const rt = { + query: schema.object({ + token: schema.string(), + }), + params: schema.object({ + fileName: schema.maybe(fileNameWithExt), + }), +}; -type Response = FilePublicDownloadHttpEndpoint['output']; +export type Endpoint = CreateRouteDefinition; -const handler: FilesRequestHandler = async ({ files }, req, res) => { +const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { query: { token }, @@ -46,7 +40,7 @@ const handler: FilesRequestHandler = async ({ files }, req, res) try { const file = await fileService.asInternalUser().getByToken(token); - const body: Response = await file.downloadContent(); + const body: Readable = await file.downloadContent(); return res.ok({ body, headers: getDownloadHeadersForFile(file, fileName), @@ -73,10 +67,7 @@ export function register(router: FilesRouter) { router[method]( { path: FILES_API_ROUTES.public.download, - validate: { - query: querySchema, - params: paramsSchema, - }, + validate: { ...rt }, options: { authRequired: false, }, diff --git a/x-pack/plugins/files/server/routes/types.ts b/x-pack/plugins/files/server/routes/types.ts index 357ad0984244c..f47808a85c3bf 100644 --- a/x-pack/plugins/files/server/routes/types.ts +++ b/x-pack/plugins/files/server/routes/types.ts @@ -16,6 +16,7 @@ import type { } from '@kbn/core/server'; import type { FileServiceStart } from '../file_service'; import { Counters } from '../usage'; +import { AnyEndpoint } from './api_routes'; export interface FilesRequestHandlerContext extends RequestHandlerContext { files: Promise<{ @@ -38,3 +39,9 @@ export type FilesRequestHandler< > = RequestHandler; export type AsyncResponse = Promise>; + +export type CreateHandler = FilesRequestHandler< + E['inputs']['params'], + E['inputs']['query'], + E['inputs']['body'] +>;