From 20ab889cf86153d521213932f7beb5be6d6e813e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LUDWIG?= Date: Sun, 18 Aug 2024 01:31:02 +0100 Subject: [PATCH] chore: improve type safety Tuyau --- TODO.md | 3 +-- apps/api/.eslintrc.json | 3 +-- .../health/get_health_controller.ts | 26 +++++++++++++++---- .../pages/[id]/get_wikipedia_page_by_id.ts | 14 ++++++++-- .../wikipedia/pages/get_wikipedia_pages.ts | 7 +++-- apps/api/app/exceptions/handler.ts | 4 +++ .../middleware/app_key_security_middleware.ts | 6 ++++- apps/api/app/middleware/auth_middleware.ts | 2 +- .../container_bindings_middleware.ts | 2 +- .../force_json_response_middleware.ts | 2 +- apps/api/config/tuyau.ts | 2 +- package.json | 4 ++- .../src/WikipediaClient.tsx | 14 ++++++++-- patches/@tuyau__client@0.1.2.patch | 19 ++++++++++++++ patches/@tuyau__openapi@0.2.0.patch | 13 ++++++++++ pnpm-lock.yaml | 14 +++++++--- 16 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 patches/@tuyau__client@0.1.2.patch create mode 100644 patches/@tuyau__openapi@0.2.0.patch diff --git a/TODO.md b/TODO.md index ac7912d..5bdd6a9 100644 --- a/TODO.md +++ b/TODO.md @@ -40,8 +40,7 @@ - [ ] Share VineJS validators between `website` and `api` - [ ] Implement Wikipedia Game Solver (`website`) - [x] Init Next.js project - - [ ] Try to use for API calls - - [ ] Hard code 2 pages to test if it works with `console.log` in the browser + - [x] Try to use for API calls - [ ] Implement a form with inputs, button to submit, and list all pages to go from one to another, or none if it is not possible - [ ] Add images, links to the pages + good UI/UX - [ ] Autocompletion page titles diff --git a/apps/api/.eslintrc.json b/apps/api/.eslintrc.json index 036b3ab..2a05dd4 100644 --- a/apps/api/.eslintrc.json +++ b/apps/api/.eslintrc.json @@ -11,9 +11,8 @@ } }, { - "files": ["app/controllers/**/*.ts", "app/middleware/**/*.ts"], + "files": ["app/controllers/**/*.ts"], "rules": { - "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/naming-convention": "off" } } diff --git a/apps/api/app/controllers/health/get_health_controller.ts b/apps/api/app/controllers/health/get_health_controller.ts index a9f5d1a..dedc27c 100644 --- a/apps/api/app/controllers/health/get_health_controller.ts +++ b/apps/api/app/controllers/health/get_health_controller.ts @@ -2,15 +2,31 @@ import { healthChecks } from "#start/health.ts" import { middleware } from "#start/kernel.ts" import type { HttpContext } from "@adonisjs/core/http" import router from "@adonisjs/core/services/router" +import type { HealthCheckReport } from "@adonisjs/core/types/health" export default class get_health_controller { - public async handle(context: HttpContext) { + public async handle(context: HttpContext): Promise< + | { + __response: HealthCheckReport + __status: 200 + } + | { + __response: HealthCheckReport + __status: 503 + } + > { const report = await healthChecks.run() - if (report.isHealthy) { - return context.response.ok(report) + if (!report.isHealthy) { + return context.response.serviceUnavailable(report) } - return context.response.serviceUnavailable(report) + return context.response.ok(report) } } -router.get("/health", [get_health_controller]).use(middleware.appKeySecurity()) +router + .get("/health", [get_health_controller]) + .use(middleware.appKeySecurity()) + .openapi({ + description: "Ensure that the application is in a healthy state.", + tags: ["health"], + }) diff --git a/apps/api/app/controllers/wikipedia/pages/[id]/get_wikipedia_page_by_id.ts b/apps/api/app/controllers/wikipedia/pages/[id]/get_wikipedia_page_by_id.ts index c080473..f269889 100644 --- a/apps/api/app/controllers/wikipedia/pages/[id]/get_wikipedia_page_by_id.ts +++ b/apps/api/app/controllers/wikipedia/pages/[id]/get_wikipedia_page_by_id.ts @@ -1,3 +1,4 @@ +import type { ExceptionMessage } from "#app/exceptions/handler.ts" import Page, { type PageWithInternalLinksRaw } from "#app/models/page.ts" import { throttle } from "#start/limiter.ts" import type { HttpContext } from "@adonisjs/core/http" @@ -13,13 +14,22 @@ export const get_wikipedia_page_by_id_validator = vine.compile( ) export default class get_wikipedia_page_by_id { - public async handle(context: HttpContext): Promise { + public async handle(context: HttpContext): Promise< + | { + __response: ExceptionMessage + __status: 404 + } + | { + __response: PageWithInternalLinksRaw + __status: 200 + } + > { const payload = await context.request.validateUsing( get_wikipedia_page_by_id_validator, ) const page = await Page.findOrFail(payload.params.id) await page.load("internalLinks") - return page + return context.response.ok(page) } } diff --git a/apps/api/app/controllers/wikipedia/pages/get_wikipedia_pages.ts b/apps/api/app/controllers/wikipedia/pages/get_wikipedia_pages.ts index 93b2237..e413bcc 100644 --- a/apps/api/app/controllers/wikipedia/pages/get_wikipedia_pages.ts +++ b/apps/api/app/controllers/wikipedia/pages/get_wikipedia_pages.ts @@ -25,14 +25,17 @@ export const get_wikipedia_pages_validator = vine.compile( ) export default class get_wikipedia_pages { - public async handle(context: HttpContext): Promise { + public async handle(context: HttpContext): Promise<{ + __response: PageRaw[] + __status: 200 + }> { const payload = await context.request.validateUsing( get_wikipedia_pages_validator, ) const pages = await Page.query() .whereLike("title", `${payload.title}%`) .limit(payload.limit) - return pages + return context.response.ok(pages) } } diff --git a/apps/api/app/exceptions/handler.ts b/apps/api/app/exceptions/handler.ts index 5249ba7..a4a31d4 100644 --- a/apps/api/app/exceptions/handler.ts +++ b/apps/api/app/exceptions/handler.ts @@ -2,6 +2,10 @@ import type { HttpContext } from "@adonisjs/core/http" import { ExceptionHandler } from "@adonisjs/core/http" import app from "@adonisjs/core/services/app" +export interface ExceptionMessage { + message: string +} + export default class HttpExceptionHandler extends ExceptionHandler { /** * In debug mode, the exception handler will display verbose errors with pretty printed stack traces. diff --git a/apps/api/app/middleware/app_key_security_middleware.ts b/apps/api/app/middleware/app_key_security_middleware.ts index e711ee1..6866f33 100644 --- a/apps/api/app/middleware/app_key_security_middleware.ts +++ b/apps/api/app/middleware/app_key_security_middleware.ts @@ -1,9 +1,13 @@ +import type { ExceptionMessage } from "#app/exceptions/handler.ts" import { APP_KEY, APP_KEY_HEADER_NAME } from "#config/app.ts" import type { HttpContext } from "@adonisjs/core/http" import type { NextFn } from "@adonisjs/core/types/http" export default class AppKeySecurityMiddleware { - public async handle(context: HttpContext, next: NextFn) { + public async handle( + context: HttpContext, + next: NextFn, + ): Promise { if (context.request.header(APP_KEY_HEADER_NAME) === APP_KEY) { return next() } diff --git a/apps/api/app/middleware/auth_middleware.ts b/apps/api/app/middleware/auth_middleware.ts index 322c0ea..dbf2d46 100644 --- a/apps/api/app/middleware/auth_middleware.ts +++ b/apps/api/app/middleware/auth_middleware.ts @@ -17,7 +17,7 @@ export default class AuthMiddleware { options: { guards?: Array } = {}, - ) { + ): Promise { await context.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo, }) diff --git a/apps/api/app/middleware/container_bindings_middleware.ts b/apps/api/app/middleware/container_bindings_middleware.ts index dedb7f5..f584762 100644 --- a/apps/api/app/middleware/container_bindings_middleware.ts +++ b/apps/api/app/middleware/container_bindings_middleware.ts @@ -9,7 +9,7 @@ import type { NextFn } from "@adonisjs/core/types/http" * - And bind "Logger" class to the "context.logger" object. */ export default class ContainerBindingsMiddleware { - public async handle(context: HttpContext, next: NextFn) { + public async handle(context: HttpContext, next: NextFn): Promise { context.containerResolver.bindValue(HttpContext, context) context.containerResolver.bindValue(Logger, context.logger) diff --git a/apps/api/app/middleware/force_json_response_middleware.ts b/apps/api/app/middleware/force_json_response_middleware.ts index 1ee14d6..88cfa2c 100644 --- a/apps/api/app/middleware/force_json_response_middleware.ts +++ b/apps/api/app/middleware/force_json_response_middleware.ts @@ -5,7 +5,7 @@ import type { NextFn } from "@adonisjs/core/types/http" * Updating the "Accept" header to always accept "application/json" response from the server. This will force the internals of the framework like validator errors or auth errors to return a JSON response. */ export default class ForceJsonResponseMiddleware { - public async handle({ request }: HttpContext, next: NextFn) { + public async handle({ request }: HttpContext, next: NextFn): Promise { const headers = request.headers() headers.accept = "application/json" diff --git a/apps/api/config/tuyau.ts b/apps/api/config/tuyau.ts index 1c60673..9cb8fe3 100644 --- a/apps/api/config/tuyau.ts +++ b/apps/api/config/tuyau.ts @@ -12,7 +12,7 @@ const tuyauConfig = defineConfig({ title: "Wikipedia Game Solver API", version: VERSION, }, - tags: [{ name: "users" }, { name: "wikipedia" }], + tags: [{ name: "health" }, { name: "users" }, { name: "wikipedia" }], servers: [{ url: env.get("API_URL") }], }, }, diff --git a/package.json b/package.json index e7d2c7f..838c757 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ }, "pnpm": { "patchedDependencies": { - "@tuyau/core@0.1.4": "patches/@tuyau__core@0.1.4.patch" + "@tuyau/core@0.1.4": "patches/@tuyau__core@0.1.4.patch", + "@tuyau/openapi@0.2.0": "patches/@tuyau__openapi@0.2.0.patch", + "@tuyau/client@0.1.2": "patches/@tuyau__client@0.1.2.patch" } } } diff --git a/packages/wikipedia-game-solver/src/WikipediaClient.tsx b/packages/wikipedia-game-solver/src/WikipediaClient.tsx index baea45f..50daaad 100644 --- a/packages/wikipedia-game-solver/src/WikipediaClient.tsx +++ b/packages/wikipedia-game-solver/src/WikipediaClient.tsx @@ -39,10 +39,20 @@ export const WikipediaClient: React.FC = () => { const data = await api.wikipedia .pages({ id: fromPageWikipediaLinks.pageId }) - // TODO: any - .$get({} as any) + .$get({}) .unwrap() console.log(data) + + const data2 = await api.wikipedia.pages + .$get({ + query: { + title: "Node.j", + limit: 5, + }, + }) + .unwrap() + console.log(data2) + // const deepInternalLinks = await getDeepWikipediaPageInternalLinks({ // locale: localeWikipedia, // data: { diff --git a/patches/@tuyau__client@0.1.2.patch b/patches/@tuyau__client@0.1.2.patch new file mode 100644 index 0000000..08e083b --- /dev/null +++ b/patches/@tuyau__client@0.1.2.patch @@ -0,0 +1,19 @@ +diff --git a/build/index.d.ts b/build/index.d.ts +index fd28b2fd3f91f0312e933189df5b635eeb692281..445d47f6f18a981bba7c0e784a03972bdf99c880 100644 +--- a/build/index.d.ts ++++ b/build/index.d.ts +@@ -83,11 +83,11 @@ type TuyauRpcClient> = { + response: infer Res extends Record; + request: infer Request; + } ? K extends '$get' | '$head' ? unknown extends Request ? (options?: TuyauQueryOptions & { +- query?: Request; ++ query?: Omit; + }) => ResponseOrUnwrap : {} extends Request ? (options?: TuyauQueryOptions & { +- query?: Request; ++ query?: Omit; + }) => ResponseOrUnwrap : (options: TuyauQueryOptions & { +- query: Request; ++ query?: Omit; + }) => ResponseOrUnwrap : {} extends Request ? (body?: Request | null, options?: TuyauQueryOptions) => ResponseOrUnwrap : (body: Request, options?: TuyauQueryOptions) => ResponseOrUnwrap : K extends '$url' ? () => string : CreateParams; + }; + type CreateParams> = Extract extends infer Path extends string ? IsNever extends true ? Prettify> : ((params: { diff --git a/patches/@tuyau__openapi@0.2.0.patch b/patches/@tuyau__openapi@0.2.0.patch new file mode 100644 index 0000000..6fffea8 --- /dev/null +++ b/patches/@tuyau__openapi@0.2.0.patch @@ -0,0 +1,13 @@ +diff --git a/build/chunk-DFECKFQT.js b/build/chunk-DFECKFQT.js +index 0740bf03f8c9de643f09fdcf02c43e40eb5efa55..ff348509b0e77b0b98e6e6db57c3355e3e0914f7 100644 +--- a/build/chunk-DFECKFQT.js ++++ b/build/chunk-DFECKFQT.js +@@ -266,7 +266,7 @@ var OpenApiGenerator = class { + for (const prop of type.getProperties()) { + const propName = prop.getName(); + const type2 = prop.getValueDeclaration()?.getType(); +- if (!type2) ++ if (!type2 || (mode === 'request' && ['cookies', 'headers', 'params'].includes(propName))) + continue; + if (type2.isArray()) { + properties2[propName] = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d77865..ec80ab0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,9 +245,15 @@ catalogs: version: 2.0.5 patchedDependencies: + '@tuyau/client@0.1.2': + hash: fb6mal6i73tvzidaazfbwsp5ya + path: patches/@tuyau__client@0.1.2.patch '@tuyau/core@0.1.4': hash: zzabo2h6qjt52qr7ntgx4dwdpa path: patches/@tuyau__core@0.1.4.patch + '@tuyau/openapi@0.2.0': + hash: i6nyw7jxy3i3iokbuvzj62ysse + path: patches/@tuyau__openapi@0.2.0.patch importers: @@ -315,7 +321,7 @@ importers: version: 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)) '@tuyau/openapi': specifier: 'catalog:' - version: 0.2.0(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0))) + version: 0.2.0(patch_hash=i6nyw7jxy3i3iokbuvzj62ysse)(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0))) '@tuyau/utils': specifier: 'catalog:' version: 0.0.4 @@ -598,7 +604,7 @@ importers: dependencies: '@tuyau/client': specifier: 'catalog:' - version: 0.1.2 + version: 0.1.2(patch_hash=fb6mal6i73tvzidaazfbwsp5ya) devDependencies: '@repo/api': specifier: 'workspace:' @@ -12578,7 +12584,7 @@ snapshots: mkdirp: 3.0.1 path-browserify: 1.0.1 - '@tuyau/client@0.1.2': + '@tuyau/client@0.1.2(patch_hash=fb6mal6i73tvzidaazfbwsp5ya)': dependencies: '@poppinss/matchit': 3.1.2 ky: 1.6.0 @@ -12589,7 +12595,7 @@ snapshots: '@adonisjs/core': 6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0) ts-morph: 22.0.0 - '@tuyau/openapi@0.2.0(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)))': + '@tuyau/openapi@0.2.0(patch_hash=i6nyw7jxy3i3iokbuvzj62ysse)(@tuyau/core@0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)))': dependencies: '@tuyau/core': 0.1.4(patch_hash=zzabo2h6qjt52qr7ntgx4dwdpa)(@adonisjs/core@6.12.1(@adonisjs/assembler@7.7.0(typescript@5.5.4))(@vinejs/vine@2.1.0)) defu: 6.1.4