검증게시판 기능 추가
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
package com.goldenchart.controller;
|
||||
|
||||
import com.goldenchart.dto.VerificationIssueCommentDto;
|
||||
import com.goldenchart.service.VerificationIssueCommentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/verification-issues/{issueId}/comments")
|
||||
@RequiredArgsConstructor
|
||||
public class VerificationIssueCommentController {
|
||||
|
||||
private final VerificationIssueCommentService service;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<VerificationIssueCommentDto>> list(@PathVariable Long issueId) {
|
||||
return ResponseEntity.ok(service.listByIssue(issueId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<VerificationIssueCommentDto> create(
|
||||
@PathVariable Long issueId,
|
||||
@RequestBody VerificationIssueCommentDto dto,
|
||||
@RequestHeader(value = "X-User-Id", required = false) String userIdHeader,
|
||||
@RequestHeader(value = "X-Device-Id", required = false) String deviceId) {
|
||||
try {
|
||||
return ResponseEntity.ok(service.create(issueId, dto, parseUserId(userIdHeader), deviceId));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{commentId}")
|
||||
public ResponseEntity<VerificationIssueCommentDto> update(
|
||||
@PathVariable Long issueId,
|
||||
@PathVariable Long commentId,
|
||||
@RequestBody VerificationIssueCommentDto dto) {
|
||||
return service.update(issueId, commentId, dto)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{commentId}")
|
||||
public ResponseEntity<Void> delete(
|
||||
@PathVariable Long issueId,
|
||||
@PathVariable Long commentId) {
|
||||
if (!service.delete(issueId, commentId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
private Long parseUserId(String header) {
|
||||
if (header == null || header.isBlank()) return null;
|
||||
try { return Long.parseLong(header); } catch (NumberFormatException e) { return null; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.goldenchart.controller;
|
||||
|
||||
import com.goldenchart.dto.VerificationIssueDto;
|
||||
import com.goldenchart.entity.VerificationIssueStage;
|
||||
import com.goldenchart.service.VerificationIssueService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/verification-issues")
|
||||
@RequiredArgsConstructor
|
||||
public class VerificationIssueController {
|
||||
|
||||
private final VerificationIssueService service;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<VerificationIssueDto>> list(
|
||||
@RequestParam(value = "stage", required = false) String stage) {
|
||||
VerificationIssueStage parsed = parseStage(stage);
|
||||
return ResponseEntity.ok(service.list(parsed));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<VerificationIssueDto> get(@PathVariable Long id) {
|
||||
return service.findById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<VerificationIssueDto> save(
|
||||
@RequestBody VerificationIssueDto dto,
|
||||
@RequestHeader(value = "X-User-Id", required = false) String userIdHeader,
|
||||
@RequestHeader(value = "X-Device-Id", required = false) String deviceId) {
|
||||
return ResponseEntity.ok(service.save(dto, parseUserId(userIdHeader), deviceId));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable Long id) {
|
||||
service.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
private Long parseUserId(String header) {
|
||||
if (header == null || header.isBlank()) return null;
|
||||
try { return Long.parseLong(header); } catch (NumberFormatException e) { return null; }
|
||||
}
|
||||
|
||||
private VerificationIssueStage parseStage(String stage) {
|
||||
if (stage == null || stage.isBlank()) return null;
|
||||
try {
|
||||
return VerificationIssueStage.valueOf(stage);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package com.goldenchart.controller;
|
||||
|
||||
import com.goldenchart.dto.VerificationIssueImageDto;
|
||||
import com.goldenchart.service.VerificationIssueImageService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/verification-issues/{issueId}/images")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class VerificationIssueImageController {
|
||||
|
||||
private final VerificationIssueImageService service;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<VerificationIssueImageDto>> list(@PathVariable Long issueId) {
|
||||
return ResponseEntity.ok(service.listByIssue(issueId));
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<?> upload(
|
||||
@PathVariable Long issueId,
|
||||
@RequestParam("files") List<MultipartFile> files) {
|
||||
try {
|
||||
return ResponseEntity.ok(service.upload(issueId, files));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("[VerificationImage] upload rejected issueId={}: {}", issueId, e.getMessage());
|
||||
return ResponseEntity.badRequest().body(Map.of("message", toUserMessage(e.getMessage())));
|
||||
} catch (IOException e) {
|
||||
log.error("[VerificationImage] upload io error issueId={}", issueId, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("message", "이미지 저장에 실패했습니다. 잠시 후 다시 시도하세요."));
|
||||
}
|
||||
}
|
||||
|
||||
private static String toUserMessage(String code) {
|
||||
if (code == null) return "업로드할 수 없습니다.";
|
||||
return switch (code) {
|
||||
case "unsupported image type" -> "지원하지 않는 이미지 형식입니다. (jpeg, png, gif, webp, bmp)";
|
||||
case "file too large" -> "이미지 크기는 파일당 5MB 이하여야 합니다.";
|
||||
case "no files", "no valid image files" -> "업로드할 이미지가 없습니다.";
|
||||
default -> code.startsWith("max images exceeded")
|
||||
? "이슈당 첨부 이미지는 최대 30장까지입니다."
|
||||
: code.startsWith("issue not found") ? "검증 이슈를 찾을 수 없습니다." : code;
|
||||
};
|
||||
}
|
||||
|
||||
@GetMapping("/{imageId}/file")
|
||||
public ResponseEntity<Resource> file(
|
||||
@PathVariable Long issueId,
|
||||
@PathVariable Long imageId) {
|
||||
return service.loadFile(issueId, imageId)
|
||||
.map(resource -> {
|
||||
String type = service.contentType(issueId, imageId).orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(type))
|
||||
.header(HttpHeaders.CACHE_CONTROL, "private, max-age=3600")
|
||||
.body(resource);
|
||||
})
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{imageId}")
|
||||
public ResponseEntity<Void> delete(
|
||||
@PathVariable Long issueId,
|
||||
@PathVariable Long imageId) {
|
||||
try {
|
||||
if (!service.delete(issueId, imageId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (IOException e) {
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.goldenchart.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class VerificationUploadExceptionHandler {
|
||||
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public ResponseEntity<Map<String, String>> handleMaxUpload(MaxUploadSizeExceededException e) {
|
||||
log.warn("[VerificationUpload] size exceeded: {}", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
|
||||
.body(Map.of("message", "업로드 용량이 제한(30MB)을 초과했습니다."));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public ResponseEntity<Map<String, String>> handleMultipart(MultipartException e) {
|
||||
log.warn("[VerificationUpload] multipart error: {}", e.getMessage());
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("message", "이미지 파일 형식을 확인할 수 없습니다."));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user