diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..aa497e4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-test: + name: Build & Test + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # --- Backend --- + - name: Set up Java 25 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + cache: maven + cache-dependency-path: skyroster-backend/pom.xml + + - name: Backend - verify (compile + test + coverage) + working-directory: skyroster-backend + run: chmod +x mvnw && ./mvnw verify + + # --- Frontend --- + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: skyroster-frontend/package-lock.json + + - name: Frontend - install dependencies + working-directory: skyroster-frontend + run: npm ci + + - name: Frontend - lint + working-directory: skyroster-frontend + run: npx oxlint . && npx eslint . + + - name: Frontend - build + working-directory: skyroster-frontend + run: npm run build + + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: build-and-test + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build backend image + run: docker build -t skyroster-backend ./skyroster-backend + + - name: Build frontend image + run: docker build -t skyroster-frontend ./skyroster-frontend diff --git a/skyroster-backend/.dockerignore b/skyroster-backend/.dockerignore new file mode 100644 index 0000000..46bfc50 --- /dev/null +++ b/skyroster-backend/.dockerignore @@ -0,0 +1,4 @@ +target/ +.idea/ +*.iml +.git/ diff --git a/skyroster-backend/Dockerfile b/skyroster-backend/Dockerfile new file mode 100644 index 0000000..4e6abc1 --- /dev/null +++ b/skyroster-backend/Dockerfile @@ -0,0 +1,22 @@ +# Stage 1: Build +FROM eclipse-temurin:25-jdk AS build +WORKDIR /app + +# Copy Maven wrapper and POM for dependency caching +COPY .mvn/ .mvn/ +COPY mvnw pom.xml ./ +RUN chmod +x mvnw && ./mvnw dependency:go-offline -B + +# Copy source and build (skip tests — they ran in CI job 1) +COPY src/ src/ +RUN ./mvnw package -DskipTests -B + +# Stage 2: Runtime +FROM eclipse-temurin:25-jre +WORKDIR /app + +COPY --from=build /app/target/*.jar app.jar + +EXPOSE 8001 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/skyroster-frontend/.dockerignore b/skyroster-frontend/.dockerignore new file mode 100644 index 0000000..ac33e3c --- /dev/null +++ b/skyroster-frontend/.dockerignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.git/ diff --git a/skyroster-frontend/Dockerfile b/skyroster-frontend/Dockerfile new file mode 100644 index 0000000..584b8ee --- /dev/null +++ b/skyroster-frontend/Dockerfile @@ -0,0 +1,16 @@ +# Stage 1: Build +FROM node:22-alpine AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Stage 2: Runtime +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/skyroster-frontend/nginx.conf b/skyroster-frontend/nginx.conf new file mode 100644 index 0000000..5719e3e --- /dev/null +++ b/skyroster-frontend/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA fallback — all routes to index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Gzip + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + gzip_min_length 256; +}