🚧 Port subpath-safe public URI helpers to link-preview

Port join-uri and get-public-uri from the unified jobs substrate WIP and use them for the link-preview image URLs, replacing the naive string concatenation that dropped configured subpaths. Includes the ported config tests.

AI-assisted-by: muse-spark-1.3-contributor
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrey Antukh 2026-09-09 13:51:16 +00:00
parent db84b77c7e
commit 9e8e2c4a2a
3 changed files with 51 additions and 10 deletions

View File

@ -377,6 +377,20 @@
(or (c/get config :file-clean-delay)
(ct/duration {:days 2})))
(defn join-uri
"Join path segments onto a base URI, preserving a potential subpath
(same semantics as the frontend config). The base is normalized with
a trailing slash; segments must not start with `/` (a leading slash
would resolve against the host root and drop the subpath)."
[base & segments]
(str (apply u/join (u/ensure-path-slash base) segments)))
(defn get-public-uri
"Canonical public URI builder: `join-uri` over the configured
:public-uri. With no segments, returns the normalized base."
[& segments]
(apply join-uri (c/get config :public-uri) segments))
(defn get
"A configuration getter. Helps code be more testable."
([key]

View File

@ -35,14 +35,6 @@
ORDER BY ft.revn DESC NULLS LAST
LIMIT 1")
(defn- resolve-image-uri
[media-id]
(str (cf/get :public-uri) "/assets/by-id/" media-id))
(defn- resolve-default-image-uri
[]
(str (cf/get :public-uri) "/images/penpot-link-preview.png"))
(defn- get-file-context
"Return the link preview context for a file link: the file name as title
and, when available, the last dashboard thumbnail as image."
@ -50,7 +42,7 @@
(when-let [{:keys [name media-id]} (db/exec-one! pool [sql:get-file file-id])]
(cond-> (assoc default-context :title (str name " | Penpot"))
(some? media-id)
(assoc :image (resolve-image-uri media-id)))))
(assoc :image (cf/get-public-uri (str "assets/by-id/" media-id))))))
(defn- get-context
[pool params]
@ -71,7 +63,7 @@
(let [context (when (contains? cf/flags :link-preview)
(get-context pool (:query-params request)))
context (-> (or context default-context)
(update :image #(or % (resolve-default-image-uri))))]
(update :image #(or % (cf/get-public-uri "images/penpot-link-preview.png"))))]
{::yres/status 200
::yres/headers {"content-type" "text/html; charset=utf-8"
"cache-control" "no-store, no-cache, max-age=0"}

View File

@ -0,0 +1,35 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns backend-tests.config-test
(:require
[app.config :as cf]
[clojure.test :as t]))
(t/deftest get-public-uri-normalizes-base
(t/testing "trailing slash is ensured with and without subpath"
(doseq [[base expected] [["http://localhost:3449" "http://localhost:3449/"]
["http://localhost:3449/" "http://localhost:3449/"]
["https://example.com/penpot" "https://example.com/penpot/"]
["https://example.com/penpot/" "https://example.com/penpot/"]]]
(t/testing (str "base " base)
(with-redefs [cf/config (assoc cf/config :public-uri base)]
(t/is (= expected (cf/get-public-uri))))))))
(t/deftest get-public-uri-preserves-subpath
(t/testing "joined segments keep the subpath"
(with-redefs [cf/config (assoc cf/config :public-uri "https://example.com/penpot")]
(t/is (= "https://example.com/penpot/assets/by-id/123"
(cf/get-public-uri "assets/by-id/123")))
(t/is (= "https://example.com/penpot/api/main/doc"
(cf/get-public-uri "api/main/doc"))))))
(t/deftest join-uri-joins-arbitrary-base
(t/testing "segments join onto any base with trailing slash normalization"
(t/is (= "https://nitrate.example.com/api/teams/123"
(cf/join-uri "https://nitrate.example.com" "api/teams/123")))
(t/is (= "https://nitrate.example.com/api/teams/123"
(cf/join-uri "https://nitrate.example.com/" "api/teams/123")))))