diff --git a/.github/workflows/pesayetu-deploy-dev.yml b/.github/workflows/pesayetu-deploy-dev.yml new file mode 100644 index 000000000..835044934 --- /dev/null +++ b/.github/workflows/pesayetu-deploy-dev.yml @@ -0,0 +1,84 @@ +name: Pesayetu | Deploy | DEV + +on: + push: + branches: [main] + paths: + - "apps/pesayetu/**" + - "Dockerfile.pesayetu" + - ".github/workflows/pesayetu-deploy-dev.yml" + +concurrency: + group: "${{ github.workflow }} @ ${{ github.ref }}" + cancel-in-progress: true + +env: + DOKKU_REMOTE_BRANCH: "master" + DOKKU_REMOTE_URL: "ssh://azureuser@ui-1.dev.codeforafrica.org" + IMAGE_NAME: "codeforafrica/pesayetu-ui" + NEXT_PUBLIC_APP_URL: "https://pesayetu-ui.dev.codeforafrica.org" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APP_NAME: pesayetu-ui + +jobs: + deploy: + runs-on: ${{ matrix.os }} + strategy: + matrix: + node-version: [18] + os: [ubuntu-latest] + steps: + - name: Cloning repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + key: ${{ runner.os }}-buildx-${{ github.sha }} + path: /tmp/.buildx-cache + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + username: ${{ secrets.DOCKER_HUB_USERNAME }} + + - name: Build Docker image + uses: docker/build-push-action@v3 + with: + build-args: | + WORDPRESS_URL=${{ secrets.PESAYETU_WORDPRESS_URL }} + WORDPRESS_MULTISITE_PREFIX=${{ secrets.PESAYETU_WORDPRESS_MULTISITE_PREFIX }} + WORDPRESS_PREVIEW_SECRET=${{ secrets.PESAYETU_WORDPRESS_PREVIEW_SECRET }} + WORDPRESS_APPLICATION_USERNAME=${{ secrets.PESAYETU_WORDPRESS_APPLICATION_USERNAME }} + WORDPRESS_APPLICATION_PASSWORD=${{ secrets.PESAYETU_WORDPRESS_APPLICATION_PASSWORD }} + JWT_SECRET_KEY=${{ secrets.PESAYETU_JWT_SECRET_KEY }} + HURUMAP_API_URL=${{ secrets.PESAYETU_HURUMAP_API_URL }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + context: . + file: Dockerfile.pesayetu + push: true + tags: "${{ env.IMAGE_NAME }}:${{ github.sha }}" + + # Temp fix + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + - name: Push to Dokku + uses: dokku/github-action@v1.4.0 + with: + git_remote_url: ${{ env.DOKKU_REMOTE_URL }}/${{ env.APP_NAME }} + ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} + deploy_docker_image: ${{ env.IMAGE_NAME }}:${{ github.sha }} diff --git a/Dockerfile.pesayetu b/Dockerfile.pesayetu new file mode 100644 index 000000000..c6e71c03b --- /dev/null +++ b/Dockerfile.pesayetu @@ -0,0 +1,154 @@ +FROM node:18.20.3-alpine as node + +# Always install security updated e.g. https://pythonspeed.com/articles/security-updates-in-docker/ +# Update local cache so that other stages don't need to update cache +RUN apk update \ + && apk upgrade + +# =================================================== +# base: starting image to be used in all other stages +# =================================================== +FROM node as base + +# 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 /workspace + + +# =================================================== +# pnpm-base: starting image with pnpm activated. +# should be used in all "build" stages. +# =================================================== +FROM base as pnpm-base + +ARG PNPM_VERSION=8.5.0 + +RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate + +# ========================================================= +# desp: image with all dependencies (for the app) installed +# ========================================================= +FROM pnpm-base as deps + +COPY pnpm-lock.yaml . + +RUN pnpm fetch + +COPY *.yaml *.json ./ +COPY packages/commons-ui-core/package.json ./packages/commons-ui-core/package.json +COPY packages/commons-ui-next/package.json ./packages/commons-ui-next/package.json +# Next.js lints when doing production build +COPY packages/eslint-config-commons-ui/package.json ./packages/eslint-config-commons-ui/package.json +COPY apps/pesayetu/package.json ./apps/pesayetu/package.json + +# Use virtual store: https://pnpm.io/cli/fetch#usage-scenario +RUN pnpm --filter "./apps/pesayetu" install --offline --frozen-lockfile + + +# ======================================================= +# builder: image that uses deps to build shippable output +# ======================================================= +FROM pnpm-base as builder + +ARG NEXT_TELEMETRY_DISABLED=1 \ + # Needed by Next.js at build time + NEXT_PUBLIC_APP_NAME=Pesayetu \ + NEXT_PUBLIC_APP_URL=http://localhost:3000 \ + NEXT_PUBLIC_SENTRY_DSN="" \ + NEXT_PUBLIC_SEO_DISABLED="true" \ + NEXT_PUBLIC_IMAGE_DOMAINS="cms.dev.codeforafrica.org,hurumap-v2.s3.amazonaws.com" \ + NEXT_PUBLIC_IMAGE_SCALE_FACTOR=2 \ + NEXT_PUBLIC_OPENAFRICA_DOMAINS="open.africa,openafrica.net,africaopendata.org" \ + NEXT_PUBLIC_SOURCEAFRICA_DOMAINS="dc.sourceafrica.net" \ + NEXT_PUBLIC_GOOGLE_ANALYTICS="" \ + # Needed by Next.js and server.ts at build time + PORT=3000 \ + # Sentry config for source maps upload (needed at build time only) + SENTRY_AUTH_TOKEN="" \ + SENTRY_ENV="" \ + SENTRY_ORG="" \ + SENTRY_PROJECT="" \ + # Wordpress config + WORDPRESS_URL \ + WORDPRESS_MULTISITE_PREFIX="/pesayetu" \ + WORDPRESS_PREVIEW_SECRET \ + WORDPRESS_APPLICATION_USERNAME \ + WORDPRESS_APPLICATION_PASSWORD \ + JWT_SECRET_KEY \ + HURUMAP_API_URL + +COPY --from=deps /workspace/node_modules ./node_modules +COPY --from=deps /workspace/packages/commons-ui-core/node_modules ./packages/commons-ui-core/node_modules +COPY --from=deps /workspace/packages/eslint-config-commons-ui/node_modules ./packages/eslint-config-commons-ui/node_modules +COPY --from=deps /workspace/packages/commons-ui-next/node_modules ./packages/commons-ui-next/node_modules +COPY --from=deps /workspace/apps/pesayetu/node_modules ./apps/pesayetu/node_modules + +# Next.js lints when doing production build +COPY packages/eslint-config-commons-ui ./packages/eslint-config-commons-ui +COPY apps/pesayetu ./apps/pesayetu + +RUN pnpm --filter "./apps/pesayetu" build + + +# ============================== +# runner: final deployable image +# ============================== +FROM base as runner + +ARG NEXT_TELEMETRY_DISABLED \ + NEXT_PUBLIC_APP_NAME \ + NEXT_PUBLIC_APP_URL \ + NEXT_PUBLIC_SENTRY_DSN \ + NEXT_PUBLIC_SEO_DISABLED \ + NEXT_PUBLIC_IMAGE_DOMAINS \ + NEXT_PUBLIC_IMAGE_SCALE_FACTOR \ + NEXT_PUBLIC_OPENAFRICA_DOMAINS \ + NEXT_PUBLIC_SOURCEAFRICA_DOMAINS \ + NEXT_PUBLIC_GOOGLE_ANALYTICS \ + PORT \ + SENTRY_ENV + +ENV NODE_ENV=production \ + NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME} \ + NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} \ + NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN} \ + NEXT_PUBLIC_SEO_DISABLED=${NEXT_PUBLIC_SEO_DISABLED} \ + NEXT_PUBLIC_IMAGE_DOMAINS=${NEXT_PUBLIC_IMAGE_DOMAINS} \ + NEXT_PUBLIC_IMAGE_SCALE_FACTOR=${NEXT_PUBLIC_IMAGE_SCALE_FACTOR} \ + NEXT_PUBLIC_OPENAFRICA_DOMAINS=${NEXT_PUBLIC_OPENAFRICA_DOMAINS} \ + NEXT_PUBLIC_SOURCEAFRICA_DOMAINS=${NEXT_PUBLIC_SOURCEAFRICA_DOMAINS} \ + NEXT_PUBLIC_GOOGLE_ANALYTICS=${NEXT_PUBLIC_GOOGLE_ANALYTICS} \ + PORT=${PORT} \ + SENTRY_ENV=${SENTRY_ENV} + +RUN set -ex \ + # Create a non-root user + && addgroup --system -g 1001 nodejs \ + && adduser --system -u 1001 -g 1001 nextjs \ + # Create nextjs cache dir w/ correct permissions + && mkdir -p ./apps/pesayetu/.next \ + && chown nextjs:nodejs ./apps/pesayetu/.next \ + # Delete system cached files we don't need anymore + && rm -rf /var/cache/apk/* + +# PNPM symlink some dependencies +COPY --from=builder --chown=nextjs:nodejs /workspace/node_modules ./node_modules +# Public assets +COPY --from=builder --chown=nextjs:nodejs /workspace/apps/pesayetu/public ./apps/pesayetu/public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /workspace/apps/pesayetu/.next/standalone ./apps/pesayetu +COPY --from=builder --chown=nextjs:nodejs /workspace/apps/pesayetu/.next/static ./apps/pesayetu/.next/static + +USER nextjs + +EXPOSE ${PORT} + +# set hostname to localhost +ENV HOSTNAME "0.0.0.0" + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "apps/pesayetu/server.js"] diff --git a/Makefile b/Makefile index 1c3ba1c13..0c0d1b0e9 100644 --- a/Makefile +++ b/Makefile @@ -14,4 +14,6 @@ mongodb: mongodb-keyfile: openssl rand -base64 741 > ./mongo-keyfile chmod 600 ./mongo-keyfile - \ No newline at end of file + +pesayetu: + BUILDKIT_PROGRESS=plain docker compose --env-file apps/pesayetu/.env.local up pesayetu --build diff --git a/apps/pesayetu/src/components/HURUmap/Map/Layers.js b/apps/pesayetu/src/components/HURUmap/Map/Layers.js index e29dfedc0..e3ad603ba 100644 --- a/apps/pesayetu/src/components/HURUmap/Map/Layers.js +++ b/apps/pesayetu/src/components/HURUmap/Map/Layers.js @@ -236,10 +236,13 @@ function Layers({ }); layer.addLayer(featuredGeo); if (!isPinOrCompare) { - map.fitBounds(layer.getBounds(), { - animate: true, - duration: 0.5, // in seconds - }); + const bounds = layer.getBounds(); + if (bounds.isValid()) { + map.fitBounds(layer.getBounds(), { + animate: true, + duration: 0.5, // in seconds + }); + } } else { const mark = new L.Marker(layer.getBounds().getCenter(), { icon: pinIcon, diff --git a/apps/pesayetu/src/pages/stories/[[...slug]].js b/apps/pesayetu/src/pages/stories/[[...slug]].js index fd7eead3e..7c4c629b9 100644 --- a/apps/pesayetu/src/pages/stories/[[...slug]].js +++ b/apps/pesayetu/src/pages/stories/[[...slug]].js @@ -108,7 +108,15 @@ Index.defaultProps = { }; export async function getStaticPaths() { - return getPostTypeStaticPaths(postType); + const { paths: allPaths = [] } = + (await getPostTypeStaticPaths(postType)) || {}; + // Generate static pages for main stories pages i.e. stories/news and stories/insights + const paths = allPaths.filter(({ params }) => params?.slug?.length === 1); + + return { + paths, + fallback: "blocking", + }; } export async function getStaticProps({ params, preview, previewData }) { diff --git a/docker-compose.yml b/docker-compose.yml index add6a16e9..47ff7455e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.8" - services: charterafrica: depends_on: @@ -29,6 +27,31 @@ services: ports: - 3000:3000 + pesayetu: + build: + context: . + dockerfile: Dockerfile.pesayetu + args: + WORDPRESS_URL: ${WORDPRESS_URL} + WORDPRESS_PREVIEW_SECRET: ${WORDPRESS_PREVIEW_SECRET} + WORDPRESS_APPLICATION_USERNAME: ${WORDPRESS_APPLICATION_USERNAME} + WORDPRESS_APPLICATION_PASSWORD: ${WORDPRESS_APPLICATION_PASSWORD} + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + HURUMAP_API_URL: ${HURUMAP_API_URL} + environment: + WORDPRESS_URL: ${WORDPRESS_URL} + WORDPRESS_PREVIEW_SECRET: ${WORDPRESS_PREVIEW_SECRET} + WORDPRESS_APPLICATION_USERNAME: ${WORDPRESS_APPLICATION_USERNAME} + WORDPRESS_APPLICATION_PASSWORD: ${WORDPRESS_APPLICATION_PASSWORD} + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + HURUMAP_API_URL: ${HURUMAP_API_URL} + S3_UPLOAD_KEY: ${S3_UPLOAD_KEY} + S3_UPLOAD_SECRET: ${S3_UPLOAD_SECRET} + S3_UPLOAD_BUCKET: ${S3_UPLOAD_BUCKET} + S3_UPLOAD_REGION: ${S3_UPLOAD_REGION} + ports: + - 3000:3000 + mongodb: image: mongo:6.0.13 environment: