meals-made-easy-api/src/main/java/app/mealsmadeeasy/api/image/ImageController.java
2026-01-24 14:39:07 -06:00

138 lines
5.8 KiB
Java

package app.mealsmadeeasy.api.image;
import app.mealsmadeeasy.api.image.body.ImageUpdateBody;
import app.mealsmadeeasy.api.image.converter.ImageToViewConverter;
import app.mealsmadeeasy.api.image.spec.ImageCreateSpec;
import app.mealsmadeeasy.api.image.spec.ImageUpdateSpec;
import app.mealsmadeeasy.api.image.view.ImageView;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserService;
import app.mealsmadeeasy.api.util.AccessDeniedView;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/images")
@RequiredArgsConstructor
public class ImageController {
private final ImageService imageService;
private final UserService userService;
private final ImageToViewConverter imageToViewConverter;
private ImageUpdateSpec getImageUpdateSpec(ImageUpdateBody body) {
final var builder = ImageUpdateSpec.builder()
.alt(body.getAlt())
.caption(body.getCaption())
.isPublic(body.getIsPublic())
.clearAllViewers(body.getClearAllViewers());
if (body.getViewersToAdd() != null) {
builder.viewersToAdd(body.getViewersToAdd().stream()
.map(this.userService::getUser)
.collect(Collectors.toSet())
);
}
if (body.getViewersToRemove() != null) {
builder.viewersToRemove(body.getViewersToRemove().stream()
.map(this.userService::getUser)
.collect(Collectors.toSet())
);
}
return builder.build();
}
@ExceptionHandler
public ResponseEntity<AccessDeniedView> onAccessDenied(AccessDeniedException e) {
if (e instanceof AuthorizationDeniedException) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.body(new AccessDeniedView(HttpStatus.FORBIDDEN.value(), e.getMessage()));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.contentType(MediaType.APPLICATION_JSON)
.body(new AccessDeniedView(HttpStatus.UNAUTHORIZED.value(), e.getMessage()));
}
}
@GetMapping("/{username}/{filename}")
public ResponseEntity<InputStreamResource> getImage(
@AuthenticationPrincipal User principal,
@PathVariable String username,
@PathVariable String filename
) throws IOException {
final User owner = this.userService.getUser(username);
final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
final InputStream imageInputStream = this.imageService.getImageContent(image, principal);
return ResponseEntity.status(200)
.contentType(MediaType.parseMediaType(image.getMimeType()))
.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,
@RequestParam(required = false) Set<String> viewers,
@AuthenticationPrincipal User principal
) throws IOException, ImageException {
final var specBuilder = ImageCreateSpec.builder()
.alt(alt)
.caption(caption)
.isPublic(isPublic);
if (viewers != null) {
specBuilder.viewersToAdd(viewers.stream().map(this.userService::getUser).collect(Collectors.toSet()));
}
final Image saved = this.imageService.create(
principal,
filename,
image.getInputStream(),
image.getSize(),
specBuilder.build()
);
return ResponseEntity.status(201).body(this.imageToViewConverter.convert(saved, principal, true));
}
@PostMapping("/{username}/{filename}")
public ResponseEntity<ImageView> updateInfo(
@AuthenticationPrincipal User principal,
@PathVariable String username,
@PathVariable String filename,
@RequestBody ImageUpdateBody body
) {
final User owner = this.userService.getUser(username);
final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
final Image updated = this.imageService.update(image, principal, this.getImageUpdateSpec(body));
return ResponseEntity.ok(this.imageToViewConverter.convert(updated, principal, true));
}
@DeleteMapping("/{username}/{filename}")
public ResponseEntity<Object> deleteImage(
@AuthenticationPrincipal User principal,
@PathVariable String username,
@PathVariable String filename
) throws IOException {
final User owner = this.userService.getUser(username);
final Image image = this.imageService.getByOwnerAndFilename(owner, filename, principal);
this.imageService.deleteImage(image, principal);
return ResponseEntity.noContent().build();
}
}