UserAvatarController.java

package api.controllers.users;

import java.io.IOException;
import java.io.InputStream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import api.dtos.ErrorDto;
import api.entities.S3Object;
import api.entities.User;
import api.exceptions.EntityNotFoundException;
import api.services.S3ObjectService;
import api.services.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;


/**
 * {@link UserAvatarController}.
 */
@RestController
@RequestMapping("/users")
@Tag(name = "User Avatars", description = "The avatars displayed for a user.")
public class UserAvatarController {
    @Autowired
    private S3ObjectService s3ObjectService;
    @Autowired
    private UserService userService;

    /**
     * Get my avatar.
     *
     * @param user User
     * @return {@link ResponseEntity} {@link Resource}
     * @throws IOException Failed to read file
     */
    // region
    @Operation(
        summary = "Get My Avatar",
        description = "Gets the current user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = {
                @Content(mediaType = "image/png"),
                @Content(mediaType = "image/jpeg"),
                @Content(mediaType = "image/gif"),
                @Content(mediaType = "image/svg+xml")
            }
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @GetMapping("/me/avatar")
    public ResponseEntity<Resource> getMyAvatar(@AuthenticationPrincipal User user) throws IOException {
        S3Object s3Object = user.getAvatar();
        if (s3Object == null) {
            throw EntityNotFoundException.fromUserAvatar(user.getUsername());
        }

        InputStream in = s3ObjectService.download(s3Object);

        byte[] bytes = in.readAllBytes();
        ByteArrayResource resource = new ByteArrayResource(bytes);

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(s3Object.getMimeType().getName()))
            .contentLength(bytes.length)
            .body(resource);
    }

    /**
     * Update my avatar.
     *
     * @param user User
     * @param file Avatar
     * @throws IOException Failed to read file
     */
    // region
    @Operation(
        summary = "Update My Avatar",
        description = "Updates the current user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = Void.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "400",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @PutMapping("/me/avatar")
    public void updateMyAvatar(
        @AuthenticationPrincipal User user,
        @RequestParam("file") MultipartFile file
    ) throws IOException {
        userService.updateAvatar(user, file);
    }

    /**
     * Delete my avatar.
     *
     * @param user User
     */
    // region
    @Operation(
        summary = "Delete My Avatar",
        description = "Deletes the current user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = Void.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @DeleteMapping("/me/avatar")
    public void deleteMyAvatar(@AuthenticationPrincipal User user) {
        userService.deleteAvatar(user);
    }


    /**
     * Get user's avatar.
     *
     * @param userId User id
     * @return User's avatar
     * @throws IOException Failed to read file
     */
    // region
    @Operation(
        summary = "Get User's Avatar",
        description = "Gets specified user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = {
                @Content(mediaType = "image/png"),
                @Content(mediaType = "image/jpeg"),
                @Content(mediaType = "image/gif"),
                @Content(mediaType = "image/svg+xml")
            }
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @GetMapping("/{userId}/avatar")
    @PreAuthorize("hasAuthority(@DbSetup.USER_READ)")
    public ResponseEntity<Resource> getUserAvatar(@PathVariable int userId) throws IOException {
        User user = userService.get(userId);
        S3Object s3Object = user.getAvatar();
        if (s3Object == null) {
            throw EntityNotFoundException.fromUserAvatar(user.getUsername());
        }

        InputStream in = s3ObjectService.download(s3Object);

        byte[] bytes = in.readAllBytes();
        ByteArrayResource resource = new ByteArrayResource(bytes);

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(s3Object.getMimeType().getName()))
            .contentLength(bytes.length)
            .body(resource);
    }

    /**
     * Update user's avatar.
     *
     * @param userId User id
     * @param file User's avatar
     * @throws IOException Failed to read file
     */
    // region
    @Operation(
        summary = "Update User's Avatar",
        description = "Updates specified user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = @Content(
                schema = @Schema(implementation = Void.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "400",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @PutMapping("/{userId}/avatar")
    @PreAuthorize("hasAuthority(@DbSetup.USER_WRITE)")
    public void updateUserAvatar(
        @PathVariable int userId,
        @RequestParam("file") MultipartFile file
    ) throws IOException {
        userService.updateAvatar(userId, file);
    }

    /**
     * Delete user's avatar.
     *
     * @param userId User id
     */
    // region
    @Operation(
        summary = "Delete User's Avatar",
        description = "Deletes specified user's avatar."
    )
    @ApiResponses({
        @ApiResponse(
            responseCode = "200",
            content = {
                @Content(mediaType = "image/png"),
                @Content(mediaType = "image/jpeg"),
                @Content(mediaType = "image/gif"),
                @Content(mediaType = "image/svg+xml")
            }
        ),
        @ApiResponse(
            responseCode = "404",
            content = @Content(
                schema = @Schema(implementation = ErrorDto.class),
                mediaType = "application/json"
            )
        ),
    })
    // endregion
    @DeleteMapping("/{userId}/avatar")
    @PreAuthorize("hasAuthority(@DbSetup.USER_WRITE)")
    public void deleteUserAvatar(@PathVariable int userId) {
        userService.deleteAvatar(userId);
    }
}