Skip to content

CELDEV-1304 - add PageAttachmentController as backend for PageAttachments app script#291

Open
fpichler wants to merge 2 commits into
devfrom
CELDEV-1304-page-attachments
Open

CELDEV-1304 - add PageAttachmentController as backend for PageAttachments app script#291
fpichler wants to merge 2 commits into
devfrom
CELDEV-1304-page-attachments

Conversation

@fpichler

@fpichler fpichler commented May 23, 2026

Copy link
Copy Markdown
Member

Summary

Adds the backend controller for the new Page Attachments app.

This introduces /attachments/{spaceName}/{docName} endpoints for listing, searching, uploading, and deleting attachments on a specific page, with authentication, document reference resolution, path normalization, and per-action rights checks.

Details

  • Adds PageAttachmentsController for the Page Attachments VueFinder backend.
  • Supports listing and searching page attachments with download and preview URLs.
  • Supports multipart uploads with filename cleanup.
  • Supports attachment deletion via request DTOs.
  • Adds top-level record DTOs for AttachmentRequest, DeleteRequest, and DeleteItem.
  • Adds controller tests covering filename normalization, list access, denied list access, search, and delete.

@fpichler fpichler requested a review from msladek May 23, 2026 01:18
@fpichler fpichler changed the title add PageAttachmentController as backend for PageAttachments app script CELDEV-1304 - add PageAttachmentController as backend for PageAttachments app script May 26, 2026
Base automatically changed from CELDEV-1304 to dev June 7, 2026 08:11
@fpichler fpichler force-pushed the CELDEV-1304-page-attachments branch from ac67a3d to 4da789d Compare June 7, 2026 22:37
Comment on lines +278 to +279
org.springframework.web.bind.MissingServletRequestParameterException.class,
org.springframework.web.multipart.support.MissingServletRequestPartException.class

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please import classes (already commented this issue in recent PR's)

Comment on lines +293 to +295
if (request instanceof org.springframework.web.multipart.MultipartHttpServletRequest) {
org.springframework.web.multipart.MultipartHttpServletRequest multipartRequest =
(org.springframework.web.multipart.MultipartHttpServletRequest) request;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (request instanceof org.springframework.web.multipart.MultipartHttpServletRequest) {
org.springframework.web.multipart.MultipartHttpServletRequest multipartRequest =
(org.springframework.web.multipart.MultipartHttpServletRequest) request;
if (request instanceof MultipartHttpServletRequest multipartRequest) {

use modern language features, possible since java 16 and we moved to 21

import com.xpn.xwiki.doc.XWikiDocument;

@RestController
@RestControllerAdvice

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@RestControllerAdvice

this annotation looks wrong to me. it makes the @ExceptionHandler methods global REST advice, not local to the controller. same paplies to MediaLibController, i think the @RestControllerAdvice should also be removed there.

if these handlers are only for this endpoint @RestController is enough, otherwise the shared error handling should be moved to a dedicated class.

Comment on lines +209 to +266
private String normalizeDirPath(String spaceName, String docName, String path) {
String expectedPrefix = STORAGE + "://" + spaceName + "/" + docName;
String p = StringUtils.hasText(path) ? path.trim() : expectedPrefix;
if (!p.startsWith(STORAGE + "://")) {
p = expectedPrefix + "/" + p;
}
if (p.endsWith("/") && !p.equals(expectedPrefix)) {
p = p.substring(0, p.length() - 1);
}
return p;
}

String normalizeFileName(String path) {
String p = StringUtils.hasText(path) ? path.trim() : "";
int lastSlash = p.lastIndexOf('/');
if (lastSlash >= 0) {
return p.substring(lastSlash + 1);
}
return p;
}

private FileItem toFileItem(String dirPath, XWikiAttachment att) {
String name = att.getFilename();
AttachmentReference attachmentRef = RefBuilder.from(att.getDoc().getDocumentReference())
.att(name).build(AttachmentReference.class);
String query = "celwidth=" + MAX_WIDTH + "&celheight=" + MAX_HEIGHT;
return new FileItem(
dirPath,
name,
extensionOf(name),
dirPath.endsWith("/") ? (dirPath + name) : (dirPath + "/" + name),
urlService.getURL(attachmentRef, "download"),
urlService.getURL(attachmentRef, "download", query),
STORAGE,
"file",
(long) att.getFilesize(),
toUnixSeconds(att.getDate()),
guessMimeType(name),
"public");
}

private String guessMimeType(String filename) {
String mime = URLConnection.guessContentTypeFromName(filename);
return (mime != null) ? mime : "application/octet-stream";
}

private String extensionOf(String name) {
int i = name.lastIndexOf('.');
return ((i > 0) && (i < (name.length() - 1))) ? name.substring(i + 1).toLowerCase(Locale.ROOT)
: "";
}

private long toUnixSeconds(Date date) {
if (date == null) {
return Instant.now().getEpochSecond();
}
return date.toInstant().getEpochSecond();
}

@msladek msladek Jun 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY, this duplicates quite a bit of MediaLibController behavior. can you extract the shared parts?

java.util.Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
LOGGER.warn("Header {}: {}", headerName, request.getHeader(headerName));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logging all request headers is risky because it can expose credentials or session data. this looks like temporary debugging code, can we remove it or at least avoid logging raw headers?

@msladek msladek assigned fpichler and unassigned msladek Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants