diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a593a25..7b1f535 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,7 @@ "ms-azuretools.vscode-docker", "vscode-icons-team.vscode-icons", "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "yoavbls.pretty-ts-errors" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index b7bb8b8..c55feba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "editor.formatOnSave": true, "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, "[prisma]": { "editor.defaultFormatter": "Prisma.prisma" }, diff --git a/sdk/kickstart_fastify_1.0.4.ts b/sdk/kickstart_fastify_1.0.4.ts index c331cdd..40832bf 100644 --- a/sdk/kickstart_fastify_1.0.4.ts +++ b/sdk/kickstart_fastify_1.0.4.ts @@ -20,8 +20,18 @@ export interface LoginRequest { export type LoginResult = object +export interface LoginResponse { + result: LoginResult + message?: string +} + export type LogoutResult = object +export interface LogoutResponse { + result: LogoutResult + message?: string +} + export interface CreateProductRequest { /** * @minLength 1 @@ -57,6 +67,11 @@ export interface CreateProductResult { productPrice: number } +export interface CreateProductResponse { + result: CreateProductResult + message?: string +} + export interface DeleteProductByIdParams { /** * @minLength 12 @@ -67,6 +82,11 @@ export interface DeleteProductByIdParams { export type DeleteProductByIdResult = object +export interface DeleteProductByIdResponse { + result: DeleteProductByIdResult + message?: string +} + export interface GetProductByIdParams { /** * @minLength 12 @@ -95,6 +115,11 @@ export interface GetProductByIdResult { productPrice: number } +export interface GetProductByIdResponse { + result: GetProductByIdResult + message?: string +} + export interface GetProductsQuery { /** * @minLength 12 @@ -137,6 +162,11 @@ export type GetProductsResult = { productPrice: number }[] +export interface GetProductsResponse { + result: GetProductsResult + message?: string +} + export interface UpdateProductRequest { /** * @minLength 12 @@ -177,6 +207,11 @@ export interface UpdateProductResult { productPrice: number } +export interface UpdateProductResponse { + result: UpdateProductResult + message?: string +} + export type QueryParamsType = Record export type ResponseFormat = keyof Omit @@ -382,13 +417,7 @@ export class Api extends HttpClient - this.request< - { - result: LoginResult - message?: string - }, - any - >({ + this.request({ path: `/auth/login`, method: 'POST', body: data, @@ -406,13 +435,7 @@ export class Api extends HttpClient - this.request< - { - result: LogoutResult - message?: string - }, - any - >({ + this.request({ path: `/auth/logout`, method: 'POST', format: 'json', @@ -428,30 +451,8 @@ export class Api extends HttpClient - this.request< - { - result: CreateProductResult - message?: string - }, - any - >({ + createProduct: (data: CreateProductRequest, params: RequestParams = {}) => + this.request({ path: `/product`, method: 'POST', body: data, @@ -492,13 +493,7 @@ export class Api extends HttpClient - this.request< - { - result: GetProductsResult - message?: string - }, - any - >({ + this.request({ path: `/product`, method: 'GET', query: query, @@ -514,35 +509,8 @@ export class Api extends HttpClient - this.request< - { - result: UpdateProductResult - message?: string - }, - any - >({ + updateProduct: (data: UpdateProductRequest, params: RequestParams = {}) => + this.request({ path: `/product`, method: 'PUT', body: data, @@ -560,13 +528,7 @@ export class Api extends HttpClient - this.request< - { - result: GetProductByIdResult - message?: string - }, - any - >({ + this.request({ path: `/product/${productId}`, method: 'GET', format: 'json', @@ -582,13 +544,7 @@ export class Api extends HttpClient - this.request< - { - result: DeleteProductByIdResult - message?: string - }, - any - >({ + this.request({ path: `/product/${productId}`, method: 'DELETE', format: 'json', diff --git a/src/constants/systemConstants.ts b/src/constants/systemConstants.ts index 1d7e1c2..3ce6d92 100644 --- a/src/constants/systemConstants.ts +++ b/src/constants/systemConstants.ts @@ -4,10 +4,12 @@ export const SYS_CONSTANTS: { JWT_COOKIE_KEY: string SALT_ROUNDS: number SWAGGER_ROUTE: string + SCHEMA_SYMBOL: symbol } = { NANOID_LENGTH: 12, DEFAULT_ENCODING: 'utf-8', JWT_COOKIE_KEY: 'jwt', SALT_ROUNDS: 10, SWAGGER_ROUTE: 'docs', + SCHEMA_SYMBOL: Symbol('Schema symbol'), } diff --git a/src/handlers/auth/login/request.ts b/src/handlers/auth/login/request.ts index 98acd7b..edc9e7b 100644 --- a/src/handlers/auth/login/request.ts +++ b/src/handlers/auth/login/request.ts @@ -1,13 +1,16 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const loginRequest = Type.Object( - { - userId: field.userId, - password: Type.String(), - }, - { $id: 'loginRequest' }, +export const loginRequest = handlerUtils.buildRequestSchema( + Type.Object( + { + userId: field.userId, + password: Type.String(), + }, + { $id: 'loginRequest' }, + ), ) export type LoginRequest = Static diff --git a/src/handlers/auth/login/response.ts b/src/handlers/auth/login/response.ts index 3b264af..856e68c 100644 --- a/src/handlers/auth/login/response.ts +++ b/src/handlers/auth/login/response.ts @@ -1,10 +1,10 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' +import { handlerUtils } from '@/utils/handler' export const loginResult = Type.Object({}, { $id: 'loginResult' }) -export const loginResponse = createDefaultResponseSchema(loginResult, 'loginResponse') +export const loginResponse = handlerUtils.buildResponseSchema(loginResult, 'loginResponse') export type LoginResult = Static diff --git a/src/handlers/auth/login/schema.ts b/src/handlers/auth/login/schema.ts index c48950d..0d2d948 100644 --- a/src/handlers/auth/login/schema.ts +++ b/src/handlers/auth/login/schema.ts @@ -17,6 +17,6 @@ export const loginSchema: FastifySchema = { description: description, body: Type.Ref(loginRequest), response: { - 200: loginResponse, + 200: Type.Ref(loginResponse), }, } diff --git a/src/handlers/auth/logout/response.ts b/src/handlers/auth/logout/response.ts index 9705a54..2bce3a2 100644 --- a/src/handlers/auth/logout/response.ts +++ b/src/handlers/auth/logout/response.ts @@ -1,10 +1,10 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' +import { handlerUtils } from '@/utils/handler' export const logoutResult = Type.Object({}, { $id: 'logoutResult' }) -export const logoutResponse = createDefaultResponseSchema(logoutResult, 'logoutResponse') +export const logoutResponse = handlerUtils.buildResponseSchema(logoutResult, 'logoutResponse') export type LogoutResult = Static diff --git a/src/handlers/auth/logout/schema.ts b/src/handlers/auth/logout/schema.ts index b177a39..f094a0c 100644 --- a/src/handlers/auth/logout/schema.ts +++ b/src/handlers/auth/logout/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { logoutResponse } from './response' @@ -14,6 +15,6 @@ export const logoutSchema: FastifySchema = { tags: ['Auth'], description: description, response: { - 200: logoutResponse, + 200: Type.Ref(logoutResponse), }, } diff --git a/src/handlers/base/defaultResponse.ts b/src/handlers/base/defaultResponse.ts deleted file mode 100644 index a56d050..0000000 --- a/src/handlers/base/defaultResponse.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TObject, TRef, TSchema, Type } from '@sinclair/typebox' - -export const messageSchema = Type.Optional(Type.String()) - -type DefaultResponse = TObject<{ - result: TRef - message: typeof messageSchema -}> - -export const createDefaultResponseSchema = (resultSchema: T, $id: string): DefaultResponse => { - return Type.Object( - { - result: Type.Ref(resultSchema), - message: messageSchema, - }, - { $id: $id }, - ) -} diff --git a/src/handlers/product/createProduct/request.ts b/src/handlers/product/createProduct/request.ts index b3d4436..a72ea9a 100644 --- a/src/handlers/product/createProduct/request.ts +++ b/src/handlers/product/createProduct/request.ts @@ -1,14 +1,17 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const createProductRequest = Type.Object( - { - productName: field.productName, - productDescription: field.productDescription, - productPrice: field.productPrice, - }, - { $id: 'createProductRequest' }, +export const createProductRequest = handlerUtils.buildRequestSchema( + Type.Object( + { + productName: field.productName, + productDescription: field.productDescription, + productPrice: field.productPrice, + }, + { $id: 'createProductRequest' }, + ), ) export type CreateProductRequest = Static diff --git a/src/handlers/product/createProduct/response.ts b/src/handlers/product/createProduct/response.ts index 6ac3b68..c9abe5b 100644 --- a/src/handlers/product/createProduct/response.ts +++ b/src/handlers/product/createProduct/response.ts @@ -1,8 +1,9 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' import { field } from '@/schemas/fields' +import { handlerUtils } from './../../../utils/handler' + export const createProductResult = Type.Object( { productId: field.productId, @@ -13,7 +14,7 @@ export const createProductResult = Type.Object( { $id: 'createProductResult' }, ) -export const createProductResponse = createDefaultResponseSchema(createProductResult, 'createProductResponse') +export const createProductResponse = handlerUtils.buildResponseSchema(createProductResult, 'createProductResponse') export type CreateProductResult = Static diff --git a/src/handlers/product/createProduct/schema.ts b/src/handlers/product/createProduct/schema.ts index 0b80980..082c2c3 100644 --- a/src/handlers/product/createProduct/schema.ts +++ b/src/handlers/product/createProduct/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { createProductRequest } from './request' @@ -14,8 +15,8 @@ export const createProductSchema: FastifySchema = { summary: `create a new product`, tags: ['Product'], description: description, - body: createProductRequest, + body: Type.Ref(createProductRequest), response: { - 201: createProductResponse, + 201: Type.Ref(createProductResponse), }, } diff --git a/src/handlers/product/deleteProductById/parameter.ts b/src/handlers/product/deleteProductById/parameter.ts index a7a6caa..d9fa6e0 100644 --- a/src/handlers/product/deleteProductById/parameter.ts +++ b/src/handlers/product/deleteProductById/parameter.ts @@ -1,12 +1,15 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const deleteProductByIdParams = Type.Object( - { - productId: field.productId, - }, - { $id: 'deleteProductByIdParams' }, +export const deleteProductByIdParams = handlerUtils.buildParamSchema( + Type.Object( + { + productId: field.productId, + }, + { $id: 'deleteProductByIdParams' }, + ), ) export type DeleteProductByIdParams = Static diff --git a/src/handlers/product/deleteProductById/response.ts b/src/handlers/product/deleteProductById/response.ts index 1ac9a77..ed294c9 100644 --- a/src/handlers/product/deleteProductById/response.ts +++ b/src/handlers/product/deleteProductById/response.ts @@ -1,10 +1,10 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' +import { handlerUtils } from '@/utils/handler' export const deleteProductByIdResult = Type.Object({}, { $id: 'deleteProductByIdResult' }) -export const deleteProductByIdResponse = createDefaultResponseSchema(deleteProductByIdResult, 'deleteProductByIdResponse') +export const deleteProductByIdResponse = handlerUtils.buildResponseSchema(deleteProductByIdResult, 'deleteProductByIdResponse') export type DeleteProductByIdResult = Static export type DeleteProductByIdResponse = Static diff --git a/src/handlers/product/deleteProductById/schema.ts b/src/handlers/product/deleteProductById/schema.ts index d5572fd..93040bf 100644 --- a/src/handlers/product/deleteProductById/schema.ts +++ b/src/handlers/product/deleteProductById/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { deleteProductByIdParams } from './parameter' @@ -14,8 +15,8 @@ export const deleteProductByIdSchema: FastifySchema = { summary: `delete product by productId`, tags: ['Product'], description: description, - params: deleteProductByIdParams, + params: Type.Ref(deleteProductByIdParams), response: { - 204: deleteProductByIdResponse, + 204: Type.Ref(deleteProductByIdResponse), }, } diff --git a/src/handlers/product/getProductById/parameter.ts b/src/handlers/product/getProductById/parameter.ts index 62d3106..a607979 100644 --- a/src/handlers/product/getProductById/parameter.ts +++ b/src/handlers/product/getProductById/parameter.ts @@ -1,12 +1,15 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const getProductByIdParams = Type.Object( - { - productId: field.productId, - }, - { $id: 'getProductByIdParams' }, +export const getProductByIdParams = handlerUtils.buildParamSchema( + Type.Object( + { + productId: field.productId, + }, + { $id: 'getProductByIdParams' }, + ), ) export type GetProductByIdParams = Static diff --git a/src/handlers/product/getProductById/response.ts b/src/handlers/product/getProductById/response.ts index 5ab8cc7..9ab1ddc 100644 --- a/src/handlers/product/getProductById/response.ts +++ b/src/handlers/product/getProductById/response.ts @@ -1,7 +1,7 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' export const getProductByIdResult = Type.Object( { @@ -13,7 +13,7 @@ export const getProductByIdResult = Type.Object( { $id: 'getProductByIdResult' }, ) -export const getProductByIdResponse = createDefaultResponseSchema(getProductByIdResult, 'getProductByIdResponse') +export const getProductByIdResponse = handlerUtils.buildResponseSchema(getProductByIdResult, 'getProductByIdResponse') export type GetProductByIdResult = Static diff --git a/src/handlers/product/getProductById/schema.ts b/src/handlers/product/getProductById/schema.ts index 06be76f..bf48c3f 100644 --- a/src/handlers/product/getProductById/schema.ts +++ b/src/handlers/product/getProductById/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { getProductByIdParams } from './parameter' @@ -14,8 +15,8 @@ export const getProductByIdSchema: FastifySchema = { summary: `get a product by productId`, tags: ['Product'], description: description, - params: getProductByIdParams, + params: Type.Ref(getProductByIdParams), response: { - 200: getProductByIdResponse, + 200: Type.Ref(getProductByIdResponse), }, } diff --git a/src/handlers/product/getProducts/query.ts b/src/handlers/product/getProducts/query.ts index 9fbc64f..2365e2a 100644 --- a/src/handlers/product/getProducts/query.ts +++ b/src/handlers/product/getProducts/query.ts @@ -1,16 +1,19 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const getProductsQuery = Type.Object( - { - productId: Type.Optional(field.productId), - productName: Type.Optional(field.productName), - productDescription: Type.Optional(field.productDescription), - productPriceFrom: Type.Optional(field.productPrice), - productPriceTo: Type.Optional(field.productPrice), - }, - { $id: 'getProductsQuery' }, +export const getProductsQuery = handlerUtils.buildQuerySchema( + Type.Object( + { + productId: Type.Optional(field.productId), + productName: Type.Optional(field.productName), + productDescription: Type.Optional(field.productDescription), + productPriceFrom: Type.Optional(field.productPrice), + productPriceTo: Type.Optional(field.productPrice), + }, + { $id: 'getProductsQuery' }, + ), ) export type GetProductsQuery = Static diff --git a/src/handlers/product/getProducts/response.ts b/src/handlers/product/getProducts/response.ts index 2aafc70..06ca26b 100644 --- a/src/handlers/product/getProducts/response.ts +++ b/src/handlers/product/getProducts/response.ts @@ -1,7 +1,7 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' export const getProductsResult = Type.Array( Type.Object({ @@ -13,7 +13,7 @@ export const getProductsResult = Type.Array( { $id: 'getProductsResult' }, ) -export const getProductsResponse = createDefaultResponseSchema(getProductsResult, 'getProductsResponse') +export const getProductsResponse = handlerUtils.buildResponseSchema(getProductsResult, 'getProductsResponse') export type GetProductsResult = Static diff --git a/src/handlers/product/getProducts/schema.ts b/src/handlers/product/getProducts/schema.ts index dcbd3f1..e9c3ae9 100644 --- a/src/handlers/product/getProducts/schema.ts +++ b/src/handlers/product/getProducts/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { getProductsQuery } from './query' @@ -14,8 +15,8 @@ export const getProductsSchema: FastifySchema = { summary: `get products by filter`, tags: ['Product'], description: description, - querystring: getProductsQuery, + querystring: Type.Ref(getProductsQuery), response: { - 200: getProductsResponse, + 200: Type.Ref(getProductsResponse), }, } diff --git a/src/handlers/product/updateProduct/request.ts b/src/handlers/product/updateProduct/request.ts index 8472463..c183438 100644 --- a/src/handlers/product/updateProduct/request.ts +++ b/src/handlers/product/updateProduct/request.ts @@ -1,15 +1,18 @@ import { Static, Type } from '@sinclair/typebox' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' -export const updateProductRequest = Type.Object( - { - productId: field.productId, - productName: field.productName, - productDescription: field.productDescription, - productPrice: field.productPrice, - }, - { $id: 'updateProductRequest' }, +export const updateProductRequest = handlerUtils.buildRequestSchema( + Type.Object( + { + productId: field.productId, + productName: field.productName, + productDescription: field.productDescription, + productPrice: field.productPrice, + }, + { $id: 'updateProductRequest' }, + ), ) export type UpdateProductRequest = Static diff --git a/src/handlers/product/updateProduct/response.ts b/src/handlers/product/updateProduct/response.ts index 297f20e..d487f2d 100644 --- a/src/handlers/product/updateProduct/response.ts +++ b/src/handlers/product/updateProduct/response.ts @@ -1,7 +1,7 @@ import { Static, Type } from '@sinclair/typebox' -import { createDefaultResponseSchema } from '@/handlers/base/defaultResponse' import { field } from '@/schemas/fields' +import { handlerUtils } from '@/utils/handler' export const updateProductResult = Type.Object( { @@ -13,7 +13,7 @@ export const updateProductResult = Type.Object( { $id: 'updateProductResult' }, ) -export const updateProductResponse = createDefaultResponseSchema(updateProductResult, 'updateProductResponse') +export const updateProductResponse = handlerUtils.buildResponseSchema(updateProductResult, 'updateProductResponse') export type UpdateProductResult = Static diff --git a/src/handlers/product/updateProduct/schema.ts b/src/handlers/product/updateProduct/schema.ts index 1d9f05c..d748ac3 100644 --- a/src/handlers/product/updateProduct/schema.ts +++ b/src/handlers/product/updateProduct/schema.ts @@ -1,3 +1,4 @@ +import { Type } from '@sinclair/typebox' import { FastifySchema } from 'fastify' import { updateProductRequest } from './request' @@ -14,8 +15,8 @@ export const updateProductSchema: FastifySchema = { summary: `update a product`, tags: ['Product'], description: description, - body: updateProductRequest, + body: Type.Ref(updateProductRequest), response: { - 201: updateProductResponse, + 201: Type.Ref(updateProductResponse), }, } diff --git a/src/plugins/swagger.ts b/src/plugins/swagger.ts index 3c1c582..db0ae6b 100644 --- a/src/plugins/swagger.ts +++ b/src/plugins/swagger.ts @@ -68,7 +68,7 @@ const autoloadSchemas = function (path: string) { }) const onlySchemaObj: { [key: string]: OpenAPIV3.SchemaObject } = {} for (const key in obj) { - if (key.endsWith('Params') || key.endsWith('Query') || key.endsWith('Request') || key.endsWith('Result') || key === 'responseMessage') { + if (Reflect.get(obj[key], SYS_CONSTANTS.SCHEMA_SYMBOL)) { onlySchemaObj[key] = obj[key] as OpenAPIV3.SchemaObject } } diff --git a/src/utils/handler.ts b/src/utils/handler.ts new file mode 100644 index 0000000..921e68b --- /dev/null +++ b/src/utils/handler.ts @@ -0,0 +1,54 @@ +import { TObject, TRef, TSchema, Type } from '@sinclair/typebox' + +import { SYS_CONSTANTS } from '@/constants/systemConstants' + +const messageSchema = Type.Optional(Type.String()) + +const buildParamSchema = (requestSchema: T): T => { + if (!requestSchema.$id) { + throw new Error('Missing schema $id property') + } + Reflect.defineProperty(requestSchema, SYS_CONSTANTS.SCHEMA_SYMBOL, { value: true }) + return requestSchema +} + +const buildQuerySchema = (requestSchema: T): T => { + if (!requestSchema.$id) { + throw new Error('Missing schema $id property') + } + Reflect.defineProperty(requestSchema, SYS_CONSTANTS.SCHEMA_SYMBOL, { value: true }) + return requestSchema +} + +const buildRequestSchema = (requestSchema: T): T => { + if (!requestSchema.$id) { + throw new Error('Missing schema $id property') + } + Reflect.defineProperty(requestSchema, SYS_CONSTANTS.SCHEMA_SYMBOL, { value: true }) + return requestSchema +} + +type DefaultResponse = TObject<{ + result: TRef + message: typeof messageSchema +}> + +const buildResponseSchema = (resultSchema: T, $id: string): DefaultResponse => { + Reflect.defineProperty(resultSchema, SYS_CONSTANTS.SCHEMA_SYMBOL, { value: true }) + const responseSchema = Type.Object( + { + result: Type.Ref(resultSchema), + message: messageSchema, + }, + { $id: $id }, + ) + Reflect.defineProperty(responseSchema, SYS_CONSTANTS.SCHEMA_SYMBOL, { value: true }) + return responseSchema +} + +export const handlerUtils = { + buildParamSchema, + buildQuerySchema, + buildRequestSchema, + buildResponseSchema, +}