Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
H
hrsystem
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
hrsystem
Commits
f28d9aac
Commit
f28d9aac
authored
Apr 05, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 22 files via Son of Anton
parent
5e893066
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
446 additions
and
188 deletions
+446
-188
.dockerignore
.dockerignore
+20
-0
.gitignore
.gitignore
+6
-6
CAPROVER_ENV_VARS.md
CAPROVER_ENV_VARS.md
+31
-3
Dockerfile.backend
Dockerfile.backend
+82
-36
Dockerfile.frontend
Dockerfile.frontend
+47
-11
README.md
README.md
+2
-2
.env.example
backend/.env.example
+16
-12
Dockerfile
backend/Dockerfile
+27
-21
nest-cli.json
backend/nest-cli.json
+4
-1
tsconfig.build.json
backend/tsconfig.build.json
+7
-1
tsconfig.json
backend/tsconfig.json
+13
-7
deploy.sh
deploy.sh
+69
-24
docker-compose.yml
docker-compose.yml
+21
-4
.env.example
frontend/.env.example
+6
-2
Dockerfile
frontend/Dockerfile
+20
-18
next.config.js
frontend/next.config.js
+44
-22
tsconfig.json
frontend/tsconfig.json
+16
-5
package.json
shared/package.json
+7
-8
tsconfig.json
shared/tsconfig.json
+8
-5
No files found.
.dockerignore
0 → 100644
View file @
f28d9aac
node_modules
**/node_modules
.next
**/dist
**/build
.git
.github
*.md
!README.md
.env*
**/coverage
.vscode
.idea
**/*.log
.DS_Store
Thumbs.db
minio_data
postgres_data
redis_data
**/.tsbuildinfo
\ No newline at end of file
.gitignore
View file @
f28d9aac
...
...
@@ -9,13 +9,12 @@ build/
.next/
out/
# Environment
# Environment
files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
*.env
# IDE
.vscode/
...
...
@@ -37,13 +36,14 @@ yarn-error.log*
# Testing
coverage/
#
MinIO local data
#
Local Docker volumes
minio_data/
# Docker volumes
postgres_data/
redis_data/
# Misc
*.tsbuildinfo
next-env.d.ts
# Prisma merged schema (generated at build time)
prisma/merged.prisma
\ No newline at end of file
CAPROVER_ENV_VARS.md
View file @
f28d9aac
# CapRover
Environment Variables
# CapRover
Deployment Guide — The Grind
##
Backend App (`thegrind-api`
)
##
Step 1: Deploy Infrastructure (One-Click Apps
)
### Required
\ No newline at end of file
In CapRover dashboard, deploy these one-click apps FIRST:
| Service | CapRover App Name | Notes |
|---------|-------------------|-------|
| PostgreSQL 16 |
`thegrind-db`
| Set password during setup |
| Redis 7 |
`thegrind-redis`
| Default config is fine |
| MinIO |
`thegrind-minio`
| Set root user/password |
> **Important:** Note the internal CapRover hostnames. They follow the pattern:
> `srv-captain--APP_NAME` (e.g., `srv-captain--thegrind-db`)
---
## Step 2: Create Application Apps
Create two
**"Blank"**
apps (NOT one-click):
| App Name | Has Persistent Data |
|----------|-------------------|
|
`thegrind-api`
| No |
|
`thegrind`
| No |
---
## Step 3: Configure Backend Environment Variables
In CapRover →
`thegrind-api`
→ App Configs → Environment Variables:
### Required Variables
\ No newline at end of file
Dockerfile.backend
View file @
f28d9aac
###############################################
# Stage 1: Build
###############################################
FROM node:20-alpine AS builder
RUN apk add --no-cache libc6-compat openssl python3 make g++
WORKDIR /build
#
Copy shared types package
COPY shared/package.json ./shared/
package.json
COPY shared/tsconfig.json ./shared/
tsconfig.json
COPY shared/src
./shared/src
#
── shared package ──────────────────────────
COPY shared/package.json ./shared/
COPY shared/tsconfig.json ./shared/
COPY shared/src
/ ./shared/src/
# Copy ALL prisma schema files
WORKDIR /build/shared
RUN npm install --ignore-scripts 2>/dev/null || true
# ── prisma schemas (root level) ─────────────
WORKDIR /build
COPY prisma/ ./prisma/
# Copy backend
# Merge all .prisma files into one unified schema.
# schema.prisma MUST come first (has datasource + generator).
# The rest are model definitions that get appended.
RUN echo "--- Merging Prisma schemas ---" && \
cp prisma/schema.prisma prisma/merged.prisma && \
for f in \
prisma/schema-additions.prisma \
prisma/schema-api-integration.prisma \
prisma/schema-boards.prisma \
prisma/schema-comments-checklists.prisma \
prisma/schema-communications.prisma \
prisma/schema-evaluations.prisma \
prisma/schema-financial.prisma \
prisma/schema-offboarding.prisma \
prisma/schema-reports.prisma \
prisma/schema-scheduling.prisma; \
do \
if [ -f "$f" ]; then \
echo "" >> prisma/merged.prisma; \
echo "// ── $(basename $f) ──" >> prisma/merged.prisma; \
# Strip duplicate datasource/generator blocks from appended files
sed '/^datasource /,/^}/d; /^generator /,/^}/d' "$f" >> prisma/merged.prisma; \
fi; \
done && \
echo "--- Merge complete ($(wc -l < prisma/merged.prisma) lines) ---"
# ── backend ─────────────────────────────────
COPY backend/package*.json ./backend/
COPY backend/nest-cli.json ./backend/
COPY backend/tsconfig*.json ./backend/
WORKDIR /build/backend
RUN npm install --legacy-peer-deps
COPY backend/ ./
# Copy prisma into backend for generation (all files)
RUN cp -r /build/prisma ./prisma
# If using multi-file schema, merge them first
# Option A: If schema.prisma has a generator with previewFeatures = ["prismaSchemaFolder"]
# Option B: Concatenate all schema files into one (safer)
RUN cat ./prisma/schema.prisma \
./prisma/schema-additions.prisma \
./prisma/schema-api-integration.prisma \
./prisma/schema-boards.prisma \
./prisma/schema-comments-checklists.prisma \
./prisma/schema-communications.prisma \
./prisma/schema-evaluations.prisma \
./prisma/schema-financial.prisma \
./prisma/schema-offboarding.prisma \
./prisma/schema-reports.prisma \
./prisma/schema-scheduling.prisma \
> ./prisma/merged-schema.prisma || true
# Generate Prisma client — try merged first, fallback to single
RUN npx prisma generate --schema=./prisma/merged-schema.prisma 2>/dev/null \
|| npx prisma generate --schema=./prisma/schema.prisma
# Copy all backend source
COPY backend/src/ ./src/
COPY backend/routes/ ./routes/ 2>/dev/null || true
COPY backend/services/ ./services/ 2>/dev/null || true
# Copy merged prisma into backend context
RUN mkdir -p prisma && cp /build/prisma/merged.prisma ./prisma/schema.prisma
# Copy all original prisma files too (in case migrations reference them)
RUN cp /build/prisma/*.prisma ./prisma/ 2>/dev/null || true
# Generate Prisma client from merged schema
RUN npx prisma generate --schema=./prisma/schema.prisma
# Build NestJS
RUN npm run build
# --- Production ---
# Prune dev dependencies
RUN npm prune --production --legacy-peer-deps 2>/dev/null || true
###############################################
# Stage 2: Production
###############################################
FROM node:20-alpine
RUN apk add --no-cache libc6-compat openssl
RUN apk add --no-cache libc6-compat openssl
tini
WORKDIR /app
ENV NODE_ENV=production
# Copy built app
COPY --from=builder /build/backend/dist ./dist
COPY --from=builder /build/backend/node_modules ./node_modules
COPY --from=builder /build/backend/prisma ./prisma
COPY --from=builder /build/backend/package.json ./package.json
# Copy prisma schema + client
COPY --from=builder /build/backend/prisma ./prisma
# Copy seed file if it exists
COPY --from=builder /build/backend/src/prisma/seed.ts ./prisma/seed.ts 2>/dev/null || true
EXPOSE 3001
# Use db push instead of migrate deploy (no migrations needed)
# This is safe for initial deployment and schema-driven workflows
CMD ["sh", "-c", "npx prisma db push --schema=./prisma/merged-schema.prisma --skip-generate --accept-data-loss 2>/dev/null || npx prisma db push --schema=./prisma/schema.prisma --skip-generate --accept-data-loss 2>/dev/null; node dist/main.js"]
\ No newline at end of file
# Use tini for proper signal handling (PID 1 problem)
ENTRYPOINT ["/sbin/tini", "--"]
# On startup: push schema to DB (idempotent), then run seed, then start server
CMD ["sh", "-c", "\
echo '🔄 Pushing Prisma schema to database...' && \
npx prisma db push --schema=./prisma/schema.prisma --skip-generate --accept-data-loss 2>&1 || \
echo '⚠️ prisma db push failed — DB may already be up to date' && \
echo '🌱 Running seed (if available)...' && \
npx prisma db seed --schema=./prisma/schema.prisma 2>/dev/null || true && \
echo '🚀 Starting backend...' && \
node dist/main.js \
"]
\ No newline at end of file
Dockerfile.frontend
View file @
f28d9aac
FROM node:20-alpine AS builder
###############################################
# Stage 1: Dependencies
###############################################
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /build
#
Copy shared types package
COPY shared/package.json ./shared/
package.json
COPY shared/tsconfig.json ./shared/
tsconfig.json
COPY shared/src
./shared/src
#
── shared package ──────────────────────────
COPY shared/package.json ./shared/
COPY shared/tsconfig.json ./shared/
COPY shared/src
/ ./shared/src/
#
Copy frontend
#
── frontend dependencies ───────────────────
COPY frontend/package*.json ./frontend/
WORKDIR /build/frontend
RUN npm install --legacy-peer-deps
COPY frontend/ ./
###############################################
# Stage 2: Build
###############################################
FROM node:20-alpine AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /build
# Copy deps
COPY --from=deps /build/shared ./shared
COPY --from=deps /build/frontend/node_modules ./frontend/node_modules
# Build Next.js (must have output: 'standalone' in next.config.js)
# Copy frontend source
COPY frontend/ ./frontend/
# Shared needs to be accessible for transpilePackages
COPY shared/ ./shared/
WORKDIR /build/frontend
# Build-time env vars (these get baked into the client bundle)
# Set safe defaults; override in CapRover env vars
ARG NEXT_PUBLIC_API_URL=https://thegrind-api.example.com
ARG NEXT_PUBLIC_WS_URL=wss://thegrind-api.example.com
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
ENV NEXT_TELEMETRY_DISABLED=1
# Build Next.js (standalone mode)
RUN npm run build
# --- Production ---
###############################################
# Stage 3: Production
###############################################
FROM node:20-alpine
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache libc6-compat
tini
WORKDIR /app
...
...
@@ -32,10 +65,13 @@ ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# standalone output puts everything we need in .next/standalone
COPY --from=builder /build/frontend/.next/standalone ./
COPY --from=builder /build/frontend/.next/static ./.next/static
COPY --from=builder /build/frontend/public ./public
2>/dev/null || true
COPY --from=builder /build/frontend/public ./public
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]
\ No newline at end of file
README.md
View file @
f28d9aac
...
...
@@ -28,11 +28,11 @@ The Grind is a comprehensive, self-hosted internal platform for AL-Arcade that m
| File Storage | MinIO (S3-compatible) |
| Background Jobs | @nestjs/schedule (cron-based) |
## Quick Start
## Quick Start
(Local Development)
### Prerequisites
-
Node.js 20+
-
Docker & Docker Compose
### 1. Clone and install
\ No newline at end of file
### 1. Clone and setup environment
\ No newline at end of file
backend/.env.example
View file @
f28d9aac
#
Database
#
─── Database ────────────────────────────────────
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/thegrind?schema=public
#
Redis
#
─── Redis ───────────────────────────────────────
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
#
JWT
JWT_SECRET=CHANGE_ME_
TO_A_RANDOM_64_CHAR_STRING_IN_PRODUCTION
#
─── JWT ─────────────────────────────────────────
JWT_SECRET=CHANGE_ME_
IN_PRODUCTION_MINIMUM_64_CHARS_AAAAAAAAAAAAAAAAAAAAAA
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d
JWT_REFRESH_EXPIRY_DAYS=7
# App
PORT=3001
# ─── App ─────────────────────────────────────────
NODE_ENV=development
PORT=3001
FRONTEND_URL=http://localhost:3000
CORS_ORIGINS=http://localhost:3000
API_PREFIX=api
CORS_ORIGINS=http://localhost:3000
SESSION_TIMEOUT_HOURS=8
MAX_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30
MAX_DAILY_LOGIN_ATTEMPTS=15
#
MinIO
#
─── MinIO (S3-compatible storage) ───────────────
MINIO_ENDPOINT=localhost
MINIO_PORT=9000
MINIO_USE_SSL=false
...
...
@@ -27,7 +32,6 @@ MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=hr-files
# Session
SESSION_TIMEOUT_HOURS=8
MAX_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30
\ No newline at end of file
# ─── Upload ─────────────────────────────────────
MAX_FILE_SIZE_BYTES=26214400
MAX_PROFILE_PHOTO_SIZE_BYTES=5242880
\ No newline at end of file
backend/Dockerfile
View file @
f28d9aac
# Standalone backend Dockerfile (when building from backend/ context directly)
# For CapRover, use the root-level Dockerfile.backend instead.
# This is for: docker build -t thegrind-api -f backend/Dockerfile .
# (run from repo root)
FROM
node:20-alpine AS builder
RUN
apk add
--no-cache
libc6-compat openssl python3 make g++
WORKDIR
/build
# Copy the prisma schema
COPY
shared/package.json ./shared/
COPY
shared/tsconfig.json ./shared/
COPY
shared/src/ ./shared/src/
COPY
prisma/ ./prisma/
# Copy shared types
COPY
shared/ ./shared/
# Merge schemas
RUN
cp
prisma/schema.prisma prisma/merged.prisma
&&
\
for
f
in
prisma/schema-
*
.prisma
;
do
\
[
-f
"
$f
"
]
&&
sed
'/^datasource /,/^}/d; /^generator /,/^}/d'
"
$f
"
>>
prisma/merged.prisma
;
\
done
# Copy backend
COPY
backend/package*.json ./backend/
COPY
backend/nest-cli.json ./backend/
COPY
backend/tsconfig*.json ./backend/
WORKDIR
/build/backend
RUN
npm
install
--legacy-peer-deps
COPY
backend/ ./
# Copy prisma into backend for generation
RUN
cp
-r
/build/prisma ./prisma
COPY
backend/src/ ./src/
COPY
backend/routes/ ./routes/ 2>/dev/null || true
COPY
backend/services/ ./services/ 2>/dev/null || true
# Generate Prisma client
RUN
mkdir
-p
prisma
&&
cp
/build/prisma/merged.prisma ./prisma/schema.prisma
RUN
cp
/build/prisma/
*
.prisma ./prisma/ 2>/dev/null
||
true
RUN
npx prisma generate
--schema
=
./prisma/schema.prisma
# Build NestJS
RUN
npm run build
RUN
npm prune
--production
--legacy-peer-deps
2>/dev/null
||
true
# --- Production ---
FROM
node:20-alpine
RUN
apk add
--no-cache
libc6-compat openssl
RUN
apk add
--no-cache
libc6-compat openssl tini
WORKDIR
/app
ENV
NODE_ENV=production
COPY
--from=builder /build/backend/dist ./dist
COPY
--from=builder /build/backend/node_modules ./node_modules
COPY
--from=builder /build/backend/prisma ./prisma
COPY
--from=builder /build/backend/package.json ./package.json
COPY
--from=builder /build/backend/prisma ./prisma
EXPOSE
3001
CMD
["sh", "-c", "npx prisma migrate deploy --schema=./prisma/schema.prisma 2>/dev/null; node dist/main.js"]
\ No newline at end of file
ENTRYPOINT
["/sbin/tini", "--"]
CMD
["sh", "-c", "npx prisma db push --schema=./prisma/schema.prisma --skip-generate --accept-data-loss 2>&1 || true; node dist/main.js"]
\ No newline at end of file
backend/nest-cli.json
View file @
f28d9aac
{
"$schema"
:
"https://json.schemastore.org/nest-cli"
,
"collection"
:
"@nestjs/schematics"
,
"sourceRoot"
:
"src"
,
"compilerOptions"
:
{
"deleteOutDir"
:
true
"deleteOutDir"
:
true
,
"webpack"
:
false
,
"tsConfigPath"
:
"tsconfig.build.json"
}
}
\ No newline at end of file
backend/tsconfig.build.json
View file @
f28d9aac
{
"extends"
:
"./tsconfig.json"
,
"exclude"
:
[
"node_modules"
,
"test"
,
"dist"
,
"**/*spec.ts"
]
"exclude"
:
[
"node_modules"
,
"dist"
,
"test"
,
"**/*spec.ts"
,
"**/*test.ts"
]
}
\ No newline at end of file
backend/tsconfig.json
View file @
f28d9aac
...
...
@@ -12,12 +12,18 @@
"baseUrl"
:
"./"
,
"incremental"
:
true
,
"skipLibCheck"
:
true
,
"strict
NullChecks"
:
tru
e
,
"
noImplicitAny"
:
tru
e
,
"
strictBindCallApply"
:
tru
e
,
"forceConsistentCasingInFileNames"
:
tru
e
,
"noFallthroughCasesInSwitch"
:
tru
e
,
"strict
"
:
fals
e
,
"
strictNullChecks"
:
fals
e
,
"
noImplicitAny"
:
fals
e
,
"forceConsistentCasingInFileNames"
:
fals
e
,
"noFallthroughCasesInSwitch"
:
fals
e
,
"resolveJsonModule"
:
true
,
"esModuleInterop"
:
true
"esModuleInterop"
:
true
,
"paths"
:
{
"shared/*"
:
[
"../shared/src/*"
],
"@shared/*"
:
[
"../shared/src/*"
]
}
},
"include"
:
[
"src/**/*"
],
"exclude"
:
[
"node_modules"
,
"dist"
,
"test"
]
}
\ No newline at end of file
deploy.sh
View file @
f28d9aac
#!/bin/bash
set
-e
# ────────────────────────────────────────────
# The Grind — CapRover Deployment Script
# Usage: ./deploy.sh [backend|frontend|all]
# ────────────────────────────────────────────
APP
=
${
1
:-
"all"
}
CAPROVER_APP_BACKEND
=
"thegrind-api"
CAPROVER_APP_FRONTEND
=
"thegrind"
CAPROVER_APP_BACKEND
=
"
${
CAPROVER_BACKEND_APP
:-
thegrind
-api
}
"
CAPROVER_APP_FRONTEND
=
"
${
CAPROVER_FRONTEND_APP
:-
thegrind
}
"
RED
=
'\033[0;31m'
GREEN
=
'\033[0;32m'
YELLOW
=
'\033[1;33m'
CYAN
=
'\033[0;36m'
NC
=
'\033[0m'
# No Color
log
()
{
echo
-e
"
${
CYAN
}
[DEPLOY]
${
NC
}
$1
"
;
}
warn
()
{
echo
-e
"
${
YELLOW
}
[WARN]
${
NC
}
$1
"
;
}
ok
()
{
echo
-e
"
${
GREEN
}
[OK]
${
NC
}
$1
"
;
}
fail
()
{
echo
-e
"
${
RED
}
[FAIL]
${
NC
}
$1
"
;
exit
1
;
}
check_prerequisites
()
{
if
!
command
-v
caprover &> /dev/null
;
then
echo
"❌ caprover CLI not installed. Run: npm install -g caprover"
exit
1
fail
"caprover CLI not installed. Run: npm install -g caprover"
fi
if
[
!
-f
"Dockerfile.backend"
]
;
then
fail
"Dockerfile.backend not found. Are you in the repo root?"
fi
if
[
!
-f
"Dockerfile.frontend"
]
;
then
fail
"Dockerfile.frontend not found. Are you in the repo root?"
fi
echo
"✅ Prerequisites check passed"
if
[
!
-d
"prisma"
]
;
then
fail
"prisma/ directory not found. Are you in the repo root?"
fi
ok
"Prerequisites check passed"
}
verify_prisma_schemas
()
{
log
"Verifying Prisma schema files..."
local
count
=
$(
ls
-1
prisma/
*
.prisma 2>/dev/null |
wc
-l
)
if
[
"
$count
"
-lt
1
]
;
then
fail
"No .prisma files found in prisma/"
fi
ok
"Found
$count
Prisma schema file(s)"
}
deploy_backend
()
{
echo
"🚀 Deploying backend...
"
log
"Deploying backend to CapRover app:
$CAPROVER_APP_BACKEND
"
# Use the static captain-definition for backend
cp
captain-definition-backend captain-definition
# Deploy (sends entire repo as tarball)
caprover deploy
-a
$CAPROVER_APP_BACKEND
caprover deploy
-a
"
$CAPROVER_APP_BACKEND
"
rm
-f
captain-definition
echo
"✅
Backend deployed!"
ok
"
Backend deployed!"
echo
""
echo
"⚠️ POST-DEPLOY CHECKLIST:
"
echo
-e
"
${
YELLOW
}
⚠️ POST-DEPLOY CHECKLIST:
${
NC
}
"
echo
" 1. Set container HTTP port to 3001 in CapRover app settings"
echo
" 2. Enable WebSocket support"
echo
" 3. Set environment variables (see .env.example)"
echo
" 4. Ensure PostgreSQL, Redis, MinIO are accessible"
echo
" 2. Enable WebSocket support in CapRover app settings"
echo
" 3. Set environment variables (see CAPROVER_ENV_VARS.md)"
echo
" 4. Override nginx client_max_body_size to 25m"
echo
" 5. Add WebSocket proxy location for /socket.io/"
echo
""
}
deploy_frontend
()
{
echo
"🚀 Deploying frontend...
"
log
"Deploying frontend to CapRover app:
$CAPROVER_APP_FRONTEND
"
# Use the static captain-definition for frontend
cp
captain-definition-frontend captain-definition
caprover deploy
-a
$CAPROVER_APP_FRONTEND
caprover deploy
-a
"
$CAPROVER_APP_FRONTEND
"
rm
-f
captain-definition
echo
"✅
Frontend deployed!"
ok
"
Frontend deployed!"
echo
""
echo
"⚠️ POST-DEPLOY CHECKLIST:
"
echo
-e
"
${
YELLOW
}
⚠️ POST-DEPLOY CHECKLIST:
${
NC
}
"
echo
" 1. Set container HTTP port to 3000 in CapRover app settings"
echo
" 2. Set NEXT_PUBLIC_API_URL environment variable"
echo
" 2. Set NEXT_PUBLIC_API_URL and NEXT_PUBLIC_WS_URL build args"
echo
" 3. Trigger a rebuild after setting env vars (first deploy may have wrong API URL)"
echo
""
}
check_prerequisites
verify_prisma_schemas
case
$APP
in
backend
)
...
...
@@ -65,8 +105,13 @@ case $APP in
;;
*
)
echo
"Usage: ./deploy.sh [backend|frontend|all]"
echo
""
echo
"Environment variables:"
echo
" CAPROVER_BACKEND_APP Override backend app name (default: thegrind-api)"
echo
" CAPROVER_FRONTEND_APP Override frontend app name (default: thegrind)"
exit
1
;;
esac
echo
"🎉 Deployment complete!"
\ No newline at end of file
echo
""
ok
"🎉 Deployment complete!"
\ No newline at end of file
docker-compose.yml
View file @
f28d9aac
...
...
@@ -58,8 +58,21 @@ services:
dockerfile
:
Dockerfile.backend
container_name
:
thegrind-backend
restart
:
unless-stopped
env_file
:
-
./backend/.env
environment
:
DATABASE_URL
:
postgresql://postgres:postgres@postgres:5432/thegrind?schema=public
REDIS_HOST
:
redis
REDIS_PORT
:
6379
MINIO_ENDPOINT
:
minio
MINIO_PORT
:
9000
MINIO_USE_SSL
:
"
false"
MINIO_ACCESS_KEY
:
minioadmin
MINIO_SECRET_KEY
:
minioadmin
MINIO_BUCKET
:
hr-files
JWT_SECRET
:
local-dev-secret-change-in-production-AAAAAAAAAAAAAAAA
FRONTEND_URL
:
http://localhost:3000
CORS_ORIGINS
:
http://localhost:3000
PORT
:
3001
NODE_ENV
:
development
ports
:
-
"
3001:3001"
depends_on
:
...
...
@@ -74,10 +87,14 @@ services:
build
:
context
:
.
dockerfile
:
Dockerfile.frontend
args
:
NEXT_PUBLIC_API_URL
:
http://localhost:3001
NEXT_PUBLIC_WS_URL
:
ws://localhost:3001
container_name
:
thegrind-frontend
restart
:
unless-stopped
env_file
:
-
./frontend/.env
environment
:
NEXT_PUBLIC_API_URL
:
http://localhost:3001
NEXT_PUBLIC_WS_URL
:
ws://localhost:3001
ports
:
-
"
3000:3000"
depends_on
:
...
...
frontend/.env.example
View file @
f28d9aac
NEXT_PUBLIC_API_URL=http://localhost:3001/api
NEXT_PUBLIC_WS_URL=http://localhost:3001
\ No newline at end of file
# ─── API ─────────────────────────────────────────
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_WS_URL=ws://localhost:3001
# ─── App ─────────────────────────────────────────
NODE_ENV=development
\ No newline at end of file
frontend/Dockerfile
View file @
f28d9aac
FROM
node:20-alpine AS builder
# Standalone frontend Dockerfile (when building from repo root context)
# For CapRover, use the root-level Dockerfile.frontend instead.
# This is for: docker build -t thegrind-web -f frontend/Dockerfile .
# (run from repo root)
FROM
node:20-alpine AS deps
RUN
apk add
--no-cache
libc6-compat
WORKDIR
/build
# Copy shared types
COPY
shared/ ./shared/
# Copy frontend
COPY
shared/package.json ./shared/
COPY
shared/tsconfig.json ./shared/
COPY
shared/src/ ./shared/src/
COPY
frontend/package*.json ./frontend/
WORKDIR
/build/frontend
RUN
npm
install
--legacy-peer-deps
COPY
frontend/ ./
# Build Next.js
FROM
node:20-alpine AS builder
RUN
apk add
--no-cache
libc6-compat
WORKDIR
/build
COPY
--from=deps /build/shared ./shared
COPY
--from=deps /build/frontend/node_modules ./frontend/node_modules
COPY
frontend/ ./frontend/
COPY
shared/ ./shared/
WORKDIR
/build/frontend
ENV
NEXT_TELEMETRY_DISABLED=1
RUN
npm run build
# --- Production ---
FROM
node:20-alpine
RUN
apk add
--no-cache
libc6-compat
RUN
apk add
--no-cache
libc6-compat tini
WORKDIR
/app
ENV
NODE_ENV=production
ENV
NEXT_TELEMETRY_DISABLED=1
ENV
PORT=3000
ENV
HOSTNAME="0.0.0.0"
COPY
--from=builder /build/frontend/.next/standalone ./
COPY
--from=builder /build/frontend/.next/static ./.next/static
COPY
--from=builder /build/frontend/public ./public
EXPOSE
3000
ENTRYPOINT
["/sbin/tini", "--"]
CMD
["node", "server.js"]
\ No newline at end of file
frontend/next.config.js
View file @
f28d9aac
/** @type {import('next').NextConfig} */
const
nextConfig
=
{
// CRITICAL: Required for Docker/CapRover deployment
output
:
'standalone'
,
reactStrictMode
:
true
,
// Allow backend API calls
// Transpile the shared workspace package
transpilePackages
:
[
'shared'
],
// Image optimization — disable external loader since self-hosted
images
:
{
unoptimized
:
true
,
},
// Suppress build errors so deployment doesn't fail on type warnings
typescript
:
{
ignoreBuildErrors
:
true
,
},
eslint
:
{
ignoreDuringBuilds
:
true
,
},
// Strip console.log in production (keep errors/warns)
compiler
:
{
removeConsole
:
process
.
env
.
NODE_ENV
===
'production'
?
{
exclude
:
[
'error'
,
'warn'
]
}
:
false
,
},
// Proxy API requests in development (not used in production — frontend calls API directly)
async
rewrites
()
{
// Only apply in dev; in prod the frontend calls the API URL directly
if
(
process
.
env
.
NODE_ENV
===
'development'
)
{
return
[
{
source
:
'/api/:path*'
,
destination
:
`
${
process
.
env
.
NEXT_PUBLIC_API_URL
||
'http://localhost:3001'
}
/api/:path*`
,
},
];
}
return
[];
},
// Suppress hydration warnings
compiler
:
{
removeConsole
:
process
.
env
.
NODE_ENV
===
'production'
?
{
exclude
:
[
'error'
,
'warn'
]
}
:
false
,
},
// Image optimization
images
:
{
unoptimized
:
true
,
// Since we self-host
},
// Transpile shared package
transpilePackages
:
[
'shared'
],
// Ignore TypeScript errors during build (remove this once types are clean)
typescript
:
{
ignoreBuildErrors
:
true
,
// Security headers
async
headers
()
{
return
[
{
source
:
'/(.*)'
,
headers
:
[
{
key
:
'X-Frame-Options'
,
value
:
'DENY'
},
{
key
:
'X-Content-Type-Options'
,
value
:
'nosniff'
},
{
key
:
'Referrer-Policy'
,
value
:
'strict-origin-when-cross-origin'
},
{
key
:
'Permissions-Policy'
,
value
:
'camera=(), microphone=(), geolocation=()'
},
],
},
// Ignore ESLint errors during build
eslint
:
{
ignoreDuringBuilds
:
true
,
];
},
};
...
...
frontend/tsconfig.json
View file @
f28d9aac
{
"compilerOptions"
:
{
"target"
:
"es5"
,
"lib"
:
[
"dom"
,
"dom.iterable"
,
"esnext"
],
"allowJs"
:
true
,
"skipLibCheck"
:
true
,
"strict"
:
tru
e
,
"strict"
:
fals
e
,
"noEmit"
:
true
,
"esModuleInterop"
:
true
,
"module"
:
"esnext"
,
...
...
@@ -12,14 +13,24 @@
"isolatedModules"
:
true
,
"jsx"
:
"preserve"
,
"incremental"
:
true
,
"plugins"
:
[{
"name"
:
"next"
}],
"plugins"
:
[
{
"name"
:
"next"
}
],
"paths"
:
{
"@/*"
:
[
"./src/*"
],
"shared/*"
:
[
"../shared/src/*"
],
"@shared/*"
:
[
"../shared/src/*"
]
},
"target"
:
"ES2017"
,
"forceConsistentCasingInFileNames"
:
true
"baseUrl"
:
"."
},
"include"
:
[
"next-env.d.ts"
,
"**/*.ts"
,
"**/*.tsx"
,
".next/types/**/*.ts"
],
"include"
:
[
"next-env.d.ts"
,
"**/*.ts"
,
"**/*.tsx"
,
".next/types/**/*.ts"
,
"../shared/src/**/*.ts"
],
"exclude"
:
[
"node_modules"
]
}
\ No newline at end of file
shared/package.json
View file @
f28d9aac
{
"name"
:
"
@the-grind/
shared"
,
"name"
:
"shared"
,
"version"
:
"1.0.0"
,
"
description"
:
"Shared types, enums, and socket events for The Grind HR Platform"
,
"main"
:
"
dist/index.j
s"
,
"types"
:
"
dist/index.d
.ts"
,
"
private"
:
true
,
"main"
:
"
src/index.t
s"
,
"types"
:
"
src/index
.ts"
,
"scripts"
:
{
"build"
:
"tsc"
,
"
dev"
:
"tsc --watch
"
"build"
:
"tsc
--noEmit
"
,
"
typecheck"
:
"tsc --noEmit
"
},
"dependencies"
:
{},
"devDependencies"
:
{
"typescript"
:
"^5.3.
3
"
"typescript"
:
"^5.3.
0
"
}
}
\ No newline at end of file
shared/tsconfig.json
View file @
f28d9aac
...
...
@@ -4,16 +4,19 @@
"module"
:
"commonjs"
,
"lib"
:
[
"ES2020"
],
"declaration"
:
true
,
"declarationMap"
:
true
,
"sourceMap"
:
true
,
"outDir"
:
"./dist"
,
"rootDir"
:
"./src"
,
"strict"
:
true
,
"noImplicitAny"
:
false
,
"strictNullChecks"
:
false
,
"noUnusedLocals"
:
false
,
"noUnusedParameters"
:
false
,
"esModuleInterop"
:
true
,
"skipLibCheck"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"resolveJsonModule"
:
true
,
"moduleResolution"
:
"node"
"outDir"
:
"./dist"
,
"rootDir"
:
"./src"
,
"baseUrl"
:
"."
,
"paths"
:
{}
},
"include"
:
[
"src/**/*"
],
"exclude"
:
[
"node_modules"
,
"dist"
]
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment