diff --git a/.env b/.env new file mode 100644 index 000000000..7cb97cea6 --- /dev/null +++ b/.env @@ -0,0 +1,25 @@ +# Application +APP_PORT=3000 + +# Cognito +COGNITO_AWS_REGION=eu-west-1 +COGNITO_CLIENT_ID=10h1tga4i933buv25lelalmtrn +COGNITO_POOL_ID=eu-west-1_dbfEb2FuH + +# S3 +S3_AWS_REGION=eu-west-1 +AWS_ACCESS_KEY_ID=minio +AWS_SECRET_ACCESS_KEY=minio123 +S3_BUCKET=ngmpub-userdata-local +PROJECTS_S3_BUCKET=ngmpub-project-files-local +S3_ENDPOINT=http://minio:9000 + +# Database +PGUSER=www-data +PGPASSWORD=www-data +PGHOST=db +PGPORT=5432 +PGDATABASE=swissgeol-local + +# sqlx +DATABASE_URL=postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE} diff --git a/.github/actions/create-image/action.yaml b/.github/actions/create-image/action.yaml new file mode 100644 index 000000000..3b06dd178 --- /dev/null +++ b/.github/actions/create-image/action.yaml @@ -0,0 +1,71 @@ +name: "Create Docker Image" +description: "Builds a docker image and tags it" +inputs: + IMAGE_NAME: + description: "The image name" + required: true + VERSION: + description: "The version of the image" + required: true + TAG: + description: "The tag of the image, in addition to the version" + required: true + OTHER_TAGS: + description: "Any additional tags, passed directly to docker/metadata-action" + DOCKERFILE: + description: "The path to the Dockerfile" + required: true + GITHUB_TOKEN: + description: "The github token" + required: true + CONTEXT: + description: "The build context" + default: "./" + required: false + +runs: + using: "composite" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set environment variables + shell: bash + run: | + echo COMMITED_AT=$(git show -s --format=%cI ${{ github.sha }}) >> $GITHUB_ENV + echo REVISION=$(git rev-parse --short HEAD) >> $GITHUB_ENV + + - name: Collect docker image metadata + id: meta-data + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.IMAGE_NAME }} + labels: | + org.opencontainers.image.created=${{ env.COMMITED_AT }} + org.opencontainers.image.version=v${{ inputs.VERSION }} + org.opencontainers.image.maintainer=EBP Schweiz AG + flavor: | + latest=${{ inputs.TAG == 'latest' }} + tags: | + type=raw,value=${{ inputs.TAG }} + type=raw,value=${{ inputs.VERSION }} + ${{ inputs.OTHER_TAGS }} + + - name: Log in to the GitHub container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ inputs.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ${{ inputs.CONTEXT }} + file: ${{ inputs.DOCKERFILE }} + push: true + tags: ${{ steps.meta-data.outputs.tags }} + labels: ${{ steps.meta-data.outputs.labels }} + no-cache: true + build-args: | + APP_VERSION=${{ inputs.VERSION }} diff --git a/.github/actions/tag-commit/action.yaml b/.github/actions/tag-commit/action.yaml new file mode 100644 index 000000000..d178e9bd8 --- /dev/null +++ b/.github/actions/tag-commit/action.yaml @@ -0,0 +1,34 @@ +name: "Tag Commit" +description: "Creates or updates a commit tag" +inputs: + TAG_NAME: + description: "The tag's name" + required: true + SHA: + description: "The SHA of the commit to be tagged" + required: true + +runs: + using: "composite" + steps: + - name: Create/update tag + uses: actions/github-script@v7 + env: + TAG: ${{ inputs.TAG_NAME }} + SHA: ${{ inputs.SHA }} + with: + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${process.env.TAG}`, + sha: process.env.SHA + }).catch(err => { + if (err.status !== 422) throw err; + github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${process.env.TAG}`, + sha: process.env.SHA + }); + }) diff --git a/.github/scripts/find-version.js b/.github/scripts/find-version.js new file mode 100644 index 000000000..f549771ca --- /dev/null +++ b/.github/scripts/find-version.js @@ -0,0 +1,107 @@ +const findNextVersion = (tags, branch) => { + const version = findMostRecentVersion(tags); + if (branch.startsWith("feature/")) { + // It's a minor feature. + + // If the previous version was a full release or a patch dev release, + // we are a completely new minor dev release. + // Otherwise, the previous version was itself a minor dev release, + // and we can reuse its number. + if (version.preRelease == null || version.patch !== 0) { + version.minor += 1; + version.patch = 0; + } + } else { + // It's a patch. + + // If the previous version was a full release, + // we are a completely new patch dev release. + // Otherwise, we can simply reuse the previous version's number. + if (version.preRelease == null) { + version.patch += 1; + } + } + + version.preRelease ??= 0; + version.preRelease += 1; + return version; +}; + +const findMostRecentVersion = (tags) => { + const versions = findAllVersions(tags); + if (versions.length === 0) { + throw new Error("unable to find a valid version on current edge tag"); + } + return versions[0]; +}; + +const findOutdatedVersions = (tags, recentTag) => { + const recentVersion = parseVersion(recentTag); + if (recentVersion == null) { + throw new Error(`recent tag '${recentTag}' is not a version number`); + } + const versions = findAllVersions(tags); + return versions.filter( + (version) => + // Select all pre-releases that appear before the most recent one. + version.preRelease != null && compareVersions(recentVersion, version) > 0 + ); +}; + +const findAllVersions = (tags) => { + return tags + .map(parseVersion) + .filter((it) => it != null) + .sort((a, b) => compareVersions(a, b) * -1); +}; + +const SEMANTIC_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-dev\d+)?$/; +const parseVersion = (tag) => { + if (!SEMANTIC_VERSION_PATTERN.test(tag)) { + return null; + } + const [major, minor, patch, preRelease] = tag.split(/[.\-]/); + return { + major: parseInt(major), + minor: parseInt(minor), + patch: parseInt(patch), + preRelease: preRelease && parseInt(preRelease.substring(3)), + }; +}; + +const compareVersions = (a, b) => { + if (a.major !== b.major) { + return a.major - b.major; + } + if (a.minor !== b.minor) { + return a.minor - b.minor; + } + if (a.patch !== b.patch) { + return a.patch - b.patch; + } + if (a.preRelease !== b.preRelease) { + if (a.preRelease == null) { + return 1; + } + if (b.preRelease == null) { + return -1; + } + return a.preRelease - b.preRelease; + } + return 0; +}; + +const makeVersionTag = ({ major, minor, patch, preRelease }) => { + const tag = `${major}.${minor}.${patch}`; + if (preRelease == null) { + return tag; + } + return `${tag}-dev${preRelease}`; +}; + +module.exports = { + findNextVersion, + findMostRecentVersion, + findOutdatedVersions, + makeVersionTag, +}; diff --git a/.github/scripts/remove-packages.js b/.github/scripts/remove-packages.js new file mode 100644 index 000000000..c4896bc7d --- /dev/null +++ b/.github/scripts/remove-packages.js @@ -0,0 +1,51 @@ +const removePackageVersions = async (imageUrl, imageVersions) => { + const { Octokit } = await import("@octokit/rest"); + + const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN, + }); + + const [_imageHost, imageOwner, imageName] = imageUrl.split("/"); + const imageIds = await loadOutdatedVersionIds(octokit, imageOwner, imageName, imageVersions); + for (const imageId of imageIds) { + await octokit.rest.packages.deletePackageVersionForOrg({ + package_type: "container", + package_name: imageName, + org: imageOwner, + package_version_id: imageId, + }); + } +}; + +const loadOutdatedVersionIds = async (octokit, imageOwner, imageName, versions) => { + let page = 0; + versions = new Set(versions); + + const ids = new Set(); + while (true) { + const response = await octokit.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({ + package_type: "container", + package_name: imageName, + org: imageOwner, + page, + }); + if (response.data.length === 0) { + break; + } + for (const entry of response.data) { + // Match any of the requested version's ids, + // as well as any ids that do not have a tag anymore, i.e. are fully unused. + const { tags } = entry.metadata.container; + const matchedTags = tags.filter((tag) => versions.delete(tag)); + if (tags.length === 0 || matchedTags.length !== 0) { + ids.add(entry.id); + } + } + page += 1; + } + return ids; +}; + +module.exports = { + removePackageVersions, +}; diff --git a/.github/scripts/wait_for_service.sh b/.github/scripts/wait_for_service.sh new file mode 100644 index 000000000..a17a3eaa5 --- /dev/null +++ b/.github/scripts/wait_for_service.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +MAX_RETRY_COUNT=10 +SERVICE_NAME="$1" + +if [ ! -n "$SERVICE_NAME" ]; then + echo "Usage: wait_for_container.sh " + exit 1 +fi + +for i in $(seq 1 $MAX_RETRY_COUNT); do + if [ "$(docker inspect -f '{{.State.Health.Status}}' "swissgeol-viewer-app-$SERVICE_NAME-1")" == "healthy" ]; then + echo "Service $SERVICE_NAME is healthy!" + exit 0 + else + echo "Waiting for $SERVICE_NAME to be healthy... (attempt $i of $MAX_RETRY_COUNT)" + sleep 10 + fi +done + +echo "Service $SERVICE_NAME did not become healthy in time." +exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 7d918b320..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: CI - -on: - push: - branches: - - master - pull_request: - branches: - - master - - next-prod - -jobs: - - add_review_links: - runs-on: ubuntu-22.04 - timeout-minutes: 3 - steps: - - uses: actions/checkout@v4 - - name: Add review links - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - run: scripts/github_add_link_for_reviews.sh - - build_and_test_local_api: - runs-on: ubuntu-22.04 - timeout-minutes: 15 - steps: - # - run: docker system prune --all --force --volumes - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - cache: npm - cache-dependency-path: ui/package-lock.json - - run: docker pull rust:1.63 - - name: Run local api tests - run: make acceptance - - build_and_deploy_api: - runs-on: ubuntu-22.04 - timeout-minutes: 15 - steps: - # - run: docker system prune --all --force --volumes - - uses: actions/checkout@v4 - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - # Make sure we are building and deploying image with latest security fixes - - run: docker pull rust:1.63 - - run: docker pull alpine:3.16 - - name: Build api - run: make build_api - - name: Push to github packages and deploy to dev - env: - AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}} - AWS_ACCESS_KEY_ID: ${{secrets.FARGATE_API_AWS_ACCESS_KEY_ID}} - AWS_SECRET_ACCESS_KEY: ${{secrets.FARGATE_API_AWS_SECRET_ACCESS_KEY}} - NGM_ARGOCD_DEPLOYKEY: ${{secrets.NGM_ARGOCD_DEPLOYKEY}} - run: | - if [[ ${{github.ref}} == "refs/heads/master" ]] - then - docker push ghcr.io/swisstopo/swissgeol-viewer-app-api:latest - mkdir myspace - chmod go-rwx myspace - echo -n $NGM_ARGOCD_DEPLOYKEY | base64 -d > myspace/id_key - chmod go-rwx myspace/id_key - ssh-keygen -l -f myspace/id_key - export GIT_SSH_COMMAND="ssh -i `pwd`/myspace/id_key -F none -o StrictHostKeyChecking=off" - $GIT_SSH_COMMAND git@git.swisstopo.admin.ch - git config --global user.email "swissgeol-ci-bot@camptocamp.com" - git config --global user.name "Swissgeol repo CI bot" - scripts/deploy-to-env.sh dev - fi - - build_and_deploy_ui: - runs-on: ubuntu-22.04 - timeout-minutes: 24 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: npm - cache-dependency-path: ui/package-lock.json - - - name: Npm setup - run: cd ui && npm ci && du -sh node_modules - - - name: Lint - run: cd ui && npm run lint - - - name: Test - run: cd ui && npm run test - - - name: Cypress run - uses: cypress-io/github-action@v6 - with: - command: npm run test:e2e - working-directory: ui - - - name: Build - run: export RELEASE_NAME="${scripts/get_github_name.sh}"; echo $RELEASE_NAME; cd ui; npm run build - - - name: Deploy to S3 - env: - AWS_REGION: "eu-west-1" - AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} - AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} - run: | - if [[ ${{github.ref}} == "refs/heads/master" ]] - then - cd ui; scripts/deploy_to_s3.sh dev - elif [[ $GITHUB_EVENT_NAME == "pull_request" ]] - then - cd ui; scripts/deploy_to_s3.sh review $GITHUB_HEAD_REF - fi diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 000000000..0825a6ca3 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,362 @@ +name: Code Quality + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - "**" + - "!main" + +env: + NODE_VERSION: "22.x" + RUST_VERSION: "1.73" + SQLX_OFFLINE: true + SQLX_VERSION: 0.7.3 + +jobs: + # add_review_links: + # runs-on: ubuntu-22.04 + # timeout-minutes: 3 + # steps: + # - uses: actions/checkout@v4 + # - name: Add review links + # env: + # GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + # run: scripts/github_add_link_for_reviews.sh + + dependency-review: + name: "Dependency Review" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Review Dependencies + uses: actions/dependency-review-action@v4 + + + install-ui: + name: "Install UI" + runs-on: ubuntu-latest + needs: + - dependency-review + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Get npm cache directory + id: npm-cache-dir + run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} + - name: Cache npm + uses: actions/cache@v4 + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: "${{ runner.os }}-npm-${{ env.NODE_VERSION }}-${{ hashFiles('./ui/package-lock.json') }}" + restore-keys: | + ${{ runner.os }}-npm- + - name: Cache node modules + uses: actions/cache@v4 + with: + path: ./ui/node_modules + key: "${{ runner.os }}-node_modules-${{ env.NODE_VERSION }}-${{ hashFiles('./ui/package-lock.json') }}" + restore-keys: | + ${{ runner.os }}-node_modules- + - name: Install node dependencies + run: cd ui && npm install + + + check-ui: + name: "Check UI" + runs-on: ubuntu-latest + needs: + - install-ui + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Restore cached node modules + uses: actions/cache/restore@v4 + with: + path: ./ui/node_modules + key: "${{ runner.os }}-node_modules-${{ env.NODE_VERSION }}-${{ hashFiles('./ui/package-lock.json') }}" + - name: Run build + run: cd ui && npm run check + + + test-ui: + name: "Test UI" + runs-on: ubuntu-latest + needs: + - check-ui + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Restore cached node modules + uses: actions/cache/restore@v4 + with: + path: ./ui/node_modules + key: "${{ runner.os }}-node_modules-${{ env.NODE_VERSION }}-${{ hashFiles('./ui/package-lock.json') }}" + - name: Run tests + run: cd ui && npm run test + + + lint-ui: + name: "Lint UI" + runs-on: ubuntu-latest + needs: + - check-ui + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Restore cached node modules + uses: actions/cache/restore@v4 + with: + path: ./ui/node_modules + key: "${{ runner.os }}-node_modules-${{ env.NODE_VERSION }}-${{ hashFiles('./ui/package-lock.json') }}" + - name: Run lint + run: cd ui && npm run lint + + + install-api: + name: "Install API" + runs-on: ubuntu-latest + needs: + - dependency-review + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo_registry-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + restore-keys: | + ${{ runner.os }}-cargo_registry- + - name: Cache cargo index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo_index-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + restore-keys: | + ${{ runner.os }}-cargo_index- + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: ./api/target + key: "${{ runner.os }}-cargo_build-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + restore-keys: | + ${{ runner.os }}-cargo_build- + - name: Cache sqlx binary + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/sqlx + key: "${{ runner.os }}-sqlx-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + restore-keys: | + ${{ runner.os }}-sqlx- + - name: Setup SQLx + run: | + cd api + SQLX_VERSION=0.7.3 + if [[ ! -f ~/.cargo/bin/sqlx ]] || [[ $(sqlx --version) != "sqlx-cli $SQLX_VERSION" ]]; then + cargo install sqlx-cli --version $SQLX_VERSION --no-default-features --features native-tls,postgres --locked --quiet + fi + - name: Install dependencies + run: | + cd api + rm -r src/* + rm -r tests/ + echo "fn main() {}" > src/main.rs + cargo build --all-targets --locked --quiet + + + check-api: + name: "Check API" + runs-on: ubuntu-latest + needs: + - install-api + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - name: Restore cargo registry + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo_registry-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo index + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo_index-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo build + uses: actions/cache/restore@v4 + with: + path: ./api/target + key: "${{ runner.os }}-cargo_build-${{ env.RUST_VERSION }}-${{ hashFiles('/api/Cargo.lock') }}" + - name: Run check + run: | + cd api + cargo check --frozen --quiet + + + lint-api: + name: "Lint API" + runs-on: ubuntu-latest + needs: + - check-api + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + components: clippy, rustfmt + - name: Restore cargo registry + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo_registry-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo index + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo_index-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo build + uses: actions/cache/restore@v4 + with: + path: ./api/target + key: "${{ runner.os }}-cargo_build-${{ env.RUST_VERSION }}-${{ hashFiles('/api/Cargo.lock') }}" + - name: Run rustfmt + run: | + cd api + cargo fmt --check + - name: Run clippy + run: | + cd api + cargo clippy --frozen --quiet + + + test-api: + name: "Test API" + runs-on: ubuntu-latest + needs: + - check-api + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - name: Restore cargo registry + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo_registry-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo index + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo_index-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Restore cargo build + uses: actions/cache/restore@v4 + with: + path: ./api/target + key: "${{ runner.os }}-cargo_build-${{ env.RUST_VERSION }}-${{ hashFiles('/api/Cargo.lock') }}" + - name: Restore sqlx binary + uses: actions/cache/restore@v4 + with: + path: ~/.cargo/bin/sqlx + key: "${{ runner.os }}-sqlx-${{ env.RUST_VERSION }}-${{ hashFiles('./api/Cargo.lock') }}" + - name: Start DB + run: | + docker compose up --quiet-pull -d db + chmod +x ./.github/scripts/wait_for_service.sh + ./.github/scripts/wait_for_service.sh db + - name: Setup DB + run: | + cd api + sqlx database create + sqlx migrate run + - name: Start API + run: | + cd api + nohup cargo run --frozen > ../api.log 2>&1 & + echo $! > ../api.pid + - name: Run test + run: | + cd api + cargo test --frozen + - name: Stop services + run: | + kill $(cat api.pid) + docker compose down + + + check-changelog: + name: "Check CHANGELOG" + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # Fetch full history for comparison + fetch-depth: 0 + - name: Determine if branch is a feature branch + id: check_feature_branch + run: | + if [[ "${{ github.head_ref || github.ref_name }} " =~ ^feature/* ]]; then + echo "is_feature=true" >> $GITHUB_ENV + else + echo "is_feature=false" >> $GITHUB_ENV + fi + - name: Check if CHANGELOG.md has changed + if: env.is_feature == 'true' + run: | + # Compare the CHANGELOG.md file in the current branch with the `develop` branch + if git diff --name-only origin/develop...HEAD | grep -q '^CHANGELOG.md$'; then + echo "CHANGELOG.md has been updated." + else + echo "::warning file=CHANGELOG.md::CHANGELOG.md has not been updated." + fi + + + prefer-single-commit: + name: "Prefer Single Commit" + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # Fetch full history for comparison + fetch-depth: 0 + - name: Count commits + id: count_commits + run: | + commit_count=$(git rev-list --count HEAD ^origin/${{ github.event.pull_request.base.ref }}) + echo "commit_count=$commit_count" >> $GITHUB_ENV + - name: Fail if more than one commit + if: env.commit_count > 1 + run: | + echo "::warning::Pull request contains more than one commit ($commit_count commits). Please squash your commits." diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..662d6ca7a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +name: CodeQL + +on: + push: + branches: + - develop + + workflow_dispatch: + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["javascript"] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/earthquakes.yml b/.github/workflows/earthquakes.yml index 2cb864923..a46fd7617 100644 --- a/.github/workflows/earthquakes.yml +++ b/.github/workflows/earthquakes.yml @@ -1,10 +1,12 @@ name: Earthquake-Fetcher on: - # Run this workflow every night - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '0 10 * * *' + workflow_dispatch: + +# # Run this workflow every night +# schedule: +# # * is a special character in YAML so you have to quote this string +# - cron: '0 10 * * *' jobs: # Set the job key. The key is displayed as the job name diff --git a/.github/workflows/publish-edge.yml b/.github/workflows/publish-edge.yml new file mode 100644 index 000000000..db6a883f3 --- /dev/null +++ b/.github/workflows/publish-edge.yml @@ -0,0 +1,132 @@ +name: Publish Edge + +on: + push: + branches: + - "develop" + + workflow_dispatch: + inputs: + version: + type: string + description: | + Version number (e.g. 1.2.3-dev1). + Leave empty to determine the next version automatically. + required: false + default: "" + is-edge: + type: boolean + description: "Tag the commit and published image with `edge`." + default: true + +permissions: write-all + +env: + IS_EDGE: ${{ github.event_name == 'push' || github.event.inputs.is-edge == 'true' }} + +jobs: + determine_version: + name: "determine version" + runs-on: ubuntu-latest + outputs: + version: ${{ steps.find_version.outputs.result || github.event.inputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + if: ${{ github.event.inputs.version == '' }} + - name: Get tags of edge commit + id: get_edge_tags + if: ${{ github.event.inputs.version == '' }} + run: | + git fetch --tags + EDGE_COMMIT=$(git rev-list -n 1 edge) + EDGE_TAGS=$(printf "%s," $(git tag --contains $EDGE_COMMIT)) + EDGE_TAGS=${EDGE_TAGS%,} + echo "edge_tags=$EDGE_TAGS" >> "$GITHUB_OUTPUT" + - name: Find next version + id: find_version + if: ${{ github.event.inputs.version == '' }} + uses: actions/github-script@v7 + env: + EDGE_TAGS: ${{ steps.get_edge_tags.outputs.edge_tags }} + with: + result-encoding: string + script: | + const { findNextVersion } = require('./.github/scripts/find-version.js'); + const tags = process.env.EDGE_TAGS.split(','); + const targetBranch = context.payload.ref.replace('refs/heads/', ''); + + const pullRequests = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + base: targetBranch, + sort: 'updated', + direction: 'desc' + }); + + const mergedPullRequest = pullRequests.data.find(pr => pr.merge_commit_sha === context.payload.after); + const sourceBranch = mergedPullRequest == null + ? targetBranch + : mergedPullRequest.head.ref.replace('refs/heads/', '') + + const version = findNextVersion(tags, sourceBranch); + return `${version.major}.${version.minor}.${version.patch}-dev${version.preRelease}`; + + build_and_push_api: + name: "build and push api" + needs: + - determine_version + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create image + uses: ./.github/actions/create-image + with: + IMAGE_NAME: ${{ vars.BASE_IMAGE_NAME }}-api + TAG: ${{ env.IS_EDGE == 'true' && 'edge' || '' }} + VERSION: ${{ needs.determine_version.outputs.version }} + DOCKERFILE: ./api/Dockerfile + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTEXT: ./api + + build_and_push_ui: + name: "build and push ui" + needs: + - determine_version + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create image + uses: ./.github/actions/create-image + with: + IMAGE_NAME: ${{ vars.BASE_IMAGE_NAME }}-ui + TAG: ${{ env.IS_EDGE == 'true' && 'edge' || '' }} + VERSION: ${{ needs.determine_version.outputs.version }} + DOCKERFILE: ./ui/Dockerfile + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTEXT: ./ui + + tag_commit: + name: "tag commit" + needs: + - determine_version + - build_and_push_api + - build_and_push_ui + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: tag edge + if: ${{ env.IS_EDGE == 'true' }} + uses: ./.github/actions/tag-commit + with: + TAG_NAME: edge + SHA: ${{ github.sha }} + - name: tag version + uses: ./.github/actions/tag-commit + with: + TAG_NAME: ${{ needs.determine_version.outputs.version }} + SHA: ${{ github.sha }} diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml new file mode 100644 index 000000000..43a52e6a4 --- /dev/null +++ b/.github/workflows/publish-rc.yml @@ -0,0 +1,75 @@ +name: Publish Release Candidate + +on: + push: + branches: + - "main" + + workflow_dispatch: + inputs: + base: + type: string + description: | + The tag of the commit that will be published as release-candidate. + Make sure that you also select that tag as the workflow's run location. + required: false + default: "edge" + +permissions: write-all + +env: + BASE: ${{ github.event.inputs.base || 'edge' }} + +jobs: + tag_rc_image_ui: + name: tag rc image ui + runs-on: ubuntu-latest + steps: + - name: Login to GitHub Packages + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin + + - name: Pull docker image + run: docker pull ${{ vars.BASE_IMAGE_NAME }}-ui:${{ env.BASE }} + + - name: Tag docker image + run: docker tag ${{ vars.BASE_IMAGE_NAME }}-ui:${{ env.BASE }} ${{ vars.BASE_IMAGE_NAME }}-ui:release-candidate + + - name: Push docker image + run: docker push ${{ vars.BASE_IMAGE_NAME }}-ui:release-candidate + + tag_rc_image_api: + name: tag rc image api + runs-on: ubuntu-latest + steps: + - name: Login to GitHub Packages + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin + + - name: Pull docker image + run: docker pull ${{ vars.BASE_IMAGE_NAME }}-api:${{ env.BASE }} + + - name: Tag docker image + run: docker tag ${{ vars.BASE_IMAGE_NAME }}-api:${{ env.BASE }} ${{ vars.BASE_IMAGE_NAME }}-api:release-candidate + + - name: Push docker image + run: docker push ${{ vars.BASE_IMAGE_NAME }}-api:release-candidate + + tag_rc_commit: + name: "tag rc commit" + needs: + - tag_rc_image_ui + - tag_rc_image_api + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Get base commit + id: get_base_commit + run: | + git fetch --tags + BASE_COMMIT=$(git rev-list -n 1 $BASE) + echo "sha=$BASE_COMMIT" >> "$GITHUB_OUTPUT" + - name: tag release-candidate + uses: ./.github/actions/tag-commit + with: + TAG_NAME: release-candidate + SHA: ${{ steps.get_base_commit.outputs.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..d59b88d0b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,198 @@ +name: Release + +on: + workflow_dispatch: + inputs: + base: + type: string + description: | + The tag of the commit that will be released. + Make sure that you also select that tag as the workflow's run location. + required: false + default: "release-candidate" + is-edge: + type: boolean + description: | + Assign the `edge` tag to this release. + default: true + is-release-candidate: + type: boolean + description: | + Assign the `release-candidate` tag to this release. + default: true + +permissions: write-all + +env: + BASE: ${{ github.event.inputs.base || 'release-candidate' }} + IS_EDGE: ${{ github.event.inputs.is-edge == 'true' }} + IS_RC: ${{ github.event.inputs.is-release-candidate == 'true' }} + +jobs: + determine_version: + name: "determine version" + runs-on: ubuntu-latest + outputs: + version: ${{ steps.find_version.outputs.result }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Get tags of base commit + id: get_base_tags + run: | + git fetch --tags + BASE_COMMIT=$(git rev-list -n 1 release-candidate) + BASE_TAGS=$(printf "%s," $(git tag --contains $BASE_COMMIT)) + BASE_TAGS=${BASE_TAGS%,} + echo "base_tags=$BASE_TAGS" >> "$GITHUB_OUTPUT" + - name: Find next version + id: find_version + uses: actions/github-script@v7 + env: + BASE_TAGS: ${{ steps.get_base_tags.outputs.base_tags }} + with: + result-encoding: string + script: | + const { findMostRecentVersion, makeVersionTag } = require('./.github/scripts/find-version.js'); + const tags = process.env.BASE_TAGS.split(','); + const version = findMostRecentVersion(tags); + version.preRelease = null; + return makeVersionTag(version); + + build_and_push_api: + name: "build and push api" + needs: + - determine_version + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create image + uses: ./.github/actions/create-image + with: + IMAGE_NAME: ${{ vars.BASE_IMAGE_NAME }}-api + TAG: latest + OTHER_TAGS: | + type=raw,value=${{ env.IS_EDGE == 'true' && 'edge' || '' }} + type=raw,value=${{ env.IS_RC == 'true' && 'release-candidate' || '' }} + VERSION: ${{ needs.determine_version.outputs.version }} + DOCKERFILE: ./api/Dockerfile + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTEXT: ./api + + build_and_push_ui: + name: "build and push ui" + needs: + - determine_version + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create image + uses: ./.github/actions/create-image + with: + IMAGE_NAME: ${{ vars.BASE_IMAGE_NAME }}-ui + TAG: latest + OTHER_TAGS: | + type=raw,value=${{ env.IS_EDGE == 'true' && 'edge' || '' }} + type=raw,value=${{ env.IS_RC == 'true' && 'release-candidate' || '' }} + VERSION: ${{ needs.determine_version.outputs.version }} + DOCKERFILE: ./ui/Dockerfile + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONTEXT: ./ui + + tag_commit: + name: "tag commit" + needs: + - determine_version + - build_and_push_api + - build_and_push_ui + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: tag latest + uses: ./.github/actions/tag-commit + with: + TAG_NAME: latest + SHA: ${{ github.sha }} + - name: Tag release-candidate + if: ${{ env.IS_RC == 'true' }} + uses: ./.github/actions/tag-commit + with: + TAG_NAME: release-candidate + SHA: ${{ github.sha }} + - name: Tag edge + if: ${{ env.IS_EDGE == 'true' }} + uses: ./.github/actions/tag-commit + with: + TAG_NAME: edge + SHA: ${{ github.sha }} + - name: Tag version + uses: ./.github/actions/tag-commit + with: + TAG_NAME: ${{ needs.determine_version.outputs.version }} + SHA: ${{ github.sha }} + + create_release: + name: "create release" + needs: + - determine_version + - build_and_push_api + - build_and_push_ui + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: "${{ needs.determine_version.outputs.version }}" + name: "swissgeol-viewer v${{ needs.determine_version.outputs.version }}" + generate_release_notes: true + make_latest: true + + cleanup: + name: "cleanup" + needs: + - determine_version + - create_release + - tag_commit + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup node + run: | + npm install @octokit/rest + - name: Get tags + id: get_tags + run: | + git fetch --tags + TAGS=$(printf "%s," $(git tag)) + TAGS=${TAGS%,} + echo "tags=$TAGS" >> "$GITHUB_OUTPUT" + - name: Remove outdated versions + uses: actions/github-script@v7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BASE_IMAGE_NAME: ${{ vars.BASE_IMAGE_NAME }} + CURRENT_VERSION: ${{ needs.determine_version.outputs.version }} + TAGS: ${{ steps.get_tags.outputs.tags }} + with: + script: | + const { findOutdatedVersions, makeVersionTag } = require('./.github/scripts/find-version.js'); + const { removePackageVersions } = require('./.github/scripts/remove-packages.js'); + + const tags = process.env.TAGS.split(','); + const outdatedVersions = findOutdatedVersions(tags, process.env.CURRENT_VERSION).map(makeVersionTag); + for (const version of outdatedVersions) { + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${version}`, + }); + } + + await removePackageVersions(`${process.env.BASE_IMAGE_NAME}-api`, outdatedVersions); + await removePackageVersions(`${process.env.BASE_IMAGE_NAME}-ui`, outdatedVersions); diff --git a/.gitignore b/.gitignore index fa8b65b29..72e148a8d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,10 @@ /ui/types /docs /secrets.txt + +# JetBrains IDE files +/.idea/ + +# Local Docker volumes +/volumes/ + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..dde23eb81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## [Unreleased] + +### Added + +### Changed + +### Fixed diff --git a/api/.env b/api/.env index 8dc3d9d6b..c7e39898a 100644 --- a/api/.env +++ b/api/.env @@ -8,11 +8,11 @@ COGNITO_POOL_ID=eu-west-1_dbfEb2FuH # S3 S3_AWS_REGION=eu-west-1 -AWS_ACCESS_KEY_ID=bla -AWS_SECRET_ACCESS_KEY=hu +AWS_ACCESS_KEY_ID=minio +AWS_SECRET_ACCESS_KEY=minio123 S3_BUCKET=ngmpub-userdata-local PROJECTS_S3_BUCKET=ngmpub-project-files-local -S3_ENDPOINT="http://minio:9000" +S3_ENDPOINT=http://minio:9000 # Database PGUSER=www-data @@ -21,5 +21,5 @@ PGHOST=localhost PGPORT=15432 PGDATABASE=swissgeol-local -# Required for using sqlx & tracing macros -DATABASE_URL="postgres://www-data:www-data@localhost:15432/swissgeol-local" +# sqlx +DATABASE_URL=postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE} diff --git a/api/Dockerfile b/api/Dockerfile index 63d8cc98c..e7fab32ad 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,102 +1,42 @@ -# local dev and dev/int/prod images are separate because they -# are built using a different and incompatible mode (default vs release) - -FROM rust:1.73 AS builder -ARG mac - -ENV MAC_INSTALL=${mac:+"build-essential gcc-x86-64-linux-gnu clang llvm"} -RUN apt-get update && apt-get install -y musl-tools musl-dev $MAC_INSTALL - +FROM rust:1.73 AS build +RUN apt update && apt install -y musl-tools musl-dev RUN rustup target add x86_64-unknown-linux-musl -RUN rustup component add clippy rustfmt - WORKDIR /app -# First we handle fetching and building our dependencies -# This rarely changes so is done once and for all -# Cargo needs a stub entry point otherwise it fails -COPY Cargo.toml Cargo.lock ./ -RUN mkdir src; echo "fn main() {}" > src/main.rs -RUN cargo fetch --target x86_64-unknown-linux-musl - -ENV SQLX_OFFLINE=true -RUN if [ "$mac" = "true" ]; then \ - RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" \ - CC_x86_64_unknown_linux_musl="clang" \ - AR_x86_64_unknown_linux_musl="llvm-ar" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUNNER="qemu-x86_64 -L /usr/x86-64-linux-gnu" \ - cargo build --offline --target x86_64-unknown-linux-musl --release; \ - else \ - cargo build --offline --target x86_64-unknown-linux-musl --release; \ - fi +COPY . . +RUN cargo build --target x86_64-unknown-linux-musl --release --quiet -# Then we copy every files, and detect linting / formating errors -# This is only detection, fixing the errors should be done outside docker -# We handle this section in offline mode to avoid unecessary and terribly long crates index update -COPY ./ . -RUN cargo fmt --all -- --check -# we don't care about linting dependencies, but clippy still does so despite our use -# of --package and --no-deps flags -# this is relatively fast so not a blocker -RUN if [ "$mac" = "true" ]; then \ - RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" \ - CC_x86_64_unknown_linux_musl="clang" \ - AR_x86_64_unknown_linux_musl="llvm-ar" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUNNER="qemu-x86_64 -L /usr/x86-64-linux-gnu" \ - cargo clippy --package api --target x86_64-unknown-linux-musl --tests --examples --offline --release --no-deps; \ - else \ - cargo clippy --package api --target x86_64-unknown-linux-musl --tests --examples --offline --release --no-deps; \ - fi - -# The tests actually requires a live DB so it must be run after the image is built -# See "make acceptance" - -# We build the app -RUN if [ "$mac" = "true" ]; then \ - RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" \ - CC_x86_64_unknown_linux_musl="clang" \ - AR_x86_64_unknown_linux_musl="llvm-ar" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld" \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUNNER="qemu-x86_64 -L /usr/x86-64-linux-gnu" \ - cargo build --target x86_64-unknown-linux-musl --release --offline; \ - else \ - cargo build --target x86_64-unknown-linux-musl --release --offline; \ - fi - - -## The final image is quite small and almost minimal FROM alpine:3.17 -RUN apk add util-linux +RUN apk add --no-cache util-linux -# Import from builder. -COPY --from=builder /etc/passwd /etc/passwd -COPY --from=builder /etc/group /etc/group +## Import from builder. +#COPY --from=builder /etc/passwd /etc/passwd +#COPY --from=builder /etc/group /etc/group WORKDIR /app # Create appuser -ENV USER=appuser -ENV UID=10001 +#ENV USER=appuser +#ENV UID=10001 + +#RUN adduser \ +# --disabled-password \ +# --gecos "" \ +# --home "/nonexistent" \ +# --shell "/sbin/nologin" \ +# --no-create-home \ +# --uid "${UID}" \ +# "${USER}" -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - "${USER}" +COPY --from=build /app/target/x86_64-unknown-linux-musl/release/api ./ -# Copy the app (we assume the musl libc from alpine is available and compatible) -COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/api ./ +EXPOSE 3000 -USER 10001 +#USER 10001 CMD ["/app/api"] diff --git a/docker-compose.yaml b/docker-compose.yaml index b33548858..538235024 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,38 +1,22 @@ services: api: init: true # handle kill signals for rust - image: ghcr.io/swisstopo/swissgeol-viewer-app-local_api:latest + image: local_api:latest + build: + context: ./api + dockerfile: DockerfileDev platform: linux/amd64 ports: - - 8480:3000 + - "8480:3000" environment: - # S3 - S3_AWS_REGION: &minio_region eu-west-1 - AWS_ACCESS_KEY_ID: &minio_user minio - AWS_SECRET_ACCESS_KEY: &minio_pass minio123 - S3_BUCKET: ngmpub-userdata-local - PROJECTS_S3_BUCKET: ngmpub-project-files-local - S3_ENDPOINT: http://minio:9000 - # Cognito - COGNITO_CLIENT_ID: 10h1tga4i933buv25lelalmtrn - COGNITO_POOL_ID: eu-west-1_dbfEb2FuH - # Postgres - PGUSER: &db_user www-data - PGPASSWORD: &db_pass www-data - PGDATABASE: &db_name swissgeol-local - PGHOST: db - PGPORT: "5432" - # Api - APP_PORT: "3000" - SQLX_OFFLINE: "true" - DATABASE_URL: "postgres://www-data:www-data@db:5432/swissgeol-local" + SQLX_OFFLINE: true command: ["cargo", "watch", "--poll", "--shell", "cargo run --offline --target x86_64-unknown-linux-musl"] volumes: - ./api/src:/app/src:ro - ./api/migrations:/app/migrations:ro - ./api/.sqlx:/app/.sqlx:ro - ./api/Cargo.toml:/app/Cargo.toml:ro - links: + depends_on: - db ui: @@ -43,7 +27,7 @@ services: ENVIRONMENT_NAME: local-dev working_dir: /app command: ['node_modules/.bin/webpack', 'serve'] - links: + depends_on: - api - abbreviator volumes: @@ -54,15 +38,15 @@ services: image: minio/minio:latest command: server /data --console-address :9001 volumes: - - miniodata:/data + - minio:/data ports: - "9000:9000" - "9001:9001" environment: MINIO_BROWSER: 'on' - MINIO_SITE_REGION: *minio_region - MINIO_ROOT_USER: *minio_user - MINIO_ROOT_PASSWORD: *minio_pass + MINIO_SITE_REGION: ${S3_AWS_REGION} + MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} + MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} abbreviator: image: ghcr.io/swisstopo/swissgeol-viewer-app-abbreviator:main @@ -78,12 +62,19 @@ services: image: camptocamp/postgres:14-postgis-3 platform: linux/amd64 environment: - POSTGRES_USER: *db_user - POSTGRES_PASSWORD: *db_pass - POSTGRES_DB: *db_name + POSTGRES_USER: ${PGUSER} + POSTGRES_PASSWORD: ${PGPASSWORD} + POSTGRES_DB: ${PGDATABASE} ports: - - 15432:5432 + - "15432:5432" + volumes: + - ./volumes/db/:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready"] + interval: 10s + timeout: 5s + retries: 5 # command: postgres -c log_statement=all for debugging volumes: - miniodata: + minio: diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 000000000..c39659765 --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,18 @@ +FROM node:22-alpine as build + +WORKDIR /app +COPY . . + +RUN npm install --ignore-scripts +RUN npm run build --omit=dev + + + +FROM nginx:alpine + +WORKDIR /usr/share/nginx/html +COPY --from=build /app/dist/ /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/ui/src/constants b/ui/src/constants deleted file mode 120000 index e699d3b6b..000000000 --- a/ui/src/constants +++ /dev/null @@ -1 +0,0 @@ -constants.ts \ No newline at end of file