From 2fd1b8e83798dd93a1dd12714fd99f1c212f1800 Mon Sep 17 00:00:00 2001 From: Prisca Date: Sat, 9 Nov 2024 15:39:32 +0100 Subject: [PATCH 1/4] chore: add and setup Tuyau --- .adonisjs/api.ts | 237 +++++++++++++++++++++++++++++++++++++++++++ .env.ci | 2 + .env.example | 2 + adonisrc.ts | 2 + config/tuyau.ts | 17 ++++ inertia/app/app.tsx | 9 +- inertia/app/ssr.tsx | 8 +- inertia/app/tuyau.ts | 7 ++ package.json | 9 +- pnpm-lock.yaml | 72 ++++++++++++- start/env.ts | 2 + 11 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 .adonisjs/api.ts create mode 100644 config/tuyau.ts create mode 100644 inertia/app/tuyau.ts diff --git a/.adonisjs/api.ts b/.adonisjs/api.ts new file mode 100644 index 0000000..d90ec64 --- /dev/null +++ b/.adonisjs/api.ts @@ -0,0 +1,237 @@ +import type { MakeTuyauRequest, MakeTuyauResponse } from '@tuyau/utils/types' +import type { InferInput } from '@vinejs/vine/types' + +type LoginGetHead = { + request: unknown + response: MakeTuyauResponse +} +type ChatPost = { + request: unknown + response: MakeTuyauResponse +} +type GameGetHead = { + request: unknown + response: MakeTuyauResponse +} +type GameSearchGetHead = { + request: unknown + response: MakeTuyauResponse +} +type GameSearchPost = { + request: unknown + response: MakeTuyauResponse +} +type GameSearchDelete = { + request: unknown + response: MakeTuyauResponse +} +type GameSessionIdAcceptGetHead = { + request: unknown + response: MakeTuyauResponse +} +type GameSessionIdReadyGetHead = { + request: unknown + response: MakeTuyauResponse +} +type GameSessionIdGetHead = { + request: unknown + response: MakeTuyauResponse +} +type GameSessionIdAnswerPost = { + request: unknown + response: MakeTuyauResponse +} +type ProfileGetHead = { + request: unknown + response: MakeTuyauResponse +} +type AuthIdRedirectGetHead = { + request: unknown + response: MakeTuyauResponse +} +type AuthIdCallbackGetHead = { + request: unknown + response: MakeTuyauResponse +} +export interface ApiDefinition { + 'login': { + '$url': { + }; + '$get': LoginGetHead; + '$head': LoginGetHead; + }; + 'chat': { + '$url': { + }; + '$post': ChatPost; + }; + 'game': { + '$url': { + }; + '$get': GameGetHead; + '$head': GameGetHead; + 'search': { + '$url': { + }; + '$get': GameSearchGetHead; + '$head': GameSearchGetHead; + '$post': GameSearchPost; + '$delete': GameSearchDelete; + }; + 'session': { + ':sessionId': { + 'accept': { + '$url': { + }; + '$get': GameSessionIdAcceptGetHead; + '$head': GameSessionIdAcceptGetHead; + }; + 'ready': { + '$url': { + }; + '$get': GameSessionIdReadyGetHead; + '$head': GameSessionIdReadyGetHead; + }; + '$url': { + }; + '$get': GameSessionIdGetHead; + '$head': GameSessionIdGetHead; + 'answer': { + '$url': { + }; + '$post': GameSessionIdAnswerPost; + }; + }; + }; + }; + 'profile': { + '$url': { + }; + '$get': ProfileGetHead; + '$head': ProfileGetHead; + }; + 'auth': { + ':provider': { + 'redirect': { + '$url': { + }; + '$get': AuthIdRedirectGetHead; + '$head': AuthIdRedirectGetHead; + }; + 'callback': { + '$url': { + }; + '$get': AuthIdCallbackGetHead; + '$head': AuthIdCallbackGetHead; + }; + }; + }; +} +const routes = [ + { + params: [], + name: 'home', + path: '/', + method: ["GET","HEAD"], + types: {} as unknown, + }, + { + params: [], + name: 'login', + path: '/login', + method: ["GET","HEAD"], + types: {} as LoginGetHead, + }, + { + params: [], + name: 'chat.store', + path: '/chat', + method: ["POST"], + types: {} as ChatPost, + }, + { + params: [], + name: 'game', + path: '/game', + method: ["GET","HEAD"], + types: {} as GameGetHead, + }, + { + params: [], + name: 'search', + path: '/game/search', + method: ["GET","HEAD"], + types: {} as GameSearchGetHead, + }, + { + params: [], + name: 'game.searchingQueue', + path: '/game/search', + method: ["POST"], + types: {} as GameSearchPost, + }, + { + params: [], + name: 'game.cancelQueue', + path: '/game/search', + method: ["DELETE"], + types: {} as GameSearchDelete, + }, + { + params: ["sessionId"], + name: 'game.handleAccept', + path: '/game/session/:sessionId/accept', + method: ["GET","HEAD"], + types: {} as GameSessionIdAcceptGetHead, + }, + { + params: ["sessionId"], + name: 'game.handleReady', + path: '/game/session/:sessionId/ready', + method: ["GET","HEAD"], + types: {} as GameSessionIdReadyGetHead, + }, + { + params: ["sessionId"], + name: 'game.session', + path: '/game/session/:sessionId', + method: ["GET","HEAD"], + types: {} as GameSessionIdGetHead, + }, + { + params: ["sessionId"], + name: 'game.handleAnswer', + path: '/game/session/:sessionId/answer', + method: ["POST"], + types: {} as GameSessionIdAnswerPost, + }, + { + params: [], + name: 'profile', + path: '/profile', + method: ["GET","HEAD"], + types: {} as ProfileGetHead, + }, + { + params: ["provider"], + name: 'auth.redirect', + path: '/auth/:provider/redirect', + method: ["GET","HEAD"], + types: {} as AuthIdRedirectGetHead, + }, + { + params: ["provider"], + name: 'auth.callback', + path: '/auth/:provider/callback', + method: ["GET","HEAD"], + types: {} as AuthIdCallbackGetHead, + }, +] as const; +export const api = { + routes, + definition: {} as ApiDefinition +} +declare module '@tuyau/inertia/types' { + type InertiaApi = typeof api + export interface Api extends InertiaApi {} +} diff --git a/.env.ci b/.env.ci index d93be5c..fe43d3c 100644 --- a/.env.ci +++ b/.env.ci @@ -27,3 +27,5 @@ TWITCH_CALLBACK_URL=http://localhost:3333/auth/twitch/callback # Game length in seconds GAME_LENGTH=90 + +VITE_API_URL=http://localhost:3333 diff --git a/.env.example b/.env.example index c7bb4b1..99af2f2 100644 --- a/.env.example +++ b/.env.example @@ -28,5 +28,7 @@ TWITCH_CALLBACK_URL= # Game length in seconds GAME_LENGTH=90 +VITE_API_URL=http://localhost:3333 + diff --git a/adonisrc.ts b/adonisrc.ts index 0342623..33db428 100644 --- a/adonisrc.ts +++ b/adonisrc.ts @@ -14,6 +14,7 @@ export default defineConfig({ () => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands'), () => import('adonisjs-scheduler/commands'), + () => import('@tuyau/core/commands') ], /* @@ -51,6 +52,7 @@ export default defineConfig({ environment: ['console'], }, () => import('#core/providers/port_provider'), + () => import('@tuyau/core/tuyau_provider') ], /* diff --git a/config/tuyau.ts b/config/tuyau.ts new file mode 100644 index 0000000..4b272c9 --- /dev/null +++ b/config/tuyau.ts @@ -0,0 +1,17 @@ +import { defineConfig } from '@tuyau/core' + +const tuyauConfig = defineConfig({ + codegen: { + /** + * Filters the definitions and named routes to be generated + */ + // definitions: { + // only: [], + // } + // routes: { + // only: [], + // } + } +}) + +export default tuyauConfig \ No newline at end of file diff --git a/inertia/app/app.tsx b/inertia/app/app.tsx index 7971c9c..a183957 100644 --- a/inertia/app/app.tsx +++ b/inertia/app/app.tsx @@ -3,7 +3,9 @@ import './app.css' import { createInertiaApp } from '@inertiajs/react' import { hydrateRoot } from 'react-dom/client' import '~/features/i18n/i18n' +import { TuyauProvider } from '@tuyau/inertia/react' import { Layout } from '~/features/layout/layout' +import { tuyau } from './tuyau' const appName = import.meta.env.VITE_APP_NAME || 'Mind Reader' @@ -22,6 +24,11 @@ createInertiaApp({ }, setup({ el, App, props }) { - hydrateRoot(el, ) + hydrateRoot( + el, + + + , + ) }, }) diff --git a/inertia/app/ssr.tsx b/inertia/app/ssr.tsx index 3ceebd5..fc74171 100644 --- a/inertia/app/ssr.tsx +++ b/inertia/app/ssr.tsx @@ -1,6 +1,8 @@ import { createInertiaApp } from '@inertiajs/react' import ReactDOMServer from 'react-dom/server' import '~/features/i18n/i18n' +import { TuyauProvider } from '@tuyau/inertia/react' +import { tuyau } from '~/app/tuyau' import { Layout } from '~/features/layout/layout' export default function render(page: string) { @@ -16,6 +18,10 @@ export default function render(page: string) { page.default.layout = (page) => {page} return page }, - setup: ({ App, props }) => , + setup: ({ App, props }) => ( + + + + ), }) } diff --git a/inertia/app/tuyau.ts b/inertia/app/tuyau.ts new file mode 100644 index 0000000..ee05a5e --- /dev/null +++ b/inertia/app/tuyau.ts @@ -0,0 +1,7 @@ +import { createTuyau } from '@tuyau/client' +import { api } from '../../.adonisjs/api' + +export const tuyau = createTuyau({ + api, + baseUrl: import.meta.env.VITE_API_URL, +}) diff --git a/package.json b/package.json index e80cc67..26f33f0 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,10 @@ "@inertiajs/react": "^1.2.0", "@poppinss/utils": "^6.8.3", "@rlanz/ally-twitch": "^0.1.2", + "@tuyau/client": "^0.1.3", + "@tuyau/core": "^0.2.1", + "@tuyau/inertia": "^0.0.4", + "@tuyau/utils": "^0.0.4", "@vinejs/vine": "^2.1.0", "adonisjs-scheduler": "^1.0.5", "edge.js": "^6.2.0", @@ -84,7 +88,10 @@ "unocss": "^0.60.4" }, "hotHook": { - "boundaries": ["./src/features/**/*.ts", "./src/core/middleware*.ts"] + "boundaries": [ + "./src/features/**/*.ts", + "./src/core/middleware*.ts" + ] }, "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a9131c..5252a48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 2.1.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@vinejs/vine@2.1.0)(edge.js@6.2.0) '@adonisjs/inertia': specifier: 2.0.1 - version: 2.0.1(vr3jjysmolwjuoldufzrimc7im) + version: 2.0.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@adonisjs/session@7.5.0(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@adonisjs/redis@8.0.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)))(@japa/api-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4))(@japa/browser-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4)(playwright@1.48.2))(edge.js@6.2.0))(@adonisjs/vite@3.0.0-11(2ihquorgoalxkeatz7xitgg2ki))(@japa/api-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4))(edge.js@6.2.0) '@adonisjs/lucid': specifier: ^20.6.0 version: 20.6.0(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(luxon@3.5.0)(pg@8.13.0) @@ -59,6 +59,18 @@ importers: '@rlanz/ally-twitch': specifier: ^0.1.2 version: 0.1.2(@adonisjs/ally@5.0.2(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)))(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)) + '@tuyau/client': + specifier: ^0.1.3 + version: 0.1.3 + '@tuyau/core': + specifier: ^0.2.1 + version: 0.2.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)) + '@tuyau/inertia': + specifier: ^0.0.4 + version: 0.0.4(@inertiajs/react@1.2.0(react@18.3.1))(@tuyau/client@0.1.3)(react@18.3.1) + '@tuyau/utils': + specifier: ^0.0.4 + version: 0.0.4 '@vinejs/vine': specifier: ^2.1.0 version: 2.1.0 @@ -1245,6 +1257,33 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tuyau/client@0.1.3': + resolution: {integrity: sha512-00IqAkbae7a154bpcavuJYcv1oRQW4nfSVND2jZn/ZACHPEcFVWoBmgM9Gl0wdELoHotsQSQ2eC+nDqfDvanAQ==} + + '@tuyau/core@0.2.1': + resolution: {integrity: sha512-CUA1bfEoqVOLFEzSQ8cTHBu+Z+Sbv8CFArr0iKctuApbQ+YJVuY8sAW4J0KgjfoTFOC+wVYtU2Dcp90ZVBZj8g==} + engines: {node: '>=20.6.0'} + peerDependencies: + '@adonisjs/core': ^6.2.0 + + '@tuyau/inertia@0.0.4': + resolution: {integrity: sha512-LrVm0uNNYgeJReM/kF3Fli3WL4fyT0mUeJeoGsr5lg58mIBlleyTuJ/GK35/14dWYjsUt8IO7pQVvAUdWqVmQg==} + peerDependencies: + '@inertiajs/react': ^1.0.16 + '@inertiajs/vue3': ^1.0.16 + '@tuyau/client': 0.1.3 + react: ^18.3.1 + vue: ^3.4.27 + peerDependenciesMeta: + '@inertiajs/react': + optional: true + '@inertiajs/vue3': + optional: true + react: + optional: true + vue: + optional: true + '@tuyau/utils@0.0.4': resolution: {integrity: sha512-ex6CAJNLiTuOvx7nUrgs8FwNG/t88Mi8QTLSO3muHbB6vBSpYimZ6iSUkk4cjEFd4XDy0y+24GDgXKoBfGf4ag==} @@ -2625,6 +2664,10 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + ky@1.7.2: + resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} + engines: {node: '>=18'} + local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -2855,6 +2898,9 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + object-to-formdata@4.5.1: + resolution: {integrity: sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw==} + object.assign@4.1.5: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} @@ -3989,7 +4035,7 @@ snapshots: '@vinejs/vine': 2.1.0 edge.js: 6.2.0 - '@adonisjs/inertia@2.0.1(vr3jjysmolwjuoldufzrimc7im)': + '@adonisjs/inertia@2.0.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@adonisjs/session@7.5.0(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@adonisjs/redis@8.0.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)))(@japa/api-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4))(@japa/browser-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4)(playwright@1.48.2))(edge.js@6.2.0))(@adonisjs/vite@3.0.0-11(2ihquorgoalxkeatz7xitgg2ki))(@japa/api-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4))(edge.js@6.2.0)': dependencies: '@adonisjs/core': 6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0) '@adonisjs/session': 7.5.0(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))(@adonisjs/redis@8.0.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0)))(@japa/api-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4))(@japa/browser-client@2.0.3(@japa/assert@3.0.0(@japa/runner@3.1.4)(openapi-types@12.1.3))(@japa/runner@3.1.4)(playwright@1.48.2))(edge.js@6.2.0) @@ -4923,6 +4969,24 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tuyau/client@0.1.3': + dependencies: + '@poppinss/matchit': 3.1.2 + ky: 1.7.2 + object-to-formdata: 4.5.1 + + '@tuyau/core@0.2.1(@adonisjs/core@6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0))': + dependencies: + '@adonisjs/core': 6.14.1(@adonisjs/assembler@7.8.2(typescript@5.6.3))(@vinejs/vine@2.1.0)(edge.js@6.2.0) + ts-morph: 23.0.0 + + '@tuyau/inertia@0.0.4(@inertiajs/react@1.2.0(react@18.3.1))(@tuyau/client@0.1.3)(react@18.3.1)': + dependencies: + '@tuyau/client': 0.1.3 + optionalDependencies: + '@inertiajs/react': 1.2.0(react@18.3.1) + react: 18.3.1 + '@tuyau/utils@0.0.4': {} '@types/babel__core@7.20.5': @@ -6388,6 +6452,8 @@ snapshots: kolorist@1.8.0: {} + ky@1.7.2: {} + local-pkg@0.5.0: dependencies: mlly: 1.7.2 @@ -6563,6 +6629,8 @@ snapshots: object-keys@1.1.1: {} + object-to-formdata@4.5.1: {} + object.assign@4.1.5: dependencies: call-bind: 1.0.7 diff --git a/start/env.ts b/start/env.ts index 6a72b9b..b5d0747 100644 --- a/start/env.ts +++ b/start/env.ts @@ -59,4 +59,6 @@ export default await Env.create(new URL('../', import.meta.url), { |---------------------------------------------------------- */ GAME_LENGTH: Env.schema.number(), + + VITE_API_URL: Env.schema.string(), }) From 58a6a76a6744eea82e39da34e3d8cc25c3d0b16c Mon Sep 17 00:00:00 2001 From: Prisca Date: Sat, 19 Apr 2025 17:53:43 +0200 Subject: [PATCH 2/4] feat: update to use tuyau --- .adonisjs/api.ts | 195 +++++++++--------- .adonisjs/index.ts | 0 adonisrc.ts | 4 +- config/cors.ts | 3 +- config/tuyau.ts | 4 +- inertia/app/tuyau.ts | 1 + .../features/home/components/profile_card.tsx | 2 +- .../features/matchmaking/use_matchmaking.ts | 25 ++- inertia/features/utils/components/link.tsx | 49 ++++- inertia/pages/game_session.tsx | 2 +- inertia/pages/home.tsx | 2 +- inertia/pages/landing_page.tsx | 2 +- inertia/pages/login.tsx | 2 +- inertia/pages/profile.tsx | 2 +- inertia/pages/search.tsx | 1 + package.json | 5 +- .../cancel_matchmaking_controller.ts | 4 +- .../search_matchmaking_controller.ts | 9 +- ...ontroller.ts => matchmaking_controller.ts} | 5 +- start/routes.ts | 14 +- 20 files changed, 190 insertions(+), 141 deletions(-) create mode 100644 .adonisjs/index.ts rename src/features/pages/controllers/{search_game_controller.ts => matchmaking_controller.ts} (81%) diff --git a/.adonisjs/api.ts b/.adonisjs/api.ts index d90ec64..dd102c2 100644 --- a/.adonisjs/api.ts +++ b/.adonisjs/api.ts @@ -19,19 +19,27 @@ type GameSearchGetHead = { } type GameSearchPost = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse< + import('../src/features/matchmaking/controllers/search_matchmaking_controller.ts').default['handle'] + > } type GameSearchDelete = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse< + import('../src/features/matchmaking/controllers/cancel_matchmaking_controller.ts').default['handle'] + > } type GameSessionIdAcceptGetHead = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse< + import('../src/features/matchmaking/controllers/accept_matchmaking_controller.ts').default['handle'] + > } type GameSessionIdReadyGetHead = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse< + import('../src/features/game_session/controllers/game_ready_controller.ts').default['handle'] + > } type GameSessionIdGetHead = { request: unknown @@ -39,7 +47,9 @@ type GameSessionIdGetHead = { } type GameSessionIdAnswerPost = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse< + import('../src/features/game_session/controllers/game_answer_controller.ts').default['handle'] + > } type ProfileGetHead = { request: unknown @@ -54,182 +64,171 @@ type AuthIdCallbackGetHead = { response: MakeTuyauResponse } export interface ApiDefinition { - 'login': { - '$url': { - }; - '$get': LoginGetHead; - '$head': LoginGetHead; - }; - 'chat': { - '$url': { - }; - '$post': ChatPost; - }; - 'game': { - '$url': { - }; - '$get': GameGetHead; - '$head': GameGetHead; - 'search': { - '$url': { - }; - '$get': GameSearchGetHead; - '$head': GameSearchGetHead; - '$post': GameSearchPost; - '$delete': GameSearchDelete; - }; - 'session': { + login: { + $url: {} + $get: LoginGetHead + $head: LoginGetHead + } + chat: { + $url: {} + $post: ChatPost + } + game: { + $url: {} + $get: GameGetHead + $head: GameGetHead + search: { + $url: {} + $get: GameSearchGetHead + $head: GameSearchGetHead + $post: GameSearchPost + $delete: GameSearchDelete + } + session: { ':sessionId': { - 'accept': { - '$url': { - }; - '$get': GameSessionIdAcceptGetHead; - '$head': GameSessionIdAcceptGetHead; - }; - 'ready': { - '$url': { - }; - '$get': GameSessionIdReadyGetHead; - '$head': GameSessionIdReadyGetHead; - }; - '$url': { - }; - '$get': GameSessionIdGetHead; - '$head': GameSessionIdGetHead; - 'answer': { - '$url': { - }; - '$post': GameSessionIdAnswerPost; - }; - }; - }; - }; - 'profile': { - '$url': { - }; - '$get': ProfileGetHead; - '$head': ProfileGetHead; - }; - 'auth': { + accept: { + $url: {} + $get: GameSessionIdAcceptGetHead + $head: GameSessionIdAcceptGetHead + } + ready: { + $url: {} + $get: GameSessionIdReadyGetHead + $head: GameSessionIdReadyGetHead + } + $url: {} + $get: GameSessionIdGetHead + $head: GameSessionIdGetHead + answer: { + $url: {} + $post: GameSessionIdAnswerPost + } + } + } + } + profile: { + $url: {} + $get: ProfileGetHead + $head: ProfileGetHead + } + auth: { ':provider': { - 'redirect': { - '$url': { - }; - '$get': AuthIdRedirectGetHead; - '$head': AuthIdRedirectGetHead; - }; - 'callback': { - '$url': { - }; - '$get': AuthIdCallbackGetHead; - '$head': AuthIdCallbackGetHead; - }; - }; - }; + redirect: { + $url: {} + $get: AuthIdRedirectGetHead + $head: AuthIdRedirectGetHead + } + callback: { + $url: {} + $get: AuthIdCallbackGetHead + $head: AuthIdCallbackGetHead + } + } + } } const routes = [ { params: [], name: 'home', path: '/', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as unknown, }, { params: [], name: 'login', path: '/login', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as LoginGetHead, }, { params: [], name: 'chat.store', path: '/chat', - method: ["POST"], + method: ['POST'], types: {} as ChatPost, }, { params: [], name: 'game', path: '/game', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as GameGetHead, }, { params: [], - name: 'search', + name: 'matchmaking', path: '/game/search', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as GameSearchGetHead, }, { params: [], name: 'game.searchingQueue', path: '/game/search', - method: ["POST"], + method: ['POST'], types: {} as GameSearchPost, }, { params: [], name: 'game.cancelQueue', path: '/game/search', - method: ["DELETE"], + method: ['DELETE'], types: {} as GameSearchDelete, }, { - params: ["sessionId"], + params: ['sessionId'], name: 'game.handleAccept', path: '/game/session/:sessionId/accept', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as GameSessionIdAcceptGetHead, }, { - params: ["sessionId"], + params: ['sessionId'], name: 'game.handleReady', path: '/game/session/:sessionId/ready', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as GameSessionIdReadyGetHead, }, { - params: ["sessionId"], + params: ['sessionId'], name: 'game.session', path: '/game/session/:sessionId', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as GameSessionIdGetHead, }, { - params: ["sessionId"], + params: ['sessionId'], name: 'game.handleAnswer', path: '/game/session/:sessionId/answer', - method: ["POST"], + method: ['POST'], types: {} as GameSessionIdAnswerPost, }, { params: [], name: 'profile', path: '/profile', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as ProfileGetHead, }, { - params: ["provider"], + params: ['provider'], name: 'auth.redirect', path: '/auth/:provider/redirect', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as AuthIdRedirectGetHead, }, { - params: ["provider"], + params: ['provider'], name: 'auth.callback', path: '/auth/:provider/callback', - method: ["GET","HEAD"], + method: ['GET', 'HEAD'], types: {} as AuthIdCallbackGetHead, }, -] as const; +] as const export const api = { routes, - definition: {} as ApiDefinition + definition: {} as ApiDefinition, } declare module '@tuyau/inertia/types' { type InertiaApi = typeof api diff --git a/.adonisjs/index.ts b/.adonisjs/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/adonisrc.ts b/adonisrc.ts index 33db428..c31b8f1 100644 --- a/adonisrc.ts +++ b/adonisrc.ts @@ -14,7 +14,7 @@ export default defineConfig({ () => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands'), () => import('adonisjs-scheduler/commands'), - () => import('@tuyau/core/commands') + () => import('@tuyau/core/commands'), ], /* @@ -52,7 +52,7 @@ export default defineConfig({ environment: ['console'], }, () => import('#core/providers/port_provider'), - () => import('@tuyau/core/tuyau_provider') + () => import('@tuyau/core/tuyau_provider'), ], /* diff --git a/config/cors.ts b/config/cors.ts index dd79007..b452951 100644 --- a/config/cors.ts +++ b/config/cors.ts @@ -1,4 +1,5 @@ import { defineConfig } from '@adonisjs/cors' +import env from '#start/env' /** * Configuration options to tweak the CORS policy. The following @@ -8,7 +9,7 @@ import { defineConfig } from '@adonisjs/cors' */ const corsConfig = defineConfig({ enabled: true, - origin: [], + origin: [env.get('HOST')], methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], headers: true, exposeHeaders: [], diff --git a/config/tuyau.ts b/config/tuyau.ts index 4b272c9..dd04438 100644 --- a/config/tuyau.ts +++ b/config/tuyau.ts @@ -11,7 +11,7 @@ const tuyauConfig = defineConfig({ // routes: { // only: [], // } - } + }, }) -export default tuyauConfig \ No newline at end of file +export default tuyauConfig diff --git a/inertia/app/tuyau.ts b/inertia/app/tuyau.ts index ee05a5e..de2bfd3 100644 --- a/inertia/app/tuyau.ts +++ b/inertia/app/tuyau.ts @@ -4,4 +4,5 @@ import { api } from '../../.adonisjs/api' export const tuyau = createTuyau({ api, baseUrl: import.meta.env.VITE_API_URL, + headers: {}, }) diff --git a/inertia/features/home/components/profile_card.tsx b/inertia/features/home/components/profile_card.tsx index f2bb7a6..0b20647 100644 --- a/inertia/features/home/components/profile_card.tsx +++ b/inertia/features/home/components/profile_card.tsx @@ -53,7 +53,7 @@ export const ProfileCard = (props: Props) => { {profileButton && ( - + {t('home.buttons.profile')} )} diff --git a/inertia/features/matchmaking/use_matchmaking.ts b/inertia/features/matchmaking/use_matchmaking.ts index 71e6c04..aa52e8d 100644 --- a/inertia/features/matchmaking/use_matchmaking.ts +++ b/inertia/features/matchmaking/use_matchmaking.ts @@ -1,4 +1,5 @@ import { router } from '@inertiajs/react' +import { useForm } from '@inertiajs/react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useTransmit } from '~/hooks/use_transmit' @@ -6,19 +7,22 @@ import type { SearchProps } from '~/pages/search' import { Api } from '~/services/api' export const useMatchmaking = (props: SearchProps) => { - const { user, existingSession } = props - const [queueCount, setQueueCount] = useState(0) + const { user, existingSession, queueCount: queueCountProps } = props const { subscription: userListener } = useTransmit({ url: `game/user/${user.id}`, }) const { subscription: queueListener } = useTransmit({ url: 'game/search' }) + const [queueCount, setQueueCount] = useState(0) + const { post, delete: destroy } = useForm() const { t } = useTranslation() const registerToQueue = async () => { - const response: { message: string; queueCount: number } = - await new Api().post('/game/search') - setQueueCount(response.queueCount) + post('/game/search', { + preserveState: true, + }) + + setQueueCount(queueCountProps) } const unsubscribe = async () => { @@ -67,14 +71,17 @@ export const useMatchmaking = (props: SearchProps) => { }) const cancelQueue = async () => { - await new Api().delete('/game/search') - await unsubscribe() - return router.visit('/game') + destroy('/game/search', { + onSuccess: async () => { + await unsubscribe() + router.visit('/game') + }, + }) } return { cancelQueue, - queueCount, + queueCount: queueCount, t, } } diff --git a/inertia/features/utils/components/link.tsx b/inertia/features/utils/components/link.tsx index c21a480..5ad3feb 100644 --- a/inertia/features/utils/components/link.tsx +++ b/inertia/features/utils/components/link.tsx @@ -1,23 +1,58 @@ +import type { + MultipleFormatsParams, + RouteName, + RoutesNameParams, +} from '@tuyau/client' +import { Link as TuyauLink } from '@tuyau/inertia/react' +import type { ValidatedApi } from '@tuyau/inertia/types' import React from 'react' -export interface Props { +type InternalLinkProps = { + external?: false + route: RouteName + params?: MultipleFormatsParams< + RoutesNameParams> + > + src?: never +} + +type ExternalLinkProps = { + external: true + src: string + route?: never + params?: never +} + +type LinkProps = { type?: 'button' | 'link' children?: React.ReactNode - src: string className?: string -} +} & (InternalLinkProps | ExternalLinkProps) export const Link = ({ children, - src, className = '', type = 'link', -}: Props) => { + external = false, + route, + src, + params, +}: LinkProps) => { const classBase = type === 'button' ? 'btn' : '' + if (external) { + return ( + +

