# =============================================================================
# @teh-riehl/server — NestJS + Prisma + envelope encryption.
#
# Built from the REPO ROOT (so the build context includes the workspace) with:
#
#   docker build -f packages/server/Dockerfile -t teh-riehl-server .
#
# Multi-stage: a `builder` stage runs the full pnpm install / prisma generate
# / nest build / pnpm deploy, then the runtime stage copies only the deployed
# bundle (flat node_modules, no workspace links).
# =============================================================================

# ---- Builder ----------------------------------------------------------------
FROM node:20-alpine AS builder
RUN corepack enable && corepack prepare pnpm@9.12.0 --activate

# Build tools needed by argon2's native bindings.
RUN apk add --no-cache python3 make g++ openssl

WORKDIR /repo

# Cache deps: copy manifests + lockfile first so node_modules can be reused
# across source-only changes.
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
COPY packages/shared/package.json packages/shared/
COPY packages/content/package.json packages/content/
COPY packages/client/package.json packages/client/
COPY packages/server/package.json packages/server/

RUN pnpm install --frozen-lockfile

# Copy the sources actually needed by the server build.
COPY packages/shared packages/shared
COPY packages/content packages/content
COPY packages/server packages/server

# Generate Prisma client + compile the Nest build.
# `exec prisma` (not bare `prisma`) so pnpm runs the CLI rather than looking
# for an npm script of that name.
RUN pnpm --filter @teh-riehl/server exec prisma generate
RUN pnpm --filter @teh-riehl/server build

# `pnpm deploy --prod` flattens workspace deps into a self-contained tree.
# Output ends up at /deploy with a normal node_modules.
RUN pnpm deploy --filter=@teh-riehl/server --prod /deploy

# `pnpm deploy` ships a stub @prisma/client (the fallback default.js that
# throws "did not initialize yet"). The real generated artifacts live in the
# builder's pnpm store and aren't tracked as a declared dep, so they get
# dropped. Replace the stub with the real generated client.
RUN set -eux; \
    src="$(find /repo/node_modules/.pnpm -maxdepth 1 -type d -name '@prisma+client@*' | head -1)/node_modules/.prisma/client"; \
    dst="$(find /deploy/node_modules/.pnpm -maxdepth 1 -type d -name '@prisma+client@*' | head -1)/node_modules/.prisma/client"; \
    rm -rf "$dst"; \
    mkdir -p "$(dirname "$dst")"; \
    cp -r "$src" "$dst"

# ---- Runtime ----------------------------------------------------------------
FROM node:20-alpine AS runtime

# Patch the base image, install runtime-only system deps, and strip out the
# npm/yarn/corepack CLI bundles. We never invoke any of those at runtime
# (CMD is `node dist/main.js`) and their bundled deps (cross-spawn, glob,
# minimatch, tar, etc.) drag in a long list of HIGH-severity CVEs that
# would fail the image scan.
RUN apk update && apk upgrade --no-cache && \
    apk add --no-cache openssl tini && \
    rm -rf /usr/local/lib/node_modules/npm \
           /usr/local/lib/node_modules/corepack \
           /usr/local/bin/npm \
           /usr/local/bin/npx \
           /usr/local/bin/corepack \
           /opt/yarn-v* \
           /var/cache/apk/*

WORKDIR /app
COPY --from=builder /deploy/node_modules ./node_modules
COPY --from=builder /deploy/dist ./dist
COPY --from=builder /deploy/prisma ./prisma
COPY --from=builder /deploy/package.json ./package.json

# Entrypoint runs `prisma migrate deploy` before the server boots so an
# image rolled to prod is self-applying — no separate migration job needed.
# `migrate deploy` is idempotent: applies only committed migrations, never
# generates new ones.
COPY --chmod=755 packages/server/docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh

# Drop privileges. The `node` user ships with the upstream image.
RUN chown -R node:node /app
USER node

ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

# tini = PID 1 with proper signal forwarding so SIGTERM cleans up the Node
# process (Prisma keeps DB connections open otherwise). The entrypoint
# script runs migrations, then `exec "$@"` drops into CMD (node).
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD ["node", "dist/main.js"]
