Implemented ImageController.putImage and related.
This commit is contained in:
parent
6f7016f870
commit
9976b7337f
@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
@ -21,7 +22,9 @@ import org.testcontainers.utility.DockerImageName;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@Testcontainers
|
||||
@ -73,7 +76,6 @@ public class ImageControllerTests {
|
||||
owner,
|
||||
USER_FILENAME,
|
||||
hal9000,
|
||||
"image/svg+xml",
|
||||
27881L
|
||||
);
|
||||
}
|
||||
@ -133,4 +135,39 @@ public class ImageControllerTests {
|
||||
this.doGetImageTestWithViewer(accessToken);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void putImage() throws Exception {
|
||||
final User owner = this.createTestUser("imageOwner");
|
||||
final String accessToken = this.getAccessToken(owner.getUsername());
|
||||
try (final InputStream hal9000 = getHal9000()) {
|
||||
final MockMultipartFile mockMultipartFile = new MockMultipartFile(
|
||||
"image", "HAL9000.svg", "image/svg+xml", hal9000
|
||||
);
|
||||
this.mockMvc.perform(
|
||||
multipart("/images")
|
||||
.file(mockMultipartFile)
|
||||
.param("filename", "HAL9000.svg")
|
||||
.param("alt", "HAL 9000")
|
||||
.param("caption", "HAL 9000, from 2001: A Space Odyssey")
|
||||
.param("isPublic", "true")
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.with(req -> {
|
||||
req.setMethod("PUT");
|
||||
return req;
|
||||
})
|
||||
)
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.created").exists())
|
||||
.andExpect(jsonPath("$.modified").value(nullValue()))
|
||||
.andExpect(jsonPath("$.filename").value(USER_FILENAME))
|
||||
.andExpect(jsonPath("$.mimeType").value("image/svg+xml"))
|
||||
.andExpect(jsonPath("$.alt").value("HAL 9000"))
|
||||
.andExpect(jsonPath("$.caption").value("HAL 9000, from 2001: A Space Odyssey"))
|
||||
.andExpect(jsonPath("$.isPublic").value(true))
|
||||
.andExpect(jsonPath("$.owner.username").value("imageOwner"))
|
||||
.andExpect(jsonPath("$.owner.id").value(owner.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -68,7 +68,6 @@ public class S3ImageServiceTests {
|
||||
owner,
|
||||
USER_FILENAME,
|
||||
hal9000,
|
||||
"image/svg+xml",
|
||||
27881L
|
||||
);
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ public class DevConfiguration {
|
||||
testUser,
|
||||
"HAL9000.svg",
|
||||
inputStream,
|
||||
"image/svg+xml",
|
||||
27881L
|
||||
);
|
||||
this.imageService.setPublic(image, testUser, true);
|
||||
|
@ -1,15 +1,16 @@
|
||||
package app.mealsmadeeasy.api.image;
|
||||
|
||||
import app.mealsmadeeasy.api.image.view.ImageView;
|
||||
import app.mealsmadeeasy.api.user.User;
|
||||
import app.mealsmadeeasy.api.user.UserService;
|
||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -18,6 +19,23 @@ import java.io.InputStream;
|
||||
@RequestMapping("/images")
|
||||
public class ImageController {
|
||||
|
||||
private static ImageView getView(Image image, User owner) {
|
||||
final ImageView imageView = new ImageView();
|
||||
imageView.setCreated(image.getCreated());
|
||||
imageView.setFilename(image.getUserFilename());
|
||||
imageView.setMimeType(image.getMimeType());
|
||||
imageView.setAlt(image.getAlt());
|
||||
imageView.setCaption(image.getCaption());
|
||||
imageView.setIsPublic(image.isPublic());
|
||||
|
||||
final UserInfoView userInfoView = new UserInfoView();
|
||||
userInfoView.setId(owner.getId());
|
||||
userInfoView.setUsername(owner.getUsername());
|
||||
imageView.setOwner(userInfoView);
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
private final ImageService imageService;
|
||||
private final UserService userService;
|
||||
|
||||
@ -40,4 +58,36 @@ public class ImageController {
|
||||
.body(new InputStreamResource(imageInputStream));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public ResponseEntity<ImageView> putImage(
|
||||
@RequestParam MultipartFile image,
|
||||
@RequestParam String filename,
|
||||
@RequestParam(required = false) String alt,
|
||||
@RequestParam(required = false) String caption,
|
||||
@RequestParam(required = false) Boolean isPublic,
|
||||
@AuthenticationPrincipal User principal
|
||||
) throws IOException, ImageException {
|
||||
if (principal == null) {
|
||||
throw new AccessDeniedException("Must be logged in.");
|
||||
}
|
||||
|
||||
Image saved = this.imageService.create(
|
||||
principal,
|
||||
filename,
|
||||
image.getInputStream(),
|
||||
image.getSize()
|
||||
);
|
||||
if (alt != null) {
|
||||
saved = this.imageService.setAlt(saved, principal, alt);
|
||||
}
|
||||
if (caption != null) {
|
||||
saved = this.imageService.setCaption(saved, principal, caption);
|
||||
}
|
||||
if (isPublic != null) {
|
||||
saved = this.imageService.setPublic(saved, principal, isPublic);
|
||||
}
|
||||
|
||||
return ResponseEntity.status(201).body(getView(saved, principal));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import java.util.List;
|
||||
|
||||
public interface ImageService {
|
||||
|
||||
Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize)
|
||||
Image create(User owner, String userFilename, InputStream inputStream, long objectSize)
|
||||
throws IOException, ImageException;
|
||||
|
||||
Image getByOwnerAndFilename(User owner, String filename, User viewer) throws ImageException;
|
||||
|
@ -13,10 +13,14 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class S3ImageService implements ImageService {
|
||||
|
||||
private static final Pattern extensionPattern = Pattern.compile(".+\\.(.+)$");
|
||||
|
||||
private final S3Manager s3Manager;
|
||||
private final S3ImageRepository imageRepository;
|
||||
private final String imageBucketName;
|
||||
@ -31,8 +35,25 @@ public class S3ImageService implements ImageService {
|
||||
this.imageBucketName = imageBucketName;
|
||||
}
|
||||
|
||||
private String getMimeType(String userFilename) {
|
||||
final Matcher m = extensionPattern.matcher(userFilename);
|
||||
if (m.matches()) {
|
||||
final String extension = m.group(1);
|
||||
return switch (extension) {
|
||||
case "jpg", "jpeg" -> "image/jpeg";
|
||||
case "png" -> "image/png";
|
||||
case "svg" -> "image/svg+xml";
|
||||
default -> throw new IllegalArgumentException("Cannot determine mime type for extension: " + extension);
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot determine mime type for filename: " + userFilename);
|
||||
}
|
||||
}
|
||||
|
||||
private String getExtension(String mimeType) throws ImageException {
|
||||
return switch (mimeType) {
|
||||
case "image/jpeg" -> "jpg";
|
||||
case "image/png" -> "png";
|
||||
case "image/svg+xml" -> "svg";
|
||||
default -> throw new ImageException(
|
||||
ImageException.Type.UNKNOWN_MIME_TYPE,
|
||||
@ -42,8 +63,9 @@ public class S3ImageService implements ImageService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image create(User owner, String userFilename, InputStream inputStream, String mimeType, long objectSize)
|
||||
public Image create(User owner, String userFilename, InputStream inputStream, long objectSize)
|
||||
throws IOException, ImageException {
|
||||
final String mimeType = this.getMimeType(userFilename);
|
||||
final String uuid = UUID.randomUUID().toString();
|
||||
final String extension = this.getExtension(mimeType);
|
||||
final String filename = uuid + "." + extension;
|
||||
|
@ -0,0 +1,83 @@
|
||||
package app.mealsmadeeasy.api.image.view;
|
||||
|
||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class ImageView {
|
||||
|
||||
private LocalDateTime created;
|
||||
private @Nullable LocalDateTime modified;
|
||||
private String filename;
|
||||
private String mimeType;
|
||||
private @Nullable String alt;
|
||||
private @Nullable String caption;
|
||||
private UserInfoView owner;
|
||||
private boolean isPublic;
|
||||
|
||||
public LocalDateTime getCreated() {
|
||||
return this.created;
|
||||
}
|
||||
|
||||
public void setCreated(LocalDateTime created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public @Nullable LocalDateTime getModified() {
|
||||
return this.modified;
|
||||
}
|
||||
|
||||
public void setModified(@Nullable LocalDateTime modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return this.mimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
public String getAlt() {
|
||||
return this.alt;
|
||||
}
|
||||
|
||||
public void setAlt(String alt) {
|
||||
this.alt = alt;
|
||||
}
|
||||
|
||||
public String getCaption() {
|
||||
return this.caption;
|
||||
}
|
||||
|
||||
public void setCaption(String caption) {
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
public UserInfoView getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public void setOwner(UserInfoView owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public boolean getIsPublic() {
|
||||
return this.isPublic;
|
||||
}
|
||||
|
||||
public void setIsPublic(boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.mealsmadeeasy.api.user.view;
|
||||
|
||||
public class UserInfoView {
|
||||
|
||||
private long id;
|
||||
private String username;
|
||||
|
||||
public long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user