diff --git a/docs/superpowers/plans/2026-08-01-absolute-upload-url.md b/docs/superpowers/plans/2026-08-01-absolute-upload-url.md new file mode 100644 index 0000000..ef3454a --- /dev/null +++ b/docs/superpowers/plans/2026-08-01-absolute-upload-url.md @@ -0,0 +1,77 @@ +# Absolute Upload URL Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Return an environment-configured absolute file URL from the common upload endpoint so deployed case-material links do not depend on frontend proxy routing. + +**Architecture:** `CommonUploadController` already receives the relative path from `IFileUtil`. It will combine that path with the existing `file.url` configuration after removing a trailing slash from the configured public base. This preserves local storage and all existing upload mechanics. + +**Tech Stack:** Spring Boot 2.7, JUnit 5, Spring Test mock multipart files. + +## Global Constraints + +- Use the existing `file.url` property as the public file base; do not hard-code a server host. +- Preserve the empty-file response contract and existing stored relative URLs. +- Build only the backend behavior needed for upload responses. + +--- + +### Task 1: Return the configured public file URL + +**Files:** +- Create: `src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java` +- Modify: `src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java` + +**Interfaces:** +- Consumes: `IFileUtil.upload(MultipartFile)` returning a relative path such as `/2026/8/1/example.docx`. +- Produces: upload result map with `url` and `fileName` equal to `/2026/8/1/example.docx`. + +- [ ] **Step 1: Write the failing test** + +```java +@Test +void uploadReturnsAbsoluteConfiguredFileUrl() { + IFileUtil fileUtil = mock(IFileUtil.class); + when(fileUtil.upload(any(MultipartFile.class))).thenReturn("/2026/8/1/example.docx"); + CommonUploadController controller = new CommonUploadController(); + ReflectionTestUtils.setField(controller, "fileUtil", fileUtil); + ReflectionTestUtils.setField(controller, "fileUrl", "https://files.example.com/file/"); + + Map result = controller.upload(new MockMultipartFile("file", "case.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "case".getBytes())); + + assertEquals("https://files.example.com/file/2026/8/1/example.docx", result.get("url")); + assertEquals(result.get("url"), result.get("fileName")); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `mvn -Dtest=CommonUploadControllerTest test` + +Expected: FAIL because the controller currently returns `/file/2026/8/1/example.docx`. + +- [ ] **Step 3: Write minimal implementation** + +```java +@Value("${file.url}") +private String fileUrl; + +private String publicFileUrl(String relativePath) { + return StringUtils.removeEnd(StringUtils.trimToEmpty(fileUrl), "/") + relativePath; +} +``` + +Use `publicFileUrl(filePath)` for both `url` and `fileName` in the successful upload response. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `mvn -Dtest=CommonUploadControllerTest test` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java +git commit -m "fix: return absolute upload file urls" +``` diff --git a/docs/superpowers/specs/2026-08-01-absolute-upload-url-design.md b/docs/superpowers/specs/2026-08-01-absolute-upload-url-design.md new file mode 100644 index 0000000..320e332 --- /dev/null +++ b/docs/superpowers/specs/2026-08-01-absolute-upload-url-design.md @@ -0,0 +1,15 @@ +# Absolute Upload URL Design + +## Goal + +Ensure uploaded case materials can be opened from a deployed frontend even when the frontend and backend do not share the same origin. + +## Decision + +`CommonUploadController` will build its `url` and `fileName` response fields from the existing `file.url` setting plus the uploaded file's relative path. The setting already represents the public `/file` endpoint and is environment-specific. + +The controller will normalize a trailing slash so both `https://host/file` and `https://host/file/` produce one valid absolute URL. Existing stored relative paths remain readable because the change only affects newly uploaded files. + +## Error Handling and Verification + +The current empty-file response remains unchanged. A focused controller test will verify the success response includes the original filename and a normalized absolute public URL. The backend test suite will be run after the change.