diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fe62144 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Force LF line endings for shell scripts (critical for Docker) +*.sh text eol=lf +docker/entrypoint-*.sh text eol=lf + +# Default behavior +* text=auto diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 6a581d8..5b02507 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -15,14 +15,16 @@ async function bootstrap() { // Security app.use(helmet()); - // CORS (веб на 3080) + // CORS — allow the web frontend origin (from NEXT_PUBLIC_APP_URL) + localhost for dev + const appUrl = configService.get('NEXT_PUBLIC_APP_URL'); const allowedOrigins = [ - configService.get('NEXT_PUBLIC_APP_URL'), - 'http://localhost:3080', - 'http://localhost:3000', - ].filter(Boolean) as string[]; + ...new Set( + [appUrl, 'http://localhost:3080', 'http://localhost:3000'].filter(Boolean) as string[], + ), + ]; + console.log('CORS allowed origins:', allowedOrigins); app.enableCors({ - origin: allowedOrigins.length ? allowedOrigins : 'http://localhost:3080', + origin: allowedOrigins, credentials: true, }); diff --git a/docker-compose.yml b/docker-compose.yml index 1925b8b..6ec701e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,7 @@ services: SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY} NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} ports: - "3125:3125" depends_on: @@ -100,11 +101,15 @@ services: dockerfile: docker/Dockerfile.web args: API_URL: http://api:3125 + NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} container_name: coursecraft-web restart: unless-stopped env_file: .env environment: API_URL: http://api:3125 + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} PORT: "3080" NODE_ENV: production ports: diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api index 0cb5485..9aa71ed 100644 --- a/docker/Dockerfile.api +++ b/docker/Dockerfile.api @@ -1,5 +1,8 @@ FROM node:20-slim +# Prisma Query Engine needs OpenSSL shared libraries +RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* + RUN corepack enable && corepack prepare pnpm@9.0.0 --activate WORKDIR /app @@ -18,7 +21,7 @@ COPY . . RUN pnpm build --filter=@coursecraft/api... COPY docker/entrypoint-api.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh EXPOSE 3125 CMD ["/entrypoint.sh"] diff --git a/docker/Dockerfile.web b/docker/Dockerfile.web index fb2051c..68e7a0b 100644 --- a/docker/Dockerfile.web +++ b/docker/Dockerfile.web @@ -1,5 +1,8 @@ FROM node:20-slim +# OpenSSL for potential native dependencies +RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* + RUN corepack enable && corepack prepare pnpm@9.0.0 --activate WORKDIR /app @@ -15,9 +18,16 @@ RUN pnpm install --frozen-lockfile COPY . . -# API_URL нужен на этапе сборки: Next.js «запекает» rewrites в билд +# These are needed at build time: Next.js inlines NEXT_PUBLIC_* into client bundle ARG API_URL=http://api:3125 +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY +ARG NEXT_PUBLIC_APP_URL=http://localhost:3080 + ENV API_URL=${API_URL} +ENV NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} +ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} +ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} RUN pnpm build --filter=@coursecraft/web... diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 13c1961..758c5b3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -65,11 +65,11 @@ services: MEILISEARCH_HOST: http://meilisearch:7700 PORT: "3125" NODE_ENV: production - # Явно из хостового .env (подставляются при docker compose up из текущей папки) JWT_SECRET: ${JWT_SECRET} SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY} NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} ports: - "3125:3125" depends_on: @@ -96,11 +96,17 @@ services: build: context: .. dockerfile: docker/Dockerfile.web + args: + API_URL: http://api:3125 + NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} container_name: coursecraft-web restart: unless-stopped env_file: ../.env environment: API_URL: http://api:3125 + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3080} PORT: "3080" NODE_ENV: production ports: diff --git a/docker/entrypoint-api.sh b/docker/entrypoint-api.sh index 8d3bfe7..45e61ae 100644 --- a/docker/entrypoint-api.sh +++ b/docker/entrypoint-api.sh @@ -1,5 +1,22 @@ #!/bin/sh set -e -# Синхронизация схемы БД при старте (таблицы создадутся, если их ещё нет) -cd /app/packages/database && npx prisma db push --skip-generate --accept-data-loss 2>/dev/null || true + +echo "=== CourseCraft API Entrypoint ===" +echo "DATABASE_URL is set: $([ -n "$DATABASE_URL" ] && echo 'yes' || echo 'NO — this will fail!')" +echo "REDIS_HOST=$REDIS_HOST REDIS_PORT=$REDIS_PORT" + +# Ensure PostgreSQL extensions exist (idempotent) +echo "Ensuring PostgreSQL extensions..." +cd /app/packages/database +npx prisma db execute --stdin <<'SQL' || echo "WARNING: extension creation returned non-zero (may already exist)" +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS vector; +SQL + +# Push Prisma schema (create/update tables) +echo "Running prisma db push..." +npx prisma db push --skip-generate --accept-data-loss +echo "Prisma db push completed successfully." + +echo "Starting API server..." cd /app && exec node apps/api/dist/main.js