diff --git a/.gitignore b/.gitignore index f217d4d..76c7fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ openapi.jar *.db-wal /db/ -package-lock.json -/pnpm-lock.yaml +pnpm-lock.yaml +.idea diff --git a/Docker.mk b/Docker.mk index 5f6efcb..3d7bcb4 100644 --- a/Docker.mk +++ b/Docker.mk @@ -17,6 +17,7 @@ DOCKER_SERVICE= \ auth \ chat \ + tic-tac-toe \ frontend \ nginx \ user \ diff --git a/docker-compose.yml b/docker-compose.yml index a3bec84..8f841ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ networks: driver: bridge monitoring: driver: bridge + services: frontend: build: ./frontend @@ -67,6 +68,33 @@ services: gelf-address: "udp://127.0.0.1:12201" tag: "{{.Name}}" + ############### + # TIC-TAC-TOE # + ############### + tic-tac-toe: + build: + context: ./src/ + args: + - SERVICE=tic-tac-toe + container_name: app-tic-tac-toe + restart: always + networks: + - app + volumes: + - sqlite-volume:/volumes/database + - static-volume:/volumes/static + environment: + - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA + - DATABASE_DIR=/volumes/database + logging: + driver: gelf + options: + gelf-address: "udp://127.0.0.1:12201" + tag: "{{.Name}}" + + ############### + # CHAT # + ############### chat: build: context: ./src/ diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ec12443..02fe5dd 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -214,113 +214,113 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@rollup/rollup-android-arm-eabi@4.53.5': - resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==} + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.5': - resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==} + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.5': - resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==} + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.5': - resolution: {integrity: sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==} + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.5': - resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==} + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.5': - resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==} + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': - resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==} + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.53.5': - resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==} + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.53.5': - resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==} + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.53.5': - resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==} + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.53.5': - resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==} + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.53.5': - resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==} + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.53.5': - resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==} + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.53.5': - resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==} + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.53.5': - resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==} + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.53.5': - resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==} + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.53.5': - resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==} + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.53.5': - resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==} + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.5': - resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==} + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.5': - resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==} + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.5': - resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==} + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.5': - resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==} + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} cpu: [x64] os: [win32] @@ -667,8 +667,8 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - rollup@4.53.5: - resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -905,70 +905,70 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@rollup/rollup-android-arm-eabi@4.53.5': + '@rollup/rollup-android-arm-eabi@4.54.0': optional: true - '@rollup/rollup-android-arm64@4.53.5': + '@rollup/rollup-android-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-arm64@4.53.5': + '@rollup/rollup-darwin-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-x64@4.53.5': + '@rollup/rollup-darwin-x64@4.54.0': optional: true - '@rollup/rollup-freebsd-arm64@4.53.5': + '@rollup/rollup-freebsd-arm64@4.54.0': optional: true - '@rollup/rollup-freebsd-x64@4.53.5': + '@rollup/rollup-freebsd-x64@4.54.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.5': + '@rollup/rollup-linux-arm-musleabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.5': + '@rollup/rollup-linux-arm64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.5': + '@rollup/rollup-linux-arm64-musl@4.54.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.5': + '@rollup/rollup-linux-loong64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.5': + '@rollup/rollup-linux-ppc64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.5': + '@rollup/rollup-linux-riscv64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.5': + '@rollup/rollup-linux-riscv64-musl@4.54.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.5': + '@rollup/rollup-linux-s390x-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.53.5': + '@rollup/rollup-linux-x64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-musl@4.53.5': + '@rollup/rollup-linux-x64-musl@4.54.0': optional: true - '@rollup/rollup-openharmony-arm64@4.53.5': + '@rollup/rollup-openharmony-arm64@4.54.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.5': + '@rollup/rollup-win32-arm64-msvc@4.54.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.5': + '@rollup/rollup-win32-ia32-msvc@4.54.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.53.5': + '@rollup/rollup-win32-x64-gnu@4.54.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.53.5': + '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true '@socket.io/component-emitter@3.1.2': {} @@ -1256,32 +1256,32 @@ snapshots: require-main-filename@2.0.0: {} - rollup@4.53.5: + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.5 - '@rollup/rollup-android-arm64': 4.53.5 - '@rollup/rollup-darwin-arm64': 4.53.5 - '@rollup/rollup-darwin-x64': 4.53.5 - '@rollup/rollup-freebsd-arm64': 4.53.5 - '@rollup/rollup-freebsd-x64': 4.53.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.5 - '@rollup/rollup-linux-arm-musleabihf': 4.53.5 - '@rollup/rollup-linux-arm64-gnu': 4.53.5 - '@rollup/rollup-linux-arm64-musl': 4.53.5 - '@rollup/rollup-linux-loong64-gnu': 4.53.5 - '@rollup/rollup-linux-ppc64-gnu': 4.53.5 - '@rollup/rollup-linux-riscv64-gnu': 4.53.5 - '@rollup/rollup-linux-riscv64-musl': 4.53.5 - '@rollup/rollup-linux-s390x-gnu': 4.53.5 - '@rollup/rollup-linux-x64-gnu': 4.53.5 - '@rollup/rollup-linux-x64-musl': 4.53.5 - '@rollup/rollup-openharmony-arm64': 4.53.5 - '@rollup/rollup-win32-arm64-msvc': 4.53.5 - '@rollup/rollup-win32-ia32-msvc': 4.53.5 - '@rollup/rollup-win32-x64-gnu': 4.53.5 - '@rollup/rollup-win32-x64-msvc': 4.53.5 + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 set-blocking@2.0.0: {} @@ -1350,7 +1350,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.5 + rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.0.3 diff --git a/frontend/src/pages/ttt/README.md b/frontend/src/pages/ttt/README.md index d8169ae..d5de0ed 100644 --- a/frontend/src/pages/ttt/README.md +++ b/frontend/src/pages/ttt/README.md @@ -18,12 +18,13 @@ enhancing user engagement with gameplay history, and facilitating matchmaking for an enjoyable gaming experience. # TO-DO -For now I am prohibited from working on the backend as per Maieul's request. -[ ] - Implement other game +For now I am prohibited from working on the backend as per Maieul's request. +[X] - Implement other game [X] - (Done on Dec. 7 2025) Task for December 08 or 09: Tic-tac-toe should lock up once it's finished (i.e., draw or win) and print who won, how many turns and which row/col/diag the win happened in -[ ] - Implement user history -[ ] - Implement matchmaking -[ ] - -[ ] - -[ ] - \ No newline at end of file +[ ] - Implement user history (ongoing) + [ ] - Write to database the outcome of each game +[ ] - Implement matchmaking +[ ] - +[ ] - +[ ] - \ No newline at end of file diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts index e895914..75c0e37 100644 --- a/frontend/src/pages/ttt/ttt.ts +++ b/frontend/src/pages/ttt/ttt.ts @@ -1,191 +1,95 @@ -import { addRoute, setTitle, type RouteHandlerReturn } from "@app/routing"; +import { addRoute, type RouteHandlerReturn } from "@app/routing"; import tttPage from "./ttt.html?raw"; import { showError, showInfo, showSuccess } from "@app/toast"; +import { io, Socket } from "socket.io-client"; -// Represents the possible states of a cell on the board. -// `null` means that the cell is empty. -type CellState = 'O' | 'X' | null +// get the name of the machine used to connect +const machineHostName = window.location.hostname; +console.log( + "connect to login at https://" + machineHostName + ":8888/app/login", +); -// Encapsulates the game logic. -class TTC { +export let __socket: Socket | undefined = undefined; +document.addEventListener("ft:pageChange", () => { + if (__socket !== undefined) __socket.close(); + __socket = undefined; + console.log("Page changed"); +}); - private isGameOver: boolean; - - private board: [ - CellState, CellState, CellState, - CellState, CellState, CellState, - CellState, CellState, CellState]; - private currentPlayer: 'O' | 'X'; - - constructor() { - this.board = [null,null,null,null,null,null,null,null,null]; - this.isGameOver = false; - this.currentPlayer = 'X'; - } - - private changePlayer() - { - if (this.currentPlayer === 'X') - this.currentPlayer = 'O'; - else - this.currentPlayer = 'X'; - } - - // Analyzes the current board to determine if the game has ended. - private checkState(): 'winX' | 'winO' | 'draw' | 'ongoing' - { - const checkRow = (row: number): ('X' | 'O' | null) => { - if (this.board[row * 3] === null) - return null; - if (this.board[row * 3] === this.board[row * 3 + 1] && this.board[row * 3 + 1] === this.board[row * 3 + 2]) - return this.board[row * 3]; - return null; - } - - const checkCol = (col: number): ('X' | 'O' | null) => { - if (this.board[col] === null) return null; - - if (this.board[col] === this.board[col + 3] && this.board[col + 3] === this.board[col + 6]) - return this.board[col]; - return null; - } - - const checkDiag = (): ('X' | 'O' | null) => { - if (this.board[4] === null) return null - - if (this.board[0] === this.board[4] && this.board[4] === this.board[8]) - return this.board[4] - - if (this.board[2] === this.board[4] && this.board[4] === this.board[6]) - return this.board[4] - return null; - } - - - const row = (checkRow(0) ?? checkRow(1)) ?? checkRow(2); - const col = (checkCol(0) ?? checkCol(1)) ?? checkCol(2); - const diag = checkDiag(); - - if (row !== null) return `win${row}`; - if (col !== null) return `win${col}`; - if (diag !== null ) return `win${diag}`; - - if (this.board.filter(c => c === null).length === 0) - return 'draw'; - return 'ongoing'; - } - - public reset(): void { - this.board = [null,null,null,null,null,null,null,null,null]; - this.currentPlayer = 'X'; - this.isGameOver = false; - }; - - // Attempts to place the current player's mark on the specified cell. - // @param idx - The index of the board (0-8) to place the mark. - // @returns The resulting game state, or `invalidMove` if the move is illegal. - public makeMove(idx: number): 'winX' | 'winO' | 'draw' | 'ongoing' | 'invalidMove' { - if (this.isGameOver) { - return 'invalidMove'; - } - if (idx < 0 || idx >= this.board.length) { - return 'invalidMove'; - } - if (this.board[idx] !== null) { - return 'invalidMove'; - } - this.board[idx] = this.currentPlayer; - this.changePlayer(); - - const result = this.checkState(); - - if (result !== 'ongoing') { - this.isGameOver = true; - } - - return result; - } - - public getBoard(): [ - CellState, CellState, CellState, - CellState, CellState, CellState, - CellState, CellState, CellState] - { - return this.board; - } +export function getSocket(): Socket { + let addressHost = `wss://${machineHostName}:8888`; + // let addressHost = `wss://localhost:8888`; + if (__socket === undefined) + __socket = io(addressHost, { + path: "/api/ttt/socket.io/", + secure: true, + transports: ["websocket"], + }); + return __socket; } // Route handler for the Tic-Tac-Toe page. // Instantiates the game logic and binds UI events. -async function handleTTT(): Promise -{ - // Create a fresh instance for every page load. - let board = new TTC(); +async function handleTTT(): Promise { + const socket: Socket = getSocket(); - return { - html: tttPage, - postInsert: async (app) => { - if (!app) { - return; - } + return { + html: tttPage, + postInsert: async (app) => { + if (!app) { + return; + } - const cells = app.querySelectorAll(".ttt-grid-cell"); - const restartBtn = app.querySelector("#ttt-restart-btn"); + const cells = + app.querySelectorAll(".ttt-grid-cell"); + const restartBtn = + app.querySelector("#ttt-restart-btn"); + const grid = app.querySelector(".ttt-grid"); // Not sure about this one - const updateUI = () => { - const board_state = board.getBoard(); - board_state.forEach((cell_state, cell_idx) => { - cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; - }); - }; + const updateUI = (boardState: (string | null)[]) => { + boardState.forEach((state, idx) => { + cells[idx].innerText = state || " "; + }); + }; - console.log(cells); + socket.on("gameState", (data) => { + updateUI(data.board); - cells?.forEach(function (c, idx) { - c.addEventListener('click', () => { - const result = board.makeMove(idx); - switch(result) - { - case ('draw'): { - showInfo('Game is a draw'); - break; - } - case ('invalidMove'): { - showError('Move is invalid'); - break; - } + if (data.lastResult && data.lastResult !== "ongoing") { + grid?.classList.add("pointer-events-none"); + if (data.lastResult === "winX") { + showSuccess("X won !"); + } + if (data.lastResult === "winO") { + showSuccess("O won !"); + } + if (data.lastResult === "draw") { + showInfo("Draw !"); + } + } - case ('winX'): { - showSuccess('X won'); - app?.querySelector('.ttt-grid')?.classList.add('pointer-events-none'); - break; - } - case ('winO'): { - showSuccess('O won'); - app?.querySelector('.ttt-grid')?.classList.add('pointer-events-none'); - break; - } - } + if (data.reset) { + grid?.classList.remove("pointer-events-none"); + showInfo("Game Restarted"); + } + }); - // Sync UI with Game State - const board_state = board.getBoard(); - board_state.forEach( function (cell_state, cell_idx) { - cells[cell_idx].innerText = cell_state !== null ? cell_state : " "; - }); + socket.on("error", (msg) => { + showError(msg); + }); - updateUI(); - }); - }); - restartBtn?.addEventListener('click', () => { - board.reset(); - // Remove pointer-events-none to re-enable the board if it was disabled - app?.querySelector('.ttt-grid')?.classList.remove('pointer-events-none'); - updateUI(); - showInfo('Game Restarted'); - }); - } - } + cells?.forEach(function(c, idx) { + c.addEventListener("click", () => { + socket.emit("makeMove", idx); + }); + }); + + restartBtn?.addEventListener("click", () => { + socket.emit("resetGame"); + }); + }, + }; } +addRoute("/ttt", handleTTT); -addRoute('/ttt', handleTTT) \ No newline at end of file diff --git a/nginx/conf/locations/ttt.conf b/nginx/conf/locations/ttt.conf new file mode 100644 index 0000000..eee6057 --- /dev/null +++ b/nginx/conf/locations/ttt.conf @@ -0,0 +1,14 @@ +#forward the post request to the microservice +location /api/ttt/ { + proxy_pass http://app-tic-tac-toe; +} + +location /api/ttt/socket.io/ { + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 3600s; + proxy_pass http://app-tic-tac-toe; +} diff --git a/src/@shared/package.json b/src/@shared/package.json index 4cdbef0..43efa32 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -20,7 +20,7 @@ "fastify-plugin": "^5.1.0", "joi": "^18.0.2", "otp": "^1.1.2", - "typebox": "^1.0.64", + "typebox": "^1.0.65", "uuidv7": "^1.1.0" }, "devDependencies": { diff --git a/src/@shared/src/database/init.sql b/src/@shared/src/database/init.sql index 2406225..f2d2af4 100644 --- a/src/@shared/src/database/init.sql +++ b/src/@shared/src/database/init.sql @@ -23,3 +23,12 @@ CREATE TABLE IF NOT EXISTS blocked ( CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair ON blocked(user, blocked); +CREATE TABLE IF NOT EXISTS tictactoe ( + id INTEGER PRIMARY KEY NOT NULL, + player1 TEXT NOT NULL, + player2 TEXT NOT NULL, + outcome TEXT NOT NULL, + + FOREIGN KEY(player1) REFERENCES user(id) + FOREIGN KEY(player2) REFERENCES user(id) +); diff --git a/src/@shared/src/database/mixin/tictactoe.ts b/src/@shared/src/database/mixin/tictactoe.ts new file mode 100644 index 0000000..5cb3e7d --- /dev/null +++ b/src/@shared/src/database/mixin/tictactoe.ts @@ -0,0 +1,54 @@ +import type { Database } from './_base'; +// import { UserId } from './user'; + +// describe every function in the object +export interface ITicTacToeDb extends Database { + setGameOutcome(this: ITicTacToeDb, id: GameId): void, +// asyncFunction(id: TemplateId): Promise, +}; + +export const TicTacToeImpl: Omit = { + /** + * @brief Write the outcome of the specified game to the database. + * + * @param gameId The game we want to write the outcome of. + * + */ + setGameOutcome(this: ITicTacToeDb, id: GameId): void { + // Find a way to retrieve the outcome of the game. + this.prepare('INSERT INTO tictactoe (game, outcome) VALUES (@id, "draw" /* replace w/ game outcome */)').run({ id }); + }, + /** + * whole function description + * + * @param id the argument description + * + * @returns what does the function return ? + */ +// async asyncFunction(this: ITemplateDb, id: TemplateId): Promise { +// void id; +// return undefined; +// }, +}; + +export type TicTacToeId = number & { readonly __brand: unique symbol }; + +export type TicTacToeData = { + readonly id: TicTacToeId; + readonly player1: string; + readonly player2: string; + readonly outcome: string; +}; + +// this function will be able to be called from everywhere +// export async function freeFloatingExportedFunction(): Promise { +// return false; +// } + +// this function will never be able to be called outside of this module +// async function privateFunction(): Promise { +// return undefined; +// } + +// silence warnings +void privateFunction; diff --git a/src/auth/package.json b/src/auth/package.json index b709575..ace48e9 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.64" + "typebox": "^1.0.65" }, "devDependencies": { "@types/node": "^22.19.3", diff --git a/src/chat/package.json b/src/chat/package.json index 555055b..502b538 100644 --- a/src/chat/package.json +++ b/src/chat/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-plugin": "^5.1.0", "socket.io": "^4.8.1", - "typebox": "^1.0.64" + "typebox": "^1.0.65" }, "devDependencies": { "@types/node": "^22.19.3", diff --git a/src/package.json b/src/package.json index 3169a11..d58f871 100644 --- a/src/package.json +++ b/src/package.json @@ -33,7 +33,7 @@ "vite": "^7.3.0" }, "dependencies": { - "@redocly/cli": "^2.13.0", + "@redocly/cli": "^2.14.0", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index d00e5ab..8932a9b 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.13.0 - version: 2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.14.0 + version: 2.14.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -79,8 +79,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 typebox: - specifier: ^1.0.64 - version: 1.0.64 + specifier: ^1.0.65 + version: 1.0.65 uuidv7: specifier: ^1.1.0 version: 1.1.0 @@ -122,15 +122,15 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.64 - version: 1.0.64 + specifier: ^1.0.65 + version: 1.0.65 devDependencies: '@types/node': specifier: ^22.19.3 version: 22.19.3 rollup-plugin-node-externals: specifier: ^8.1.2 - version: 8.1.2(rollup@4.53.5) + version: 8.1.2(rollup@4.54.0) vite: specifier: ^7.3.0 version: 7.3.0(@types/node@22.19.3)(yaml@2.8.2) @@ -168,15 +168,64 @@ importers: specifier: ^4.8.1 version: 4.8.1 typebox: - specifier: ^1.0.64 - version: 1.0.64 + specifier: ^1.0.65 + version: 1.0.65 devDependencies: '@types/node': specifier: ^22.19.3 version: 22.19.3 rollup-plugin-node-externals: specifier: ^8.1.2 - version: 8.1.2(rollup@4.53.5) + version: 8.1.2(rollup@4.54.0) + vite: + specifier: ^7.3.0 + version: 7.3.0(@types/node@22.19.3)(yaml@2.8.2) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.3)(vite@7.3.0(@types/node@22.19.3)(yaml@2.8.2)) + + tic-tac-toe: + dependencies: + '@fastify/autoload': + specifier: ^6.3.1 + version: 6.3.1 + '@fastify/formbody': + specifier: ^8.0.2 + version: 8.0.2 + '@fastify/multipart': + specifier: ^9.3.0 + version: 9.3.0 + '@fastify/sensible': + specifier: ^6.0.4 + version: 6.0.4 + '@fastify/static': + specifier: ^8.3.0 + version: 8.3.0 + '@fastify/websocket': + specifier: ^11.2.0 + version: 11.2.0 + fastify: + specifier: ^5.6.2 + version: 5.6.2 + fastify-plugin: + specifier: ^5.1.0 + version: 5.1.0 + socket.io: + specifier: ^4.8.1 + version: 4.8.1 + typebox: + specifier: ^1.0.65 + version: 1.0.65 + devDependencies: + '@types/node': + specifier: ^22.19.3 + version: 22.19.3 + fastify-socket.io: + specifier: ^5.1.0 + version: 5.1.0(fastify@5.6.2)(socket.io@4.8.1) + rollup-plugin-node-externals: + specifier: ^8.1.2 + version: 8.1.2(rollup@4.54.0) vite: specifier: ^7.3.0 version: 7.3.0(@types/node@22.19.3)(yaml@2.8.2) @@ -211,15 +260,15 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.64 - version: 1.0.64 + specifier: ^1.0.65 + version: 1.0.65 devDependencies: '@types/node': specifier: ^22.19.3 version: 22.19.3 rollup-plugin-node-externals: specifier: ^8.1.2 - version: 8.1.2(rollup@4.53.5) + version: 8.1.2(rollup@4.54.0) vite: specifier: ^7.3.0 version: 7.3.0(@types/node@22.19.3)(yaml@2.8.2) @@ -679,136 +728,136 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.13.0': - resolution: {integrity: sha512-VOGh8p5gKy+u94SbvMGaHvDM6TPw668D9iQkNSztoi4T5sj3ZwM7Y8Z3yZnMqC5s5epDcLAMq4jCO8UVn5ZWHg==} + '@redocly/cli@2.14.0': + resolution: {integrity: sha512-LvVYV7KJGtVqltBc8Cbw2s4QpFOzend5nCsgR1JgWvHNt70f1AzqoHr5y7GO+3ThwumrTzPvjta+Ln+n3x5NmA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.41.0': - resolution: {integrity: sha512-8yJ2e+ex8KVF25zijdpDbAEjyubk7NLfHsLI8h0MUnLEo2iEg6rTCDT9Qw71XDqd5UlXvfJb0Z0h6dd+Y6pWLw==} + '@redocly/config@0.41.1': + resolution: {integrity: sha512-LcMCzFbP/sqkCLSG3YswmeScP4fM5SjDCQizwa+psZ0PhYrKOMF7azZ6ZBkWs115uv5RfOk+jYAWLdKkZGGGXg==} '@redocly/openapi-core@1.34.6': resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.13.0': - resolution: {integrity: sha512-xQ4z5tsrXbIa4EfCniHv1zZ4etmQ0lpRcxy750iOamV5A/+19mgbPtD+UQCoT18puDAjcnOgpX7x2ha72qKrnw==} + '@redocly/openapi-core@2.14.0': + resolution: {integrity: sha512-GeSIesfbh5TdqoWBu7wPzCAGUvKfLBnN60rKnhZCyxrs6M0tn7GYhtET+P5HsNlXmvW4vFNDBlLDoATW/dKrrQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.13.0': - resolution: {integrity: sha512-35OidNXWkmmsJiwgX+tFw7FaU8usZVvZ/lFBFNJga65pivEvaDlfiwKxIRTzM4iuNbc2FRvP2q30dlGAztv0tg==} + '@redocly/respect-core@2.14.0': + resolution: {integrity: sha512-7HYB66oNUOcBjBZpK/i5xPpXIYXt09a98WX0subaAQZJinLGq8D3hbgLAp+pXZgosHNeZ0QKahOExsZ25JZSHw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@rollup/rollup-android-arm-eabi@4.53.5': - resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==} + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.5': - resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==} + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.5': - resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==} + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.5': - resolution: {integrity: sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==} + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.5': - resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==} + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.5': - resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==} + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': - resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==} + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.53.5': - resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==} + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.53.5': - resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==} + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.53.5': - resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==} + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.53.5': - resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==} + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.53.5': - resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==} + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.53.5': - resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==} + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.53.5': - resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==} + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.53.5': - resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==} + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.53.5': - resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==} + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.53.5': - resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==} + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.53.5': - resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==} + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.5': - resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==} + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.5': - resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==} + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.5': - resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==} + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.5': - resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==} + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} cpu: [x64] os: [win32] @@ -1385,9 +1434,18 @@ packages: resolution: {integrity: sha512-7Jsfj2uLuGWvnxjrGDrHWpSm65+OcVx0ZbTD2wwkz6Wt6KjGm6+ZYwwpdXdwAlzbJYq+LCEMNvDJc4485AQ1vQ==} hasBin: true + fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} + fastify-plugin@5.1.0: resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + fastify-socket.io@5.1.0: + resolution: {integrity: sha512-GC1gjrxBGeTbMWV779XHF4uw3AtgKwSQJ9MnjGiMp91ZBuPXEdBYa7NnAMDEl3oZPgK9JO4BlNncTV+UAN+1kg==} + peerDependencies: + fastify: 4.x.x + socket.io: '>=4' + fastify@5.6.2: resolution: {integrity: sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==} @@ -2179,8 +2237,8 @@ packages: peerDependencies: rollup: ^4.0.0 - rollup@4.53.5: - resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2427,6 +2485,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -2438,8 +2499,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typebox@1.0.64: - resolution: {integrity: sha512-U6quDhQMzQRzBX8jvlE5mZlUnlMRTaZrG/QMAhOYVJ0D0rhq1iOXBQVSzBX0JgAh55jXQ7fWIv24i+lVimXcDw==} + typebox@1.0.65: + resolution: {integrity: sha512-3WaZ4QmfAxmelhi0dwusYDoZ+DLDoVrsc3aORzgtk1I8JfIf4wn+F8i1TtrnU2jJKM/hZgjJGfzXrwS4B31zZw==} typescript-eslint@8.50.0: resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} @@ -3065,14 +3126,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.13.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.14.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.13.0(ajv@8.17.1) - '@redocly/respect-core': 2.13.0(ajv@8.17.1) + '@redocly/openapi-core': 2.14.0(ajv@8.17.1) + '@redocly/respect-core': 2.14.0(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3106,7 +3167,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.41.0': + '@redocly/config@0.41.1': dependencies: json-schema-to-ts: 2.7.2 @@ -3124,10 +3185,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.13.0(ajv@8.17.1)': + '@redocly/openapi-core@2.14.0(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 - '@redocly/config': 0.41.0 + '@redocly/config': 0.41.1 ajv-formats: 3.0.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 @@ -3138,12 +3199,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.13.0(ajv@8.17.1)': + '@redocly/respect-core@2.14.0(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.13.0(ajv@8.17.1) + '@redocly/openapi-core': 2.14.0(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -3153,70 +3214,70 @@ snapshots: transitivePeerDependencies: - ajv - '@rollup/rollup-android-arm-eabi@4.53.5': + '@rollup/rollup-android-arm-eabi@4.54.0': optional: true - '@rollup/rollup-android-arm64@4.53.5': + '@rollup/rollup-android-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-arm64@4.53.5': + '@rollup/rollup-darwin-arm64@4.54.0': optional: true - '@rollup/rollup-darwin-x64@4.53.5': + '@rollup/rollup-darwin-x64@4.54.0': optional: true - '@rollup/rollup-freebsd-arm64@4.53.5': + '@rollup/rollup-freebsd-arm64@4.54.0': optional: true - '@rollup/rollup-freebsd-x64@4.53.5': + '@rollup/rollup-freebsd-x64@4.54.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.5': + '@rollup/rollup-linux-arm-musleabihf@4.54.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.5': + '@rollup/rollup-linux-arm64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.5': + '@rollup/rollup-linux-arm64-musl@4.54.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.5': + '@rollup/rollup-linux-loong64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.5': + '@rollup/rollup-linux-ppc64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.5': + '@rollup/rollup-linux-riscv64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.5': + '@rollup/rollup-linux-riscv64-musl@4.54.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.5': + '@rollup/rollup-linux-s390x-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.53.5': + '@rollup/rollup-linux-x64-gnu@4.54.0': optional: true - '@rollup/rollup-linux-x64-musl@4.53.5': + '@rollup/rollup-linux-x64-musl@4.54.0': optional: true - '@rollup/rollup-openharmony-arm64@4.53.5': + '@rollup/rollup-openharmony-arm64@4.54.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.5': + '@rollup/rollup-win32-arm64-msvc@4.54.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.5': + '@rollup/rollup-win32-ia32-msvc@4.54.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.53.5': + '@rollup/rollup-win32-x64-gnu@4.54.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.53.5': + '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true '@socket.io/component-emitter@3.1.2': {} @@ -3858,8 +3919,17 @@ snapshots: semver: 7.7.3 yargs-parser: 22.0.0 + fastify-plugin@4.5.1: {} + fastify-plugin@5.1.0: {} + fastify-socket.io@5.1.0(fastify@5.6.2)(socket.io@4.8.1): + dependencies: + fastify: 5.6.2 + fastify-plugin: 4.5.1 + socket.io: 4.8.1 + tslib: 2.8.1 + fastify@5.6.2: dependencies: '@fastify/ajv-compiler': 4.0.5 @@ -4659,36 +4729,36 @@ snapshots: rfdc@1.4.1: {} - rollup-plugin-node-externals@8.1.2(rollup@4.53.5): + rollup-plugin-node-externals@8.1.2(rollup@4.54.0): dependencies: - rollup: 4.53.5 + rollup: 4.54.0 - rollup@4.53.5: + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.5 - '@rollup/rollup-android-arm64': 4.53.5 - '@rollup/rollup-darwin-arm64': 4.53.5 - '@rollup/rollup-darwin-x64': 4.53.5 - '@rollup/rollup-freebsd-arm64': 4.53.5 - '@rollup/rollup-freebsd-x64': 4.53.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.5 - '@rollup/rollup-linux-arm-musleabihf': 4.53.5 - '@rollup/rollup-linux-arm64-gnu': 4.53.5 - '@rollup/rollup-linux-arm64-musl': 4.53.5 - '@rollup/rollup-linux-loong64-gnu': 4.53.5 - '@rollup/rollup-linux-ppc64-gnu': 4.53.5 - '@rollup/rollup-linux-riscv64-gnu': 4.53.5 - '@rollup/rollup-linux-riscv64-musl': 4.53.5 - '@rollup/rollup-linux-s390x-gnu': 4.53.5 - '@rollup/rollup-linux-x64-gnu': 4.53.5 - '@rollup/rollup-linux-x64-musl': 4.53.5 - '@rollup/rollup-openharmony-arm64': 4.53.5 - '@rollup/rollup-win32-arm64-msvc': 4.53.5 - '@rollup/rollup-win32-ia32-msvc': 4.53.5 - '@rollup/rollup-win32-x64-gnu': 4.53.5 - '@rollup/rollup-win32-x64-msvc': 4.53.5 + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 safe-buffer@5.2.1: {} @@ -4966,6 +5036,8 @@ snapshots: tslib@2.6.2: {} + tslib@2.8.1: {} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -4980,7 +5052,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typebox@1.0.64: {} + typebox@1.0.65: {} typescript-eslint@8.50.0(eslint@9.39.2)(typescript@5.9.3): dependencies: @@ -5039,7 +5111,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.5 + rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.19.3 @@ -5052,7 +5124,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.5 + rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.0.3 diff --git a/src/redocly.yaml b/src/redocly.yaml index 2c061f2..e54e41f 100644 --- a/src/redocly.yaml +++ b/src/redocly.yaml @@ -5,6 +5,8 @@ apis: root: ./user/openapi.json chat: root: ./chat/openapi.json + ttt: + root: ./tic-tac-toe/openapi.json rules: info-license: warn diff --git a/src/tic-tac-toe/entrypoint.sh b/src/tic-tac-toe/entrypoint.sh new file mode 100644 index 0000000..f1735d5 --- /dev/null +++ b/src/tic-tac-toe/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e +# do anything here + +# run the CMD [ ... ] from the dockerfile +exec "$@" diff --git a/src/tic-tac-toe/openapi.json b/src/tic-tac-toe/openapi.json new file mode 100644 index 0000000..38cd725 --- /dev/null +++ b/src/tic-tac-toe/openapi.json @@ -0,0 +1,21 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "9.6.1", + "title": "@fastify/swagger" + }, + "components": { + "schemas": {} + }, + "paths": {}, + "servers": [ + { + "url": "https://local.maix.me:8888", + "description": "direct from docker" + }, + { + "url": "https://local.maix.me:8000", + "description": "using fnginx" + } + ] +} diff --git a/src/tic-tac-toe/package.json b/src/tic-tac-toe/package.json new file mode 100644 index 0000000..e75880b --- /dev/null +++ b/src/tic-tac-toe/package.json @@ -0,0 +1,36 @@ +{ + "name": "tic-tac-toe", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "npm run build && node dist/run.js", + "build": "vite build", + "build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false && mv /dist/run.js /dist/run.cjs", + "build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && mv dist/openapi.js dist/openapi.cjs && node dist/openapi.cjs >openapi.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.24.0", + "dependencies": { + "@fastify/autoload": "^6.3.1", + "@fastify/formbody": "^8.0.2", + "@fastify/multipart": "^9.3.0", + "@fastify/sensible": "^6.0.4", + "@fastify/static": "^8.3.0", + "@fastify/websocket": "^11.2.0", + "fastify": "^5.6.2", + "fastify-plugin": "^5.1.0", + "socket.io": "^4.8.1", + "typebox": "^1.0.65" + }, + "devDependencies": { + "@types/node": "^22.19.3", + "fastify-socket.io": "^5.1.0", + "rollup-plugin-node-externals": "^8.1.2", + "socket.io": "^4.8.1", + "vite": "^7.3.0", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/src/tic-tac-toe/src/app.ts b/src/tic-tac-toe/src/app.ts new file mode 100644 index 0000000..977bb8f --- /dev/null +++ b/src/tic-tac-toe/src/app.ts @@ -0,0 +1,99 @@ +import { TTC } from './game'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; +import fastifyFormBody from '@fastify/formbody'; +import fastifyMultipart from '@fastify/multipart'; +import * as db from '@shared/database'; +import * as auth from '@shared/auth'; +import * as swagger from '@shared/swagger'; +import * as utils from '@shared/utils'; +import { Server } from 'socket.io'; +// import type { TicTacToeImpl } from '@shared/database/mixin/tictactoe'; + +declare const __SERVICE_NAME: string; + +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); + +const app: FastifyPluginAsync = async (fastify, opts): Promise => { + void opts; + + await fastify.register(utils.useMonitoring); + await fastify.register(utils.useMakeResponse); + await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME }); + await fastify.register(db.useDatabase as FastifyPluginAsync, {}); + await fastify.register(auth.jwtPlugin as FastifyPluginAsync, {}); + await fastify.register(auth.authPlugin as FastifyPluginAsync, {}); + + // Place here your custom code! + for (const plugin of Object.values(plugins)) { + void fastify.register(plugin as FastifyPluginAsync, {}); + } + for (const route of Object.values(routes)) { + void fastify.register(route as FastifyPluginAsync, {}); + } + + void fastify.register(fastifyFormBody, {}); + void fastify.register(fastifyMultipart, {}); + + const game = new TTC(); + + fastify.ready((err) => { + if (err) throw err; + onReady(fastify, game); + }); +}; +export default app; + +// When using .decorate you have to specify added properties for Typescript +declare module 'fastify' { + interface FastifyInstance { + io: Server<{ + hello: (message: string) => string; + // idk you put something + // eslint-disable-next-line @typescript-eslint/no-explicit-any + gameState: any; + makeMove: (idx: number) => void; + resetGame: () => void; + error: string, + }>; + } +} + +async function onReady(fastify: FastifyInstance, game: TTC) { + fastify.io.on('connection', (socket) => { + fastify.log.info(`Client connected: ${socket.id}`); + + socket.emit('gameState', { + board: game.board, + turn: game.currentPlayer, + gameOver: game.isGameOver, + }); + + socket.on('makeMove', (idx: number) => { + const result = game.makeMove(idx); + + if (result === 'invalidMove') { + socket.emit('error', 'Invalid Move'); + } + else { + fastify.io.emit('gameState', { + board: game.board, + turn: game.currentPlayer, + lastResult: result, + }); + // setGameOutcome(); + } + }); + + socket.on('resetGame', () => { + game.reset(); + fastify.io.emit('gameState', { + board: game.board, + turn: game.currentPlayer, + reset: true, + }); + }); + }); +} \ No newline at end of file diff --git a/src/tic-tac-toe/src/game.ts b/src/tic-tac-toe/src/game.ts new file mode 100644 index 0000000..721ec91 --- /dev/null +++ b/src/tic-tac-toe/src/game.ts @@ -0,0 +1,83 @@ +// import type { TicTacToeData } from '@shared/database/mixin/tictactoe'; + +// Represents the possible states of a cell on the board. +// `null` means that the cell is empty. +type CellState = 'O' | 'X' | null + +export class TTC { + private isGameOver: boolean = false; + public board: CellState[] = Array(9).fill(null); + private currentPlayer: 'O' | 'X' = 'X'; + + private changePlayer() { + this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X'; + } + + // Analyzes the current board to determine if the game has ended. + private checkState(): 'winX' | 'winO' | 'draw' | 'ongoing' { + const checkRow = (row: number): ('X' | 'O' | null) => { + if (this.board[row * 3] === null) {return null;} + if (this.board[row * 3] === this.board[row * 3 + 1] && this.board[row * 3 + 1] === this.board[row * 3 + 2]) {return this.board[row * 3];} + return null; + }; + + const checkCol = (col: number): ('X' | 'O' | null) => { + if (this.board[col] === null) return null; + + if (this.board[col] === this.board[col + 3] && this.board[col + 3] === this.board[col + 6]) {return this.board[col];} + return null; + }; + + const checkDiag = (): ('X' | 'O' | null) => { + if (this.board[4] === null) return null; + + if (this.board[0] === this.board[4] && this.board[4] === this.board[8]) {return this.board[4];} + + if (this.board[2] === this.board[4] && this.board[4] === this.board[6]) {return this.board[4];} + return null; + }; + + + const row = (checkRow(0) ?? checkRow(1)) ?? checkRow(2); + const col = (checkCol(0) ?? checkCol(1)) ?? checkCol(2); + const diag = checkDiag(); + + if (row !== null) return `win${row}`; + if (col !== null) return `win${col}`; + if (diag !== null) return `win${diag}`; + + if (this.board.filter(c => c === null).length === 0) {return 'draw';} + return 'ongoing'; + } + + public reset(): void { + this.board = [null, null, null, null, null, null, null, null, null]; + this.currentPlayer = 'X'; + this.isGameOver = false; + }; + + // Attempts to place the current player's mark on the specified cell. + // @param idx - The index of the board (0-8) to place the mark. + // @returns The resulting game state, or `invalidMove` if the move is illegal. + public makeMove(idx: number): 'winX' | 'winO' | 'draw' | 'ongoing' | 'invalidMove' { + if (this.isGameOver) { + return 'invalidMove'; + } + if (idx < 0 || idx >= this.board.length) { + return 'invalidMove'; + } + if (this.board[idx] !== null) { + return 'invalidMove'; + } + this.board[idx] = this.currentPlayer; + this.changePlayer(); + + const result = this.checkState(); + + if (result !== 'ongoing') { + this.isGameOver = true; + } + + return result; + } +} diff --git a/src/tic-tac-toe/src/openapi.ts b/src/tic-tac-toe/src/openapi.ts new file mode 100644 index 0000000..d66d7a7 --- /dev/null +++ b/src/tic-tac-toe/src/openapi.ts @@ -0,0 +1,21 @@ +import f, { FastifyPluginAsync } from 'fastify'; +import * as swagger from '@shared/swagger'; +import * as auth from '@shared/auth'; + +declare const __SERVICE_NAME: string; + +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... +const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); + +async function start() { + const fastify = f({ logger: false }); + await fastify.register(auth.authPlugin, { onlySchema: true }); + await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME }); + + for (const route of Object.values(routes)) { + await fastify.register(route as FastifyPluginAsync, {}); + } + await fastify.ready(); + console.log(JSON.stringify(fastify.swagger(), undefined, 4)); +} +start(); diff --git a/src/tic-tac-toe/src/plugins/.gitkeep b/src/tic-tac-toe/src/plugins/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/tic-tac-toe/src/plugins/socket.ts b/src/tic-tac-toe/src/plugins/socket.ts new file mode 100644 index 0000000..f485775 --- /dev/null +++ b/src/tic-tac-toe/src/plugins/socket.ts @@ -0,0 +1,31 @@ +import type { + FastifyInstance, + FastifyPluginAsync, + HookHandlerDoneFunction, +} from 'fastify'; +import fp from 'fastify-plugin'; +import { Server } from 'socket.io'; + +const F: ( + f: FastifyInstance, +) => Omit & { io: Server } = (f) => + f as Omit & { io: Server }; + +const fastifySocketIO: FastifyPluginAsync = fp(async (fastify) => { + function defaultPreClose(done: HookHandlerDoneFunction) { + F(fastify).io.local.disconnectSockets(true); + done(); + } + fastify.decorate( + 'io', + new Server(fastify.server, { path: '/api/ttt/socket.io' }), + ); + fastify.addHook('preClose', defaultPreClose); + fastify.addHook('onClose', (instance: FastifyInstance, done) => { + F(instance).io.close(); + done(); + }); +}); + +export default fastifySocketIO; + diff --git a/src/tic-tac-toe/src/routes/.gitkeep b/src/tic-tac-toe/src/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/tic-tac-toe/src/run.ts b/src/tic-tac-toe/src/run.ts new file mode 100644 index 0000000..3c59d5d --- /dev/null +++ b/src/tic-tac-toe/src/run.ts @@ -0,0 +1,21 @@ +// this sould only be used by the docker file ! + +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; + +const start = async () => { + const f: FastifyInstance = fastify({ logger: { level: 'info' } }); + process.on('SIGTERM', () => { + f.log.warn('Requested to shutdown'); + process.exit(134); + }); + try { + await f.register(app, {}); + await f.listen({ port: 80, host: '0.0.0.0' }); + } + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/tic-tac-toe/tsconfig.json b/src/tic-tac-toe/tsconfig.json new file mode 100644 index 0000000..e6d24e2 --- /dev/null +++ b/src/tic-tac-toe/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": {}, + "include": ["src/**/*.ts"] +} diff --git a/src/tic-tac-toe/vite.config.js b/src/tic-tac-toe/vite.config.js new file mode 100644 index 0000000..3d8be45 --- /dev/null +++ b/src/tic-tac-toe/vite.config.js @@ -0,0 +1,54 @@ +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import nodeExternals from 'rollup-plugin-node-externals'; +import path from 'node:path'; +import fs from 'node:fs'; + +function collectDeps(...pkgJsonPaths) { + const allDeps = new Set(); + for (const pkgPath of pkgJsonPaths) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + for (const dep of Object.keys(pkg.dependencies || {})) { + allDeps.add(dep); + } + for (const peer of Object.keys(pkg.peerDependencies || {})) { + allDeps.add(peer); + } + } + return Array.from(allDeps); +} + +const externals = collectDeps( + './package.json', + '../@shared/package.json', +); + + +export default defineConfig({ + root: __dirname, + define: { + __SERVICE_NAME: '"ttt"', + }, + // service root + plugins: [tsconfigPaths(), nodeExternals()], + build: { + ssr: true, + outDir: 'dist', + emptyOutDir: true, + lib: { + entry: path.resolve(__dirname, process.env.VITE_ENTRYPOINT ?? 'src/run.ts'), + // adjust main entry + formats: ['cjs'], + // CommonJS for Node.js + fileName: (format, entryName) => `${entryName}.cjs`, + }, + rollupOptions: { + external: externals, + }, + target: 'node22', + // or whatever Node version you use + sourcemap: true, + minify: false, + // for easier debugging + }, +}); diff --git a/src/user/package.json b/src/user/package.json index 2987bf2..e5c94cc 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -26,7 +26,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.64" + "typebox": "^1.0.65" }, "devDependencies": { "@types/node": "^22.19.3",