Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build-api:
cd api && docker build -t bingo-api:local -f Containerfile .

build-web:
cd web && docker build -t bingo-web:local -f Containerfile .

compose:
docker compose up --build
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function.

##### Web
- NEXT_PUBLIC_API_PATH is the url that the api service is running at
- NEXT_PUBLIC_SOCKET_PATH is the url that the web socket is available at

#### Optional
##### API
Expand Down
4 changes: 4 additions & 0 deletions api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.log
*.db
node_modules/*
build/*
16 changes: 16 additions & 0 deletions api/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM node:20

WORKDIR /api

COPY tsconfig.json package.json package-lock.json ./
COPY ./prisma ./prisma
COPY .eslintrc ./
COPY .prettierignore ./

RUN npm i && npm i -g tsx


COPY ./src ./src
COPY start.sh ./

CMD [ "bash", "start.sh" ]
38 changes: 38 additions & 0 deletions api/prisma/ready.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PrismaClient } from "@prisma/client";
import { exec } from "child_process";
import util from "util";

const prisma = new PrismaClient();
const execAsync = util.promisify(exec);

const waitForDatabase = async (retries: number = 5, delay: number = 2000) => {
for (let i = 0; i < retries; i++) {
try {
await prisma.$connect();
console.log("Database is up!");
return;
} catch {
console.log(`Attempt ${i + 1} failed. Retrying in ${delay / 1000}s...`);
await new Promise(res => setTimeout(res, delay));
}
}
throw new Error("Database is not reachable after retries.");
};

const checkDatabaseAndMigrate = async () => {
try {
await waitForDatabase();
console.log("Running Prisma migrations...");
const { stdout, stderr } = await execAsync("npx prisma migrate dev");
console.log(stdout);
if (stderr) console.error(stderr);
console.log("Migrations applied successfully.");
} catch (error: any) {
console.error("Error:", error.message);
process.exit(1);
} finally {
await prisma.$disconnect();
}
};

checkDatabaseAndMigrate();
11 changes: 8 additions & 3 deletions api/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ const prisma = new PrismaClient();
async function main() {
console.log('Seeding database');

const users = await prisma.user.findMany()
if (users.length != 0) {
console.log('Database already has some users, skipping seeding')
return
}

console.log('Creating users');
await prisma.user.deleteMany();
const salt = randomBytes(16);
Expand Down Expand Up @@ -338,9 +344,8 @@ async function main() {
},
...Array.from({ length: 54 }).map((_, i) => ({
goal: `Side Quest ${i + 27}`,
description: `Complete side quest number ${
i + 27
} for extra rewards.`,
description: `Complete side quest number ${i + 27
} for extra rewards.`,
categories: [realisticCategories[i % realisticCategories.length]],
difficulty: ((i + 1) % 25) + 1,
})),
Expand Down
10 changes: 10 additions & 0 deletions api/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -e

tsx prisma/ready.ts

npx prisma migrate dev

tsx prisma/seed.ts

npm run dev
18 changes: 15 additions & 3 deletions api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@
"outDir": "build",
"baseUrl": "./src",
"declarationMap": true,
"types": [
"node"
]
},
"ts-node": {
"esm": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/main.ts", "prisma/seed.ts"],
"exclude": ["src/__tests__/**/*", "src/coverage/**/*"]
}
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/main.ts",
"prisma/seed.ts",
"prisma/ready.ts"
],
"exclude": [
"src/__tests__/**/*",
"src/coverage/**/*"
]
}
53 changes: 53 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
version: "3.9"

services:
api:
build:
context: ./api
dockerfile: Containerfile
container_name: api
ports:
- "8000:8000" # Map container port to host port
environment:
ROOM_TOKEN_SECRET: your-room-token-secret
SESSION_SECRET: your-session-secret
CLIENT_URL: http://web:3000 # Assuming the Web service runs on this URL
DATABASE_URL: postgres://devuser:devpassword@database:5432/devdb
# SMTP_HOST: your-smtp-host
# SMTP_USER: your-email-address
# SMTP_PASSWORD: your-email-password
PORT: 8000
# volumes:
# - ./api:/api
depends_on:
- database # Optional if you have a database container defined separately

web:
build:
context: ./web
dockerfile: Containerfile
container_name: web
ports:
- "3000:3000" # Map container port to host port
environment:
NEXT_PUBLIC_API_PATH: http://api:8000 # Assuming the API service runs on this URL
NEXT_PUBLIC_SOCKET_PATH: "ws://localhost:8000" # this needs to be routable from the client
# volumes:
# - ./web:/web
depends_on:
- api

database:
image: postgres:latest
container_name: database
ports:
- "5432:5432"
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: devdb
volumes:
- postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:
4 changes: 4 additions & 0 deletions web/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.log
*.db
node_modules/*
build/*
13 changes: 13 additions & 0 deletions web/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:20

WORKDIR /web

COPY tsconfig.json package.json package-lock.json ./
COPY ./public ./public
COPY .eslintrc.json .prettierignore next.config.js ./

RUN npm i

COPY ./src ./src

CMD [ "npm", "run", "dev" ]
29 changes: 13 additions & 16 deletions web/src/context/RoomContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import { RoomData } from '../types/RoomData';
import { ChatMessage, Player, ServerMessage } from '../types/ServerMessage';
import { alertError } from '../lib/Utils';

const websocketBase = (process.env.NEXT_PUBLIC_API_PATH ?? '').replace(
'http',
'ws',
);
const websocketBase = (process.env.NEXT_PUBLIC_SOCKET_PATH ?? '')

export enum ConnectionStatus {
UNINITIALIZED, // the room connection is uninitialized and there is no authentication data present
Expand Down Expand Up @@ -76,17 +73,17 @@ export const RoomContext = createContext<RoomContext>({
async connect() {
return { success: false };
},
sendChatMessage(message) {},
markGoal(row, col) {},
unmarkGoal(row, col) {},
changeColor() {},
regenerateCard() {},
disconnect() {},
createRacetimeRoom() {},
updateRacetimeRoom() {},
joinRacetimeRoom() {},
racetimeReady() {},
racetimeUnready() {},
sendChatMessage(message) { },
markGoal(row, col) { },
unmarkGoal(row, col) { },
changeColor() { },
regenerateCard() { },
disconnect() { },
createRacetimeRoom() { },
updateRacetimeRoom() { },
joinRacetimeRoom() { },
racetimeReady() { },
racetimeUnready() { },
});

interface RoomContextProps {
Expand Down Expand Up @@ -239,7 +236,7 @@ export function RoomContextProvider({ slug, children }: RoomContextProps) {
},
},
connectionStatus === ConnectionStatus.CONNECTING ||
connectionStatus === ConnectionStatus.CONNECTED,
connectionStatus === ConnectionStatus.CONNECTED,
);

// actions
Expand Down