Skip to content

Commit 0079bfe

Browse files
authored
Merge branch 'dev' into refactor/DASOMBE-16-generation
2 parents 9f31849 + d884a28 commit 0079bfe

27 files changed

+1220
-4
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package dmu.dasom.api.domain.activity.controller;
2+
3+
import dmu.dasom.api.domain.activity.dto.ActivityResponseDto;
4+
import dmu.dasom.api.domain.activity.service.ActivityService;
5+
import dmu.dasom.api.domain.common.exception.ErrorResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.web.bind.annotation.GetMapping;
16+
import org.springframework.web.bind.annotation.RequestMapping;
17+
import org.springframework.web.bind.annotation.RestController;
18+
19+
import java.util.List;
20+
21+
@RestController
22+
@RequestMapping("/api/activities")
23+
@RequiredArgsConstructor
24+
@Tag(name = "Activity API", description = "활동 연혁 조회 API")
25+
public class ActivityController {
26+
27+
private final ActivityService activityService;
28+
29+
@Operation(summary = "활동 연혁 전체 조회", description = "모든 활동 연혁을 연도별, 섹션별로 그룹화하여 조회합니다.")
30+
@ApiResponses(value = {
31+
@ApiResponse(responseCode = "200", description = "활동 연혁 전체 조회 성공"),
32+
@ApiResponse(responseCode = "405", description = "허용되지 않은 요청 방식",
33+
content = @Content(
34+
mediaType = "application/json",
35+
schema = @Schema(implementation = ErrorResponse.class),
36+
examples = {
37+
@ExampleObject(
38+
name = "허용되지 않은 메서드",
39+
value = "{ \"code\": \"C007\", \"message\": \"허용되지 않은 요청 방식입니다.\" }"
40+
)
41+
}
42+
)),
43+
@ApiResponse(responseCode = "500", description = "서버 내부 오류",
44+
content = @Content(
45+
mediaType = "application/json",
46+
schema = @Schema(implementation = ErrorResponse.class),
47+
examples = {
48+
@ExampleObject(
49+
name = "서버 문제 발생",
50+
value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }"
51+
)
52+
}
53+
))
54+
})
55+
@GetMapping
56+
public ResponseEntity<List<ActivityResponseDto>> getActivities() {
57+
return ResponseEntity.ok(activityService.getActivities());
58+
}
59+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package dmu.dasom.api.domain.activity.controller;
2+
3+
import dmu.dasom.api.domain.activity.dto.ActivityRequestDto;
4+
import dmu.dasom.api.domain.activity.service.ActivityService;
5+
import dmu.dasom.api.domain.common.exception.ErrorResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import jakarta.validation.Valid;
14+
import jakarta.validation.constraints.Min;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@RestController
20+
@RequestMapping("/api/admin/activities")
21+
@RequiredArgsConstructor
22+
@Tag(name = "ADMIN - Activity API", description = "어드민 활동 연혁 관리 API")
23+
public class AdminActivityController {
24+
25+
private final ActivityService activityService;
26+
27+
@Operation(summary = "활동 연혁 생성")
28+
@ApiResponses(value = {
29+
@ApiResponse(responseCode = "201", description = "활동 연혁 생성 성공"),
30+
@ApiResponse(responseCode = "400", description = "요청 값 유효성 검사 실패", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "유효성 검사 실패", value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"))),
31+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
32+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
33+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
34+
})
35+
@PostMapping
36+
public ResponseEntity<Void> createActivity(@Valid @RequestBody ActivityRequestDto requestDto) {
37+
activityService.createActivity(requestDto);
38+
return ResponseEntity.status(201).build();
39+
}
40+
41+
@Operation(summary = "활동 연혁 수정")
42+
@ApiResponses(value = {
43+
@ApiResponse(responseCode = "200", description = "활동 연혁 수정 성공"),
44+
@ApiResponse(responseCode = "400", description = "요청 값 유효성 검사 실패", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "유효성 검사 실패", value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"))),
45+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
46+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
47+
@ApiResponse(responseCode = "404", description = "수정할 리소스를 찾을 수 없음", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "조회 결과 없음", value = "{ \"code\": \"C010\", \"message\": \"해당 리소스를 찾을 수 없습니다.\" }"))),
48+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
49+
})
50+
@PutMapping("/{activityId}")
51+
public ResponseEntity<Void> updateActivity(
52+
@PathVariable @Min(1) Long activityId,
53+
@Valid @RequestBody ActivityRequestDto requestDto
54+
) {
55+
activityService.updateActivity(activityId, requestDto);
56+
return ResponseEntity.ok().build();
57+
}
58+
59+
@Operation(summary = "활동 연혁 삭제")
60+
@ApiResponses(value = {
61+
@ApiResponse(responseCode = "204", description = "활동 연혁 삭제 성공"),
62+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
63+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
64+
@ApiResponse(responseCode = "404", description = "삭제할 리소스를 찾을 수 없음", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "조회 결과 없음", value = "{ \"code\": \"C010\", \"message\": \"해당 리소스를 찾을 수 없습니다.\" }"))),
65+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
66+
})
67+
@DeleteMapping("/{activityId}")
68+
public ResponseEntity<Void> deleteActivity(@PathVariable Long activityId) {
69+
activityService.deleteActivity(activityId);
70+
return ResponseEntity.noContent().build();
71+
}
72+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dmu.dasom.api.domain.activity.dto;
2+
3+
import dmu.dasom.api.domain.activity.entity.Activity;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import java.time.format.DateTimeFormatter;
8+
9+
@Getter
10+
@Builder
11+
@Schema(name = "ActivityItemDto", description = "개별 활동 목록")
12+
public class ActivityItemDto {
13+
14+
@Schema(description = "활동 고유 ID", example = "1")
15+
private final Long id;
16+
17+
@Schema(description = "활동 날짜", example = "05.10")
18+
private final String monthDay; // 날짜 필드 추가
19+
20+
@Schema(description = "활동 제목", example = "컴퓨터 공학부 경진대회")
21+
private final String title;
22+
23+
@Schema(description = "수상 내역", example = "최우수상")
24+
private final String award;
25+
26+
public static ActivityItemDto of(Activity activity) {
27+
String formattedMonthDay = activity.getActivityDate()
28+
.format(DateTimeFormatter.ofPattern("MM.dd"));
29+
30+
return ActivityItemDto.builder()
31+
.id(activity.getId())
32+
.monthDay(formattedMonthDay)
33+
.title(activity.getTitle())
34+
.award(activity.getAward())
35+
.build();
36+
}
37+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dmu.dasom.api.domain.activity.dto;
2+
3+
import dmu.dasom.api.domain.activity.entity.Activity;
4+
import dmu.dasom.api.domain.activity.entity.Section;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import jakarta.validation.constraints.NotBlank;
7+
import jakarta.validation.constraints.NotNull;
8+
import jakarta.validation.constraints.Size;
9+
import lombok.Builder;
10+
import lombok.Getter;
11+
12+
import java.time.LocalDate;
13+
14+
@Getter
15+
@Builder
16+
@Schema(name = "ActivityHistoryRequestDto", description = "활동 연혁 생성/수정 요청 DTO")
17+
public class ActivityRequestDto {
18+
19+
@NotNull(message = "활동 날짜는 필수입니다.")
20+
@Schema(description = "활동 날짜", example = "2024-08-21")
21+
private LocalDate activityDate;
22+
23+
@Schema(description = "활동 섹션", example = "교내 경진대회", maxLength = 50)
24+
private String section;
25+
26+
@NotBlank(message = "활동 제목은 필수입니다.")
27+
@Size(max = 50, message = "제목은 50자 이내로 입력해주세요.")
28+
@Schema(description = "활동 제목", example = "컴퓨터 공학부 경진대회", maxLength = 50)
29+
private String title;
30+
31+
@Size(max = 50, message = "수상 내역은 50자 이내로 입력해주세요.")
32+
@Schema(description = "수상 내역 (선택 사항)", example = "최우수상", maxLength = 50)
33+
private String award;
34+
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package dmu.dasom.api.domain.activity.dto;
2+
3+
import dmu.dasom.api.domain.activity.entity.Activity;
4+
import dmu.dasom.api.domain.activity.entity.Section;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
import java.util.Comparator;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.stream.Collectors;
12+
13+
@Getter
14+
@Builder
15+
public class ActivityResponseDto {
16+
17+
private final int year;
18+
private final List<SectionItemDto> sections;
19+
20+
public static List<ActivityResponseDto> of(List<Activity> activities) {
21+
return activities.stream()
22+
.collect(Collectors.groupingBy(activity -> activity.getActivityDate().getYear()))
23+
.entrySet().stream()
24+
.sorted(Map.Entry.comparingByKey()) // 1. 연도 오름차순 정렬
25+
.map(entryByYear -> {
26+
List<SectionItemDto> sectionDtos = groupAndSortSections(entryByYear.getValue());
27+
return ActivityResponseDto.builder()
28+
.year(entryByYear.getKey())
29+
.sections(sectionDtos)
30+
.build();
31+
})
32+
.collect(Collectors.toList());
33+
}
34+
35+
private static List<SectionItemDto> groupAndSortSections(List<Activity> activitiesForYear) {
36+
return activitiesForYear.stream()
37+
.collect(Collectors.groupingBy(Activity::getSection))
38+
.entrySet().stream()
39+
.map(entryBySection -> {
40+
Section section = entryBySection.getKey();
41+
List<ActivityItemDto> activityDtos = mapAndSortActivities(entryBySection.getValue());
42+
return SectionItemDto.builder()
43+
.id(section.getId())
44+
.section(section.getName())
45+
.activities(activityDtos)
46+
.build();
47+
})
48+
// 2. 섹션 ID 오름차순 정렬
49+
.sorted(Comparator.comparing(SectionItemDto::getId))
50+
.collect(Collectors.toList());
51+
}
52+
53+
private static List<ActivityItemDto> mapAndSortActivities(List<Activity> activitiesForSection) {
54+
return activitiesForSection.stream()
55+
// 3. 활동 날짜 오름차순 정렬
56+
.sorted(Comparator.comparing(Activity::getActivityDate))
57+
.map(ActivityItemDto::of)
58+
.collect(Collectors.toList());
59+
}
60+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dmu.dasom.api.domain.activity.dto;
2+
3+
import dmu.dasom.api.domain.activity.entity.Section;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
import java.util.List;
9+
10+
@Getter
11+
@Builder
12+
@Schema(name = "SectionItemDto", description = "섹션별 활동 목록")
13+
public class SectionItemDto {
14+
15+
@Schema(description = "활동 섹션 고유 ID", example = "1")
16+
private final Long id;
17+
18+
@Schema(description = "활동 섹션", example = "교내 경진대회")
19+
private final String section;
20+
21+
@Schema(description = "활동 목록")
22+
private final List<ActivityItemDto> activities;
23+
24+
public static SectionItemDto of(Section section, List<ActivityItemDto> activities) {
25+
return SectionItemDto.builder()
26+
.id(section.getId())
27+
.section(section.getName())
28+
.activities(activities)
29+
.build();
30+
}
31+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dmu.dasom.api.domain.activity.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
import java.time.LocalDate;
6+
7+
@Entity
8+
@Getter
9+
@Builder
10+
@AllArgsConstructor
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
@Table(name = "activities")
13+
public class Activity {
14+
15+
@Id
16+
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
private Long id;
18+
19+
@ManyToOne(fetch = FetchType.LAZY)
20+
@JoinColumn(name = "section_id", nullable = false)
21+
private Section section;
22+
23+
@Column(nullable = false)
24+
private LocalDate activityDate;
25+
26+
@Column(length = 50, nullable = false)
27+
private String title;
28+
29+
@Column(length = 50)
30+
private String award;
31+
32+
public static Activity create(Section section, LocalDate activityDate, String title, String award) {
33+
return Activity.builder()
34+
.section(section)
35+
.activityDate(activityDate)
36+
.title(title)
37+
.award(award)
38+
.build();
39+
}
40+
41+
public void update(Section section, LocalDate activityDate, String title, String award) {
42+
this.section = section;
43+
this.activityDate = activityDate;
44+
this.title = title;
45+
this.award = award;
46+
}
47+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dmu.dasom.api.domain.activity.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
@Entity
9+
@Getter
10+
@Builder
11+
@AllArgsConstructor
12+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
13+
@Table(name = "sections")
14+
public class Section {
15+
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long id;
19+
20+
@Column(length = 50, nullable = false, unique = true)
21+
private String name;
22+
23+
@OneToMany(mappedBy = "section", cascade = CascadeType.ALL, orphanRemoval = true)
24+
private List<Activity> activities = new ArrayList<>();
25+
26+
public static Section create(String name) {
27+
return Section.builder().name(name).build();
28+
}
29+
30+
public void update(String name) {
31+
this.name = name;
32+
}
33+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dmu.dasom.api.domain.activity.repository;
2+
3+
import dmu.dasom.api.domain.activity.entity.Activity;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface ActivityRepository extends JpaRepository<Activity, Long> {
7+
8+
}

0 commit comments

Comments
 (0)