1+ package com .Rootin .domain .s3 .controller ;
2+
3+ import com .Rootin .domain .garden .service .PotService ;
4+ import com .Rootin .global .common .ApiResponse ;
5+ import com .Rootin .global .exception .CustomException ;
6+ import com .Rootin .global .jwt .JwtUserDetails ;
7+ import com .Rootin .domain .s3 .dto .PresignedUrlRequest ;
8+ import com .Rootin .domain .s3 .dto .PresignedUrlResponse ;
9+ import com .Rootin .global .s3 .S3Service ;
10+ import jakarta .validation .Valid ;
11+ import lombok .RequiredArgsConstructor ;
12+ import org .springframework .http .ResponseEntity ;
13+ import org .springframework .security .core .annotation .AuthenticationPrincipal ;
14+ import org .springframework .web .bind .annotation .PostMapping ;
15+ import org .springframework .web .bind .annotation .RequestBody ;
16+ import org .springframework .web .bind .annotation .RestController ;
17+
18+ import java .util .UUID ;
19+
20+ @ RestController
21+ @ RequiredArgsConstructor
22+ public class PresignedUrlController {
23+
24+ private final S3Service s3Service ;
25+ private final PotService potService ;
26+
27+ @ PostMapping ("/tils/image/presigned-url" )
28+ public ResponseEntity <ApiResponse <PresignedUrlResponse >> getPresignedUrl (
29+ @ AuthenticationPrincipal JwtUserDetails userDetails ,
30+ @ Valid @ RequestBody PresignedUrlRequest request
31+ ) {
32+ Long userId = userDetails .getUserId ();
33+ Long potId = request .getPotId ();
34+
35+ // DB에서 이 화분이 이 사용자의 것인지 검증하는 절차
36+ potService .validateOwnership (userId , potId );
37+
38+ // TIL 본문의 이미지 저장Key
39+ String ext = extractExtension (request .getContentType ());
40+ String objectKey = String .format ("til-images/%d/%d/%s.%s" ,userId , potId , UUID .randomUUID (), ext );
41+
42+ // S3에 접근하도록 임시 URL 제공 및 이미지 URL
43+ String presignedUrl = s3Service .generatePresignedPutUrl (objectKey , request .getContentType ());
44+ String imageUrl = s3Service .getFileUrl (objectKey );
45+
46+ return ResponseEntity .ok (ApiResponse .success (
47+ new PresignedUrlResponse (imageUrl , presignedUrl )
48+ ));
49+ }
50+
51+ private String extractExtension (String contentType ) {
52+ if (contentType == null ) {
53+ throw CustomException .badRequest ("contentType이 없습니다." );
54+ }
55+
56+ return switch (contentType ) {
57+ case "image/png" -> "png" ;
58+ case "image/jpeg" -> "jpg" ;
59+ case "image/webp" -> "webp" ;
60+ default -> throw CustomException .badRequest ("지원하지 않는 이미지 형식입니다: " + contentType );
61+ };
62+ }
63+ }
0 commit comments