From 34285a0ddc492b9c345803bdb44f553e583a0ef1 Mon Sep 17 00:00:00 2001 From: Stefan Probst Date: Thu, 10 Oct 2024 09:28:51 +0200 Subject: [PATCH] refactor: align with acdh app template --- .devcontainer/devcontainer.json | 20 + .dockerignore | 45 +- .env.example | 9 - .env.local.example | 31 + .github/workflows/build-deploy.yml | 131 + .github/workflows/playwright.yml | 32 - .github/workflows/starter.yml | 96 - .github/workflows/validate.yml | 108 + .gitignore | 34 +- .npmrc | 1 + .vscode/app.code-snippets | 25 + .vscode/extensions.json | 5 +- .vscode/launch.json | 2 +- .vscode/settings.json | 20 +- Dockerfile | 33 +- app.vue | 5 - app/app.vue | 8 + {components => app/components}/app-footer.vue | 10 +- {components => app/components}/app-header.vue | 4 +- .../components}/autocomplete.vue | 6 +- app/components/centered.vue | 5 + {components => app/components}/chip.vue | 0 .../components}/content/content-wrapper.vue | 0 .../components}/content/divider.vue | 0 .../components}/content/gray-box.vue | 0 .../components}/content/header-wrapper.vue | 0 .../components}/content/index-button.vue | 2 +- .../components}/content/paragraph-wrapper.vue | 0 .../components}/content/parallel-content.vue | 0 .../components}/content/section-wrapper.vue | 0 .../components}/current-uri.vue | 2 +- .../components}/detail-disclosure.vue | 34 +- .../components}/detail-page.vue | 6 +- .../components}/download-menu.vue | 10 +- .../components}/download-results-buttons.vue | 24 +- .../components}/download-results-wrapper.vue | 8 +- app/components/error-boundary.vue | 17 + .../components}/facet-disclosures.vue | 10 +- .../components}/facet-field.vue | 84 +- .../components}/generic-disclosure.vue | 8 +- .../components}/generic-listbox.vue | 8 +- .../components}/hierarchy-link-button.vue | 4 +- .../components}/hierarchy-wrapper.vue | 13 +- {components => app/components}/indicator.vue | 8 +- {components => app/components}/info-menu.vue | 0 .../components}/json-download-button.vue | 0 .../components}/loading-bar.vue | 0 .../components}/main-content.vue | 2 +- .../components}/map-component.vue | 4 +- .../components}/menu-transition.vue | 0 .../components}/page-indicator.vue | 0 {components => app/components}/pagination.vue | 12 +- .../components}/per-page-selector.vue | 0 .../components}/range-slider.vue | 16 +- .../components}/search-table.vue | 45 +- app/components/skip-link.vue | 25 + {components => app/components}/sort-menu.vue | 6 +- .../components}/sortable-column.vue | 4 +- app/components/tailwind-indicator.vue | 17 + .../components}/vis-container.vue | 4 +- .../components}/xlsx-button-detail.vue | 13 +- .../components}/xlsx-button-table.vue | 42 +- .../components}/xlsx-button.vue | 15 +- .../composables}/use-locale.ts | 0 app/composables/use-page-metadata.ts | 19 + .../composables}/use-translations.ts | 0 .../composables}/use-ts-data.ts | 0 {config => app/config}/i18n.config.ts | 14 +- app/config/imprint.config.ts | 11 + app/error.vue | 62 + app/layouts/default.vue | 110 + {lib => app/lib}/facets.ts | 11 +- {lib => app/lib}/get-tree-data.ts | 0 {lib => app/lib}/get-ts-data.ts | 10 +- {lib => app/lib}/helpers.ts | 2 +- {lib => app/lib}/tree.js | 64 +- app/lib/types.ts | 6 + .../pages/detail/courts/[id]/index.vue | 68 +- .../pages/detail/events/[id]/index.vue | 6 +- .../pages/detail/institutions/[id]/index.vue | 78 +- .../pages/detail/persons/[id]/index.vue | 122 +- .../pages/detail/places/[id]/index.vue | 66 +- .../pages/detail/references/[id]/index.vue | 6 +- .../pages/documentation/[...slug]/index.vue | 6 +- {pages => app/pages}/hierarchy.vue | 24 +- app/pages/imprint.vue | 36 + {pages => app/pages}/index.vue | 12 +- {pages => app/pages}/search.vue | 34 +- {pages => app/pages}/search/courts.vue | 40 +- {pages => app/pages}/search/events.vue | 38 +- {pages => app/pages}/search/institutions.vue | 38 +- {pages => app/pages}/search/persons.vue | 36 +- {pages => app/pages}/search/places.vue | 36 +- {pages => app/pages}/search/references.vue | 34 +- {plugins => app/plugins}/query-client.ts | 7 +- app/router.options.ts | 13 - {styles => app/styles}/index.css | 4 +- app/types/i18n.d.ts | 11 + {types => app/types}/schema.d.ts | 0 app/utils/analytics.ts | 47 + app/utils/safe-json-ld-replacer.ts | 48 + app/utils/slice-trailing-slash.ts | 5 + components/centered.vue | 5 - components/skip-link.vue | 15 - config/imprint.config.ts | 22 - config/metadata.config.ts | 80 - content/de/landing-page.mdc | 2 +- e2e/lib/fixtures/a11y.ts | 24 + e2e/lib/fixtures/i18n.ts | 24 + e2e/lib/fixtures/imprint-page.ts | 28 + e2e/lib/fixtures/index-page.ts | 28 + e2e/lib/test.ts | 50 + ...-have-visible-changes-1-chromium-linux.png | Bin 0 -> 89893 bytes ...t-have-visible-changes-1-firefox-linux.png | Bin 0 -> 142732 bytes ...-have-visible-changes-1-chromium-linux.png | Bin 0 -> 1017921 bytes ...t-have-visible-changes-1-firefox-linux.png | Bin 0 -> 987575 bytes e2e/tests/app/analytics.test.ts | 25 + e2e/tests/app/app.test.ts | 120 + e2e/tests/app/metadata.test.ts | 141 + .../tests/app/navigation.test.ts | 19 +- e2e/tests/global.setup.ts | 14 + e2e/tests/pages/imprint.test.ts | 51 + e2e/tests/pages/index.test.ts | 37 + e2e/tests/pages/search.test.ts | 38 + e2e/tsconfig.json | 11 + eslint.config.js | 30 + i18n.config.ts => i18n/i18n.config.ts | 0 {locales => i18n/messages}/de.json | 48 +- layouts/default.vue | 40 - layouts/max-content.vue | 38 - lib/cn.ts | 1 - lib/get-imprint.ts | 6 - lib/types.ts | 16 - licence => license | 2 +- nuxt.config.ts | 83 +- package.json | 132 +- pages/imprint.vue | 40 - playwright.config.ts | 100 +- pnpm-lock.yaml | 3401 ++++++++++------- public/android-chrome-192x192.png | Bin 0 -> 4306 bytes public/android-chrome-512x512.png | Bin 0 -> 13587 bytes public/apple-icon.png | Bin 0 -> 4037 bytes .../assets/images/background-court-ship.jpg | Bin 338504 -> 0 bytes public/favicon.ico | Bin 0 -> 4158 bytes public/opengraph-image.png | Bin 0 -> 29392 bytes readme.md | 34 +- server/middleware/log.ts | 9 + server/routes/manifest.webmanifest.get.ts | 26 + server/routes/robots.txt.get.ts | 30 + server/routes/sitemap.xml.get.ts | 58 + server/tsconfig.json | 4 + tailwind.config.ts | 69 +- tsconfig.json | 7 +- types/facets.d.ts | 0 types/nuxt.d.ts | 7 - 155 files changed, 4450 insertions(+), 2662 deletions(-) create mode 100644 .devcontainer/devcontainer.json delete mode 100644 .env.example create mode 100644 .env.local.example create mode 100644 .github/workflows/build-deploy.yml delete mode 100644 .github/workflows/playwright.yml delete mode 100644 .github/workflows/starter.yml create mode 100644 .github/workflows/validate.yml create mode 100644 .vscode/app.code-snippets delete mode 100644 app.vue create mode 100644 app/app.vue rename {components => app/components}/app-footer.vue (57%) rename {components => app/components}/app-header.vue (95%) rename {components => app/components}/autocomplete.vue (96%) create mode 100644 app/components/centered.vue rename {components => app/components}/chip.vue (100%) rename {components => app/components}/content/content-wrapper.vue (100%) rename {components => app/components}/content/divider.vue (100%) rename {components => app/components}/content/gray-box.vue (100%) rename {components => app/components}/content/header-wrapper.vue (100%) rename {components => app/components}/content/index-button.vue (100%) rename {components => app/components}/content/paragraph-wrapper.vue (100%) rename {components => app/components}/content/parallel-content.vue (100%) rename {components => app/components}/content/section-wrapper.vue (100%) rename {components => app/components}/current-uri.vue (94%) rename {components => app/components}/detail-disclosure.vue (90%) rename {components => app/components}/detail-page.vue (84%) rename {components => app/components}/download-menu.vue (92%) rename {components => app/components}/download-results-buttons.vue (85%) rename {components => app/components}/download-results-wrapper.vue (72%) create mode 100644 app/components/error-boundary.vue rename {components => app/components}/facet-disclosures.vue (95%) rename {components => app/components}/facet-field.vue (79%) rename {components => app/components}/generic-disclosure.vue (94%) rename {components => app/components}/generic-listbox.vue (97%) rename {components => app/components}/hierarchy-link-button.vue (89%) rename {components => app/components}/hierarchy-wrapper.vue (84%) rename {components => app/components}/indicator.vue (95%) rename {components => app/components}/info-menu.vue (100%) rename {components => app/components}/json-download-button.vue (100%) rename {components => app/components}/loading-bar.vue (100%) rename {components => app/components}/main-content.vue (76%) rename {components => app/components}/map-component.vue (84%) rename {components => app/components}/menu-transition.vue (100%) rename {components => app/components}/page-indicator.vue (100%) rename {components => app/components}/pagination.vue (89%) rename {components => app/components}/per-page-selector.vue (100%) rename {components => app/components}/range-slider.vue (81%) rename {components => app/components}/search-table.vue (91%) create mode 100644 app/components/skip-link.vue rename {components => app/components}/sort-menu.vue (96%) rename {components => app/components}/sortable-column.vue (92%) create mode 100644 app/components/tailwind-indicator.vue rename {components => app/components}/vis-container.vue (78%) rename {components => app/components}/xlsx-button-detail.vue (96%) rename {components => app/components}/xlsx-button-table.vue (82%) rename {components => app/components}/xlsx-button.vue (95%) rename {composables => app/composables}/use-locale.ts (100%) create mode 100644 app/composables/use-page-metadata.ts rename {composables => app/composables}/use-translations.ts (100%) rename {composables => app/composables}/use-ts-data.ts (100%) rename {config => app/config}/i18n.config.ts (53%) create mode 100644 app/config/imprint.config.ts create mode 100644 app/error.vue create mode 100644 app/layouts/default.vue rename {lib => app/lib}/facets.ts (83%) rename {lib => app/lib}/get-tree-data.ts (100%) rename {lib => app/lib}/get-ts-data.ts (51%) rename {lib => app/lib}/helpers.ts (83%) rename {lib => app/lib}/tree.js (76%) create mode 100644 app/lib/types.ts rename pages/detail/courts/[id].vue => app/pages/detail/courts/[id]/index.vue (93%) rename pages/detail/events/[id].vue => app/pages/detail/events/[id]/index.vue (85%) rename pages/detail/institutions/[id].vue => app/pages/detail/institutions/[id]/index.vue (91%) rename pages/detail/persons/[id].vue => app/pages/detail/persons/[id]/index.vue (96%) rename pages/detail/places/[id].vue => app/pages/detail/places/[id]/index.vue (94%) rename pages/detail/references/[id].vue => app/pages/detail/references/[id]/index.vue (81%) rename pages/documentation/[...slug].vue => app/pages/documentation/[...slug]/index.vue (89%) rename {pages => app/pages}/hierarchy.vue (91%) create mode 100644 app/pages/imprint.vue rename {pages => app/pages}/index.vue (85%) rename {pages => app/pages}/search.vue (92%) rename {pages => app/pages}/search/courts.vue (71%) rename {pages => app/pages}/search/events.vue (72%) rename {pages => app/pages}/search/institutions.vue (72%) rename {pages => app/pages}/search/persons.vue (70%) rename {pages => app/pages}/search/places.vue (69%) rename {pages => app/pages}/search/references.vue (68%) rename {plugins => app/plugins}/query-client.ts (94%) delete mode 100644 app/router.options.ts rename {styles => app/styles}/index.css (93%) create mode 100644 app/types/i18n.d.ts rename {types => app/types}/schema.d.ts (100%) create mode 100644 app/utils/analytics.ts create mode 100644 app/utils/safe-json-ld-replacer.ts create mode 100644 app/utils/slice-trailing-slash.ts delete mode 100644 components/centered.vue delete mode 100644 components/skip-link.vue delete mode 100644 config/imprint.config.ts delete mode 100644 config/metadata.config.ts create mode 100644 e2e/lib/fixtures/a11y.ts create mode 100644 e2e/lib/fixtures/i18n.ts create mode 100644 e2e/lib/fixtures/imprint-page.ts create mode 100644 e2e/lib/fixtures/index-page.ts create mode 100644 e2e/lib/test.ts create mode 100644 e2e/snapshots/tests/pages/imprint.test.ts-snapshots/imprint-page-should-not-have-visible-changes-1-chromium-linux.png create mode 100644 e2e/snapshots/tests/pages/imprint.test.ts-snapshots/imprint-page-should-not-have-visible-changes-1-firefox-linux.png create mode 100644 e2e/snapshots/tests/pages/index.test.ts-snapshots/index-page-should-not-have-visible-changes-1-chromium-linux.png create mode 100644 e2e/snapshots/tests/pages/index.test.ts-snapshots/index-page-should-not-have-visible-changes-1-firefox-linux.png create mode 100644 e2e/tests/app/analytics.test.ts create mode 100644 e2e/tests/app/app.test.ts create mode 100644 e2e/tests/app/metadata.test.ts rename tests/navigation-tests.spec.ts => e2e/tests/app/navigation.test.ts (66%) create mode 100644 e2e/tests/global.setup.ts create mode 100644 e2e/tests/pages/imprint.test.ts create mode 100644 e2e/tests/pages/index.test.ts create mode 100644 e2e/tests/pages/search.test.ts create mode 100644 e2e/tsconfig.json create mode 100644 eslint.config.js rename i18n.config.ts => i18n/i18n.config.ts (100%) rename {locales => i18n/messages}/de.json (90%) delete mode 100644 layouts/default.vue delete mode 100644 layouts/max-content.vue delete mode 100644 lib/cn.ts delete mode 100644 lib/get-imprint.ts delete mode 100644 lib/types.ts rename licence => license (92%) delete mode 100644 pages/imprint.vue create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-icon.png delete mode 100644 public/assets/images/background-court-ship.jpg create mode 100644 public/favicon.ico create mode 100644 public/opengraph-image.png create mode 100644 server/middleware/log.ts create mode 100644 server/routes/manifest.webmanifest.get.ts create mode 100644 server/routes/robots.txt.get.ts create mode 100644 server/routes/sitemap.xml.get.ts create mode 100644 server/tsconfig.json delete mode 100644 types/facets.d.ts delete mode 100644 types/nuxt.d.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..2637b31 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Nuxt", + "image": "ghcr.io/acdh-oeaw/devcontainer-frontend:22", + "customizations": { + "vscode": { + "extensions": [ + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "lokalise.i18n-ally", + "mikestead.dotenv", + "ms-playwright.playwright", + "nuxt.mdc", + "stylelint.vscode-stylelint", + "vue.volar" + ] + } + } +} diff --git a/.dockerignore b/.dockerignore index 278606d..1020af6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,42 +4,45 @@ node_modules/ .pnpm-store/ -# nuxt.js -dist -.nuxt/ -.nitro/ -.output/ - # logs *.log # non-public environment variables -.env .env.local .env.*.local # caches .eslintcache +.prettiercache .stylelintcache *.tsbuildinfo +# vercel +.vercel + # misc .DS_Store +.idea/ -# generated assets -/public/**/favicon.ico -/public/**/icon.svg -/public/**/android-chrome-192x192.png -/public/**/android-chrome-512x512.png -/public/**/apple-touch-icon.png -/public/**/app.webmanifest -/public/**/image.webp +# nuxt.js +dist +.data/ +.nuxt/ +.nitro/ +.output/ + +# playwright +/blob-report/ +/playwright/.cache/ +/playwright-report/ +/test-results/ ## .dockerignore ## # git .git/ +.gitattributes .gitignore # github @@ -47,3 +50,15 @@ dist # vscode settings .vscode/ + +# environment variables +.env +.env.* + +# tests +playwright.config.ts +/e2e/ +/test/ + +# misc +.editorconfig diff --git a/.env.example b/.env.example deleted file mode 100644 index bde2b1e..0000000 --- a/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -NUXT_PUBLIC_APP_BASE_URL="http://localhost:3000" -NUXT_PUBLIC_MATOMO_BASE_URL="https://matomo.acdh.oeaw.ac.at" -NUXT_PUBLIC_MATOMO_ID= -NUXT_PUBLIC_REDMINE_ID="21622" -NUXT_PUBLIC_TYPESENSE_API_KEY="nFUaLp9SuREfD8Z5V4YospgXLtXyjfRG" -NUXT_PUBLIC_TYPESENSE_PORT="443" -NUXT_PUBLIC_TYPESENSE_PROTOCOL="https" -NUXT_PUBLIC_TYPESENSE_HOST="typesense.acdh-dev.oeaw.ac.at" -NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX="viecpro_" diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..732704c --- /dev/null +++ b/.env.local.example @@ -0,0 +1,31 @@ +# ------------------------------------------------------------------------------------------------- +# environment variables +# ------------------------------------------------------------------------------------------------- +# - public environment variables must be prefixed with `NUXT_PUBLIC_`. +# - when adding new environment variables, don't forget to also update the `runtimeConfig` +# section in `./nuxt.config.ts`. + +# ------------------------------------------------------------------------------------------------- +# app +# ------------------------------------------------------------------------------------------------- +NUXT_PUBLIC_APP_BASE_URL="http://localhost:3000" +# imprint service +NUXT_PUBLIC_REDMINE_ID="21622" +# web crawlers +NUXT_PUBLIC_BOTS="disabled" + +# ------------------------------------------------------------------------------------------------- +# analytics +# ------------------------------------------------------------------------------------------------- +# NUXT_PUBLIC_GOOGLE_SITE_VERIFICATION= +NUXT_PUBLIC_MATOMO_BASE_URL="https://matomo.acdh.oeaw.ac.at" +# NUXT_PUBLIC_MATOMO_ID= + +# ------------------------------------------------------------------------------------------------- +# typesense +# ------------------------------------------------------------------------------------------------- +NUXT_PUBLIC_TYPESENSE_API_KEY="catYoetXCv1rTfAp5LzcQ6TKI48gkB13" +NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX="viecpro_" +NUXT_PUBLIC_TYPESENSE_HOST="typesense.acdh-dev.oeaw.ac.at" +NUXT_PUBLIC_TYPESENSE_PORT="443" +NUXT_PUBLIC_TYPESENSE_PROTOCOL="https" diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml new file mode 100644 index 0000000..9717ab6 --- /dev/null +++ b/.github/workflows/build-deploy.yml @@ -0,0 +1,131 @@ +name: Build and deploy + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-build-deploy + cancel-in-progress: true + +on: + workflow_call: + workflow_dispatch: + +jobs: + env: + name: Generate environment variables + runs-on: ubuntu-latest + steps: + - name: Derive environment from git ref + id: environment + run: | + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + ENVIRONMENT="production" + APP_NAME_SUFFIX="" + elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then + ENVIRONMENT="development" + APP_NAME_SUFFIX="-development" + elif [ "${{github.event_name}}" = "pull_request"]; then + ENVIRONMENT="pr/${{ github.event.pull_request.number }}" + APP_NAME_SUFFIX="-pr-${{ github.event.pull_request.number }}" + else + exit 1 + fi + + echo "ENVIRONMENT=$ENVIRONMENT" >> $GITHUB_OUTPUT + echo "APP_NAME_SUFFIX=$APP_NAME_SUFFIX" >> $GITHUB_OUTPUT + outputs: + environment: "${{ steps.environment.outputs.ENVIRONMENT }}" + app_name: "viecpro-frontend${{ steps.environment.outputs.APP_NAME_SUFFIX }}" + registry: ghcr.io + image: ${{ github.repository }} + + vars: + name: Generate public url + needs: [env] + runs-on: ubuntu-latest + environment: + name: ${{ needs.env.outputs.environment }} + steps: + - name: Generate public URL + id: public_url + run: | + if [ -z "${{ vars.PUBLIC_URL }}" ]; then + PUBLIC_URL="https://${{ needs.env.outputs.app_name }}.${{ vars.KUBE_INGRESS_BASE_DOMAIN }}" + else + PUBLIC_URL="${{ vars.PUBLIC_URL }}" + fi + + echo "PUBLIC_URL=$PUBLIC_URL" >> $GITHUB_OUTPUT + outputs: + public_url: ${{ steps.public_url.outputs.PUBLIC_URL }} + + build: + name: Build and push docker image + needs: [env, vars] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + environment: + name: "${{ needs.env.outputs.environment }}" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ needs.env.outputs.registry }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ needs.env.outputs.registry }}/${{ needs.env.outputs.image }} + tags: | + type=raw,value={{sha}} + type=ref,event=branch + # type=ref,event=pr + # type=semver,pattern={{version}} + # type=semver,pattern={{major}}.{{minor}} + # type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + "NUXT_PUBLIC_APP_BASE_URL=${{ needs.vars.outputs.public_url }}" + "NUXT_PUBLIC_BOTS=${{ vars.NUXT_PUBLIC_BOTS }}" + "NUXT_PUBLIC_GOOGLE_SITE_VERIFICATION=${{ vars.NUXT_PUBLIC_GOOGLE_SITE_VERIFICATION }}" + "NUXT_PUBLIC_MATOMO_BASE_URL=${{ vars.NUXT_PUBLIC_MATOMO_BASE_URL }}" + "NUXT_PUBLIC_MATOMO_ID=${{ vars.NUXT_PUBLIC_MATOMO_ID }}" + "NUXT_PUBLIC_REDMINE_ID=${{ vars.SERVICE_ID }}" + "NUXT_PUBLIC_TYPESENSE_API_KEY"="${{ vars.NUXT_PUBLIC_TYPESENSE_API_KEY }}" + "NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX"="${{ vars.NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX }}" + "NUXT_PUBLIC_TYPESENSE_HOST"="${{ vars.NUXT_PUBLIC_TYPESENSE_HOST }}" + "NUXT_PUBLIC_TYPESENSE_PORT"="${{ vars.NUXT_PUBLIC_TYPESENSE_PORT }}" + "NUXT_PUBLIC_TYPESENSE_PROTOCOL"="${{ vars.NUXT_PUBLIC_TYPESENSE_PROTOCOL }}" + cache-from: type=ghax + cache-to: type=gha,mode=max + + deploy: + name: Deploy docker image + needs: [env, vars, build] + uses: acdh-oeaw/gl-autodevops-minimal-port/.github/workflows/deploy.yml@main + secrets: inherit + with: + environment: ${{ needs.env.outputs.environment }} + DOCKER_TAG: ${{ needs.env.outputs.registry }}/${{ needs.env.outputs.image }} + APP_NAME: "${{ needs.env.outputs.app_name }}" + APP_ROOT: "/" + SERVICE_ID: "${{ vars.SERVICE_ID }}" + PUBLIC_URL: "${{ needs.vars.outputs.public_url }}" + default_port: "3000" diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index bba2e43..0000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [main] - pull_request: - branches: [main] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 20 - - name: Install dependencies - run: npm install -g pnpm@8 && pnpm install - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - - name: Build app - run: pnpm run build - env: - NUXT_PUBLIC_APP_BASE_URL: "http://localhost:3000" - NUXT_PUBLIC_REDMINE_ID: "${{ vars.NUXT_PUBLIC_REDMINE_ID }}" - - name: Run Playwright tests - run: pnpm exec playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/.github/workflows/starter.yml b/.github/workflows/starter.yml deleted file mode 100644 index 8dc7a05..0000000 --- a/.github/workflows/starter.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: workflows starter -# env: is empty, see setup-env and the outputs there -on: - push: - branches: [main] - workflow_dispatch: {} -jobs: - setup_workflow_env: - runs-on: ubuntu-latest - outputs: - environment: ${{ steps.get_environment_from_git_ref.outputs.environment }} - environment_short: ${{ steps.get_environment_from_git_ref.outputs.environment_short }} - image_name: viecpro-frontend - registry_root: ghcr.io/${{ github.repository }}/ - default_port: "3000" - fetch-depth: 10 - submodules: "false" - APP_NAME: viecpro-frontend - APP_ROOT: "/" - SERVICE_ID: ${{ vars.NUXT_PUBLIC_REDMINE_ID }} - PUBLIC_URL: ${{ vars.PUBLIC_URL }} # Use GitHub environment variables for a stable custom public url - BUILDPACK_URL: "https://github.com/unfold/heroku-buildpack-pnpm" - # POSTGRES_ENABLED: "false" # needs to be set to true to enable a postgres db installed next to the deployed app - # You should not need to have to change anything below this line - #----------------------------------------------------------------------------------------------------- - steps: - - name: Get environment from git ref - id: get_environment_from_git_ref - run: | - echo "Running on branch ${{ github.ref_name }}" - if [ "${{ github.ref }}" = "refs/heads/main" ]; then - echo "environment=production" - echo "environment=production" >> $GITHUB_OUTPUT - echo "environment_short=prod" >> $GITHUB_OUTPUT - else - echo "environment=review/${{ github.ref_name }}" - echo "environment=review/${{ github.ref_name }}" >> $GITHUB_OUTPUT - echo "environment_short=$(echo -n ${{ github.ref_name }} | sed 's/feat\(ure\)\{0,1\}[_/]//' | tr '_' '-' | tr '[:upper:]' '[:lower:]' | cut -c -63 )" >> $GITHUB_OUTPUT - fi - generate_workflow_vars: - needs: [setup_workflow_env] - environment: - name: ${{ needs.setup_workflow_env.outputs.environment }} - runs-on: ubuntu-latest - steps: - - name: Generate PUBLIC_URL if not set - id: generate_public_url - run: | - kube_ingress_base_domain="${{ vars.PUBLIC_URL }}" - public_url="${{ needs.setup_workflow_env.outputs.PUBLIC_URL || vars.PUBLIC_URL }}" - if [ "${public_url}x" == 'x' ] - then public_url=https://${{ needs.setup_workflow_env.outputs.environment_short }}.${kube_ingress_base_domain} - fi - echo "public_url=$public_url" >> $GITHUB_OUTPUT - outputs: - PUBLIC_URL: ${{ steps.generate_public_url.outputs.public_url }} - _1: - needs: [setup_workflow_env, generate_workflow_vars] - uses: acdh-oeaw/gl-autodevops-minimal-port/.github/workflows/build-herokuish-and-push-to-registry.yaml@main - secrets: inherit - # if you run this outside of of an org that provides KUBE_CONFIG etc as a secret, you need to specify every secret you want to pass by name - with: - environment: ${{ needs.setup_workflow_env.outputs.environment }} - registry_root: ${{ needs.setup_workflow_env.outputs.registry_root }} - image_name: ${{ needs.setup_workflow_env.outputs.image_name }} - BUILDPACK_URL: ${{ needs.setup_workflow_env.outputs.BUILDPACK_URL }} - default_port: ${{ needs.setup_workflow_env.outputs.default_port }} - PUBLIC_URL: ${{ needs.generate_workflow_vars.outputs.PUBLIC_URL }} - fetch-depth: ${{ fromJson(needs.setup_workflow_env.outputs.fetch-depth) }} - submodules: ${{ needs.setup_workflow_env.outputs.submodules }} - _3: - needs: [setup_workflow_env, generate_workflow_vars, _1] - uses: acdh-oeaw/gl-autodevops-minimal-port/.github/workflows/deploy.yml@main - secrets: inherit - # if you run this outside of acdh-oeaw yo uneed to specify every secret you want to pass by name - # KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} - # KUBE_INGRESS_BASE_DOMAIN: ${{ secrets.KUBE_INGRESS_BASE_DOMAIN }} - # POSTGRES_USER: ${{ secrets.POSTGRES_USER }} - # POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} - # POSTGRES_DB: ${{ secrets.POSTGRES_DB }} - # K8S_SECRET_A_VAR_NAME: ${{ }} - with: - environment: ${{ needs.setup_workflow_env.outputs.environment}} - fetch-depth: ${{ fromJson(needs.setup_workflow_env.outputs.fetch-depth) }} - DOCKER_TAG: - ${{ needs.setup_workflow_env.outputs.registry_root }}${{ - needs.setup_workflow_env.outputs.image_name }} - APP_NAME: - ${{ needs.setup_workflow_env.outputs.APP_NAME }}-${{ - needs.setup_workflow_env.outputs.environment_short }} - APP_ROOT: ${{ needs.setup_workflow_env.outputs.APP_ROOT }} - SERVICE_ID: ${{ needs.setup_workflow_env.outputs.SERVICE_ID }} - PUBLIC_URL: ${{ needs.generate_workflow_vars.outputs.PUBLIC_URL }} - POSTGRES_ENABLED: ${{ needs.setup_workflow_env.outputs.POSTGRES_ENABLED == 'true'}} - default_port: "${{ needs.setup_workflow_env.outputs.default_port}}" - submodules: ${{ needs.setup_workflow_env.outputs.submodules }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..d1b45e5 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,108 @@ +name: Validate + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-validate + cancel-in-progress: true + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + validate: + name: Validate + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + + strategy: + fail-fast: true + matrix: + node-version: [22.x] + os: [ubuntu-latest] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Necessary because `actions/setup-node` does not yet support `corepack`. + # @see https://github.com/actions/setup-node/issues/531 + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Format + run: pnpm run format:check + + - name: Lint + run: pnpm run lint:check + + # - name: Typecheck + # run: pnpm run types:check + + - name: Test + run: pnpm run test + + - name: Get playwright version + run: | + PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version') + echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV + + - name: Cache playwright browsers + uses: actions/cache@v4 + id: cache-playwright-browsers + with: + path: "~/.cache/ms-playwright" + key: ${{ matrix.os }}-playwright-browsers-${{ env.PLAYWRIGHT_VERSION }} + + - name: Install playwright browsers + if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' + run: pnpm exec playwright install --with-deps + - name: Install playwright browsers (os dependencies) + if: steps.cache-playwright-browsers.outputs.cache-hit == 'true' + run: pnpm exec playwright install-deps + + - name: Build app + run: pnpm run build + env: + NUXT_PUBLIC_APP_BASE_URL: "http://localhost:3000" + NUXT_PUBLIC_REDMINE_ID: "${{ vars.SERVICE_ID }}" + NUXT_PUBLIC_TYPESENSE_API_KEY: "${{ vars.NUXT_PUBLIC_TYPESENSE_API_KEY }}" + NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX: + "${{ vars.NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX }}" + NUXT_PUBLIC_TYPESENSE_HOST: "${{ vars.NUXT_PUBLIC_TYPESENSE_HOST }}" + NUXT_PUBLIC_TYPESENSE_PORT: "${{ vars.NUXT_PUBLIC_TYPESENSE_PORT }}" + NUXT_PUBLIC_TYPESENSE_PROTOCOL: "${{ vars.NUXT_PUBLIC_TYPESENSE_PROTOCOL }}" + + - name: Run e2e tests + run: pnpm run test:e2e + env: + NUXT_PUBLIC_APP_BASE_URL: "http://localhost:3000" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + build-deploy: + if: ${{ github.event_name == 'push' }} + needs: [validate] + uses: ./.github/workflows/build-deploy.yml + secrets: inherit + # https://docs.github.com/en/actions/using-workflows/reusing-workflows#access-and-permissions + permissions: + contents: read + packages: write diff --git a/.gitignore b/.gitignore index 82505a3..7e43634 100644 --- a/.gitignore +++ b/.gitignore @@ -2,41 +2,35 @@ node_modules/ .pnpm-store/ -# nuxt.js -dist -.nuxt/ -.nitro/ -.output/ - # logs *.log # non-public environment variables -.env .env.local .env.*.local # caches .eslintcache +.prettiercache .stylelintcache *.tsbuildinfo +# vercel +.vercel + # misc .DS_Store -.idea +.idea/ -# generated assets -/public/**/favicon.ico -/public/**/icon.svg -/public/**/android-chrome-192x192.png -/public/**/android-chrome-512x512.png -/public/**/apple-touch-icon.png -/public/**/app.webmanifest -/public/**/image.webp +# nuxt.js +dist +.data/ +.nuxt/ +.nitro/ +.output/ -# notes -DEV_NOTES.md -/test-results/ -/playwright-report/ +# playwright /blob-report/ /playwright/.cache/ +/playwright-report/ +/test-results/ diff --git a/.npmrc b/.npmrc index cd00af4..92db421 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,4 @@ engine-strict=true +manage-package-manager-versions=true package-manager-strict=false shell-emulator=true diff --git a/.vscode/app.code-snippets b/.vscode/app.code-snippets new file mode 100644 index 0000000..2111199 --- /dev/null +++ b/.vscode/app.code-snippets @@ -0,0 +1,25 @@ +{ + "Nuxt static page component": { + "scope": "vue", + "prefix": "nuxt-page-static", + "body": [ + "", + "", + "", + ], + }, +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 54de482..3f73966 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,12 @@ "recommendations": [ "bradlc.vscode-tailwindcss", "dbaeumer.vscode-eslint", - "editorConfig.editorConfig", + "editorconfig.editorconfig", "esbenp.prettier-vscode", + "lokalise.i18n-ally", "mikestead.dotenv", + "ms-playwright.playwright", + "nuxt.mdc", "stylelint.vscode-stylelint", "vue.volar" ] diff --git a/.vscode/launch.json b/.vscode/launch.json index f340a49..73f0650 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "name": "App: Web (server-side)", "request": "launch", "runtimeArgs": ["run", "dev"], - "runtimeExecutable": "npm", + "runtimeExecutable": "pnpm", "skipFiles": ["/**"], "type": "node" }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 5bdc48f..0515a5b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "css.validate": false, + "debug.toolBarLocation": "docked", "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, @@ -11,6 +12,7 @@ "strings": true }, "editor.rulers": [100], + "editor.stickyScroll.enabled": true, "eslint.enable": true, "eslint.validate": ["javascript", "typescript", "vue"], "files.associations": { @@ -18,7 +20,15 @@ }, "files.eol": "\n", "html.autoCreateQuotes": false, + "i18n-ally.annotations": false, + "i18n-ally.keepFulfilled": true, + "i18n-ally.keystyle": "nested", + "i18n-ally.localesPaths": ["./messages"], + "i18n-ally.review.enabled": false, + "i18n-ally.sortKeys": true, + "i18n-ally.tabStyle": "tab", "less.validate": false, + "prettier.ignorePath": ".gitignore", "scss.validate": false, "stylelint.enable": true, "stylelint.snippet": ["css", "postcss", "tailwindcss", "vue"], @@ -28,14 +38,14 @@ "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.inlayHints.parameterNames.enabled": "all", "typescript.preferences.importModuleSpecifier": "non-relative", + "typescript.preferences.preferTypeOnlyAutoImports": true, "typescript.tsdk": "node_modules/typescript/lib", - "volar.inlayHints.eventArgumentInInlineHandlers": false, + "workbench.editor.labelFormat": "medium", + "workbench.tree.enableStickyScroll": true, "[markdown]": { "editor.wordWrap": "on" }, "[mdc]": { - "editor.defaultFormatter": null - }, - "i18n-ally.localesPaths": ["locales"], - "i18n-ally.keystyle": "nested" + "editor.wordWrap": "on" + } } diff --git a/Dockerfile b/Dockerfile index db4d62c..ff52359 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,44 @@ # syntax=docker/dockerfile:1 +# using alpine base image to avoid `sharp` memory leaks. +# @see https://sharp.pixelplumbing.com/install#linux-memory-allocator + # build -FROM node:22-slim AS build +FROM node:22-alpine AS build + +RUN corepack enable RUN mkdir /app && chown -R node:node /app WORKDIR /app USER node +COPY --chown=node:node .npmrc package.json pnpm-lock.yaml ./ + +RUN pnpm fetch + +COPY --chown=node:node ./ ./ + ARG NUXT_PUBLIC_APP_BASE_URL +ARG NUXT_PUBLIC_BOTS +ARG NUXT_PUBLIC_GOOGLE_SITE_VERIFICATION ARG NUXT_PUBLIC_MATOMO_BASE_URL ARG NUXT_PUBLIC_MATOMO_ID ARG NUXT_PUBLIC_REDMINE_ID ARG NUXT_PUBLIC_TYPESENSE_API_KEY +ARG NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX +ARG NUXT_PUBLIC_TYPESENSE_HOST ARG NUXT_PUBLIC_TYPESENSE_PORT ARG NUXT_PUBLIC_TYPESENSE_PROTOCOL -ARG NUXT_PUBLIC_TYPESENSE_HOST -ARG NUXT_PUBLIC_TYPESENSE_COLLECTION_PREFIX - -COPY --chown=node:node .npmrc package.json package-lock.json ./ -COPY --chown=node:node nuxt.config.ts tailwind.config.cjs tsconfig.json ./ -COPY --chown=node:node scripts ./scripts -COPY --chown=node:node config ./config -COPY --chown=node:node public ./public -COPY --chown=node:node src ./ -COPY --chown=node:node patches ./patches -RUN npm install --ci --no-audit --no-fund +RUN pnpm install --frozen-lockfile --offline ENV NODE_ENV=production -RUN npm run build +RUN pnpm run build # serve -FROM node:22-slim AS serve +FROM node:22-alpine AS serve RUN mkdir /app && chown -R node:node /app WORKDIR /app diff --git a/app.vue b/app.vue deleted file mode 100644 index a43864c..0000000 --- a/app.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/app/app.vue b/app/app.vue new file mode 100644 index 0000000..d3ad388 --- /dev/null +++ b/app/app.vue @@ -0,0 +1,8 @@ + diff --git a/components/app-footer.vue b/app/components/app-footer.vue similarity index 57% rename from components/app-footer.vue rename to app/components/app-footer.vue index 37c6f63..2ce539e 100644 --- a/components/app-footer.vue +++ b/app/components/app-footer.vue @@ -5,10 +5,10 @@ const t = useTranslations();
- +
diff --git a/app/components/centered.vue b/app/components/centered.vue new file mode 100644 index 0000000..708944c --- /dev/null +++ b/app/components/centered.vue @@ -0,0 +1,5 @@ + diff --git a/components/chip.vue b/app/components/chip.vue similarity index 100% rename from components/chip.vue rename to app/components/chip.vue diff --git a/components/content/content-wrapper.vue b/app/components/content/content-wrapper.vue similarity index 100% rename from components/content/content-wrapper.vue rename to app/components/content/content-wrapper.vue diff --git a/components/content/divider.vue b/app/components/content/divider.vue similarity index 100% rename from components/content/divider.vue rename to app/components/content/divider.vue diff --git a/components/content/gray-box.vue b/app/components/content/gray-box.vue similarity index 100% rename from components/content/gray-box.vue rename to app/components/content/gray-box.vue diff --git a/components/content/header-wrapper.vue b/app/components/content/header-wrapper.vue similarity index 100% rename from components/content/header-wrapper.vue rename to app/components/content/header-wrapper.vue diff --git a/components/content/index-button.vue b/app/components/content/index-button.vue similarity index 100% rename from components/content/index-button.vue rename to app/components/content/index-button.vue index d6e1556..d925fa2 100644 --- a/components/content/index-button.vue +++ b/app/components/content/index-button.vue @@ -7,8 +7,8 @@ defineProps<{ @@ -183,8 +189,8 @@ useHead({ data.details.data.owners[0] && data.details.data.resolution.includes(String(data.details.data.owners[0].target.name)) " - :to="`/detail/persons/${data.details.data.owners[0].target.object_id}`" class="underline" + :to="`/detail/persons/${data.details.data.owners[0].target.object_id}`" > {{ data.details.data?.resolution }} @@ -218,8 +224,8 @@ useHead({ :headers="[]" /> -->

{{ t("detail-page.relations") }}

{{ t("ui.no-data") }}.
diff --git a/pages/detail/events/[id].vue b/app/pages/detail/events/[id]/index.vue similarity index 85% rename from pages/detail/events/[id].vue rename to app/pages/detail/events/[id]/index.vue index b864cad..fd76b67 100644 --- a/pages/detail/events/[id].vue +++ b/app/pages/detail/events/[id]/index.vue @@ -13,7 +13,9 @@ const id = String(route.params.id); const data = ref({ entity: useQuery({ queryKey: ["event", id], - queryFn: () => getDocument("viecpro_events", `Event_${id}`), + queryFn: () => { + return getDocument("viecpro_events", `Event_${id}`); + }, }), }); @@ -33,7 +35,7 @@ useHead({