From 4551d9df014e01cf4c8aab9c44f002b9c011bdf5 Mon Sep 17 00:00:00 2001 From: Esko Luontola Date: Fri, 12 Jul 2024 16:54:38 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20Switch=20to=20using=20SSR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: - Continuing the migration from SPA to SSR. - The SSR site is now feature complete with the SPA site, so we can remove the SPA site and switch to using SSR. - Changed the test and build to happen outside the docker build to simplify the docker build and to make CI faster. --- .dockerignore | 14 +--- .semaphore/semaphore.yml | 69 +++---------------- Dockerfile | 23 +++++++ Dockerfile-api | 88 ------------------------- Dockerfile-web | 39 ----------- dev-config.edn | 2 +- docker-compose.yml | 21 ++---- project.clj | 3 +- scripts/build.sh | 20 +++++- scripts/release-ci.sh | 17 ++--- scripts/release-local.sh | 18 +++++ scripts/release.sh | 21 ------ src/territory_bro/main.clj | 2 +- src/territory_bro/ui.clj | 12 +--- test/territory_bro/infra/auth0_test.clj | 21 +++--- vite.config.ts | 9 --- 16 files changed, 97 insertions(+), 282 deletions(-) create mode 100644 Dockerfile delete mode 100644 Dockerfile-api delete mode 100644 Dockerfile-web create mode 100755 scripts/release-local.sh delete mode 100755 scripts/release.sh diff --git a/.dockerignore b/.dockerignore index fb6a4994..2300cf17 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,3 @@ -/target/ -/node_modules/ -/.lein-* -/.nrepl-port -/profiles.clj -.DS_Store -*.tmp -*.bak -*.log -*.qgs~ -/.git/ +* +!/target/uberjar/territory-bro.jar +!/target/uberjar/classes.list diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 605e49c1..225bce34 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -3,8 +3,8 @@ name: Territory Bro CI agent: machine: - type: e1-standard-2 - os_image: ubuntu2004 + type: e2-standard-2 + os_image: ubuntu2204 blocks: - name: Build @@ -17,71 +17,22 @@ blocks: - checkout jobs: - - name: Test API + - name: Build commands: - cache restore - cache restore m2-$SEMAPHORE_GIT_BRANCH-revision-$(checksum project.clj) - cache restore lein-$SEMAPHORE_GIT_BRANCH-revision-$(checksum project.clj) - sem-version java 17 - nvm install 21 + - sudo apt-get install -y zopfli - - npm install - - npm run build - - docker-compose up -d db - - lein test + - ./scripts/build.sh + - docker compose build --pull app + + - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin + - docker tag luontola/territory-bro luontola/territory-bro:ci + - docker push luontola/territory-bro:ci - cache store - cache store m2-$SEMAPHORE_GIT_BRANCH-revision-$(checksum project.clj) ~/.m2 - cache store lein-$SEMAPHORE_GIT_BRANCH-revision-$(checksum project.clj) ~/.lein/self-installs - - - name: Test Web - commands: - - cache restore - - nvm install 21 - - - npm install - - npm run test - - - cache store - - - name: Build API - commands: - - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin - - docker pull luontola/territory-bro-api:ci-builder || true - - docker pull luontola/territory-bro-api:ci || true - - - docker build --pull --progress=plain - --tag luontola/territory-bro-api:ci-builder - --cache-from luontola/territory-bro-api:ci-builder - --file Dockerfile-api --target builder . - - - docker build --pull --progress=plain - --tag luontola/territory-bro-api:ci - --cache-from luontola/territory-bro-api:ci - --cache-from luontola/territory-bro-api:ci-builder - --file Dockerfile-api . - - - docker push luontola/territory-bro-api:ci-builder - - docker push luontola/territory-bro-api:ci - - - name: Build Web - commands: - - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin - - docker pull luontola/territory-bro-web:ci-builder || true - - docker pull luontola/territory-bro-web:ci || true - - - docker build --pull --progress=plain - --tag luontola/territory-bro-web:ci-builder - --cache-from luontola/territory-bro-web:ci-builder - --build-arg VITE_GIT_COMMIT=$(git rev-parse --short HEAD) - --file Dockerfile-web --target builder . - - - docker build --pull --progress=plain - --tag luontola/territory-bro-web:ci - --cache-from luontola/territory-bro-web:ci - --cache-from luontola/territory-bro-web:ci-builder - --build-arg VITE_GIT_COMMIT=$(git rev-parse --short HEAD) - --file Dockerfile-web . - - - docker push luontola/territory-bro-web:ci-builder - - docker push luontola/territory-bro-web:ci diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a9e34eba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM eclipse-temurin:17-jre-focal + +EXPOSE 8080 +ENV PORT=8080 \ + DATABASE_URL="jdbc:postgresql://localhost/territorybro?user=territorybro&password=territorybro" + +# the user should have no write permissions, even to its home +RUN adduser --no-create-home --home /app app && \ + mkdir /app +WORKDIR /app + +COPY target/uberjar/territory-bro.jar /app/ + +# prepare AppCDS shared archive +COPY target/uberjar/classes.list /app/ +RUN java -Xshare:dump \ + -XX:SharedClassListFile=classes.list \ + -XX:SharedArchiveFile=classes.jsa \ + --class-path territory-bro.jar && \ + rm classes.list + +USER app +ENTRYPOINT ["java", "-Xshare:on", "-XX:SharedArchiveFile=classes.jsa", "-jar", "territory-bro.jar"] diff --git a/Dockerfile-api b/Dockerfile-api deleted file mode 100644 index 4b2d863c..00000000 --- a/Dockerfile-api +++ /dev/null @@ -1,88 +0,0 @@ -FROM eclipse-temurin:17-jdk-focal AS builder - -# Leiningen -RUN curl -sSL https://raw.githubusercontent.com/technomancy/leiningen/2.9.10/bin/lein > /usr/local/bin/lein && \ - chmod 755 /usr/local/bin/lein - -# avoid tzdata installation starting to ask questions -ARG DEBIAN_FRONTEND=noninteractive - -# PostgreSQL -RUN apt-get update && \ - apt-get install -y gnupg && \ - echo 'deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main' > /etc/apt/sources.list.d/pgdg.list && \ - curl -SL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ - apt-get update && \ - apt-get install -y postgresql-13 postgresql-13-postgis-3 sudo && \ - rm -rf /var/lib/apt/lists/* - -# Node.js -RUN curl -fsSL https://deb.nodesource.com/setup_21.x | sudo -E bash - &&\ - apt-get install -y nodejs && \ - rm -rf /var/lib/apt/lists/* - -# working directory -RUN mkdir -p /project -WORKDIR /project - -# cache Leiningen dependencies -COPY project.clj /project/ -RUN lein deps - -# cache NPM dependencies -COPY package.json package-lock.json /project/ -RUN npm ci - -# source code -COPY resources /project/resources -COPY test /project/test -COPY src /project/src -COPY src-java /project/src-java -COPY tsconfig.json tsconfig.node.json vite.config.ts /project/ -COPY web /project/web - -# build frontend assets -RUN npm run build - -# run tests -COPY test-config.edn tests.edn postgres-init.sql /project/ -RUN service postgresql start && \ - sudo -u postgres psql -c "CREATE USER territorybro WITH CREATEROLE PASSWORD 'territorybro';" && \ - sudo -u postgres psql -c "CREATE DATABASE territorybro OWNER territorybro;" && \ - sudo -u postgres psql -d territorybro -f postgres-init.sql && \ - lein kaocha fast slow && \ - service postgresql stop - -# build the binary -RUN lein uberjar - -# prepare AppCDS class list -RUN service postgresql start && \ - java -XX:DumpLoadedClassList=classes.lst -Dconf=test-config.edn -jar "target/default+uberjar/territory-bro.jar" app-cds-setup && \ - service postgresql stop - -# ------------------------------------------------------------ - -FROM eclipse-temurin:17-jre-focal - -EXPOSE 8080 -ENV PORT=8080 \ - DATABASE_URL="jdbc:postgresql://localhost/territorybro?user=territorybro&password=territorybro" - -# the user should have no write permissions, even to its home -RUN adduser --no-create-home --home /app app && \ - mkdir /app -WORKDIR /app - -COPY --from=builder "/project/target/default+uberjar/territory-bro.jar" /app/ - -# prepare AppCDS shared archive -COPY --from=builder /project/classes.lst /app/ -RUN java -Xshare:dump \ - -XX:SharedClassListFile=classes.lst \ - -XX:SharedArchiveFile=classes.jsa \ - --class-path territory-bro.jar && \ - rm classes.lst - -USER app -ENTRYPOINT ["java", "-Xshare:on", "-XX:SharedArchiveFile=classes.jsa", "-jar", "territory-bro.jar"] diff --git a/Dockerfile-web b/Dockerfile-web deleted file mode 100644 index 4f5a95e0..00000000 --- a/Dockerfile-web +++ /dev/null @@ -1,39 +0,0 @@ -FROM node:21-alpine AS builder - -RUN apk add findutils zopfli - -# working directory -RUN mkdir -p /project && \ - chown node /project -WORKDIR /project -USER node - -# cache node_modules -COPY --chown=node package.json package-lock.json /project/ -RUN npm ci - -# do the build -COPY --chown=node tsconfig.json tsconfig.node.json vite.config.ts /project/ -COPY --chown=node web /project/web -ARG VITE_GIT_COMMIT -RUN npm run test && \ - npm run build - -# offline compress static resources -RUN find /project/target/web-dist/public \ - -type f \ - -regextype posix-extended \ - -iregex '.*\.(html?|js|map|css|svg|otf|ttf|txt|json)' \ - -exec zopfli '{}' \; - -# ------------------------------------------------------------ - -FROM nginx:1.16-alpine - -COPY web/docker/nginx-default.conf.template /etc/nginx/conf.d/default.conf.template - -COPY web/docker/docker-entrypoint.sh / -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["nginx", "-g", "daemon off;"] - -COPY --from=builder /project/target/web-dist/public /usr/share/nginx/html diff --git a/dev-config.edn b/dev-config.edn index 697e4c94..cfe50e1b 100644 --- a/dev-config.edn +++ b/dev-config.edn @@ -1,5 +1,5 @@ {:dev true - :port 8081 + :port 8080 :database-url "jdbc:postgresql://localhost/territorybro?user=territorybro&password=territorybro" :gis-database-host "localhost" :auth0-domain "luontola.eu.auth0.com" diff --git a/docker-compose.yml b/docker-compose.yml index 46adfe19..239d3d98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,10 @@ -version: '2' services: - web: + app: build: context: . - dockerfile: Dockerfile-web - image: luontola/territory-bro-web - environment: - API_URL: http://api:8080 - mem_reservation: 10m - mem_limit: 20m - ports: - - "127.0.0.1:8080:80" - - api: - build: - context: . - dockerfile: Dockerfile-api - image: luontola/territory-bro-api + dockerfile: Dockerfile + image: luontola/territory-bro environment: PUBLIC_URL: http://localhost:8080 AUTH0_DOMAIN: luontola.eu.auth0.com @@ -30,7 +17,7 @@ services: mem_reservation: 300m mem_limit: 500m ports: - - "127.0.0.1:8081:8080" + - "127.0.0.1:8080:8080" - "127.0.0.1:7000:7000" db: diff --git a/project.clj b/project.clj index 61d61ed4..a45a32ce 100644 --- a/project.clj +++ b/project.clj @@ -91,7 +91,8 @@ :test-selectors {:default (fn [m] (not (:e2e m))) :e2e :e2e} - :profiles {:uberjar {:omit-source true + :profiles {:uberjar {:auto-clean false + :omit-source true :aot :all :uberjar-name "territory-bro.jar"} diff --git a/scripts/build.sh b/scripts/build.sh index 3ece21ba..e788936d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -6,9 +6,25 @@ export VITE_GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d db lein clean -lein test -lein uberjar npm install npm run test npm run build + +# macOS find doesn't support -regextype, and GNU find doesn't support -E +if [[ "$OSTYPE" == "darwin"* ]]; then + find -E target/web-dist/public \ + -type f \ + -iregex '.*\.(html|js|map|css|svg|otf|ttf|txt|json)' \ + -print -exec zopfli '{}' \; -exec brotli -f '{}' \; +else + find target/web-dist/public \ + -type f \ + -regextype posix-extended \ + -iregex '.*\.(html|js|map|css|svg|otf|ttf|txt|json)' \ + -print -exec zopfli '{}' \; -exec brotli -f '{}' \; +fi + +lein do kaocha fast slow, uberjar + +java -XX:DumpLoadedClassList=target/uberjar/classes.list -Dconf=test-config.edn -jar "target/uberjar/territory-bro.jar" app-cds-setup diff --git a/scripts/release-ci.sh b/scripts/release-ci.sh index 973a8174..7040c344 100755 --- a/scripts/release-ci.sh +++ b/scripts/release-ci.sh @@ -6,19 +6,14 @@ TAG="${1:-$DEFAULT_TAG}" echo TAG="$TAG" set -x -git tag "$TAG" +docker pull "luontola/territory-bro:ci" -docker pull "luontola/territory-bro-api:ci" -docker pull "luontola/territory-bro-web:ci" +git tag "$TAG" -docker tag "luontola/territory-bro-api:ci" "luontola/territory-bro-api:$TAG" -docker tag "luontola/territory-bro-api:ci" "luontola/territory-bro-api:latest" -docker tag "luontola/territory-bro-web:ci" "luontola/territory-bro-web:$TAG" -docker tag "luontola/territory-bro-web:ci" "luontola/territory-bro-web:latest" +docker tag "luontola/territory-bro:ci" "luontola/territory-bro:$TAG" +docker tag "luontola/territory-bro:ci" "luontola/territory-bro:latest" -docker push "luontola/territory-bro-api:$TAG" -docker push "luontola/territory-bro-api:latest" -docker push "luontola/territory-bro-web:$TAG" -docker push "luontola/territory-bro-web:latest" +docker push "luontola/territory-bro:$TAG" +docker push "luontola/territory-bro:latest" git push origin "$TAG" diff --git a/scripts/release-local.sh b/scripts/release-local.sh new file mode 100755 index 00000000..e1fec3d7 --- /dev/null +++ b/scripts/release-local.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -eu +#: ${1:? Usage: $0 RELEASE_VERSION} +DEFAULT_TAG=$(date +%Y-%m-%d) +TAG="${1:-$DEFAULT_TAG}" +echo TAG="$TAG" +set -x + +docker compose build --pull app + +git tag "$TAG" + +docker tag "luontola/territory-bro:latest" "luontola/territory-bro:$TAG" + +docker push "luontola/territory-bro:$TAG" +docker push "luontola/territory-bro:latest" + +git push origin "$TAG" diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 5fffb278..00000000 --- a/scripts/release.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -eu -#: ${1:? Usage: $0 RELEASE_VERSION} -DEFAULT_TAG=$(date +%Y-%m-%d) -TAG="${1:-$DEFAULT_TAG}" -echo TAG="$TAG" -set -x - -docker compose build --pull - -git tag "$TAG" - -docker tag "luontola/territory-bro-api" "luontola/territory-bro-api:$TAG" -docker tag "luontola/territory-bro-web" "luontola/territory-bro-web:$TAG" - -docker push "luontola/territory-bro-api:$TAG" -docker push "luontola/territory-bro-api:latest" -docker push "luontola/territory-bro-web:$TAG" -docker push "luontola/territory-bro-web:latest" - -git push origin "$TAG" diff --git a/src/territory_bro/main.clj b/src/territory_bro/main.clj index 305f9e01..0253e8f3 100644 --- a/src/territory_bro/main.clj +++ b/src/territory_bro/main.clj @@ -80,7 +80,7 @@ (let [client (-> (HttpClient/newBuilder) (.build)) request (-> (HttpRequest/newBuilder) - (.uri (URI. (format "http://localhost:%s/api/settings" (:port config/env)))) + (.uri (URI. (str "http://localhost:" (:port config/env)))) (.build))] (.send client request (HttpResponse$BodyHandlers/ofString))) (stop-app) diff --git a/src/territory_bro/ui.clj b/src/territory_bro/ui.clj index 77550018..c792f82c 100644 --- a/src/territory_bro/ui.clj +++ b/src/territory_bro/ui.clj @@ -8,7 +8,6 @@ [ring.util.http-response :refer :all] [territory-bro.infra.auth0 :as auth0] [territory-bro.infra.authentication :as auth] - [territory-bro.infra.config :as config] [territory-bro.ui.congregation-page :as congregation-page] [territory-bro.ui.error-page :as error-page] [territory-bro.ui.home-page :as home-page] @@ -30,14 +29,6 @@ (let [request (update request :params merge (:path-params request))] (handler request)))) -(defn wrap-base-url-compat [handler] - (fn [request] - ;; the SPA site runs on port 8080, the SSR site runs on port 8081 - (binding [config/env (if (= "http://localhost:8080" (:public-url config/env)) - (assoc config/env :public-url "http://localhost:8081") - config/env)] - (handler request)))) - (defn wrap-current-user [handler] (fn [request] (auth/with-user-from-session request @@ -45,8 +36,7 @@ (def routes ["" - {:middleware [wrap-base-url-compat ; outermost middleware first - [html/wrap-page-path nil] + {:middleware [[html/wrap-page-path nil] ; outermost middleware first auth0/wrap-redirect-to-login i18n/wrap-current-language wrap-current-user diff --git a/test/territory_bro/infra/auth0_test.clj b/test/territory_bro/infra/auth0_test.clj index a04e655b..aa2e7f57 100644 --- a/test/territory_bro/infra/auth0_test.clj +++ b/test/territory_bro/infra/auth0_test.clj @@ -16,8 +16,7 @@ [territory-bro.infra.authentication :as auth] [territory-bro.infra.config :as config] [territory-bro.infra.jwt-test :as jwt-test] - [territory-bro.test.testutil :refer [replace-in]] - [territory-bro.ui :as ui]) + [territory-bro.test.testutil :refer [replace-in]]) (:import (com.auth0 AuthenticationController IdentityVerificationException Tokens) (java.net URL) (java.util UUID) @@ -57,7 +56,7 @@ (let [request {:request-method :get :uri "/login" :headers {}} - response ((ui/wrap-base-url-compat auth0/login-handler) request) + response (auth0/login-handler request) location (URL. (response/get-header response "Location")) query-params (-> (codec/form-decode (.getQuery location)) (update-keys keyword))] @@ -65,7 +64,7 @@ (testing "redirects to Auth0" (is (http-predicates/see-other? response)) (is (= "luontola.eu.auth0.com" (.getHost location))) - (is (= {:redirect_uri "http://localhost:8081/login-callback" + (is (= {:redirect_uri "http://localhost:8080/login-callback" :client_id "8tVkdfnw8ynZ6rXNndD6eZ6ErsHdIgPi" :response_type "code" :scope "openid email profile" @@ -86,12 +85,12 @@ :uri "/login" :params {:return-to-url "/some/page?foo=bar&gazonk"} :headers {}} - response ((ui/wrap-base-url-compat auth0/login-handler) request) + response (auth0/login-handler request) location (URL. (response/get-header response "Location")) query-params (-> (codec/form-decode (.getQuery location)) (update-keys keyword)) callback-url (:redirect_uri query-params)] - (is (= "http://localhost:8081/login-callback?return-to-url=%2Fsome%2Fpage%3Ffoo%3Dbar%26gazonk" + (is (= "http://localhost:8080/login-callback?return-to-url=%2Fsome%2Fpage%3Ffoo%3Dbar%26gazonk" callback-url)))))) @@ -104,7 +103,7 @@ (let [request {:request-method :get :scheme :http :server-name "localhost" - :server-port 8081 + :server-port 8080 :uri "/login-callback" :params {:code "mjuZmU8Tw3WO9U4n6No3PJ1g3kTJzYoEYX2nfK8_0U8wY" :state "XJ3KBCcGcXmLnH09gb2AVsQp9bpjiVxLXvo5N4SKEqw"} @@ -196,7 +195,7 @@ :uri "/logout" :headers {} :session {::auth/user {:user/id (UUID. 0 1)}}} - response ((ui/wrap-base-url-compat auth0/logout-handler) request)] + response (auth0/logout-handler request)] (testing "clears the application session" (is (= {:session nil} @@ -207,7 +206,7 @@ ;; https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0 ;; https://auth0.com/docs/api/authentication#oidc-logout (is (= {:status 303 - :headers {"Location" "https://luontola.eu.auth0.com/v2/logout?returnTo=http://localhost:8081/&client_id=8tVkdfnw8ynZ6rXNndD6eZ6ErsHdIgPi"}} + :headers {"Location" "https://luontola.eu.auth0.com/v2/logout?returnTo=http://localhost:8080/&client_id=8tVkdfnw8ynZ6rXNndD6eZ6ErsHdIgPi"}} (select-keys response [:status :headers])))))) @@ -218,9 +217,9 @@ (testing "http, custom port" (let [[^HttpServletRequest request _ _] (auth0/ring->servlet {:scheme :http :server-name "localhost" - :server-port 8081 + :server-port 8080 :uri "/the/path"})] - (is (= "http://localhost:8081/the/path" (str (.getRequestURL request)))))) + (is (= "http://localhost:8080/the/path" (str (.getRequestURL request)))))) (testing "https, default port" (let [[^HttpServletRequest request _ _] (auth0/ring->servlet {:scheme :https diff --git a/vite.config.ts b/vite.config.ts index a2afda74..16563ac4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -64,15 +64,6 @@ export default defineConfig(({command}) => ({ }, } }, - server: { - port: 8080, - proxy: { - '/api': { - target: 'http://localhost:8081', - changeOrigin: true, - }, - } - }, test: { globals: true, environment: "jsdom",