diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5f6749e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +**/node_modules +out + +# Storage +**/storage/ + +**/.git + +**/generated + +node_modules + +**/dist + +**/.nuxt + +**/.output \ No newline at end of file diff --git a/.github/workflows/build-frontend.yaml b/.github/workflows/build-frontend.yaml new file mode 100644 index 0000000..7b88008 --- /dev/null +++ b/.github/workflows/build-frontend.yaml @@ -0,0 +1,39 @@ +name: Build the Web Frontend Service + +on: + push: + branches: + - main + paths: + - 'apps/frontend/**' + - 'packages/**' + - '.github/workflows/**' + + pull_request: + branches: + - main + - dev + paths: + - 'apps/frontend/**' + - 'packages/**' + - '.github/workflows/**' + +jobs: + setup-and-build: + runs-on: ubuntu-latest + steps: + - name: check out the code + uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9.0.0 + run_install: false + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + - name: Install Package & Setup + run: pnpm install --frozen-lockfile --filter=frontend... -w + - name: Try to build + run: pnpm dlx turbo build --filter=frontend... diff --git a/.github/workflows/build-heartbeat.yaml b/.github/workflows/build-heartbeat.yaml new file mode 100644 index 0000000..7ea0210 --- /dev/null +++ b/.github/workflows/build-heartbeat.yaml @@ -0,0 +1,39 @@ +name: Build the Heartbeat Service + +on: + push: + branches: + - main + paths: + - 'apps/heartbeat/**' + - 'packages/domain/**' + - '.github/workflows/**' + + pull_request: + branches: + - main + - dev + paths: + - 'apps/heartbeat/**' + - 'packages/domain/**' + - '.github/workflows/**' + +jobs: + setup-and-build: + runs-on: ubuntu-latest + steps: + - name: check out the code + uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9.0.0 + run_install: false + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + - name: Install Package & Setup + run: pnpm install --frozen-lockfile --filter=heartbeat... -w + - name: Try to build + run: pnpm dlx turbo build --filter=heartbeat... diff --git a/.github/workflows/build_test-backend.yaml b/.github/workflows/build_test-backend.yaml new file mode 100644 index 0000000..662060e --- /dev/null +++ b/.github/workflows/build_test-backend.yaml @@ -0,0 +1,43 @@ +name: Build and Testing Backend Service + +on: + push: + branches: + - main + paths: + - 'apps/backend/**' + - 'packages/**' + - '.github/workflows/**' + + pull_request: + branches: + - main + - dev + paths: + - 'apps/backend/**' + - 'packages/**' + - '.github/workflows/**' + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Set up Docker + uses: docker/setup-docker-action@v4 + - name: check out the code + uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9.0.0 + run_install: false + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + - name: Install Package & Setup + run: pnpm install --frozen-lockfile --filter=backend... -w + - name: Try to build + run: pnpm dlx turbo build --filter=backend... + - name: RUN E2E Test + run: pnpm test:e2e diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ee96585 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +link-workspace-packages=true \ No newline at end of file diff --git a/apps/backend/.dockerignore b/apps/backend/.dockerignore new file mode 100644 index 0000000..3d0a649 --- /dev/null +++ b/apps/backend/.dockerignore @@ -0,0 +1,8 @@ +**/node_modules +out + +**/.git + +**/generated + +**/dist \ No newline at end of file diff --git a/apps/backend/.env.development b/apps/backend/.env.development index bec5299..4705114 100644 --- a/apps/backend/.env.development +++ b/apps/backend/.env.development @@ -1,8 +1,8 @@ NODE_ENV=development -NODE_PORT=3001 +NODE_PORT=8000 NODE_HOST=0.0.0.0 DB_TYPE=postgres -DB_HOST=db +DB_HOST=127.0.0.1 DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile new file mode 100644 index 0000000..b126d72 --- /dev/null +++ b/apps/backend/Dockerfile @@ -0,0 +1,53 @@ +#syntax=docker/dockerfile:1.7-labs +# 支援exclude文法 + +############### +# BUILD STAGE # This stage is used to build application before production (Typescript => Javascript) +############### +FROM node:20-alpine AS base +# Create and move into /app folder +WORKDIR /app +# Activate corepack to use pnpm +RUN npm install -g corepack@latest turbo +RUN corepack use pnpm@9.0.0 + +FROM base AS builder +WORKDIR /app +COPY . . +RUN turbo prune backend --docker + +# 安裝階段:安裝依賴並構建項目 +FROM base AS installer +WORKDIR /app +COPY --from=builder /app/out/json/ . +COPY pnpm-*.yaml ./ +COPY .npmrc ./ +# 安裝依賴,因為還包含了devDependencies,所以會比較大 +RUN pnpm i --frozen-lockfile +COPY --from=builder /app/out/full/ . +RUN turbo build --filter=backend... + +# 運行階段:設定最終的運行環境 +FROM base AS runner +WORKDIR /app +COPY --exclude=**/node_modules --from=installer /app . +RUN pnpm i --frozen-lockfile --prod +## 1.暫時不知道為啥這裏不生效,換上麵方法 +# COPY --from=installer /app . +# RUN pnpm prune --prod +## 2.方法2 +# RUN rm -rf ./node_modules && rm -rf ./**/*/node_modules +# RUN pnpm i --prod + +# 拷貝部署 +FROM base AS deploy +LABEL org.opencontainers.image.source="https://github.com/NTD-Driven-Development/lobby" +WORKDIR /app +# Don't run production as root +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 fastifyapp +USER fastifyapp +COPY --from=runner --chown=fastifyapp:nodejs /app . + +# 設定容器啓動命令 +CMD ["pnpm", "-F", "backend", "start"] \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index 639aeaf..9f7fff2 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,12 +1,12 @@ { - "name": "lobby-backend", + "name": "backend", "version": "0.0.0", "description": "", "main": "src/index.ts", "scripts": { "rebuild": "rm -rf ./dist && npm run build", - "build": "cross-env NODE_ENV=production esbuild --bundle --platform=node --target=node18 --outdir=dist src/index.ts", - "start": "cross-env NODE_ENV=production node dist/index", + "build": "cross-env NODE_ENV=production esbuild --bundle --platform=node --target=node20 --outdir=dist src/index.ts", + "start": "node dist/index", "dev": "cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register --project . --respawn src/index.ts", "format": "prettier --no-error-on-unmatched-pattern --write \"src/**/*.ts\" \"test/**/*.ts\"", "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint \"{src,apps,libs,test}/**/*.ts\"", @@ -25,9 +25,14 @@ "ts-jest": { "tsconfig": "./tsconfig.json" }, - "SOCKET_URL": "http://localhost:8002" + "SOCKET_URL": "http://localhost:8002", + "CLOSE_TEST_DB": false }, - "globalSetup": "./test/setupGlobal.ts", + "globalSetup": "./test/setup.ts", + "globalTeardown": "./test/teardown.ts", + "setupFilesAfterEnv": [ + "jest-extended" + ], "preset": "ts-jest", "moduleNameMapper": { "~/routes": "/src/routes", @@ -57,11 +62,12 @@ "devDependencies": { "@eslint/js": "^9.1.1", "@jest/globals": "^29.7.0", + "@jest/types": "^29.6.3", "@swc/core": "^1.5.5", "@swc/jest": "^0.2.36", + "@testcontainers/postgresql": "^10.22.0", "@tsconfig/node-lts": "^20.1.3", "@types/jest": "^29.5.0", - "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "cross-env": "^7.0.3", @@ -75,6 +81,7 @@ "jest-extended": "^3.2.4", "prettier": "^3.2.5", "socket.io-client": "^4.7.5", + "testcontainers": "^10.22.0", "ts-jest": "^29.1.0", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", @@ -82,8 +89,9 @@ "typescript-eslint": "^7.7.1" }, "dependencies": { - "@packages/domain": "file:../../packages/domain", - "@packages/socket": "file:../../packages/socket", + "@packages/domain": "workspace:*", + "@packages/socket": "workspace:*", + "@types/uuid": "^9.0.8", "axios": "^1.7.2", "dotenv": "^16.4.5", "fastify": "^4.26.2", diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 4143333..bf8652f 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -15,6 +15,14 @@ import { container } from 'tsyringe' import { auth0Middleware } from '~/middlewares' import { AppDataSource } from './data/data-source' import { GetNewStatusHandler } from './middlewares/get-new-status' +;['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => + process.on(signal, async () => { + /** do your logic */ + console.log('Server shutting down...') + await AppDataSource.destroy() + process.exit() + }), +) ;(async () => { try { // import { UserRoutes, RoomRoutes, GameRoutes } from '~/routes' @@ -27,8 +35,6 @@ import { GetNewStatusHandler } from './middlewares/get-new-status' app.register(socketIO, { cors: { origin: '*' } }) // prefix api app.register(RoomRoutes, { prefix: '/api/rooms' }) - // app.register(GameRoutes, { prefix: '/api/games' }) - // app.register(UserRoutes, { prefix: '/api/users' }) app.ready(async (err) => { if (err) throw err container.registerInstance('ServerSocket', app.io) diff --git a/apps/backend/test/environment.d.ts b/apps/backend/test/environment.d.ts new file mode 100644 index 0000000..b68ae07 --- /dev/null +++ b/apps/backend/test/environment.d.ts @@ -0,0 +1 @@ +import 'jest-extended' diff --git a/apps/backend/test/setup.ts b/apps/backend/test/setup.ts index 8d709f7..6a8dea4 100644 --- a/apps/backend/test/setup.ts +++ b/apps/backend/test/setup.ts @@ -1 +1,36 @@ -import 'jest-extended/all' +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-var */ +/* eslint-disable @typescript-eslint/no-var-requires */ +import 'tsconfig-paths/register' +import { Config } from '@jest/types' +import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql' + +declare global { + var SOCKET_URL: string + var pgContainer: StartedPostgreSqlContainer +} + +require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` }) + +export default async function (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) { + if (globalConfig.testPathPattern.includes('/unit')) { + console.log('is unit test, skip setup server') + return + } + // Connect our container + console.log('starting test db...') + const pgsqlContainer = await new PostgreSqlContainer('postgres:16-alpine3.19') + .withUsername(process.env.DB_USER) + .withPassword(process.env.DB_PASSWORD) + .withDatabase(process.env.DB_NAME) + .withExposedPorts({ + container: 5432, + host: Number(process.env.DB_PORT), + }) + .withReuse() + .start() + globalThis.pgContainer = pgsqlContainer + console.log('test db started...') + console.log('setup test server') + await require('../src/index') +} diff --git a/apps/backend/test/setupGlobal.ts b/apps/backend/test/setupGlobal.ts deleted file mode 100644 index c9929ed..0000000 --- a/apps/backend/test/setupGlobal.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-var */ -/* eslint-disable @typescript-eslint/no-var-requires */ -import 'tsconfig-paths/register' -import { Config } from '@jest/types' - -declare global { - var SOCKET_URL: string -} - -export default async function (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) { - if (globalConfig.testPathPattern.includes('/unit')) { - console.log('is unit test, skip setup server') - return - } - console.log('setup test server') - await require('../src/index') -} diff --git a/apps/backend/test/teardown.ts b/apps/backend/test/teardown.ts new file mode 100644 index 0000000..3924ccb --- /dev/null +++ b/apps/backend/test/teardown.ts @@ -0,0 +1,13 @@ +import { Config } from '@jest/types' + +export default async function (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) { + if (projectConfig.globals.CLOSE_TEST_DB) { + await closeDb() + } +} + +async function closeDb() { + console.log('stopping db...') + await globalThis.pgContainer.stop() + console.log('db stopped...') +} diff --git a/apps/backend/test/test.d.ts b/apps/backend/test/test.d.ts deleted file mode 100644 index 335d5d1..0000000 --- a/apps/backend/test/test.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index a5b4d0e..e63529c 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "@tsconfig/node-lts", "include": ["src", "test"], - "files": ["test/test.d.ts"], "compilerOptions": { "declaration": true, "removeComments": true, diff --git a/apps/backend/turbo.json b/apps/backend/turbo.json new file mode 100644 index 0000000..8b05331 --- /dev/null +++ b/apps/backend/turbo.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "dev": { + "dependsOn": ["^package:setup"], + "cache": false, + "persistent": true, + "interruptible": true + } + } +} \ No newline at end of file diff --git a/apps/frontend/.dockerignore b/apps/frontend/.dockerignore new file mode 100644 index 0000000..5a3bbac --- /dev/null +++ b/apps/frontend/.dockerignore @@ -0,0 +1,12 @@ +**/node_modules +out + +**/.git + +**/generated + +**/dist + +**/.nuxt + +**/.output \ No newline at end of file diff --git a/apps/frontend/.env.development b/apps/frontend/.env.development index 6984074..b8fa412 100644 --- a/apps/frontend/.env.development +++ b/apps/frontend/.env.development @@ -1 +1 @@ -NUXT_PUBLIC_BACKEND_URL=http://127.0.0.1:3001 +NUXT_PUBLIC_BACKEND_URL=http://127.0.0.1:8000 diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile new file mode 100644 index 0000000..829ecc2 --- /dev/null +++ b/apps/frontend/Dockerfile @@ -0,0 +1,65 @@ +############### +# BUILD STAGE # This stage is used to build application before production (Typescript => Javascript) +############### +FROM node:20-alpine AS builder + +# Create and move into /app folder +WORKDIR /app + +RUN apk update +RUN apk add --no-cache libc6-compat + +RUN yarn global add turbo +COPY . . + +RUN turbo prune frontend --docker + +################### +# INSTALLER STAGE # This stage is used to build application before production +################### +FROM node:20-alpine AS installer + +# Create and move into /app folder +WORKDIR /app + +RUN apk update +RUN apk add --no-cache libc6-compat + +# First install dependencies (as they change less often) +COPY .npmrc .gitignore ./ +COPY --from=builder /app/out/json/ . +COPY pnpm-*.yaml ./ + +RUN npm pkg delete scripts.prepare + +# Activate corepack to use pnpm +RUN npm install -g corepack@latest +RUN corepack use pnpm@9.0.0 +RUN pnpm install -r --frozen-lockfile + +# Build the project +COPY --from=builder /app/out/full/ ./ + +RUN pnpm turbo build --filter=frontend... + +# TEAM CACHE: clean prerequisites +RUN rm -rf .git + +RUN cd /app/apps/frontend && pnpm build + +FROM node:20-alpine AS runner +LABEL org.opencontainers.image.source="https://github.com/NTD-Driven-Development/lobby" + +# Create and move into /app folder +WORKDIR /app + +EXPOSE 3000 + +# Don't run production as root +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nuxtjs +USER nuxtjs + +COPY --from=installer --chown=nuxtjs:nodejs /app/apps/frontend/.output /app/.output + +CMD ["node", "/app/.output/server/index.mjs"] \ No newline at end of file diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 21e2ea1..7db62a6 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,20 +1,22 @@ { - "name": "nuxt-app", + "name": "frontend", "private": true, "type": "module", "scripts": { "build": "nuxt build", - "dev": "nuxt dev", + "dev": "nuxt dev --host", "generate": "nuxt generate", "preview": "nuxt preview", + "start": "nuxt preview", "postinstall": "nuxt prepare" }, "dependencies": { "@auth0/auth0-spa-js": "^2.1.3", - "@packages/domain": "file:../../packages/domain", - "@packages/socket": "file:../../packages/socket", + "@packages/domain": "workspace:*", + "@packages/socket": "workspace:*", "@pinia/nuxt": "^0.5.1", - "nuxt": "^3.11.2", + "lodash-es": "^4.17.21", + "nuxt": "^3.16.1", "pinia": "^2.1.7", "primevue": "^3.52.0", "socket.io-client": "^4.7.5", diff --git a/apps/frontend/pages/room.vue b/apps/frontend/pages/room.vue index 561ea00..47ce3e6 100644 --- a/apps/frontend/pages/room.vue +++ b/apps/frontend/pages/room.vue @@ -38,7 +38,7 @@