From e341a90df344f41d23c4ad47d08e6b3da827511c Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 16 Oct 2026 09:12:28 -0500 Subject: [PATCH] fix(app): Storage home links use the /app/ route The home emitted /objectStorageAdmin/
(or //objectStorageAdmin/
), which Next does not serve. Links now use /app/
, configurable through ObjectStorageQBitConfig.tablePathPrefix (default /app), matching the other qbits. Co-Authored-By: Claude Opus 5.5 --- .../ObjectStorageQBitConfig.java | 34 ++++++++++++++++++ .../objectstorage/app/StorageWidgets.java | 10 ++---- .../objectstorage/index/MinioIndexTest.java | 36 +++++++++++++++++++ .../internal/TablesAndConfigTest.java | 2 +- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/kingsrook/qbits/objectstorage/ObjectStorageQBitConfig.java b/src/main/java/com/kingsrook/qbits/objectstorage/ObjectStorageQBitConfig.java index 1b91698..dc5b1f8 100644 --- a/src/main/java/com/kingsrook/qbits/objectstorage/ObjectStorageQBitConfig.java +++ b/src/main/java/com/kingsrook/qbits/objectstorage/ObjectStorageQBitConfig.java @@ -62,6 +62,7 @@ public class ObjectStorageQBitConfig implements QBitConfig private String appName = "objectStorageAdmin"; private String appLabel = "Storage"; private String parentAppName; + private String tablePathPrefix; private List allowedSecretRefPatterns = new ArrayList<>(SecretRefs.DEFAULT_ALLOWED_PATTERNS); private Integer bulkCap = 10_000; private Boolean bucketScoping = true; @@ -425,6 +426,39 @@ public class ObjectStorageQBitConfig implements QBitConfig + /******************************************************************************** + ** Getter for tablePathPrefix: the front of the links the Storage home makes to + ** tables and records. Defaults to /app, the route every QQQ frontend serves + ** tables at (/app/). + *******************************************************************************/ + public String getTablePathPrefix() + { + return (this.tablePathPrefix == null ? "/app" : this.tablePathPrefix); + } + + + + /******************************************************************************** + ** Setter for tablePathPrefix. + *******************************************************************************/ + public void setTablePathPrefix(String tablePathPrefix) + { + this.tablePathPrefix = tablePathPrefix; + } + + + + /******************************************************************************** + ** Fluent setter for tablePathPrefix. + *******************************************************************************/ + public ObjectStorageQBitConfig withTablePathPrefix(String tablePathPrefix) + { + this.tablePathPrefix = tablePathPrefix; + return (this); + } + + + /******************************************************************************** ** Getter for allowedSecretRefPatterns. *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qbits/objectstorage/app/StorageWidgets.java b/src/main/java/com/kingsrook/qbits/objectstorage/app/StorageWidgets.java index 1c89659..af3928b 100644 --- a/src/main/java/com/kingsrook/qbits/objectstorage/app/StorageWidgets.java +++ b/src/main/java/com/kingsrook/qbits/objectstorage/app/StorageWidgets.java @@ -106,16 +106,12 @@ public final class StorageWidgets /******************************************************************************* - ** The prefix of table and record links. + ** The prefix of table and record links: /app, so a link is /app/ + ** (the frontend serves every table, in any app, at that one flat route). *******************************************************************************/ public static String tablePathPrefix() { - if(!ObjectStorageRuntime.isInstalled()) - { - return ("/objectStorageAdmin"); - } - ObjectStorageQBitConfig config = ObjectStorageRuntime.getConfig(); - return (config.getParentAppName() == null ? "/" + config.getAppName() : "/" + config.getParentAppName() + "/" + config.getAppName()); + return (ObjectStorageRuntime.isInstalled() ? ObjectStorageRuntime.getConfig().getTablePathPrefix() : "/app"); } diff --git a/src/test/java/com/kingsrook/qbits/objectstorage/index/MinioIndexTest.java b/src/test/java/com/kingsrook/qbits/objectstorage/index/MinioIndexTest.java index 809b23a..5303503 100644 --- a/src/test/java/com/kingsrook/qbits/objectstorage/index/MinioIndexTest.java +++ b/src/test/java/com/kingsrook/qbits/objectstorage/index/MinioIndexTest.java @@ -574,4 +574,40 @@ class MinioIndexTest .contains("Usage trend").contains("collecting since") .contains("Connections").contains("Never tested"); } + + /******************************************************************************* + ** Every link the home emits is a /app/
[...] route that the Next frontend + ** resolves, never an //
path, with data and without. + *******************************************************************************/ + @Test + void everyHomeLinkUsesTheAppRoute() throws Exception + { + String prefix = com.kingsrook.qbits.objectstorage.app.StorageWidgets.tablePathPrefix(); + assertThat(prefix).isEqualTo("/app"); + java.util.List hrefs = new java.util.ArrayList<>(hrefs(com.kingsrook.qbits.objectstorage.app.StorageHome.render(prefix, Instant.now()))); + + rawPut(target.indexBucket, "media/1/a.jpg", 2048); + rawPut(target.indexBucket, "media/9/orphan.jpg", 1024); + ObjectIndex.scan("indexed"); + ObjectIndex.headBacklog("indexed", 10); + ReferenceScan.run("indexed"); + ObjectStorage.bucket("indexed").put(PutRequest.of("media/1/c.jpg", new byte[3])); + hrefs.addAll(hrefs(com.kingsrook.qbits.objectstorage.app.StorageHome.render(prefix, Instant.now()))); + + assertThat(hrefs).isNotEmpty().allSatisfy(href -> assertThat(href).startsWith("/app/")); + assertThat(hrefs).noneMatch(href -> href.contains("objectStorageAdmin")); + } + + + + private static java.util.List hrefs(String html) + { + java.util.List hrefs = new java.util.ArrayList<>(); + java.util.regex.Matcher m = java.util.regex.Pattern.compile("href=\"([^\"]*)\"").matcher(html); + while(m.find()) + { + hrefs.add(m.group(1).replace("&", "&")); + } + return (hrefs); + } } diff --git a/src/test/java/com/kingsrook/qbits/objectstorage/internal/TablesAndConfigTest.java b/src/test/java/com/kingsrook/qbits/objectstorage/internal/TablesAndConfigTest.java index 67ced67..c0aa044 100644 --- a/src/test/java/com/kingsrook/qbits/objectstorage/internal/TablesAndConfigTest.java +++ b/src/test/java/com/kingsrook/qbits/objectstorage/internal/TablesAndConfigTest.java @@ -205,7 +205,7 @@ class TablesAndConfigTest new com.kingsrook.qbits.objectstorage.ObjectStorageQBitProducer().withQBitConfig(config).produce(instance); assertThat(instance.getApp("objectStorageAdmin").getParentAppName()).isEqualTo("admin"); assertThat(instance.getApp("admin").getChildren()).extracting(c -> c.getName()).contains("objectStorageAdmin"); - assertThat(com.kingsrook.qbits.objectstorage.app.StorageWidgets.tablePathPrefix()).isEqualTo("/admin/objectStorageAdmin"); + assertThat(com.kingsrook.qbits.objectstorage.app.StorageWidgets.tablePathPrefix()).isEqualTo("/app"); } -- 2.54.0