package kr.itn.itnhub.project; import jakarta.validation.Valid; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.List; import java.util.Map; /** 사업관리 화면이 쓰는 엔드포인트. 기관 현황 목록 + 연락 이력 + 최종 보고서. */ @RestController public class ProjectController { private final ProjectService projectService; public ProjectController(ProjectService projectService) { this.projectService = projectService; } @GetMapping("/api/projects") public List rows() { return projectService.rows(); } @GetMapping("/api/orgs/{id}/contact-logs") public List logs(@PathVariable("id") Long orgId) { return projectService.logs(orgId); } /** 작성자는 업무메모와 마찬가지로 로그인 사용자를 서버가 기록한다. */ @PostMapping("/api/orgs/{id}/contact-logs") public List addLog(@PathVariable("id") Long orgId, @Valid @RequestBody ContactLogRequest request, Principal principal) { return projectService.addLog(orgId, request, principal.getName()); } @PutMapping("/api/orgs/{id}/contact-logs/{logId}") public List updateLog(@PathVariable("id") Long orgId, @PathVariable Long logId, @Valid @RequestBody ContactLogRequest request) { return projectService.updateLog(orgId, logId, request); } @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/api/orgs/{id}/contact-logs/{logId}") public void deleteLog(@PathVariable("id") Long orgId, @PathVariable Long logId) { projectService.deleteLog(orgId, logId); } @GetMapping("/api/orgs/{id}/reports") public List reports(@PathVariable("id") Long orgId) { return projectService.reports(orgId); } @PostMapping("/api/orgs/{id}/reports") public List addReport(@PathVariable("id") Long orgId, @RequestPart("file") MultipartFile file, Principal principal) throws IOException { String name = file.getOriginalFilename() == null ? "report" : file.getOriginalFilename(); return projectService.addReport(orgId, name, file.getContentType(), file.getBytes(), principal.getName()); } /** 한글 파일명이 깨지지 않게 ChannelFeedController.download와 같은 RFC 5987 인코딩을 쓴다. */ @GetMapping("/api/orgs/{id}/reports/{reportId}") public ResponseEntity downloadReport(@PathVariable("id") Long orgId, @PathVariable Long reportId) { ProjectMapper.ReportContent report = projectService.reportContent(orgId, reportId); String encodedName = URLEncoder.encode(report.fileName(), StandardCharsets.UTF_8) .replace("+", "%20"); MediaType type = report.contentType() == null || report.contentType().isBlank() ? MediaType.APPLICATION_OCTET_STREAM : MediaType.parseMediaType(report.contentType()); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedName) .contentType(type) .body(report.content()); } /** 올려둔 보고서를 새 파일로 바꾼다(요구사항 [25]). 목록에 새 줄이 생기지 않는다. */ @PutMapping("/api/orgs/{id}/reports/{reportId}") public List replaceReport(@PathVariable("id") Long orgId, @PathVariable Long reportId, @RequestPart("file") MultipartFile file, Principal principal) throws IOException { String name = file.getOriginalFilename() == null ? "report" : file.getOriginalFilename(); return projectService.replaceReport(orgId, reportId, name, file.getContentType(), file.getBytes(), principal.getName()); } @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/api/orgs/{id}/reports/{reportId}") public void deleteReport(@PathVariable("id") Long orgId, @PathVariable Long reportId) { projectService.deleteReport(orgId, reportId); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) public Map handleBadRequest(IllegalArgumentException e) { return Map.of("message", e.getMessage()); } }