diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..88cfa33 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +*.md +.git +.gitignore +.prettierrc +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4b54278 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM node:20-slim AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +FROM base AS build +WORKDIR /usr/src/app + +COPY pnpm-workspace.prod.yaml pnpm-workspace.yaml +COPY pnpm-lock.yaml . + +COPY server/package.json server/package.json +COPY web/package.json web/package.json + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile + +COPY . . +COPY server/.env.docker server/.env +COPY web/.env.docker web/.env + +RUN NODE_ENV=production pnpm run -r build + +RUN pnpm deploy --filter=server --prod /prod/server --ignore-scripts +RUN mv ./web /prod/web + +FROM base AS app +WORKDIR /prod/app + +COPY --from=build /prod/server . +COPY --from=build /prod/web/dist ./web/dist + +RUN pnpm swag:gen + +ENV NODE_ENV production + +EXPOSE 5000 + +CMD [ "pnpm", "start" ] \ No newline at end of file diff --git a/README.md b/README.md index 319802a..b93fa10 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,12 @@ ```bash pnpm docker:up ``` -> Make sure ports `6379`,`5432`,`5000` and `3000` are not occupied -- Run migrations -```bash -pnpm docker:db:migrate -``` +> Make sure the ports `6379`,`5432` and `5000` are not occupied - Seed database ```bash pnpm docker:db:seed ``` -- Go to [http://localhost:3000](http://localhost:3000) +- Go to [http://localhost:5000](http://localhost:5000) - Stop docker containers ```bash diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4ac4162..250dd67 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: db: image: postgres:16-alpine @@ -29,26 +27,13 @@ services: timeout: 10s retries: 5 - web: - build: - context: ./web - dockerfile: Dockerfile - ports: - - '3000:3000' - environment: - VITE_BACKEND_URL: http://localhost:5000 - depends_on: - - server - - server: - build: - context: ./server - dockerfile: Dockerfile - container_name: mchat-server + app: + build: . + container_name: mchat-app ports: - '5000:5000' depends_on: db: condition: service_healthy - redis: + dragonfly: condition: service_healthy diff --git a/package.json b/package.json index d3fa160..b772493 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "prepare": "husky install", "commitlint": "commitlint --edit", "docker:up": "docker compose -f docker-compose.prod.yml up --build -d", - "docker:db:migrate": "docker exec -it mchat-server pnpm migrate:run", - "docker:db:seed": "docker exec -it mchat-server pnpm seed", + "docker:db:migrate": "docker exec -it mchat-app pnpm migrate:run", + "docker:db:seed": "docker exec -it mchat-app pnpm seed", "docker:down": "docker compose -f docker-compose.prod.yml down", "e2e:test": "pnpm --filter e2e test" }, @@ -34,5 +34,6 @@ "workspaces": [ "client", "server" - ] + ], + "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb44521..d1cafb9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: server: dependencies: + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 '@socket.io/redis-streams-adapter': specifier: ^0.2.2 version: 0.2.2(socket.io-adapter@2.5.5) @@ -77,6 +80,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + module-alias: + specifier: ^2.2.3 + version: 2.2.3 morgan: specifier: ^1.10.0 version: 1.10.0 @@ -99,9 +105,6 @@ importers: '@eslint/js': specifier: ^9.6.0 version: 9.6.0 - '@faker-js/faker': - specifier: ^8.4.1 - version: 8.4.1 '@types/cookie-parser': specifier: ^1.4.7 version: 1.4.7 @@ -3524,6 +3527,9 @@ packages: mlly@1.7.1: resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + module-alias@2.2.3: + resolution: {integrity: sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==} + morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -8255,6 +8261,8 @@ snapshots: pkg-types: 1.1.3 ufo: 1.5.3 + module-alias@2.2.3: {} + morgan@1.10.0: dependencies: basic-auth: 2.0.1 diff --git a/pnpm-workspace.prod.yaml b/pnpm-workspace.prod.yaml new file mode 100644 index 0000000..2380e05 --- /dev/null +++ b/pnpm-workspace.prod.yaml @@ -0,0 +1,3 @@ +packages: + - 'web' + - 'server' \ No newline at end of file diff --git a/server/.dockerignore b/server/.dockerignore deleted file mode 100644 index 6c52b3e..0000000 --- a/server/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.env -eslint.config.mjs diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index 773a18f..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM node:20-alpine AS base - -WORKDIR /app - -RUN npm i -g pnpm - -COPY package.json . - -RUN pnpm i - -RUN pnpm swag:gen - -COPY .env.docker .env - -COPY . . - -ENV NODE_ENV production - -EXPOSE 5000 - -CMD ["pnpm", "start"] \ No newline at end of file diff --git a/server/package.json b/server/package.json index 2364f37..fd8716f 100644 --- a/server/package.json +++ b/server/package.json @@ -4,14 +4,14 @@ "description": "mChat server implementation", "private": true, "scripts": { - "start": "tsx src/index.ts", + "start": "node dist/index.js", "dev": "tsx watch src/index.ts", "build": "tsc", "lint": "tsc && eslint src/**/*.ts", "migrate:gen": "drizzle-kit generate", - "migrate:run": "tsx src/scripts/migrate.ts", - "seed": "tsx src/scripts/seed.ts", - "swag:gen": "tsx src/scripts/swagger.ts" + "migrate:run": "node dist/scripts/migrate.js", + "seed": "node dist/scripts/seed.js", + "swag:gen": "node dist/scripts/swagger.js" }, "keywords": [ "socket.io", @@ -33,16 +33,18 @@ "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", + "module-alias": "^2.2.3", "morgan": "^1.10.0", "pg": "^8.12.0", "socket.io": "^4.7.5", "swagger-autogen": "^2.23.7", "swagger-ui-express": "^5.0.1", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "@faker-js/faker": "^8.4.1" }, "devDependencies": { + "drizzle-kit": "^0.22.8", "@eslint/js": "^9.6.0", - "@faker-js/faker": "^8.4.1", "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", @@ -53,7 +55,6 @@ "@types/pg": "^8.11.6", "@types/swagger-ui-express": "^4.1.6", "@types/uuid": "^10.0.0", - "drizzle-kit": "^0.22.8", "eslint": "9.x", "globals": "^15.7.0", "tsx": "^4.15.6", @@ -61,6 +62,6 @@ "typescript-eslint": "^7.15.0" }, "_moduleAliases": { - "@": "src" + "@": "dist" } } diff --git a/server/src/index.ts b/server/src/index.ts index 3fda82f..f44367e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,7 +1,10 @@ +import 'module-alias/register' + import { createAdapter } from '@socket.io/redis-streams-adapter' import 'colors' import cookieParser from 'cookie-parser' import cors from 'cors' +import { migrate } from 'drizzle-orm/node-postgres/migrator' import express from 'express' import helmet from 'helmet' import morgan from 'morgan' @@ -10,7 +13,7 @@ import path from 'node:path' import { Server } from 'socket.io' import swaggerUi from 'swagger-ui-express' import { config } from './config' -import { connectDB } from './database' +import { connectDB, db } from './database' import { errorHandler } from './middlewares' import { getRedisClient } from './redis' import rootRouter from './routes' @@ -26,6 +29,9 @@ import swaggerDocument from './swagger-output.json' const createApp = async () => { await connectDB() + + await migrate(db, { migrationsFolder: './drizzle' }) + const redisClient = getRedisClient() const app = express() @@ -65,9 +71,10 @@ const createApp = async () => { app.use(errorHandler) if (config.isProd) { - app.use(express.static(path.join('../../web/dist'))) - app.get('*', (req, res) => { - res.sendFile(path.join(__dirname, '../../web/dist/index.html')) + const webStaticPath = path.join(__dirname, '..', 'web', 'dist') + app.use(express.static(webStaticPath)) + app.get('*', (_req, res) => { + res.sendFile(path.join(webStaticPath, 'index.html')) }) } diff --git a/server/src/scripts/migrate.ts b/server/src/scripts/migrate.ts index 697fcfa..6edadc7 100644 --- a/server/src/scripts/migrate.ts +++ b/server/src/scripts/migrate.ts @@ -1,3 +1,5 @@ +import 'module-alias/register' + import 'colors' import { migrate } from 'drizzle-orm/node-postgres/migrator' import { client, connectDB, db } from '../database' diff --git a/server/src/scripts/seed.ts b/server/src/scripts/seed.ts index 9269680..8b39114 100644 --- a/server/src/scripts/seed.ts +++ b/server/src/scripts/seed.ts @@ -1,3 +1,5 @@ +import 'module-alias/register' + import { connectDB, db } from '@/database' import { NewGroup, groupsTable } from '@/modules/groups/groups.schema' import { NewMember, membersTable } from '@/modules/members/members.schema' diff --git a/server/src/scripts/swagger.ts b/server/src/scripts/swagger.ts index 24cf91f..16aee60 100644 --- a/server/src/scripts/swagger.ts +++ b/server/src/scripts/swagger.ts @@ -1,3 +1,5 @@ +import 'module-alias/register' + import swaggerAutogen from 'swagger-autogen' const doc = { diff --git a/web/.dockerignore b/web/.dockerignore deleted file mode 100644 index f06235c..0000000 --- a/web/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/web/Dockerfile b/web/Dockerfile deleted file mode 100644 index 13ed1ae..0000000 --- a/web/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:20-alpine AS base - -WORKDIR /app - -RUN npm i -g pnpm - -COPY package.json . - -RUN pnpm i - -COPY .env.docker .env - -COPY . . - -RUN pnpm build - - - -FROM node:20-alpine - -WORKDIR /app - -COPY --from=base /app/dist ./dist - -RUN npm i -g serve - -ENV NODE_ENV production - -EXPOSE 3000 - -CMD [ "serve", "-s", "dist" ] \ No newline at end of file