Render images in file browser blob view
Add /repo/{name}/raw/{hash}/** endpoint serving binary file content
with correct Content-Type. Blob view detects image extensions (png,
jpg, gif, bmp, webp, svg, ico) and renders an <img> tag instead of
a <pre> text block.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,8 @@ package be.seeseepuff.webgit.controller;
|
||||
import be.seeseepuff.webgit.service.GitService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -12,11 +14,25 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class RepoController
|
||||
{
|
||||
private static final Set<String> IMAGE_EXTENSIONS = Set.of("png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico");
|
||||
private static final Map<String, String> IMAGE_MIME_TYPES = Map.of(
|
||||
"png", "image/png",
|
||||
"jpg", "image/jpeg",
|
||||
"jpeg", "image/jpeg",
|
||||
"gif", "image/gif",
|
||||
"bmp", "image/bmp",
|
||||
"webp", "image/webp",
|
||||
"svg", "image/svg+xml",
|
||||
"ico", "image/x-icon"
|
||||
);
|
||||
|
||||
private final GitService gitService;
|
||||
|
||||
@GetMapping("/repo/{name}")
|
||||
@@ -109,13 +125,32 @@ public class RepoController
|
||||
String fullPath = request.getRequestURI();
|
||||
String prefix = "/repo/" + name + "/blob/" + hash + "/";
|
||||
String filePath = fullPath.substring(prefix.length());
|
||||
String ext = filePath.contains(".") ? filePath.substring(filePath.lastIndexOf('.') + 1).toLowerCase() : "";
|
||||
boolean isImage = IMAGE_EXTENSIONS.contains(ext);
|
||||
model.addAttribute("name", name);
|
||||
model.addAttribute("hash", hash);
|
||||
model.addAttribute("filePath", filePath);
|
||||
model.addAttribute("content", gitService.getFileContentAtCommit(name, hash, filePath));
|
||||
model.addAttribute("isImage", isImage);
|
||||
if (!isImage)
|
||||
model.addAttribute("content", gitService.getFileContentAtCommit(name, hash, filePath));
|
||||
return "blob";
|
||||
}
|
||||
|
||||
@GetMapping("/repo/{name}/raw/{hash}/**")
|
||||
public ResponseEntity<byte[]> rawFile(@PathVariable String name, @PathVariable String hash,
|
||||
jakarta.servlet.http.HttpServletRequest request) throws IOException
|
||||
{
|
||||
String fullPath = request.getRequestURI();
|
||||
String prefix = "/repo/" + name + "/raw/" + hash + "/";
|
||||
String filePath = fullPath.substring(prefix.length());
|
||||
byte[] bytes = gitService.getRawFileAtCommit(name, hash, filePath);
|
||||
if (bytes == null)
|
||||
return ResponseEntity.notFound().build();
|
||||
String ext = filePath.contains(".") ? filePath.substring(filePath.lastIndexOf('.') + 1).toLowerCase() : "";
|
||||
String mimeType = IMAGE_MIME_TYPES.getOrDefault(ext, MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
return ResponseEntity.ok().contentType(MediaType.parseMediaType(mimeType)).body(bytes);
|
||||
}
|
||||
|
||||
@PostMapping("/repo/{name}/checkout-commit")
|
||||
public String checkoutCommit(@PathVariable String name, @RequestParam String hash) throws IOException, GitAPIException
|
||||
{
|
||||
|
||||
@@ -490,6 +490,14 @@ public class GitService
|
||||
}
|
||||
|
||||
public String getFileContentAtCommit(String name, String commitHash, String filePath) throws IOException
|
||||
{
|
||||
byte[] bytes = getRawFileAtCommit(name, commitHash, filePath);
|
||||
if (bytes == null)
|
||||
return null;
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public byte[] getRawFileAtCommit(String name, String commitHash, String filePath) throws IOException
|
||||
{
|
||||
try (Git git = openRepository(name))
|
||||
{
|
||||
@@ -505,7 +513,7 @@ public class GitService
|
||||
if (tw == null)
|
||||
return null;
|
||||
ObjectLoader loader = repo.open(tw.getObjectId(0));
|
||||
return new String(loader.getBytes(), StandardCharsets.UTF_8);
|
||||
return loader.getBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
</table>
|
||||
|
||||
<h3>Content</h3>
|
||||
<pre th:text="${content}">File content here</pre>
|
||||
<div th:if="${isImage}">
|
||||
<img th:src="@{/repo/{name}/raw/{hash}/{filePath}(name=${name}, hash=${hash}, filePath=${filePath})}" alt="" border="0">
|
||||
</div>
|
||||
<pre th:unless="${isImage}" th:text="${content}">File content here</pre>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,8 +9,10 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
@@ -279,6 +281,37 @@ class RepoControllerTest
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("# Hello")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blobShowsImageTag() throws Exception
|
||||
{
|
||||
mockMvc.perform(get("/repo/myrepo/blob/abc1234/photo.png"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("blob"))
|
||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("/repo/myrepo/raw/abc1234/photo.png")));
|
||||
verify(gitService, never()).getFileContentAtCommit(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rawFileServesBytes() throws Exception
|
||||
{
|
||||
byte[] imageBytes = new byte[]{(byte)0x89, 0x50, 0x4E, 0x47};
|
||||
when(gitService.getRawFileAtCommit("myrepo", "abc1234", "photo.png")).thenReturn(imageBytes);
|
||||
|
||||
mockMvc.perform(get("/repo/myrepo/raw/abc1234/photo.png"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentType("image/png"))
|
||||
.andExpect(content().bytes(imageBytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rawFileReturns404WhenNotFound() throws Exception
|
||||
{
|
||||
when(gitService.getRawFileAtCommit("myrepo", "abc1234", "missing.png")).thenReturn(null);
|
||||
|
||||
mockMvc.perform(get("/repo/myrepo/raw/abc1234/missing.png"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkoutCommitRedirectsToCommits() throws Exception
|
||||
{
|
||||
|
||||
@@ -400,6 +400,27 @@ class GitServiceTest
|
||||
assertEquals("# Test", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRawFileAtCommitReturnsBytes() throws GitAPIException, IOException
|
||||
{
|
||||
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
|
||||
|
||||
var commits = gitService.listCommits("myrepo");
|
||||
byte[] bytes = gitService.getRawFileAtCommit("myrepo", commits.getFirst().hash(), "README.md");
|
||||
assertNotNull(bytes);
|
||||
assertEquals("# Test", new String(bytes, java.nio.charset.StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRawFileAtCommitReturnsNullForMissingFile() throws GitAPIException, IOException
|
||||
{
|
||||
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
|
||||
|
||||
var commits = gitService.listCommits("myrepo");
|
||||
byte[] bytes = gitService.getRawFileAtCommit("myrepo", commits.getFirst().hash(), "nonexistent.png");
|
||||
assertNull(bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFileContentAtCommitReturnsNullForMissingFile() throws GitAPIException, IOException
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user