diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5113133 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,149 @@ +name: CI + +on: + push: + branches: [main] + release: + types: [published] + pull_request: + +env: + REGISTRY: ghcr.io + IMAGE_NAME_BASE: ${{ github.repository_owner }} + +jobs: + backend: + name: Backend + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: write + env: + # For Docker Image + IMAGE_NAME: chat-app-backend + IMAGE_TITLE: Chat App Backend + IMAGE_DESCRIPTION: Quarkus Backend for the "Use Case App Example - Chat App" + steps: + - uses: actions/checkout@v3 + + - name: "Set up JDK 17" + uses: actions/setup-java@v3 + with: + java-version: "17" + distribution: "temurin" + + - name: "Gradle: Validate Gradle Wrapper" + uses: gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3 + + - name: "Gradle: Build" + uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 + env: + GPR_USER: ${{ github.actor }} + GPR_KEY: ${{ secrets.GITHUB_TOKEN }} + with: + build-root-directory: backend + arguments: build + + - name: "Docker: Log in to the Container registry" + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: "Docker: Extract metadata (tags, labels) for Docker" + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BASE }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.title=${{ env.IMAGE_TITLE }} + org.opencontainers.image.description=${{ env.IMAGE_DESCRIPTION }} + tags: | + type=schedule + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=ref,event=branch + type=ref,event=pr + type=sha + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=release,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: "Docker: Build and Push Image" + uses: docker/build-push-action@v4 + with: + file: backend/src/main/docker/Dockerfile.jvm + context: backend + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + frontend: + name: "Frontend" + env: + # For Docker Image + IMAGE_NAME: chat-app-frontend + IMAGE_TITLE: Chat App Frontend + IMAGE_DESCRIPTION: Next.js Frontend for the "Use Case App Example - Chat App" + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + timeout-minutes: 15 + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + cache: "yarn" + cache-dependency-path: | + **/package-lock.json + **/yarn.lock + + - name: Install Dependencies + working-directory: frontend + run: npm ci + + - name: Build + working-directory: frontend + run: npm run build + + - name: "Docker: Log in to the Container registry" + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: "Docker: Extract metadata (tags, labels) for Docker" + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BASE }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.title=${{ env.IMAGE_TITLE }} + org.opencontainers.image.description=${{ env.IMAGE_DESCRIPTION }} + tags: | + type=schedule + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=ref,event=branch + type=ref,event=pr + type=sha + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=release,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: "Docker: Build and Push Image" + uses: docker/build-push-action@v4 + with: + file: frontend/Dockerfile + context: frontend + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..9ced281 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine +WORKDIR /app + +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat + + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# COPY --chown=nextjs:nodejs public ./public +COPY --chown=nextjs:nodejs .next/standalone . +COPY --chown=nextjs:nodejs .next/static .next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +CMD node server.js diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e9ffa30..55238ac 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + output: 'standalone', }; export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 955ff1d..2c214c3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "cross-env": "^7.0.3", "eslint": "^8", "eslint-config-next": "15.0.3", "postcss": "^8", @@ -1742,6 +1743,24 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index b64f614..752fc97 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "cross-env SKIP_ENV_VALIDATION=true next build", "start": "next start", "lint": "next lint", "format-all": "prettier --write ." @@ -20,12 +20,13 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "cross-env": "^7.0.3", "eslint": "^8", "eslint-config-next": "15.0.3", "postcss": "^8", - "tailwindcss": "^3.4.1", - "typescript": "^5", "prettier": "^3.1.0", - "prettier-plugin-tailwindcss": "^0.5.7" + "prettier-plugin-tailwindcss": "^0.5.7", + "tailwindcss": "^3.4.1", + "typescript": "^5" } } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..601dc26 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,9 @@ +export default function RootLayout({children}: Readonly<{children: React.ReactNode;}>) { + return ( + +
+ {children} + + + ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..8dbb2b2 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,7 @@ +export default function Home() { + return ( +