Skip to content

Commit

Permalink
adding nextjs frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
DerLev committed Apr 1, 2024
1 parent bca973c commit 09aef00
Show file tree
Hide file tree
Showing 21 changed files with 3,636 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"projects": {
"default": "einslive-playlist-creator"
},
"targets": {
"einslive-playlist-creator": {
"hosting": {
"default": [
"einslive-playlist-creator"
]
}
}
}
}
181 changes: 181 additions & 0 deletions .github/workflows/integration-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ env:
API_CLOUD_RUN_SERVICE: public-api
API_ENDPOINTS_SERVICE: api.playlists.derlev.xyz
API_ESP_BASE_IMAGE: gcr.io/endpoints-release/endpoints-runtime-serverless:2
FRONTEND_ARTIFACT_REGISTRY_LOCATION: europe-west1
FRONTEND_ARTIFACT_REGISTRY: cr-webapp
FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME: frontend
FRONTEND_CLOUD_RUN_LOCATION: europe-west1
FRONTEND_CLOUD_RUN_SERVICE: frontend

jobs:
# Path Filtering
Expand All @@ -21,6 +26,7 @@ jobs:
outputs:
functions: ${{ steps.filter.outputs.functions }}
api: ${{ steps.filter.outputs.api }}
frontend: ${{ steps.filter.outputs.frontend }}
permissions:
contents: read
steps:
Expand All @@ -37,6 +43,9 @@ jobs:
api:
- 'public-api/**'
- '!(public-api)/**/*.md'
frontend:
- 'frontend/**'
- '!(frontend)/**/*.md'
# START: Cloud Functions
func-lint-typecheck:
Expand Down Expand Up @@ -213,3 +222,175 @@ jobs:
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: gcloud run deploy ${{ env.API_CLOUD_RUN_SERVICE }} --image ${{ env.API_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.API_ARTIFACT_REGISTRY }}/${{ env.API_ARTIFACT_REGISTRY_CONTAINER_NAME }}:${{ github.sha }} --region ${{ env.API_CLOUD_RUN_LOCATION }}
# END: Public API - Cloud Endpoints

# START: Frontend
frontend-lint-typecheck:
name: 'Frontend: Lint & Type Check'
runs-on: ubuntu-latest
needs: filter-paths
if: needs.filter-paths.outputs.frontend == 'true'
permissions:
checks: write
pull-requests: read
contents: read
defaults:
run:
working-directory: frontend
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Nodejs Environment
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
cache-dependency-path: 'frontend/yarn.lock'
- name: Install Dependencies
run: yarn --frozen-lockfile
- name: Lint
run: yarn lint:nofix --output-file eslint_report.json --format json
continue-on-error: true
- name: Type Check
run: yarn tsc --noEmit > typescript.log
continue-on-error: true
- name: Annotate Code
uses: DerLev/eslint-annotations@v2
with:
eslint-report: frontend/eslint_report.json
typescript-log: frontend/typescript.log
github-token: ${{ secrets.GITHUB_TOKEN }}
error-on-warn: true
status-check-name: 'Frontend: Annotations'
fail-in-pr: false
add-notice-with-url: false

