Merge pull request #56 from Maix0/Alex/tic-tac-toe_frontend
Alex/tic tac toe frontend
This commit is contained in:
commit
7ebd129faa
14 changed files with 588 additions and 0 deletions
|
|
@ -74,6 +74,25 @@ 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
|
||||
# - EXTRA_FILES=tic-tac-toe/extra
|
||||
# container_name: tic-tac-toe
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - transcendance-network
|
||||
# volumes:
|
||||
# - sqlite-volume:/volumes/database
|
||||
# - static-volume:/volumes/static
|
||||
# environment:
|
||||
# - JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA
|
||||
# - DATABASE_DIR=/volumes/database
|
||||
|
||||
###############
|
||||
# CHAT #
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
<a href="/login" class="hover:bg-gray-700 rounded-md px-3 py-2">👤 Login</a>
|
||||
<a href="/signin" class="hover:bg-gray-700 rounded-md px-3 py-2">👤 Signin</a>
|
||||
<a href="/chat" class="hover:bg-gray-700 rounded-md px-3 py-2">👤 Chat</a>
|
||||
<a href="/ttt" class="hover:bg-gray-700 rounded-md px-3 py-2">⭕ Tic-Tac-Toe</a>
|
||||
<a href="/contact" class="hover:bg-gray-700 rounded-md px-3 py-2">⚙️ Settings</a>
|
||||
<a href="/logout" class="hover:bg-gray-700 rounded-md px-3 py-2">🚪 Logout</a>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import './root/root.ts'
|
|||
import './chat/chat.ts'
|
||||
import './login/login.ts'
|
||||
import './signin/signin.ts'
|
||||
import './ttt/ttt.ts'
|
||||
import './profile/profile.ts'
|
||||
|
||||
// ---- Initial load ----
|
||||
|
|
|
|||
29
frontend/src/pages/ttt/README.md
Normal file
29
frontend/src/pages/ttt/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
Add another game with user history and matchmaking.
|
||||
The goal of this major module, is to introduce a new game, distinct from Pong, and
|
||||
incorporate features such as user history tracking and matchmaking. Key features
|
||||
and objectives include:
|
||||
◦ Develop a new, engaging game to diversify the platform’s offerings and enter-
|
||||
tain users.
|
||||
◦ Implement user history tracking to record and display individual users’ game-
|
||||
play statistics.
|
||||
◦ Create a matchmaking system to allow users to find opponents and participate
|
||||
in fair and balanced matches.
|
||||
◦ Ensure that user game history and matchmaking data are stored securely and
|
||||
remain up-to-date.
|
||||
◦ Optimize the performance and responsiveness of the new game to provide an
|
||||
enjoyable user experience. Regularly update and maintain the game to fix
|
||||
bugs, add new features, and enhance gameplay.
|
||||
This major module aims to expand your platform by introducing a new game,
|
||||
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
|
||||
[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
|
||||
[ ] -
|
||||
[ ] -
|
||||
[ ] -
|
||||
16
frontend/src/pages/ttt/ttt.html
Normal file
16
frontend/src/pages/ttt/ttt.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<div class="bg-gray-100 p-8">
|
||||
<div class="grid grid-cols-3 gap-4 max-w-2xl mx-auto">
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
<div class="bg-blue-500 h-32 flex items-center justify-center text-white text-xl font-bold hover:bg-red-700 ttt-grid-cell"> </div>
|
||||
</div>
|
||||
<button id="ttt-restart-btn" class="mt-8 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Restart Game
|
||||
</button>
|
||||
</div>
|
||||
191
frontend/src/pages/ttt/ttt.ts
Normal file
191
frontend/src/pages/ttt/ttt.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import { addRoute, setTitle, type RouteHandlerReturn } from "@app/routing";
|
||||
import tttPage from "./ttt.html?raw";
|
||||
import { showError, showInfo, showSuccess } from "@app/toast";
|
||||
|
||||
// Represents the possible states of a cell on the board.
|
||||
// `null` means that the cell is empty.
|
||||
type CellState = 'O' | 'X' | null
|
||||
|
||||
// Encapsulates the game logic.
|
||||
class TTC {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Route handler for the Tic-Tac-Toe page.
|
||||
// Instantiates the game logic and binds UI events.
|
||||
async function handleTTT(): Promise<RouteHandlerReturn>
|
||||
{
|
||||
// Create a fresh instance for every page load.
|
||||
let board = new TTC();
|
||||
|
||||
return {
|
||||
html: tttPage,
|
||||
postInsert: async (app) => {
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cells = app.querySelectorAll<HTMLDivElement>(".ttt-grid-cell");
|
||||
const restartBtn = app.querySelector<HTMLButtonElement>("#ttt-restart-btn");
|
||||
|
||||
const updateUI = () => {
|
||||
const board_state = board.getBoard();
|
||||
board_state.forEach((cell_state, cell_idx) => {
|
||||
cells[cell_idx].innerText = cell_state !== null ? cell_state : " ";
|
||||
});
|
||||
};
|
||||
|
||||
console.log(cells);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 : " ";
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addRoute('/ttt', handleTTT)
|
||||
2
src/tic-tac-toe/.dockerignore
Normal file
2
src/tic-tac-toe/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/dist
|
||||
/node_modules
|
||||
18
src/tic-tac-toe/README.md
Normal file
18
src/tic-tac-toe/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Directory layout
|
||||
```plaintext
|
||||
./src/tic-tac-toe
|
||||
├── entrypoint.sh
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── src
|
||||
│ ├── app.ts # The microservice app file, where the major part of the backend code lives.
|
||||
│ └── run.ts # Equivalent of server.ts, it is the entrypoint for our service.
|
||||
├── tsconfig.json
|
||||
└── vite.config.js
|
||||
```
|
||||
|
||||
# Anatomy of a microservice
|
||||
|
||||
# Backend
|
||||
|
||||
# Frontend
|
||||
7
src/tic-tac-toe/entrypoint.sh
Normal file
7
src/tic-tac-toe/entrypoint.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# run the CMD [ ... ] from the dockerfile
|
||||
exec "$@"
|
||||
17
src/tic-tac-toe/package.json
Normal file
17
src/tic-tac-toe/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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",
|
||||
"build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && node dist/openapi.cjs >openapi.json",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.24.0"
|
||||
}
|
||||
198
src/tic-tac-toe/src/app.ts
Normal file
198
src/tic-tac-toe/src/app.ts
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
||||
// // TODO: Import Fastify formbody
|
||||
// // TODO: Import Fastify multipart
|
||||
// // TODO: Import shared database
|
||||
// // TODO: Import shared auth
|
||||
// // TODO: Import shared swagger
|
||||
// // TODO: Import shared utils
|
||||
// // TODO: Import socketio
|
||||
|
||||
// // @brief ???
|
||||
// declare const __SERVICE_NAME: string;
|
||||
|
||||
// // TODO: Import the plugins defined for this microservice
|
||||
// // TODO: Import the routes defined for this microservice
|
||||
|
||||
// // @brief The microservice app (as a plugin for Fastify), kinda like a main function I guess ???
|
||||
// // @param fastify
|
||||
// // @param opts
|
||||
// export const app: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
// // Register all the fastify plugins that this app will use
|
||||
|
||||
// // Once it is done:
|
||||
// fastify.ready((err) => {
|
||||
// if (err) {
|
||||
// throw err;
|
||||
// }
|
||||
// // TODO: Supposedly, something should be there I guess
|
||||
// });
|
||||
// };
|
||||
// // Export it as the default for this file.
|
||||
// export default app;
|
||||
|
||||
// // TODO: Understand what is this for in /src/chat/src/app.ts
|
||||
// // declare module 'fastify' {
|
||||
// // interface FastifyInstance {
|
||||
// // io: Server<{
|
||||
// // hello: (message: string) => string;
|
||||
// // MsgObjectServer: (data: { message: ClientMessage }) => void;
|
||||
// // message: (msg: string) => void;
|
||||
// // testend: (sock_id_client: string) => void;
|
||||
// // }>;
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // TODO: Same for this, also in /src/chat/src/app.ts
|
||||
// // async function onReady(fastify: FastifyInstance) {
|
||||
// // function connectedUser(io?: Server, target?: string): number {
|
||||
// // let count = 0;
|
||||
// // const seen = new Set<string>();
|
||||
// // // <- only log/count unique usernames
|
||||
|
||||
// // for (const [socketId, username] of clientChat) {
|
||||
// // // Basic sanity checks
|
||||
// // if (typeof socketId !== 'string' || socketId.length === 0) {
|
||||
// // clientChat.delete(socketId);
|
||||
// // continue;
|
||||
// // }
|
||||
// // if (typeof username !== 'string' || username.length === 0) {
|
||||
// // clientChat.delete(socketId);
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// // // If we have the io instance, attempt to validate the socket is still connected
|
||||
// // if (io && typeof io.sockets?.sockets?.get === 'function') {
|
||||
// // const s = io.sockets.sockets.get(socketId) as
|
||||
// // | Socket
|
||||
// // | undefined;
|
||||
// // // If socket not found or disconnected, remove from map and skip
|
||||
// // if (!s || s.disconnected) {
|
||||
// // clientChat.delete(socketId);
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// // // Skip duplicates (DO NOT delete them — just don't count)
|
||||
// // if (seen.has(username)) {
|
||||
// // continue;
|
||||
// // }
|
||||
// // // socket exists and is connected
|
||||
// // seen.add(username);
|
||||
// // count++;
|
||||
// // // console.log(color.green,"count: ", count);
|
||||
// // console.log(color.yellow, 'Client:', color.reset, username);
|
||||
|
||||
// // const targetSocketId = target;
|
||||
// // io.to(targetSocketId!).emit('listObj', username);
|
||||
|
||||
// // console.log(
|
||||
// // color.yellow,
|
||||
// // 'Chat Socket ID:',
|
||||
// // color.reset,
|
||||
// // socketId,
|
||||
// // );
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// // // If no io provided, assume entries in the map are valid and count them.
|
||||
// // count++;
|
||||
// // console.log(
|
||||
// // color.red,
|
||||
// // 'Client (unverified):',
|
||||
// // color.reset,
|
||||
// // username,
|
||||
// // );
|
||||
// // console.log(
|
||||
// // color.red,
|
||||
// // 'Chat Socket ID (unverified):',
|
||||
// // color.reset,
|
||||
// // socketId,
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // return count;
|
||||
// // }
|
||||
|
||||
// // function broadcast(data: ClientMessage, sender?: string) {
|
||||
// // fastify.io.fetchSockets().then((sockets) => {
|
||||
// // for (const s of sockets) {
|
||||
// // if (s.id !== sender) {
|
||||
// // // Send REAL JSON object
|
||||
// // const clientName = clientChat.get(s.id) || null;
|
||||
// // if (clientName !== null) {
|
||||
// // s.emit('MsgObjectServer', { message: data });
|
||||
// // }
|
||||
// // console.log(' Target window socket ID:', s.id);
|
||||
// // console.log(' Target window ID:', [...s.rooms]);
|
||||
// // console.log(' Sender window ID:', sender ? sender : 'none');
|
||||
// // }
|
||||
// // }
|
||||
// // });
|
||||
// // }
|
||||
|
||||
// // fastify.io.on('connection', (socket: Socket) => {
|
||||
// // socket.on('message', (message: string) => {
|
||||
// // console.info(
|
||||
// // color.blue,
|
||||
// // 'Socket connected!',
|
||||
// // color.reset,
|
||||
// // socket.id,
|
||||
// // );
|
||||
// // console.log(
|
||||
// // color.blue,
|
||||
// // 'Received message from client',
|
||||
// // color.reset,
|
||||
// // message,
|
||||
// // );
|
||||
|
||||
// // const obj: ClientMessage = JSON.parse(message) as ClientMessage;
|
||||
// // clientChat.set(socket.id, obj.user);
|
||||
// // console.log(
|
||||
// // color.green,
|
||||
// // 'Message from client',
|
||||
// // color.reset,
|
||||
// // `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`,
|
||||
// // );
|
||||
// // // Send object directly — DO NOT wrap it in a string
|
||||
// // broadcast(obj, obj.SenderWindowID);
|
||||
// // console.log(
|
||||
// // color.red,
|
||||
// // 'connected in the Chat :',
|
||||
// // connectedUser(fastify.io),
|
||||
// // color.reset,
|
||||
// // );
|
||||
// // });
|
||||
|
||||
// // socket.on('testend', (sock_id_cl: string) => {
|
||||
// // console.log('testend received from client socket id:', sock_id_cl);
|
||||
// // });
|
||||
|
||||
// // socket.on('list', () => {
|
||||
// // console.log(color.red, 'list activated', color.reset, socket.id);
|
||||
// // connectedUser(fastify.io, socket.id);
|
||||
// // });
|
||||
|
||||
// // socket.on('disconnecting', (reason) => {
|
||||
// // const clientName = clientChat.get(socket.id) || null;
|
||||
// // console.log(
|
||||
// // color.green,
|
||||
// // `Client disconnecting: ${clientName} (${socket.id}) reason:`,
|
||||
// // reason,
|
||||
// // );
|
||||
// // if (reason === 'transport error') return;
|
||||
|
||||
// // if (clientName !== null) {
|
||||
// // const obj = {
|
||||
// // type: 'chat',
|
||||
// // user: clientName,
|
||||
// // token: '',
|
||||
// // text: 'LEFT the chat',
|
||||
// // timestamp: Date.now(),
|
||||
// // SenderWindowID: socket.id,
|
||||
// // };
|
||||
|
||||
// // broadcast(obj, obj.SenderWindowID);
|
||||
// // // clientChat.delete(obj.user);
|
||||
// // }
|
||||
// // });
|
||||
// // });
|
||||
// // }
|
||||
31
src/tic-tac-toe/src/run.ts
Normal file
31
src/tic-tac-toe/src/run.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// @file run.ts
|
||||
// @brief The entrypoint to the service.
|
||||
|
||||
// Entry point of the microservice, ran by the Dockerfile.
|
||||
|
||||
import fastify, { FastifyInstance } from 'fastify';
|
||||
import app from './app';
|
||||
// TODO: Import the microservice app
|
||||
|
||||
// @brief Entrypoint for the microservice's backend.
|
||||
const start = async () => {
|
||||
// TODO: Thingies to send to log service (if I understood that correctly from /src/chat/src/run.ts)
|
||||
|
||||
// TODO: Add the logging thingy to the call to fastify()
|
||||
const fastInst: FastifyInstance = fastify();
|
||||
try {
|
||||
process.on('SIGTERM', () => {
|
||||
fastInst.log.info('Requested to shutdown');
|
||||
process.exit(143);
|
||||
});
|
||||
// TODO: Uncomment when app.ts will be import-able.
|
||||
await fastInst.register(app);
|
||||
await fastInst.listen({ port: 80, host: '0.0.0.0' });
|
||||
}
|
||||
catch (err) {
|
||||
fastInst.log.error(err);
|
||||
process.exit(1);
|
||||
};
|
||||
};
|
||||
|
||||
start();
|
||||
5
src/tic-tac-toe/tsconfig.json
Normal file
5
src/tic-tac-toe/tsconfig.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
53
src/tic-tac-toe/vite.config.js
Normal file
53
src/tic-tac-toe/vite.config.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
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: '"tic-tac-toe"',
|
||||
},
|
||||
// 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: () => 'index.js',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: externals,
|
||||
},
|
||||
target: 'node22',
|
||||
// or whatever Node version you use
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
// for easier debugging
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue