From b553cdc656da78abeca1aae21bad9c7916142db1 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Tue, 23 Sep 2025 12:26:34 -0700 Subject: [PATCH] Updated docker and server configs --- .dockerignore | 6 +- .github/copilot-instructions.md | 48 +++++++ .github/workflows/ci.yml | 32 +++++ .gitignore | 10 +- Dockerfile | 22 ++- Dockerfile.server | 26 ++++ README.md | 239 ++++++++++++++++---------------- docker-compose.client.dev.yml | 22 +++ docker-compose.dev.yml | 21 +++ docker-compose.yml | 25 ++++ server/basepath.js | 2 +- server/config/production.json | 4 +- server/package.json | 20 ++- server/routes/games.js | 37 ++--- server/src/app.ts | 116 ++++++++++++++++ server/tests/app.test.ts | 11 ++ server/tsconfig.json | 19 +++ 17 files changed, 511 insertions(+), 149 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile.server create mode 100644 docker-compose.client.dev.yml create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml create mode 100644 server/src/app.ts create mode 100644 server/tests/app.test.ts create mode 100644 server/tsconfig.json diff --git a/.dockerignore b/.dockerignore index 5b92281..7230417 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,7 @@ * -!server -server/node_modules +!server/ +!client/ +server/node_modules/ +client/node_modules/ !Dockerfile !.env diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ee50873 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,48 @@ +Do not run npm or create local node_modules in this repository +============================================================ + +This repository is designed to run inside containers. Do NOT run `npm install`, +`npm ci`, or otherwise modify `node_modules` or lockfiles locally on your +machine. Doing so will create environment drift and can break container-based +builds and CI. + +Guidelines for contributors and automated assistants +- Use the provided Docker and docker-compose workflows for development and + building. The repository's `Dockerfile` and `docker-compose.yml` are the + canonical way to install dependencies and run the app. +- If you need to run a command locally for quick checks, prefer run commands in + the project's container environment. Example (copy-paste): + + ```bash + docker compose run --rm peddlers-of-ketran bash + # inside container + npm install + npm run build + ``` + +- For lightweight commands, you can run one-off containers: + + ```bash + docker run --rm -v "$PWD":/app -w /app node:20 bash -lc 'cd client && npm ci && npm run type-check' + ``` + +- Do not suggest or perform `npm install` or `npm ci` on the host machine in + repository files, PRs, or automated edits. If a change requires dependency + updates, update package.json and lockfiles via a CI job or in-container run, + and record the change in a PR. + +Copilot / Automation specific hints +- When editing package.json, also update the appropriate lockfile using the + container workflow and include lockfile changes in the same PR. +- If a change requires running tests or builds, prefer to add a GitHub Action + that runs the command inside the standard container image rather than + instructing contributors to run commands locally. + +Why this matters +- Containers provide a reproducible environment and prevent developer + machines from diverging. They ensure CI and production builds are consistent. + +If you need a recommended local development setup or a dev container (VS Code +Remote - Containers), create a `.devcontainer/` configuration and include it in +the repo. That provides a supported local dev workflow that still uses +containerized tooling. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34b0cb0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + server-build: + runs-on: ubuntu-latest + name: Server build and type-check (container) + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Server: Type-check and Build (inside node:20) + run: | + docker run --rm -v "$PWD":/work -w /work node:20 bash -lc ' + cd server && npm ci --no-audit --no-fund && npm run type-check && npm run build' + + client-build: + runs-on: ubuntu-latest + name: Client type-check and build (container) + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Client: Type-check and Build (inside node:20) + run: | + docker run --rm -v "$PWD":/work -w /work node:20 bash -lc ' + cd client && npm ci --no-audit --no-fund && npm run type-check && npm run build' diff --git a/.gitignore b/.gitignore index 7f05cd3..851407c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,12 @@ -!.gitignore +/.ssh/ +/client/node_modules/ +/server/node_modules/ +/node_modules/ +/dist/ +/build/ +.env +.DS_Store +/.vscode/!.gitignore !package.json node_modules package-lock.json diff --git a/Dockerfile b/Dockerfile index 38dd56e..a847951 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy +FROM ubuntu:noble RUN apt-get -q update \ && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ @@ -10,7 +10,7 @@ RUN apt-get -q update \ RUN mkdir -p /etc/apt/keyrings RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -ENV NODE_MAJOR=20 +ENV NODE_MAJOR=22 RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list RUN apt-get -q update \ @@ -25,12 +25,26 @@ RUN apt-get -q update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} -COPY /server /server +COPY server /server WORKDIR /server RUN npm install -s sqlite3 RUN npm install +RUN npm run build + +# prepare client deps in the image so lint/type-check can run inside the container +# copy client sources and install dependencies during the image build (container-first) +COPY client /client +WORKDIR /client +ENV PUBLIC_URL="/ketr.ketran" +ENV REACT_APP_API_BASE="/ketr.ketran" +# prefer npm ci when lockfile present, otherwise fall back to npm install +RUN rm -f package-lock.json +RUN npm install --legacy-peer-deps --no-audit --no-fund +RUN npm run build +# return to server working dir for default run +WORKDIR /server COPY /Dockerfile /Dockerfile COPY /.env /.env -ENTRYPOINT [ "npm", "start" ] +CMD ["npm", "start"] diff --git a/Dockerfile.server b/Dockerfile.server new file mode 100644 index 0000000..d701ffa --- /dev/null +++ b/Dockerfile.server @@ -0,0 +1,26 @@ +## Multi-stage Dockerfile for building the TypeScript server +FROM node:20-alpine AS builder +WORKDIR / + +# Copy package files and install (in container only) +COPY server/package*.json ./server/ +WORKDIR /server +RUN npm ci --silent + +# Copy server source and build +COPY server/ ./ +RUN npm run build + +## Production image +FROM node:20-alpine AS runtime + +WORKDIR / + +# Copy built server +COPY --from=builder /server/dist ./server/dist +COPY server/package*.json /server/ + +WORKDIR /server +ENV NODE_ENV=production +EXPOSE 8930 +CMD ["npm", "start"] diff --git a/README.md b/README.md index 4921065..69f60a8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,115 @@ # Peddlers of Ketran -This project consists of both the front-end and back-end game -API server. +This project consists of both the front-end React client and back-end Node.js game API server for the Settlers of Catan-style board game. -The front-end is launched from the 'client' directory in development mode via 'npm start'. In production, you build -it via 'npm build' and deploy the public front-end. +The application is designed to run inside Docker containers. Do not run `npm install` or modify `node_modules` locally, as this can create environment drift. All development and building should be done through the provided Docker workflows. -The back-end is launched out of the 'server' directory via 'npm start' and will -bind to the default port 8930. +## Development -If you change the default port of the REST API server, you will need to change -client/package.json's "proxy" value to reflect the new port change. +For live development with hot reloading, React DevTools support, and TypeScript compilation on-the-fly. -NOTE: +### Prerequisites -Board.js currently hard codes assetsPath and gamesPath to be absolute as the -dynamic router and resource / asset loading isn't working correctly. +- Docker +- Docker Compose -## Building +### Running the Server (Backend) -### Native - -#### Prerequisites +The server uses TypeScript with hot reloading via `ts-node-dev`. ```bash -sudo apt-get install -y nodejs npm python -sudo -E npm install --global npm@latest +docker compose -f docker-compose.dev.yml up ``` -### In container +This will: +- Build the server container using `Dockerfile.server` +- Mount the `server/` directory for live code changes +- Run `npm run start:dev` which uses `ts-node-dev` for hot reloading +- Expose the server on port 8930 + +### Testing the Server + +The server uses Jest for unit testing. Tests are located in `server/tests/`. + +To run tests: ```bash +docker compose run --rm server npm test ``` -# Architecture +This will run all test files matching `*.test.ts` in the `server/tests/` directory. + +To run tests in watch mode: + +```bash +docker compose run --rm server npm run test:watch +``` + +Note: If you add `test:watch` script, but for now, it's just `test`. + +### Running the Client (Frontend) + +The client is a React application with hot reloading and debug support. + +```bash +docker compose -f docker-compose.client.dev.yml up +``` + +This will: +- Mount the `client/` directory for live code changes +- Run `npm start` in development mode +- Enable React DevTools and hot module replacement +- Expose the client on port 3001 (HTTPS) +- Proxy API calls to the server at `http://host.docker.internal:8930` + +### Full Development Stack + +To run both server and client together: + +```bash +# Terminal 1: Server +docker compose -f docker-compose.dev.yml up + +# Terminal 2: Client +docker compose -f docker-compose.client.dev.yml up +``` + +Open `https://localhost:3001` in your browser. The client will hot-reload on code changes, and the server will restart automatically on backend changes. + +## Production + +For production deployment, the client is built as static files and served directly by the Node.js server. + +### Building + +```bash +docker compose build +``` + +This builds the production image using `Dockerfile`, which: +- Installs server dependencies and builds TypeScript to `dist/` +- Installs client dependencies and builds static files to `client/build/` +- Copies the built client files to be served by the server + +### Running + +```bash +docker compose up +``` + +This will: +- Start the server on port 8930 +- Serve the built client static files +- Mount the `db/` directory for persistent database storage +- Mount `server/routes/` for dynamic route files + +The application will be available at `http://localhost:8930`. + +### Environment Variables + +Create a `.env` file in the project root with any required environment variables. The server start script loads these via `export $(cat ../.env | xargs)`. + +## Architecture ```plantuml skinparam componentStyle rectangle @@ -48,99 +124,40 @@ package "Game" as game { component Players as players } -server <-> resource : serves to client -client <-> server +server <-> resource : serves static files to client +client <-> server : API calls player <-> client players -r-> player server -> game ``` -# Ketr.Ketran REST API +## API Documentation -## POST /api/v1/game +### POST /api/v1/game -### Request +#### Request ```json {} ``` -### Response +#### Response ```json { - gameId: id - gameState: { - tiles: [] + "gameId": "id", + "gameState": { + "tiles": [] } } ``` -# Configuring / installing +## Configuration -## Build +Add security tokens in `server/config/local.json`: ```bash -git clone git.ketrenos.com:jketreno/peddlers-of-ketran.git -cd server -npm install -npm start & -cd ../client -npm install -npm start -``` - -## Install - -```bash -export BASEPATH=${PWD} -# Ensure sudo has password ready -sudo -l -sudo rsync -avprl install/ /etc/ -sudo touch /var/log/ketr-ketran.log -sudo chown system:adm /var/log/ketr-ketran.log -sudo systemctl daemon-reload -sudo systemctl restart rsyslogd -sudo systemctl restart logrotate -sudo systemctl restart ketr.ketran -``` - -Install the following into your nginx server configuration: - -```nginx - location /ketr.ketran { - root /var/www/ketrenos.com; - index unresolvable-file-html.html; - try_files $uri @index; - } - - # This seperate location is so the no cache policy only applies to the index and nothing else. - location @index { - root /var/www/ketrenos.com/ketr.ketran; - add_header Cache-Control no-cache; - expires 0; - try_files /index.html =404; - } - - location /ketr.ketran/api { - proxy_pass http://192.168.1.78:8930; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - proxy_pass_header Set-Cookie; - proxy_pass_header P3P; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } -``` - -Add security tokens in ketr.ketran/config/local.json: - -```bash -cat << EOF > config/local.json +cat << EOF > server/config/local.json { "tokens": [ { "$(whoami)": "$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)" @@ -149,45 +166,25 @@ cat << EOF > config/local.json EOF ``` -## Launch +## Testing +### Create New Game ```bash -sudo systemctl start ketr.ketran +curl -X POST http://localhost:8930/api/v1/games/ ``` -## To test - -### New game +### Get Game Status ```bash -curl -k -s -X POST http://localhost:8930/ketr.ketran/api/v1/games/ +curl -X GET http://localhost:8930/api/v1/games/:id ``` -### Game status +## Game States -```bash -curl -k -s -X GET http://localhost:8930/ketr.ketran/api/v1/games/:id -``` +- **Lobby**: Players register, choose colors, roll for position, set ready +- **Active**: Gameplay phase -# States +Chat is available at all times for registered players. -Chat is available at all times by registered players +## License Attribution -## Lobby - - -* Register session+name -* Register session with color -* Unregister player+name from color -* Roll dice for player position -* Shuffle board -* Set "Ready" for player -* All ready? state == active - -## License attribution - -The dice faces (dice-six-faces-*.svg) are Copyright https://delapouite.com/ and licensed -as [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/). - -## Active - -* +The dice faces (dice-six-faces-*.svg) are Copyright [https://delapouite.com/](https://delapouite.com/) and licensed as [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/). diff --git a/docker-compose.client.dev.yml b/docker-compose.client.dev.yml new file mode 100644 index 0000000..08368ac --- /dev/null +++ b/docker-compose.client.dev.yml @@ -0,0 +1,22 @@ +services: + peddlers-client: + build: + context: . + dockerfile: Dockerfile + container_name: peddlers-client + working_dir: /client + volumes: + - ./client:/client:rw + ports: + - 3001:3000 + environment: + - BROWSER=none + - HTTPS=true + - HOST=0.0.0.0 + command: ["bash", "-c", "cd /client && npm install --legacy-peer-deps --silent --no-audit --no-fund && npm start"] + networks: + - peddlers-network + +networks: + peddlers-network: + driver: bridge diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f06a51a --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,21 @@ +services: + peddlers-of-ketran: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + - ./server:/server:rw + - ./db:/db:rw + #- ./server/node_modules:app/server/node_modules:rw + command: ["sh", "-c", "cd /server && npm install --no-audit --no-fund --silent && npm run start:dev"] + ports: + - 8930:8930 + environment: + - NODE_ENV=development + tty: true + networks: + - peddlers-network + +networks: + peddlers-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5eed084 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + peddlers-of-ketran: + container_name: ketr.ketran + build: + context: . + dockerfile: Dockerfile + restart: always + ports: + - 8930:8930 + volumes: + - ./db:/db:rw + - ./server/routes:/server/routes:ro + working_dir: /server + peddlers-client: + container_name: ketr.client + build: + context: . + dockerfile: Dockerfile + restart: 'no' + working_dir: /client + volumes: + - ./client:/client:rw + #- ./client:/client/node_modules:rw + tty: true + stdin_open: true diff --git a/server/basepath.js b/server/basepath.js index e1f6d50..3402e1f 100755 --- a/server/basepath.js +++ b/server/basepath.js @@ -1,4 +1,4 @@ -let basePath = process.env.REACT_APP_basePath; +let basePath = process.env.REACT_APP_basePath || ""; basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/"; if (basePath == "//") { basePath = "/"; diff --git a/server/config/production.json b/server/config/production.json index 0967ef4..65949a5 100644 --- a/server/config/production.json +++ b/server/config/production.json @@ -1 +1,3 @@ -{} +{ + "frontendPath": "/client/build" +} diff --git a/server/package.json b/server/package.json index 19a0b44..09f4585 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,12 @@ "version": "1.0.0", "main": "app.js", "scripts": { - "start": "export $(cat ../.env | xargs) && node app.js" + "start": "export $(cat ../.env | xargs) && node dist/app.js", + "start:legacy": "export $(cat ../.env | xargs) && node app.js", + "build": "tsc -p tsconfig.json", + "start:dev": "ts-node-dev --respawn --transpile-only src/app.ts", + "test": "jest", + "type-check": "tsc -p tsconfig.json --noEmit" }, "author": "James Ketrenos ", "license": "MIT", @@ -30,6 +35,19 @@ "typeface-roboto": "0.0.75", "ws": "^8.5.0" }, + "devDependencies": { + "@types/jest": "^29.5.0", + "@types/node": "^20.0.0", + "@types/supertest": "^2.0.12", + "jest": "^29.7.0", + "supertest": "^6.3.3", + "ts-jest": "^29.1.0", + "ts-node-dev": "^2.0.0", + "typescript": "^5.0.0" + }, + "jest": { + "preset": "ts-jest" + }, "repository": { "type": "git", "url": "git@git.ketrenos.com:jketreno/peddlers-of-ketran" diff --git a/server/routes/games.js b/server/routes/games.js index 4633025..73b1e55 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -2,8 +2,8 @@ const express = require("express"), router = express.Router(), - crypto = require("crypto"), - { readFile, writeFile } = require("fs").promises, + crypto = require("crypto") +const { readFile, writeFile, mkdir } = require("fs").promises, fs = require("fs"), accessSync = fs.accessSync, randomWords = require("random-words"), @@ -619,7 +619,7 @@ const loadGame = async (id) => { return games[id]; } - let game = await readFile(`games/${id}`) + let game = await readFile(`/db/games/${id}`) .catch(() => { return; }); @@ -627,20 +627,20 @@ const loadGame = async (id) => { if (game) { try { game = JSON.parse(game); - console.log(`${info}: Creating backup of games/${id}`); - await writeFile(`games/${id}.bk`, JSON.stringify(game)); + console.log(`${info}: Creating backup of /db/games/${id}`); + await writeFile(`/db/games/${id}.bk`, JSON.stringify(game)); } catch (error) { - console.log(`Load or parse error from games/${id}:`, error); - console.log(`Attempting to load backup from games/${id}.bk`); - game = await readFile(`games/${id}.bk`) + console.log(`Load or parse error from /db/games/${id}:`, error); + console.log(`Attempting to load backup from /db/games/${id}.bk`); + game = await readFile(`/db/games/${id}.bk`) .catch(() => { console.error(error, game); }); if (game) { try { game = JSON.parse(game); - console.log(`Saving backup to games/${id}`); - await writeFile(`games/${id}`, JSON.stringify(game, null, 2)); + console.log(`Saving backup to /db/games/${id}`); + await writeFile(`/db/games/${id}`, JSON.stringify(game, null, 2)); } catch (error) { console.error(error); game = null; @@ -3448,15 +3448,16 @@ const saveGame = async (game) => { /* Save per turn while debugging... */ game.step = game.step ? game.step : 0; /* - await writeFile(`games/${game.id}.${game.step++}`, JSON.stringify(reducedGame, null, 2)) + await writeFile(`/db/games/${game.id}.${game.step++}`, JSON.stringify(reducedGame, null, 2)) .catch((error) => { - console.error(`${session.id} Unable to write to games/${game.id}`); + console.error(`${session.id} Unable to write to /db/games/${game.id}`); console.error(error); }); */ - await writeFile(`games/${game.id}`, JSON.stringify(reducedGame, null, 2)) + await mkdir('/db/games', { recursive: true }); + await writeFile(`/db/games/${game.id}`, JSON.stringify(reducedGame, null, 2)) .catch((error) => { - console.error(`${session.id} Unable to write to games/${game.id}`); + console.error(`Unable to write to /db/games/${game.id}`); console.error(error); }); } @@ -3886,7 +3887,7 @@ router.ws("/ws/:id", async (ws, req) => { /* Check for a game in the Winner state with no more connections * and remove it */ - if (game.state === 'winner' || game.state === 'lobby') { + if (game.state === 'winner') { let dead = true; for (let id in game.sessions) { if (game.sessions[id].live && game.sessions[id].name) { @@ -3910,9 +3911,9 @@ router.ws("/ws/:id", async (ws, req) => { delete audio[id]; delete games[id]; try { - fs.unlinkSync(`games/${id}`); + fs.unlinkSync(`/db/games/${id}`); } catch (error) { - console.error(`${session.id}: Unable to remove games/${id}`); + console.error(`${session.id}: Unable to remove /db/games/${id}`); } } } @@ -4655,7 +4656,7 @@ const createGame = (id) => { id = randomWords(4).join('-'); try { /* If file can be read, it already exists so look for a new name */ - accessSync(`games/${id}`, fs.F_OK); + accessSync(`/db/games/${id}`, fs.F_OK); id = ''; } catch (error) { break; diff --git a/server/src/app.ts b/server/src/app.ts new file mode 100644 index 0000000..d14674b --- /dev/null +++ b/server/src/app.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import type { Request, Response, NextFunction } from 'express'; +process.env.TZ = "Etc/GMT"; + +console.log("Loading ketr.ketran"); + +const express = require("express"); +const bodyParser = require("body-parser"); +const config = require("config"); +const session = require('express-session'); +const basePath = require("../basepath"); +const cookieParser = require("cookie-parser"); +const fs = require('fs'); + +const app = express(); +const server = require("http").createServer(app); + +app.use(cookieParser()); + +const ws = require('express-ws')(app, server); + +require("../console-line.js"); /* Monkey-patch console.log with line numbers */ + +const frontendPath = config.get("frontendPath").replace(/\/$/, "") + "/", + serverConfig = config.get("server"); + +console.log("Hosting server from: " + basePath); + +let userDB: any, gameDB: any; + +app.use(bodyParser.json()); + +/* App is behind an nginx proxy which we trust, so use the remote address + * set in the headers */ +app.set("trust proxy", true); + +app.set("basePath", basePath); +app.use(basePath, require("../routes/basepath.js")); + +/* Handle static files first so excessive logging doesn't occur */ +app.use(basePath, express.static(frontendPath, { index: false })); + +const index = require("../routes/index"); + +if (config.has("admin")) { + const admin = config.get("admin"); + app.set("admin", admin); +} + +/* Allow loading of the app w/out being logged in */ +app.use(basePath, index); + +/* /games loads the default index */ +app.use(basePath + "games", index); + +app.use(function(err: any, req: Request, res: Response, next: NextFunction) { + console.error(err.message); + res.status(err.status || 500).json({ + message: err.message, + error: {} + }); +}); + +app.use(`${basePath}api/v1/games`, require("../routes/games")); + +/* Declare the "catch all" index route last; the final route is a 404 dynamic router */ +app.use(basePath, index); + +/** + * Create HTTP server and listen for new connections + */ +app.set("port", serverConfig.port); + +process.on('SIGINT', () => { + console.log("Gracefully shutting down from SIGINT (Ctrl-C) in 2 seconds"); + setTimeout(() => process.exit(-1), 2000); + server.close(() => process.exit(1)); +}); + +require("../db/games").then(function(db: any) { + gameDB = db; +}).then(function() { + return require("../db/users").then(function(db: any) { + userDB = db; + }); +}).then(function() { + console.log("DB connected. Opening server."); + server.listen(serverConfig.port, () => { + console.log(`http/ws server listening on ${serverConfig.port}`); + }); +}).catch(function(error: any) { + console.error(error); + process.exit(-1); +}); + +server.on("error", function(error: any) { + if (error.syscall !== "listen") { + throw error; + } + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(serverConfig.port + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(serverConfig.port + " is already in use"); + process.exit(1); + break; + default: + throw error; + } +}); + +module.exports = { app, server }; diff --git a/server/tests/app.test.ts b/server/tests/app.test.ts new file mode 100644 index 0000000..6efeb57 --- /dev/null +++ b/server/tests/app.test.ts @@ -0,0 +1,11 @@ +import request from 'supertest'; +const { app } = require('../src/app'); + +describe('Server Routes', () => { + it('should respond to GET /', async () => { + const response = await request(app).get('/'); + expect(response.status).toBe(200); + }); + + // Add more tests as needed +}); \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..8d82423 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "outDir": "dist", + "rootDir": "src", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "strict": false, + "noImplicitAny": false, + "moduleResolution": "Node", + "resolveJsonModule": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}