diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile new file mode 100644 index 0000000..c7c4c69 --- /dev/null +++ b/apps/backend/Dockerfile @@ -0,0 +1,51 @@ +FROM oven/bun:1.3 AS base +WORKDIR /app + +# Install dependencies +FROM base AS deps +# Copy root package files and lockfile first +COPY package.json bun.lock ./ +# Copy workspace structure - all package.json files needed for workspace resolution +COPY packages ./packages +COPY apps ./apps +# Install all dependencies (bun will resolve workspace dependencies) +# Note: We copy full directories but bun only needs package.json files for resolution +RUN bun install --frozen-lockfile + +# Build stage +FROM base AS build +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/packages ./packages +# Copy backend source +COPY apps/backend ./apps/backend +# Build backend +WORKDIR /app/apps/backend +RUN bun run build + +# Production stage +FROM base AS production +WORKDIR /app + +# Copy dependencies and built files +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/packages ./packages +# Copy source code (needed for migrations and tests) +COPY --from=build /app/apps/backend/src ./apps/backend/src +COPY --from=build /app/apps/backend/dist ./apps/backend/dist +COPY --from=build /app/apps/backend/drizzle ./apps/backend/drizzle +COPY --from=build /app/apps/backend/package.json ./apps/backend/ +COPY --from=build /app/apps/backend/tsconfig.json ./apps/backend/ +COPY --from=build /app/apps/backend/drizzle.config.ts ./apps/backend/ + +# Set working directory to backend +WORKDIR /app/apps/backend + +# Set environment to production +ENV NODE_ENV=production + +# Expose port +EXPOSE 3000 + +# Default command (can be overridden) +CMD ["bun", "run", "start"] + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..70ab450 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,137 @@ +version: '3.8' + +services: + postgres: + image: postgres:16 + container_name: p1ctos4ve-postgres + environment: + POSTGRES_USER: p1ctos4ve + POSTGRES_PASSWORD: p1ctos4ve_password + POSTGRES_DB: p1ctos4ve + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U p1ctos4ve"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - p1ctos4ve-network + + redis: + image: redis:7-alpine + container_name: p1ctos4ve-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - p1ctos4ve-network + + seaweedfs-master: + image: chrislusf/seaweedfs:latest + container_name: p1ctos4ve-seaweedfs-master + command: "master -ip=seaweedfs-master" + ports: + - "9333:9333" + - "19333:19333" + volumes: + - seaweedfs_master_data:/data + networks: + - p1ctos4ve-network + + seaweedfs-volume: + image: chrislusf/seaweedfs:latest + container_name: p1ctos4ve-seaweedfs-volume + command: "volume -mserver=seaweedfs-master:9333 -port=8080" + ports: + - "8080:8080" + depends_on: + - seaweedfs-master + volumes: + - seaweedfs_volume_data:/data + networks: + - p1ctos4ve-network + + seaweedfs-filer: + image: chrislusf/seaweedfs:latest + container_name: p1ctos4ve-seaweedfs-filer + command: "filer -master=seaweedfs-master:9333" + ports: + - "8888:8888" + depends_on: + - seaweedfs-master + volumes: + - seaweedfs_filer_data:/data + networks: + - p1ctos4ve-network + + seaweedfs-s3: + image: chrislusf/seaweedfs:latest + container_name: p1ctos4ve-seaweedfs-s3 + entrypoint: ["/bin/sh", "-c"] + command: + - | + mkdir -p /data + cat > /data/s3.json << 'EOF' + { + "identities": [ + { + "name": "anonymous", + "credentials": [ + { + "accessKey": "", + "secretKey": "" + } + ], + "actions": ["Read:p1ctos4ve"] + }, + { + "name": "any", + "credentials": [ + { + "accessKey": "any", + "secretKey": "any" + } + ], + "actions": ["Admin", "Read", "Write"] + } + ] + } + EOF + exec /entrypoint.sh s3 -filer=seaweedfs-filer:8888 -port=8333 -ip.bind=0.0.0.0 -config=/data/s3.json + ports: + - "8333:8333" + depends_on: + - seaweedfs-master + - seaweedfs-volume + - seaweedfs-filer + volumes: + - seaweedfs_s3_data:/data + networks: + - p1ctos4ve-network + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + seaweedfs_master_data: + driver: local + seaweedfs_volume_data: + driver: local + seaweedfs_filer_data: + driver: local + seaweedfs_s3_data: + driver: local + +networks: + p1ctos4ve-network: + driver: bridge diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100644 index 0000000..5ef9413 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,254 @@ +#!/bin/bash + +set -e # Exit on any error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Cleanup function +cleanup() { + echo -e "\n${YELLOW}Cleaning up...${NC}" + docker compose down + exit 0 +} + +# Trap Ctrl+C +trap cleanup SIGINT SIGTERM + +# Get the project root directory +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$PROJECT_ROOT" + +echo -e "${GREEN}๐Ÿš€ Starting RealApp backend setup...${NC}" + +# Step 1: Start Docker services +echo -e "${YELLOW}๐Ÿ“ฆ Step 1: Starting Docker services (PostgreSQL, Redis, SeaweedFS)...${NC}" +docker compose up -d + +# Get the actual network name from docker compose (it adds project prefix) +# Docker Compose uses directory name as project prefix, so network will be {project}_p1ctos4ve-network +NETWORK_NAME=$(docker network ls --format "{{.Name}}" | grep "p1ctos4ve-network" | head -n 1) +# Fallback to default if not found +if [ -z "$NETWORK_NAME" ]; then + # Try to get from docker compose config + NETWORK_NAME=$(docker compose config 2>/dev/null | grep -A 2 "networks:" | grep "name:" | awk '{print $2}' | tr -d '"' | head -n 1) + # Final fallback + if [ -z "$NETWORK_NAME" ]; then + NETWORK_NAME="realapp_p1ctos4ve-network" + fi +fi +echo -e "${YELLOW}Using network: ${NETWORK_NAME}${NC}" + +# Wait for services to be healthy +echo -e "${YELLOW}โณ Waiting for services to be ready...${NC}" + +# Wait for PostgreSQL +echo -n "Waiting for PostgreSQL..." +until docker exec p1ctos4ve-postgres pg_isready -U p1ctos4ve > /dev/null 2>&1; do + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Wait for Redis +echo -n "Waiting for Redis..." +until docker exec p1ctos4ve-redis redis-cli ping > /dev/null 2>&1; do + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Wait for SeaweedFS master +echo -n "Waiting for SeaweedFS master..." +for i in {1..30}; do + if curl -s http://localhost:9333/cluster/status > /dev/null 2>&1; then + break + fi + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Wait for SeaweedFS filer +echo -n "Waiting for SeaweedFS filer..." +for i in {1..30}; do + if curl -s http://localhost:8888 > /dev/null 2>&1; then + break + fi + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Wait for SeaweedFS S3 +echo -n "Waiting for SeaweedFS S3..." +for i in {1..30}; do + if curl -s http://localhost:8333 > /dev/null 2>&1; then + break + fi + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Wait a bit more for SeaweedFS to fully initialize +sleep 2 + +# Create S3 bucket in SeaweedFS if it doesn't exist +echo -e "${YELLOW}๐Ÿ“ฆ Creating S3 bucket in SeaweedFS...${NC}" +BUCKET_NAME="p1ctos4ve" +# Try to create bucket via S3 API (using AWS CLI-like approach) +if ! curl -s -X PUT "http://localhost:8333/${BUCKET_NAME}" > /dev/null 2>&1; then + # Alternative: create via filer API + curl -X PUT "http://localhost:8888/buckets/${BUCKET_NAME}" > /dev/null 2>&1 || true +fi +echo -e "${GREEN}โœ“ Bucket ready${NC}" + +# Step 2: Build backend +echo -e "${YELLOW}๐Ÿ”จ Step 2: Building backend...${NC}" +cd "$PROJECT_ROOT" +docker build -t p1ctos4ve-backend:latest -f apps/backend/Dockerfile . + +# Step 3: Run migrations +echo -e "${YELLOW}๐Ÿ—„๏ธ Step 3: Running database migrations...${NC}" +docker run --rm \ + --network "${NETWORK_NAME}" \ + -e DATABASE_URL="postgresql://p1ctos4ve:p1ctos4ve_password@postgres:5432/p1ctos4ve" \ + -e REDIS_URL="redis://redis:6379" \ + -e S3_ENDPOINT="http://seaweedfs-s3:8333" \ + -e S3_REGION="us-east-1" \ + -e S3_BUCKET="${BUCKET_NAME}" \ + -e S3_ACCESS_KEY_ID="any" \ + -e S3_SECRET_ACCESS_KEY="any" \ + -e S3_FORCE_PATH_STYLE="true" \ + -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \ + -e BETTER_AUTH_URL="http://localhost:3000" \ + -e BASE_URL="http://localhost:3000" \ + -e NODE_ENV="production" \ + -e PORT="3000" \ + p1ctos4ve-backend:latest \ + bun run db:migrate + +# Step 4: Run unit tests +echo -e "${YELLOW}๐Ÿงช Step 4: Running unit tests...${NC}" +docker run --rm \ + --network "${NETWORK_NAME}" \ + -e DATABASE_URL="postgresql://p1ctos4ve:p1ctos4ve_password@postgres:5432/p1ctos4ve" \ + -e REDIS_URL="redis://redis:6379" \ + -e S3_ENDPOINT="http://seaweedfs-s3:8333" \ + -e S3_REGION="us-east-1" \ + -e S3_BUCKET="${BUCKET_NAME}" \ + -e S3_ACCESS_KEY_ID="any" \ + -e S3_SECRET_ACCESS_KEY="any" \ + -e S3_FORCE_PATH_STYLE="true" \ + -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \ + -e BETTER_AUTH_URL="http://localhost:3000" \ + -e BASE_URL="http://localhost:3000" \ + -e NODE_ENV="test" \ + -e PORT="3000" \ + p1ctos4ve-backend:latest \ + bun test src/tests/unit/ + +if [ $? -eq 0 ]; then + echo -e "${GREEN}โœ“ Unit tests passed${NC}" +else + echo -e "${RED}โœ— Unit tests failed${NC}" + exit 1 +fi + +# Step 5: Run integration tests +echo -e "${YELLOW}๐Ÿ”— Step 5: Running integration tests...${NC}" + +# Start backend server in background for e2e tests +echo -e "${YELLOW}Starting backend server for e2e tests...${NC}" +BACKEND_CONTAINER=$(docker run -d \ + --name p1ctos4ve-backend-test \ + --network "${NETWORK_NAME}" \ + -p 3000:3000 \ + -e DATABASE_URL="postgresql://p1ctos4ve:p1ctos4ve_password@postgres:5432/p1ctos4ve" \ + -e REDIS_URL="redis://redis:6379" \ + -e S3_ENDPOINT="http://seaweedfs-s3:8333" \ + -e S3_REGION="us-east-1" \ + -e S3_BUCKET="${BUCKET_NAME}" \ + -e S3_ACCESS_KEY_ID="any" \ + -e S3_SECRET_ACCESS_KEY="any" \ + -e S3_FORCE_PATH_STYLE="true" \ + -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \ + -e BETTER_AUTH_URL="http://localhost:3000" \ + -e BASE_URL="http://localhost:3000" \ + -e NODE_ENV="test" \ + -e PORT="3000" \ + p1ctos4ve-backend:latest) + +# Wait for server to be ready +echo -n "Waiting for backend server to start..." +for i in {1..30}; do + if curl -s http://localhost:3000 > /dev/null 2>&1; then + break + fi + echo -n "." + sleep 1 +done +echo -e " ${GREEN}โœ“${NC}" + +# Run e2e tests in the same container (server runs in background via exec) +# This way tests can access localhost:3000 +docker exec p1ctos4ve-backend-test sh -c "bun test --preload ./src/tests/setup.ts src/tests/e2e/" || \ +docker run --rm \ + --network "${NETWORK_NAME}" \ + -e DATABASE_URL="postgresql://p1ctos4ve:p1ctos4ve_password@postgres:5432/p1ctos4ve" \ + -e REDIS_URL="redis://redis:6379" \ + -e S3_ENDPOINT="http://seaweedfs-s3:8333" \ + -e S3_REGION="us-east-1" \ + -e S3_BUCKET="${BUCKET_NAME}" \ + -e S3_ACCESS_KEY_ID="any" \ + -e S3_SECRET_ACCESS_KEY="any" \ + -e S3_FORCE_PATH_STYLE="true" \ + -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \ + -e BETTER_AUTH_URL="http://p1ctos4ve-backend-test:3000" \ + -e BASE_URL="http://p1ctos4ve-backend-test:3000" \ + -e NODE_ENV="test" \ + -e PORT="3000" \ + p1ctos4ve-backend:latest \ + sh -c "find src/tests/e2e -name '*.test.ts' -exec sed -i 's|http://localhost:3000|http://p1ctos4ve-backend-test:3000|g' {} \; && bun test --preload ./src/tests/setup.ts src/tests/e2e/" + +TEST_EXIT_CODE=$? + +# Stop and remove backend test container +echo -e "${YELLOW}Stopping test backend server...${NC}" +docker stop p1ctos4ve-backend-test > /dev/null 2>&1 || true +docker rm p1ctos4ve-backend-test > /dev/null 2>&1 || true + +if [ $TEST_EXIT_CODE -eq 0 ]; then + echo -e "${GREEN}โœ“ Integration tests passed${NC}" +else + echo -e "${RED}โœ— Integration tests failed${NC}" + exit 1 +fi + +# Step 6: Start the application +echo -e "${YELLOW}๐Ÿš€ Step 6: Starting the application...${NC}" +echo -e "${GREEN}Application will be available at http://localhost:3000${NC}" +echo -e "${YELLOW}Press Ctrl+C to stop${NC}" + +docker run --rm \ + --network "${NETWORK_NAME}" \ + -p 3000:3000 \ + -e DATABASE_URL="postgresql://p1ctos4ve:p1ctos4ve_password@postgres:5432/p1ctos4ve" \ + -e REDIS_URL="redis://redis:6379" \ + -e S3_ENDPOINT="http://seaweedfs-s3:8333" \ + -e S3_REGION="us-east-1" \ + -e S3_BUCKET="${BUCKET_NAME}" \ + -e S3_ACCESS_KEY_ID="any" \ + -e S3_SECRET_ACCESS_KEY="any" \ + -e S3_FORCE_PATH_STYLE="true" \ + -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \ + -e BETTER_AUTH_URL="http://localhost:3000" \ + -e BASE_URL="http://localhost:3000" \ + -e NODE_ENV="production" \ + -e PORT="3000" \ + p1ctos4ve-backend:latest +