{children}

+
+ ) + } + + if (route === undefined) throw new Error('route is required') + return ( - +

{children}

-
+ ) } diff --git a/inertia/pages/game_session.tsx b/inertia/pages/game_session.tsx index 863ee49..cff7c34 100644 --- a/inertia/pages/game_session.tsx +++ b/inertia/pages/game_session.tsx @@ -83,7 +83,7 @@ export default function GameSession(props: GameSessionProps) { /> {isGameOver && ( - + {t('gameSession.buttons.backToMenu')} )} diff --git a/inertia/pages/home.tsx b/inertia/pages/home.tsx index 6ce194a..bce3eab 100644 --- a/inertia/pages/home.tsx +++ b/inertia/pages/home.tsx @@ -45,7 +45,7 @@ export default function Home(props: HomeProps) { {t('home.description')}

- + {t('home.buttons.start')}

{t('landingPage.title')}

{t('landingPage.description')}

- + {t('landingPage.buttons.login')} diff --git a/inertia/pages/login.tsx b/inertia/pages/login.tsx index 4dd7d33..bfe3af8 100644 --- a/inertia/pages/login.tsx +++ b/inertia/pages/login.tsx @@ -11,7 +11,7 @@ export default function LoginPage() {

{t('login.title')}

{t('login.description')}

- + {t('login.buttons.twitch')}
diff --git a/inertia/pages/profile.tsx b/inertia/pages/profile.tsx index 546f0fa..81b3b98 100644 --- a/inertia/pages/profile.tsx +++ b/inertia/pages/profile.tsx @@ -35,7 +35,7 @@ export default function Profile(props: ProfileProps) { gap={4} className={'text-center'} > - + {t('profile.buttons.home')}

{t('profile.title')}

diff --git a/inertia/pages/search.tsx b/inertia/pages/search.tsx index 4ec3346..53147dd 100644 --- a/inertia/pages/search.tsx +++ b/inertia/pages/search.tsx @@ -6,6 +6,7 @@ import User from '#models/user' export type SearchProps = { user: User existingSession: GameSessionId + queueCount: number } export default function Search(props: SearchProps) { diff --git a/package.json b/package.json index 26f33f0..8101c0e 100644 --- a/package.json +++ b/package.json @@ -88,10 +88,7 @@ "unocss": "^0.60.4" }, "hotHook": { - "boundaries": [ - "./src/features/**/*.ts", - "./src/core/middleware*.ts" - ] + "boundaries": ["./src/features/**/*.ts", "./src/core/middleware*.ts"] }, "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" } diff --git a/src/features/matchmaking/controllers/cancel_matchmaking_controller.ts b/src/features/matchmaking/controllers/cancel_matchmaking_controller.ts index 8d94460..acdf246 100644 --- a/src/features/matchmaking/controllers/cancel_matchmaking_controller.ts +++ b/src/features/matchmaking/controllers/cancel_matchmaking_controller.ts @@ -35,8 +35,6 @@ export default class CancelMatchmakingController { queueCount: queueCount, }) - return response.ok({ - message: 'You have been removed from the matchmaking queue', - }) + return response.redirect('/game') } } diff --git a/src/features/matchmaking/controllers/search_matchmaking_controller.ts b/src/features/matchmaking/controllers/search_matchmaking_controller.ts index fa1c328..0d5c450 100644 --- a/src/features/matchmaking/controllers/search_matchmaking_controller.ts +++ b/src/features/matchmaking/controllers/search_matchmaking_controller.ts @@ -10,7 +10,7 @@ import { EventStreamService } from '#services/event_stream/event_stream_service' export default class SearchMatchmakingController { @inject() - public async handle({ auth, response }: HttpContext, cache: CacheService, eventStream: EventStreamService) { + public async handle({ auth, response, inertia }: HttpContext, cache: CacheService, eventStream: EventStreamService) { const authCheck = await auth.use('web').check() if (!authCheck) { return response.unauthorized() @@ -66,9 +66,10 @@ export default class SearchMatchmakingController { queueCount: queueCount, }) - return response.ok({ - message: 'Added to queue', - queueCount, + return inertia.render('search', { + user: user, + existingSession: sessionId, + queueCount: queueCount, }) } } diff --git a/src/features/pages/controllers/search_game_controller.ts b/src/features/pages/controllers/matchmaking_controller.ts similarity index 81% rename from src/features/pages/controllers/search_game_controller.ts rename to src/features/pages/controllers/matchmaking_controller.ts index 7f558ac..9fe9eff 100644 --- a/src/features/pages/controllers/search_game_controller.ts +++ b/src/features/pages/controllers/matchmaking_controller.ts @@ -4,7 +4,7 @@ import { GameSession } from '#features/game_session/types/game_session' import { assert } from '#helpers/assert' import { CacheService } from '#services/cache/cache_service' -export default class SearchGameController { +export default class MatchmakingController { @inject() public async render({ inertia, auth, response }: HttpContext, cache: CacheService) { if (!(await auth.check())) { @@ -25,10 +25,13 @@ export default class SearchGameController { }) const sessionId = existingSession.length > 0 ? existingSession[0].split(':')[2] : null + const playersCache = await cache.get('game:queue:players') + const queueCount = playersCache && playersCache.length > 0 ? JSON.parse(playersCache).length : 1 return inertia.render('search', { user: user, existingSession: sessionId, + queueCount: queueCount, }) } } diff --git a/start/routes.ts b/start/routes.ts index 179e3b1..1f8ab41 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -16,7 +16,7 @@ const AuthCallbackController = () => import('#features/auth/controllers/auth_cal const AuthRedirectController = () => import('#features/auth/controllers/auth_redirect_controller') const ChatController = () => import('#features/chat/controllers/chat_controller') const GameSessionController = () => import('#features/pages/controllers/game_session_controller') -const SearchGameController = () => import('#features/pages/controllers/search_game_controller') +const MatchmakingController = () => import('#features/pages/controllers/matchmaking_controller') const HomeController = () => import('#features/pages/controllers/home_controller') const LandingPageController = () => import('#features/pages/controllers/landing_page_controller') const LoginController = () => import('#features/pages/controllers/login_controller') @@ -35,7 +35,7 @@ router router.post('/chat', [ChatController, 'store']).as('chat.store') router.get('/game', [HomeController, 'render']).as('game') - router.get('/game/search', [SearchGameController, 'render']).as('search') + router.get('/game/search', [MatchmakingController, 'render']).as('matchmaking') router.post('/game/search', [SearchMatchmakingController, 'handle']).as('game.searchingQueue') router.delete('/game/search', [CancelMatchmakingController, 'handle']).as('game.cancelQueue') router @@ -60,8 +60,14 @@ router }) .use(middleware.auth()) -router.get('/auth/:provider/redirect', [AuthRedirectController, 'handle']).where('provider', /twitch/) -router.get('/auth/:provider/callback', [AuthCallbackController, 'handle']).where('provider', /twitch/) +router + .get('/auth/:provider/redirect', [AuthRedirectController, 'handle']) + .where('provider', /twitch/) + .as('auth.redirect') +router + .get('/auth/:provider/callback', [AuthCallbackController, 'handle']) + .where('provider', /twitch/) + .as('auth.callback') transmit.registerRoutes((route) => { route.middleware(middleware.auth()) From dafee2676e02b9e443232d3fb4020c6aa49fd700 Mon Sep 17 00:00:00 2001 From: Prisca Date: Sun, 20 Apr 2025 18:39:46 +0200 Subject: [PATCH 3/4] chore: add make command to generate api doc --- .adonisjs/api.ts | 2 +- Makefile | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.adonisjs/api.ts b/.adonisjs/api.ts index dd102c2..eccb96b 100644 --- a/.adonisjs/api.ts +++ b/.adonisjs/api.ts @@ -15,7 +15,7 @@ type GameGetHead = { } type GameSearchGetHead = { request: unknown - response: MakeTuyauResponse + response: MakeTuyauResponse } type GameSearchPost = { request: unknown diff --git a/Makefile b/Makefile index 263e1d6..9a91305 100644 --- a/Makefile +++ b/Makefile @@ -93,3 +93,7 @@ init-prod: install docker-prod migrate .PHONY: reset-db reset-db: rollback migrate seed + +.PHONY: generate-tuyau +generate-tuyau: + node ace tuyau:generate From 14f4874d63526ef00ff9fcfaa2b63fb1804b3efa Mon Sep 17 00:00:00 2001 From: Prisca Date: Sun, 20 Apr 2025 20:38:00 +0200 Subject: [PATCH 4/4] fix: ci --- .adonisjs/api.ts | 194 +++++++++--------- .github/workflows/tools.yml | 1 - Dockerfile | 2 + Makefile | 2 +- biome.json | 2 +- docker-compose.tools.yaml | 13 +- inertia/app/app.tsx | 6 + scripts/tuyau_api_tsignore.ts | 35 ++++ .../contracts/game/game_use_case.ts | 2 +- .../matchmaking/matchmaking_accept.spec.ts | 11 - .../matchmaking/matchmaking_search.spec.ts | 60 +++--- tsconfig.json | 2 +- 12 files changed, 179 insertions(+), 151 deletions(-) create mode 100644 scripts/tuyau_api_tsignore.ts diff --git a/.adonisjs/api.ts b/.adonisjs/api.ts index eccb96b..a6738dc 100644 --- a/.adonisjs/api.ts +++ b/.adonisjs/api.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import type { MakeTuyauRequest, MakeTuyauResponse } from '@tuyau/utils/types' import type { InferInput } from '@vinejs/vine/types' @@ -19,27 +20,19 @@ type GameSearchGetHead = { } type GameSearchPost = { request: unknown - response: MakeTuyauResponse< - import('../src/features/matchmaking/controllers/search_matchmaking_controller.ts').default['handle'] - > + response: MakeTuyauResponse } type GameSearchDelete = { request: unknown - response: MakeTuyauResponse< - import('../src/features/matchmaking/controllers/cancel_matchmaking_controller.ts').default['handle'] - > + response: MakeTuyauResponse } type GameSessionIdAcceptGetHead = { request: unknown - response: MakeTuyauResponse< - import('../src/features/matchmaking/controllers/accept_matchmaking_controller.ts').default['handle'] - > + response: MakeTuyauResponse } type GameSessionIdReadyGetHead = { request: unknown - response: MakeTuyauResponse< - import('../src/features/game_session/controllers/game_ready_controller.ts').default['handle'] - > + response: MakeTuyauResponse } type GameSessionIdGetHead = { request: unknown @@ -47,9 +40,7 @@ type GameSessionIdGetHead = { } type GameSessionIdAnswerPost = { request: unknown - response: MakeTuyauResponse< - import('../src/features/game_session/controllers/game_answer_controller.ts').default['handle'] - > + response: MakeTuyauResponse } type ProfileGetHead = { request: unknown @@ -64,171 +55,182 @@ type AuthIdCallbackGetHead = { response: MakeTuyauResponse } export interface ApiDefinition { - login: { - $url: {} - $get: LoginGetHead - $head: LoginGetHead - } - chat: { - $url: {} - $post: ChatPost - } - game: { - $url: {} - $get: GameGetHead - $head: GameGetHead - search: { - $url: {} - $get: GameSearchGetHead - $head: GameSearchGetHead - $post: GameSearchPost - $delete: GameSearchDelete - } - session: { + 'login': { + '$url': { + }; + '$get': LoginGetHead; + '$head': LoginGetHead; + }; + 'chat': { + '$url': { + }; + '$post': ChatPost; + }; + 'game': { + '$url': { + }; + '$get': GameGetHead; + '$head': GameGetHead; + 'search': { + '$url': { + }; + '$get': GameSearchGetHead; + '$head': GameSearchGetHead; + '$post': GameSearchPost; + '$delete': GameSearchDelete; + }; + 'session': { ':sessionId': { - accept: { - $url: {} - $get: GameSessionIdAcceptGetHead - $head: GameSessionIdAcceptGetHead - } - ready: { - $url: {} - $get: GameSessionIdReadyGetHead - $head: GameSessionIdReadyGetHead - } - $url: {} - $get: GameSessionIdGetHead - $head: GameSessionIdGetHead - answer: { - $url: {} - $post: GameSessionIdAnswerPost - } - } - } - } - profile: { - $url: {} - $get: ProfileGetHead - $head: ProfileGetHead - } - auth: { + 'accept': { + '$url': { + }; + '$get': GameSessionIdAcceptGetHead; + '$head': GameSessionIdAcceptGetHead; + }; + 'ready': { + '$url': { + }; + '$get': GameSessionIdReadyGetHead; + '$head': GameSessionIdReadyGetHead; + }; + '$url': { + }; + '$get': GameSessionIdGetHead; + '$head': GameSessionIdGetHead; + 'answer': { + '$url': { + }; + '$post': GameSessionIdAnswerPost; + }; + }; + }; + }; + 'profile': { + '$url': { + }; + '$get': ProfileGetHead; + '$head': ProfileGetHead; + }; + 'auth': { ':provider': { - redirect: { - $url: {} - $get: AuthIdRedirectGetHead - $head: AuthIdRedirectGetHead - } - callback: { - $url: {} - $get: AuthIdCallbackGetHead - $head: AuthIdCallbackGetHead - } - } - } + 'redirect': { + '$url': { + }; + '$get': AuthIdRedirectGetHead; + '$head': AuthIdRedirectGetHead; + }; + 'callback': { + '$url': { + }; + '$get': AuthIdCallbackGetHead; + '$head': AuthIdCallbackGetHead; + }; + }; + }; } const routes = [ { params: [], name: 'home', path: '/', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as unknown, }, { params: [], name: 'login', path: '/login', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as LoginGetHead, }, { params: [], name: 'chat.store', path: '/chat', - method: ['POST'], + method: ["POST"], types: {} as ChatPost, }, { params: [], name: 'game', path: '/game', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as GameGetHead, }, { params: [], name: 'matchmaking', path: '/game/search', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as GameSearchGetHead, }, { params: [], name: 'game.searchingQueue', path: '/game/search', - method: ['POST'], + method: ["POST"], types: {} as GameSearchPost, }, { params: [], name: 'game.cancelQueue', path: '/game/search', - method: ['DELETE'], + method: ["DELETE"], types: {} as GameSearchDelete, }, { - params: ['sessionId'], + params: ["sessionId"], name: 'game.handleAccept', path: '/game/session/:sessionId/accept', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as GameSessionIdAcceptGetHead, }, { - params: ['sessionId'], + params: ["sessionId"], name: 'game.handleReady', path: '/game/session/:sessionId/ready', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as GameSessionIdReadyGetHead, }, { - params: ['sessionId'], + params: ["sessionId"], name: 'game.session', path: '/game/session/:sessionId', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as GameSessionIdGetHead, }, { - params: ['sessionId'], + params: ["sessionId"], name: 'game.handleAnswer', path: '/game/session/:sessionId/answer', - method: ['POST'], + method: ["POST"], types: {} as GameSessionIdAnswerPost, }, { params: [], name: 'profile', path: '/profile', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as ProfileGetHead, }, { - params: ['provider'], + params: ["provider"], name: 'auth.redirect', path: '/auth/:provider/redirect', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as AuthIdRedirectGetHead, }, { - params: ['provider'], + params: ["provider"], name: 'auth.callback', path: '/auth/:provider/callback', - method: ['GET', 'HEAD'], + method: ["GET","HEAD"], types: {} as AuthIdCallbackGetHead, }, -] as const +] as const; export const api = { routes, - definition: {} as ApiDefinition, + definition: {} as ApiDefinition } declare module '@tuyau/inertia/types' { type InertiaApi = typeof api diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 60aa23b..5507b2e 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -66,5 +66,4 @@ jobs: docker compose -f ${{ env.COMPOSE_FILE }} up -d redis docker compose -f ${{ env.COMPOSE_FILE_TOOLS }} run --rm install-node-deps docker compose -f ${{ env.COMPOSE_FILE_TOOLS }} up -d postgres-test - docker compose -f ${{ env.COMPOSE_FILE_TOOLS }} run --rm biome-check docker compose -f ${{ env.COMPOSE_FILE_TOOLS }} run --rm test diff --git a/Dockerfile b/Dockerfile index dd25201..cddd9c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM node:23.0 AS base FROM base AS deps WORKDIR /app ADD package.json pnpm-lock.yaml ./ +RUN npm install -g corepack@latest RUN corepack enable RUN pnpm install @@ -11,6 +12,7 @@ RUN pnpm install FROM base AS production-deps WORKDIR /app ADD package.json pnpm-lock.yaml ./ +RUN npm install -g corepack@latest RUN corepack enable RUN pnpm install --frozen-lockfile --prod diff --git a/Makefile b/Makefile index 9a91305..f2fbd24 100644 --- a/Makefile +++ b/Makefile @@ -96,4 +96,4 @@ reset-db: rollback migrate seed .PHONY: generate-tuyau generate-tuyau: - node ace tuyau:generate + node ace tuyau:generate && node --loader ts-node/esm scripts/tuyau_api_tsignore.ts diff --git a/biome.json b/biome.json index f98ad44..41d2cc2 100644 --- a/biome.json +++ b/biome.json @@ -3,7 +3,7 @@ "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, - "ignore": ["node_modules", "dist", "build", "public", "resources", ".pnpm-store"] + "ignore": ["node_modules", "dist", "build", "public", "resources", ".pnpm-store", ".adonisjs"] }, "formatter": { "enabled": true, diff --git a/docker-compose.tools.yaml b/docker-compose.tools.yaml index eead75a..903f69d 100644 --- a/docker-compose.tools.yaml +++ b/docker-compose.tools.yaml @@ -5,7 +5,7 @@ services: - .:/app - node_modules:/app/node_modules entrypoint: [ "sh", "-c" ] - command: ["corepack enable && pnpm install"] + command: ["npm install -g corepack@latest && corepack enable && pnpm install"] working_dir: /app tsc: @@ -15,7 +15,7 @@ services: - node_modules:/app/node_modules working_dir: /app entrypoint: [ "sh", "-c" ] - command: ["corepack enable && FORCE_COLOR=1 pnpm typecheck"] + command: ["npm install -g corepack@latest && corepack enable && FORCE_COLOR=1 pnpm typecheck"] biome-check: image: node:23.0 @@ -24,7 +24,7 @@ services: - node_modules:/app/node_modules working_dir: /app entrypoint: [ "sh", "-c" ] - command: ["corepack enable && FORCE_COLOR=1 pnpm lint"] + command: ["npm install -g corepack@latest && corepack enable && FORCE_COLOR=1 pnpm lint"] biome-check-fix: image: node:23.0 @@ -33,7 +33,7 @@ services: - node_modules:/app/node_modules working_dir: /app entrypoint: [ "sh", "-c" ] - command: ["corepack enable && FORCE_COLOR=1 pnpm lint --write"] + command: ["npm install -g corepack@latest && corepack enable && FORCE_COLOR=1 pnpm lint --write"] build: image: node:23.0 @@ -43,7 +43,7 @@ services: - build:/app/build working_dir: /app entrypoint: [ "sh", "-c" ] - command: ["corepack enable && FORCE_COLOR=1 pnpm build"] + command: ["npm install -g corepack@latest && corepack enable && FORCE_COLOR=1 pnpm build"] test: image: node:23.0 @@ -60,7 +60,8 @@ services: - DB_PORT=7357 entrypoint: [ "sh", "-c" ] command: [" - corepack enable + npm install -g corepack@latest + && corepack enable && pnpm install && npx playwright install chromium && npx playwright install-deps diff --git a/inertia/app/app.tsx b/inertia/app/app.tsx index a183957..07a82ad 100644 --- a/inertia/app/app.tsx +++ b/inertia/app/app.tsx @@ -1,3 +1,9 @@ +/// +/// +/// +/// +/// + import 'virtual:uno.css' import './app.css' import { createInertiaApp } from '@inertiajs/react' diff --git a/scripts/tuyau_api_tsignore.ts b/scripts/tuyau_api_tsignore.ts new file mode 100644 index 0000000..27f1d25 --- /dev/null +++ b/scripts/tuyau_api_tsignore.ts @@ -0,0 +1,35 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +// Recreate __dirname using import.meta.url +const __filename = fileURLToPath(import.meta.url) + +// Path to the `api.ts` file +const filePath = '.adonisjs/api.ts' + +// Read the file content +fs.readFile(filePath, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => { + if (err) { + console.error('Error reading the file:', err) + return + } + + // Check if the file already starts with `// @ts-nocheck` + if (data.startsWith('// @ts-nocheck')) { + console.log('The file already contains the `// @ts-nocheck` comment.') + return + } + + // Prepend `// @ts-ignore` to the file content + const updatedContent = `// @ts-nocheck\n${data}` + + // Write the updated content back to the file + fs.writeFile(filePath, updatedContent, 'utf8', (err: NodeJS.ErrnoException | null) => { + if (err) { + console.error('Error writing to the file:', err) + return + } + console.log('`// @ts-nocheck` has been added to the file.') + }) +}) diff --git a/src/features/game_session/contracts/game/game_use_case.ts b/src/features/game_session/contracts/game/game_use_case.ts index f1af3ab..58e2f2b 100644 --- a/src/features/game_session/contracts/game/game_use_case.ts +++ b/src/features/game_session/contracts/game/game_use_case.ts @@ -16,7 +16,7 @@ export class GameUseCase { this.gameRules = gameRules } - public async handle(ctx: HttpContext): Promise { + public async handle(ctx: HttpContext) { const { auth, request, response, params } = ctx if (!auth.user) { return response.unauthorized() diff --git a/tests/functional/matchmaking/matchmaking_accept.spec.ts b/tests/functional/matchmaking/matchmaking_accept.spec.ts index e27c235..3da66e1 100644 --- a/tests/functional/matchmaking/matchmaking_accept.spec.ts +++ b/tests/functional/matchmaking/matchmaking_accept.spec.ts @@ -41,17 +41,9 @@ test.group('Matchmaking - Accept matchmaking', (group) => { const responseUser1 = await client.post('/game/search').withCsrfToken().loginAs(user1) responseUser1.assertStatus(200) - responseUser1.assertBody({ - message: 'Added to queue', - queueCount: 1, - }) const responseUser2 = await client.post('/game/search').withCsrfToken().loginAs(user2) responseUser2.assertStatus(200) - responseUser2.assertBody({ - message: 'Added to queue', - queueCount: 2, - }) const playersCache = await cache.get('game:queue:players') assert.exists(playersCache) @@ -81,9 +73,6 @@ test.group('Matchmaking - Accept matchmaking', (group) => { const responseAccept = await client.get(`/game/session/${sessionId}/accept`).withCsrfToken().loginAs(user1) responseAccept.assertStatus(200) - responseAccept.assertBody({ - message: 'Accepted', - }) const session = await cache.get(`game:session:${sessionId}`) const sessionParsed = JSON.parse(session!) as GameSession diff --git a/tests/functional/matchmaking/matchmaking_search.spec.ts b/tests/functional/matchmaking/matchmaking_search.spec.ts index 31e4733..0879c6f 100644 --- a/tests/functional/matchmaking/matchmaking_search.spec.ts +++ b/tests/functional/matchmaking/matchmaking_search.spec.ts @@ -36,10 +36,6 @@ test.group('Matchmaking - Game search', (group) => { assert.exists(player.date) response.assertStatus(200) - response.assertBody({ - message: 'Added to queue', - queueCount: 1, - }) }) test('user should not be added twice to matchmaking queue', async ({ assert, client }) => { @@ -77,50 +73,48 @@ test.group('Matchmaking - Game search', (group) => { assert.exists(playerAfter.date) response.assertStatus(200) - response.assertBody({ - message: 'Added to queue', - queueCount: 1, - }) }) - test('second user should be added to matchmaking queue', async ({ assert, client }) => { - const user1 = await User.create({ - username: 'test1', - email: 'test1@test1.com', + test('adds second user to matchmaking queue', async ({ assert, client }) => { + const firstUser = await User.create({ + username: 'user_one', + email: 'user_one@test.com', elo: 500, providerId: 1, }) - const user2 = await User.create({ - username: 'test2', - email: 'test2@test.com', + const secondUser = await User.create({ + username: 'user_two', + email: 'user_two@test.com', elo: 500, providerId: 1, }) - await client.post('/game/search').withCsrfToken().loginAs(user1) - await client.post('/game/search').withCsrfToken().loginAs(user2) + // Add the first user to the matchmaking queue + await client.post('/game/search').withCsrfToken().loginAs(firstUser) - const playersCache = await cache.get('game:queue:players') - assert.exists(playersCache) - const players = JSON.parse(playersCache!) + const queueCountBeforeSecondUser = await cache.get('game:queue:count') + assert.equal(queueCountBeforeSecondUser, 1) - assert.equal(players.length, 2) + const playersCacheBeforeSecondUser = await cache.get('game:queue:players') + const playersBeforeSecondUser = JSON.parse(playersCacheBeforeSecondUser!) + assert.equal(playersBeforeSecondUser.length, 1) + assert.equal(playersBeforeSecondUser[0].id, firstUser.id) - const player1 = players.find((player: any) => player.id === user1.id) - assert.exists(player1) + // Add the second user to the matchmaking queue + await client.post('/game/search').withCsrfToken().loginAs(secondUser) - const player2 = players.find((player: any) => player.id === user2.id) - assert.exists(player2) + const queueCountAfterSecondUser = await cache.get('game:queue:count') + assert.equal(queueCountAfterSecondUser, 2) - const queueCount = await cache.get('game:queue:count') - assert.equal(queueCount, 2) + const playersCacheAfterSecondUser = await cache.get('game:queue:players') + const playersAfterSecondUser = JSON.parse(playersCacheAfterSecondUser!) + assert.equal(playersAfterSecondUser.length, 2) - const response = await client.post('/game/search').withCsrfToken().loginAs(user2) - response.assertStatus(200) - response.assertBody({ - message: 'Added to queue', - queueCount: 2, - }) + const firstUserInQueue = playersAfterSecondUser.find((player: any) => player.id === firstUser.id) + assert.exists(firstUserInQueue) + + const secondUserInQueue = playersAfterSecondUser.find((player: any) => player.id === secondUser.id) + assert.exists(secondUserInQueue) }) }) diff --git a/tsconfig.json b/tsconfig.json index 270f10d..cd8c7c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "./build", "resolveJsonModule": true }, - "exclude": ["./inertia/**/*", "node_modules", "build"] + "exclude": ["./inertia/**/*", "node_modules", "build", "scripts"] }