frontend-delete-outdated-ar:
name: "Frontend: Delete outdated Container Images from Artifact Registry"
runs-on: ubuntu-latest
needs: frontend-lint-typecheck
permissions:
id-token: write
steps:
- name: Prevent Auth from printing warning
run: echo "Shut up!" > dont_warn.txt
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_SA_EMAIL }}
- name: Cleanup Artifact Registry
uses: docker://europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli
with:
args: >-
-repo=${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }}
-keep=6
-tag-filter-any=.*
frontend-delete-outdated-cr:
name: "Frontend: Delete outdated Cloud Run Revisions"
runs-on: ubuntu-latest
needs: frontend-lint-typecheck
permissions:
id-token: write
steps:
- name: Prevent Auth from printing warning
run: echo "Shut up!" > dont_warn.txt
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
token_format: "access_token"
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_SA_EMAIL }}
access_token_lifetime: 60s
create_credentials_file: false
- name: Only leave the 2 most recent Cloud Run Revisions
uses: actions/github-script@v7
with:
script: |
const response = await fetch("https://run.googleapis.com/v2/projects/${{ vars.GCP_PROJECT_ID }}/locations/${{ env.FRONTEND_CLOUD_RUN_LOCATION }}/services/${{ env.FRONTEND_CLOUD_RUN_SERVICE }}/revisions", { headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } })
.then(res => res.json());
const toBeDeleted = response.revisions.sort((a, b) => (new Date(b.createTime) - new Date(a.createTime))).slice(2);
const deletionPromises = [];
for(const revision of toBeDeleted) {
deletionPromises.push(fetch(`https://run.googleapis.com/v2/${revision.name}`, { method: 'DELETE', headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } }));
}
await Promise.all(deletionPromises);
frontend-build-deploy:
name: "Frontend: Build Container & Deploy on Cloud Run"
runs-on: ubuntu-latest
needs: [frontend-delete-outdated-ar, frontend-delete-outdated-cr]
permissions:
contents: read
id-token: write
environment: "Webapp: production"
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
token_format: access_token
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_SA_EMAIL }}
access_token_lifetime: 600s
- name: Login to GCP Artifact Registry
uses: docker/login-action@v3
with:
registry: ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
- name: Extract Metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }}
tags: |
type=raw,value=latest
type=sha,prefix=,format=long
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: frontend/
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Get short commit SHA
id: commit_sha
uses: actions/github-script@v7
with:
script: |
const sha = "${{ github.sha }}"
const shortSha = sha.substring(0, 7)
core.setOutput('shortSha', shortSha)
- name: Create new Cloud Run Revision
env:
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: gcloud run deploy ${{ env.FRONTEND_CLOUD_RUN_SERVICE }} --image ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }}:${{ github.sha }} --region ${{ env.FRONTEND_CLOUD_RUN_LOCATION }} --tag sha-${{ steps.commit_sha.outputs.shortSha }}
- name: Get GCP Credentials File
id: creds
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const sa = fs.readFileSync('${{ steps.auth.outputs.credentials_file_path }}', { encoding: 'utf-8' });
core.setOutput('cerdsJson', sa);
- name: Update Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ steps.creds.outputs.cerdsJson }}
channelId: live
projectId: ${{ vars.GCP_PROJECT_ID }}
# END: Frontend
15 changes: 14 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,18 @@
],
"runtime": "nodejs20"
}
]
],
"hosting": [{
"target": "default",
"rewrites": [
{
"source": "**",
"run": {
"region": "europe-west1",
"serviceId": "frontend",
"pinTag": true
}
}
]
}]
}
7 changes: 7 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
17 changes: 17 additions & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/eslintrc.json",
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"prettier"
],
"rules": {
"no-console": 1,
"prettier/prettier": 2
},
"plugins": ["@typescript-eslint", "import", "prettier"],
"parser": "@typescript-eslint/parser"
}
36 changes: 36 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
54 changes: 54 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# 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
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

# If using npm comment out above and use below instead
# RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME 0.0.0.0

CMD ["node", "server.js"]
Binary file added frontend/app/favicon.ico
Binary file not shown.
5 changes: 5 additions & 0 deletions frontend/app/healthz/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NextResponse } from 'next/server'

export const GET = () => {
return NextResponse.json({ code: 200, message: 'ok' })
}
29 changes: 29 additions & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Metadata } from 'next'
import { ColorSchemeScript, MantineProvider } from '@mantine/core'
import '@mantine/core/styles.css'
import AppShell from '@/components/AppShell'

export const metadata: Metadata = {
title: '1LIVE playlist creator',
description:
'Spotify playlists generated based on the daily playlists of the radio station 1LIVE',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<head>
<ColorSchemeScript defaultColorScheme="dark" />
</head>
<body>
<MantineProvider defaultColorScheme="dark">
<AppShell>{children}</AppShell>
</MantineProvider>
</body>
</html>
)
}
Loading

0 comments on commit 09aef00

Please sign in to comment.