Skip to content

Commit 409adbc

Browse files
committed
Merge remote-tracking branch 'origin/develop' into develop
2 parents d8e5b49 + f7c1c65 commit 409adbc

File tree

46 files changed

+744
-258
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+744
-258
lines changed

src/main/java/clap/server/adapter/inbound/security/handler/JwtAccessDeniedHandler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public void handle(
2323
) throws IOException, ServletException {
2424
AuthErrorCode errorCode = AuthErrorCode.FORBIDDEN;
2525

26-
response.setContentType("application/json;charset=UTF-8");
2726
response.setStatus(errorCode.getHttpStatus().value());
2827
response.getWriter().write(errorCode.getCustomCode());
2928
}
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package clap.server.adapter.inbound.web.admin;
22

3+
import clap.server.adapter.inbound.web.dto.admin.request.DeleteMemberRequest;
34
import clap.server.application.port.inbound.admin.DeleteMemberUsecase;
45
import clap.server.common.annotation.architecture.WebAdapter;
56
import io.swagger.v3.oas.annotations.Operation;
67
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import jakarta.validation.Valid;
79
import lombok.RequiredArgsConstructor;
810
import org.springframework.security.access.annotation.Secured;
11+
import org.springframework.transaction.annotation.Transactional;
912
import org.springframework.web.bind.annotation.PatchMapping;
10-
import org.springframework.web.bind.annotation.PathVariable;
13+
import org.springframework.web.bind.annotation.RequestBody;
1114
import org.springframework.web.bind.annotation.RequestMapping;
1215

1316
@Tag(name = "05. Admin")
@@ -17,10 +20,11 @@
1720
public class DeleteMemberController {
1821
private final DeleteMemberUsecase deleteMemberUsecase;
1922

23+
@Transactional
2024
@Operation(summary = "회원 삭제 API")
2125
@Secured("ROLE_ADMIN")
22-
@PatchMapping("/members/{memberId}")
23-
public void deleteMember(@PathVariable Long memberId) {
24-
deleteMemberUsecase.deleteMember(memberId);
26+
@PatchMapping("/members/delete")
27+
public void deleteMember(@RequestBody @Valid DeleteMemberRequest deleteMemberRequest) {
28+
deleteMemberUsecase.deleteMember(deleteMemberRequest.memberId());
2529
}
2630
}

src/main/java/clap/server/adapter/inbound/web/admin/FindMemberController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
import clap.server.adapter.inbound.web.dto.admin.response.RetrieveAllMemberResponse;
55
import clap.server.adapter.inbound.web.dto.common.PageResponse;
66
import clap.server.application.port.inbound.admin.FindMembersWithFilterUsecase;
7+
78
import io.swagger.v3.oas.annotations.Operation;
89
import io.swagger.v3.oas.annotations.Parameter;
910
import io.swagger.v3.oas.annotations.tags.Tag;
10-
import lombok.RequiredArgsConstructor;
1111
import jakarta.validation.Valid;
12+
import lombok.RequiredArgsConstructor;
1213
import org.springframework.data.domain.PageRequest;
1314
import org.springframework.data.domain.Pageable;
1415
import org.springframework.http.ResponseEntity;

src/main/java/clap/server/adapter/inbound/web/admin/RegisterMemberCsvController.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import clap.server.adapter.inbound.security.service.SecurityUserDetails;
44
import clap.server.application.port.inbound.admin.RegisterMemberCSVUsecase;
55
import clap.server.common.annotation.architecture.WebAdapter;
6+
import clap.server.common.utils.FileTypeValidator;
7+
import clap.server.exception.AdapterException;
8+
import clap.server.exception.ApplicationException;
9+
import clap.server.exception.code.FileErrorcode;
610
import io.swagger.v3.oas.annotations.Operation;
711
import io.swagger.v3.oas.annotations.tags.Tag;
812
import org.springframework.http.ResponseEntity;
@@ -13,6 +17,8 @@
1317
import org.springframework.web.bind.annotation.RequestParam;
1418
import org.springframework.web.multipart.MultipartFile;
1519

20+
import java.io.IOException;
21+
1622
@Tag(name = "05. Admin")
1723
@WebAdapter
1824
@RequestMapping("/api/managements")
@@ -28,7 +34,10 @@ public RegisterMemberCsvController(RegisterMemberCSVUsecase registerMemberCSVUse
2834
@Secured("ROLE_ADMIN")
2935
public ResponseEntity<String> registerMembersFromCsv(
3036
@AuthenticationPrincipal SecurityUserDetails userInfo,
31-
@RequestParam("file") MultipartFile file) {
37+
@RequestParam("file") MultipartFile file) throws IOException {
38+
if (!FileTypeValidator.validCSVFile(file.getInputStream())) {
39+
throw new AdapterException(FileErrorcode.UNSUPPORTED_FILE_TYPE);
40+
}
3241
int addedCount = registerMemberCSVUsecase.registerMembersFromCsv(userInfo.getUserId(), file);
3342
return ResponseEntity.ok(addedCount + "명의 회원이 등록되었습니다.");
3443
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package clap.server.adapter.inbound.web.dto.admin.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
public record DeleteMemberRequest(
7+
@NotNull(message = "회원 ID는 필수 값입니다.")
8+
@Schema(description = "삭제할 회원 ID", example = "1", required = true)
9+
Long memberId
10+
) {
11+
}

src/main/java/clap/server/adapter/inbound/web/dto/admin/request/FindMemberRequest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ public record FindMemberRequest(
2121
@Schema(description = "부서 이름", example = "1")
2222
String departmentName,
2323

24-
@NotNull
2524
@Schema(description = "회원 역할", example = "ROLE_USER")
2625
MemberRole role
26+
27+
//TODO: 가입일 추가
28+
29+
2730
) {}

src/main/java/clap/server/adapter/inbound/web/dto/admin/response/RetrieveAllMemberResponse.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
44
import io.swagger.v3.oas.annotations.media.Schema;
55

6+
import java.time.LocalDateTime;
7+
68
public record RetrieveAllMemberResponse(
9+
@Schema(description = "회원 ID", example = "1")
10+
Long memberId,
11+
712
@Schema(description = "회원 이름", example = "양시훈")
813
String name,
914

@@ -23,5 +28,10 @@ public record RetrieveAllMemberResponse(
2328
MemberRole role,
2429

2530
@Schema(description = "회원 직책", example = "개발자")
26-
String departmentRole
31+
String departmentRole,
32+
33+
@Schema(description = "가입일", example = "2024-01-01T12:00:00")
34+
LocalDateTime createdAt
35+
36+
2737
) {}

src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void updateTaskState(
4545
@Parameter(description = "변경하고 싶은 작업 상태",
4646
schema = @Schema(allowableValues = {"IN_PROGRESS", "PENDING_COMPLETED", "COMPLETED"}))
4747
@RequestBody TaskStatus taskStatus) {
48+
4849
updateTaskStatusUsecase.updateTaskStatus(userInfo.getUserId(), taskId, taskStatus);
4950
}
5051

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package clap.server.adapter.outbound.api;
22

33
import clap.server.adapter.outbound.api.dto.PushNotificationTemplate;
4+
import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType;
5+
import clap.server.application.port.inbound.domain.TaskService;
46
import clap.server.application.port.outbound.webhook.SendAgitPort;
7+
import clap.server.application.service.task.UpdateTaskService;
58
import clap.server.common.annotation.architecture.ExternalApiAdapter;
9+
import clap.server.domain.model.task.Task;
10+
import clap.server.exception.ApplicationException;
11+
import clap.server.exception.code.NotificationErrorCode;
12+
import com.fasterxml.jackson.core.JsonProcessingException;
13+
import com.fasterxml.jackson.databind.JsonNode;
14+
import com.fasterxml.jackson.databind.ObjectMapper;
615
import lombok.RequiredArgsConstructor;
716
import org.springframework.beans.factory.annotation.Value;
817
import org.springframework.http.HttpEntity;
9-
import org.springframework.http.HttpHeaders;
1018
import org.springframework.http.HttpMethod;
19+
import org.springframework.http.ResponseEntity;
1120
import org.springframework.web.client.RestTemplate;
1221

1322

@@ -18,41 +27,33 @@ public class AgitClient implements SendAgitPort {
1827
@Value("${webhook.agit.url}")
1928
private String AGIT_WEBHOOK_URL;
2029

30+
private final AgitTemplateBuilder agitTemplateBuilder;
31+
private final ObjectMapper objectMapper;
32+
private final TaskService taskService;
33+
2134
@Override
22-
public void sendAgit(PushNotificationTemplate request) {
35+
public void sendAgit(PushNotificationTemplate request, Task task) {
2336

24-
HttpHeaders headers = new HttpHeaders();
25-
headers.add("Content-Type", "application/json");
37+
HttpEntity<String> entity = agitTemplateBuilder.createAgitEntity(request, task);
2638

2739
RestTemplate restTemplate = new RestTemplate();
28-
String taskUrl = "https://www.naver.com"; //Todo 작업 상세페이지 url 추가
29-
30-
String message = switch (request.notificationType()) {
31-
case TASK_REQUESTED -> "📌 *새 작업 요청:* `" + request.taskName() + "`\\n"
32-
+ "\\t\\t*•요청자: " + request.senderName() + "*\\n"
33-
+ "\\t\\t[OPEN](" + taskUrl + ")";
34-
case STATUS_SWITCHED -> "⚙️ *작업 상태 변경:* `" + request.taskName() + "\\n"
35-
+ "\\t\\t*•작업 상태: " + request.message() + "*\\n"
36-
+ "\\t\\t[OPEN](" + taskUrl + ")";
37-
case PROCESSOR_CHANGED -> "🔄 *담당자 변경:* `" + request.taskName() + "\\n"
38-
+ "\\t\\t*•새 담당자: " + request.message() + "*\\n"
39-
+ "\\t\\t[OPEN](" + taskUrl + ")";
40-
case PROCESSOR_ASSIGNED -> "👤 *작업 담당자 배정:* `" + request.taskName() + "\\n"
41-
+ "\\t\\t*•담당자: " + request.message() + "*\\n"
42-
+ "\\t\\t[OPEN](" + taskUrl + ")";
43-
case COMMENT -> "💬 *새 댓글:* `" + request.taskName() + "`\\n"
44-
+ "\\t\\t*•작성자: " + request.commenterName() + "\\n"
45-
+ "\\t\\t*•댓글 내용: " + request.message() + "\\n"
46-
+ "\\t\\t[OPEN](" + taskUrl + ")";
47-
default -> null;
48-
};
49-
50-
String payload = "{"
51-
+ "\"text\": \"" + message + "\","
52-
+ "\"mrkdwn\": true" + "}";
53-
54-
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
55-
56-
restTemplate.exchange(AGIT_WEBHOOK_URL, HttpMethod.POST, entity, String.class);
40+
if (request.notificationType() == NotificationType.TASK_REQUESTED) {
41+
ResponseEntity<String> responseEntity = restTemplate.exchange(
42+
AGIT_WEBHOOK_URL, HttpMethod.POST, entity, String.class);
43+
updateAgitPostId(responseEntity, task);
44+
}
45+
else {
46+
restTemplate.exchange(AGIT_WEBHOOK_URL, HttpMethod.POST, entity, String.class);
47+
}
48+
}
49+
50+
private void updateAgitPostId(ResponseEntity<String> responseEntity, Task task) {
51+
try {
52+
JsonNode jsonNode = objectMapper.readTree(responseEntity.getBody());
53+
task.updateAgitPostId(jsonNode.get("id").asLong());
54+
taskService.upsert(task);
55+
} catch (JsonProcessingException e) {
56+
throw new ApplicationException(NotificationErrorCode.AGIT_SEND_FAILED);
57+
}
5758
}
5859
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package clap.server.adapter.outbound.api;
2+
3+
import clap.server.adapter.outbound.api.dto.PushNotificationTemplate;
4+
import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType;
5+
import clap.server.domain.model.task.Task;
6+
import org.springframework.http.HttpEntity;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
public class AgitTemplateBuilder {
12+
13+
public HttpEntity<String> createAgitEntity(PushNotificationTemplate request, Task task) {
14+
return new HttpEntity<>(createPayLoad(request, task), createHeaders());
15+
}
16+
17+
18+
public HttpHeaders createHeaders() {
19+
HttpHeaders headers = new HttpHeaders();
20+
headers.add("Content-Type", "application/json");
21+
return headers;
22+
}
23+
24+
public String createPayLoad(PushNotificationTemplate request, Task task) {
25+
26+
String payload;
27+
if (request.notificationType() == NotificationType.TASK_REQUESTED) {
28+
payload = "{"
29+
+ "\"text\": \"" + createMessage(request) + "\","
30+
+ "\"mrkdwn\": true" + "}";
31+
}
32+
33+
else {
34+
payload = "{"
35+
+ "\"parent_id\": " + task.getAgitPostId() + ","
36+
+ "\"text\": \"" + createMessage(request) + "\","
37+
+ "\"mrkdwn\": true"
38+
+ "}";
39+
}
40+
return payload;
41+
}
42+
43+
public String createMessage(PushNotificationTemplate request) {
44+
String taskUrl = "https://www.naver.com"; //Todo 작업 상세페이지 url 추가
45+
46+
return switch (request.notificationType()) {
47+
case TASK_REQUESTED -> "📌 *새 작업 요청:* `" + request.taskName() + "`\\n"
48+
+ "\\t\\t*•요청자: " + request.senderName() + "*\\n"
49+
+ "\\t\\t[OPEN](" + taskUrl + ")";
50+
case STATUS_SWITCHED -> "⚙️ *작업 상태 변경:* `" + request.taskName() + "\\n"
51+
+ "\\t\\t*•작업 상태: " + request.message() + "*\\n"
52+
+ "\\t\\t[OPEN](" + taskUrl + ")";
53+
case PROCESSOR_CHANGED -> "🔄 *담당자 변경:* `" + request.taskName() + "\\n"
54+
+ "\\t\\t*•새 담당자: " + request.message() + "*\\n"
55+
+ "\\t\\t[OPEN](" + taskUrl + ")";
56+
case PROCESSOR_ASSIGNED -> "👤 *작업 담당자 배정:* `" + request.taskName() + "\\n"
57+
+ "\\t\\t*•담당자: " + request.message() + "*\\n"
58+
+ "\\t\\t[OPEN](" + taskUrl + ")";
59+
default -> null;
60+
};
61+
}
62+
}

0 commit comments

Comments
 (0)