diff --git a/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleApi.java b/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleApi.java new file mode 100644 index 00000000..7fbff8e6 --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleApi.java @@ -0,0 +1,59 @@ +package gg.agit.konect.admin.schedule.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import gg.agit.konect.admin.schedule.dto.AdminScheduleCreateRequest; +import gg.agit.konect.admin.schedule.dto.AdminScheduleUpsertRequest; +import gg.agit.konect.domain.user.enums.UserRole; +import gg.agit.konect.global.auth.annotation.Auth; +import gg.agit.konect.global.auth.annotation.UserId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "(Admin) Schedule: 일정", description = "어드민 일정 API") +@RequestMapping("/admin/schedules") +@Auth(roles = {UserRole.ADMIN}) +public interface AdminScheduleApi { + + @Operation(summary = "일정을 생성한다.", description = """ + **scheduleType (일정 구분):** + - `UNIVERSITY`: 대학교 일정 + - `CLUB`: 동아리 일정 + - `COUNCIL`: 총동아리연합회 일정 + - `DORM`: 기숙사 일정 + """) + @PostMapping + ResponseEntity createSchedule( + @Valid @RequestBody AdminScheduleCreateRequest request, + @UserId Integer userId + ); + + @Operation(summary = "일정을 일괄 생성/수정한다.", description = """ + scheduleId가 없으면 신규 생성, 있으면 해당 일정 수정입니다. + + **scheduleType (일정 구분):** + - `UNIVERSITY`: 대학교 일정 + - `CLUB`: 동아리 일정 + - `COUNCIL`: 총동아리연합회 일정 + - `DORM`: 기숙사 일정 + """) + @PutMapping("/batch") + ResponseEntity upsertSchedules( + @Valid @RequestBody AdminScheduleUpsertRequest request, + @UserId Integer userId + ); + + @Operation(summary = "일정을 삭제한다.") + @DeleteMapping("/{scheduleId}") + ResponseEntity deleteSchedule( + @PathVariable Integer scheduleId, + @UserId Integer userId + ); +} diff --git a/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleController.java b/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleController.java new file mode 100644 index 00000000..6c9269a7 --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/controller/AdminScheduleController.java @@ -0,0 +1,39 @@ +package gg.agit.konect.admin.schedule.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gg.agit.konect.admin.schedule.dto.AdminScheduleCreateRequest; +import gg.agit.konect.admin.schedule.dto.AdminScheduleUpsertRequest; +import gg.agit.konect.admin.schedule.service.AdminScheduleService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/schedules") +public class AdminScheduleController implements AdminScheduleApi { + + private final AdminScheduleService adminScheduleService; + + @Override + public ResponseEntity createSchedule(AdminScheduleCreateRequest request, Integer userId) { + adminScheduleService.createSchedule(request, userId); + + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity upsertSchedules(AdminScheduleUpsertRequest request, Integer userId) { + adminScheduleService.upsertSchedules(request, userId); + + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity deleteSchedule(Integer scheduleId, Integer userId) { + adminScheduleService.deleteSchedule(scheduleId, userId); + + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleCreateRequest.java b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleCreateRequest.java new file mode 100644 index 00000000..cbb37449 --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleCreateRequest.java @@ -0,0 +1,33 @@ +package gg.agit.konect.admin.schedule.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import gg.agit.konect.domain.schedule.model.ScheduleType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminScheduleCreateRequest( + @NotBlank(message = "일정 제목은 필수 입력입니다.") + @Schema(description = "일정 제목", example = "동계방학", requiredMode = REQUIRED) + String title, + + @NotNull(message = "일정 시작 일시는 필수 입력입니다.") + @Schema(description = "일정 시작 일시", example = "2025.12.22 00:00:00", requiredMode = REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss") + LocalDateTime startedAt, + + @NotNull(message = "일정 종료 일시는 필수 입력입니다.") + @Schema(description = "일정 종료 일시", example = "2026.02.27 23:59:59", requiredMode = REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss") + LocalDateTime endedAt, + + @NotNull(message = "일정 종류는 필수 입력입니다.") + @Schema(description = "일정 종류", example = "UNIVERSITY", requiredMode = REQUIRED) + ScheduleType scheduleType +) { +} diff --git a/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertItemRequest.java b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertItemRequest.java new file mode 100644 index 00000000..75af01a1 --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertItemRequest.java @@ -0,0 +1,36 @@ +package gg.agit.konect.admin.schedule.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import gg.agit.konect.domain.schedule.model.ScheduleType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminScheduleUpsertItemRequest( + @Schema(description = "수정할 일정 ID (없으면 신규 생성)", example = "1") + Integer scheduleId, + + @NotBlank(message = "일정 제목은 필수 입력입니다.") + @Schema(description = "일정 제목", example = "동계방학", requiredMode = REQUIRED) + String title, + + @NotNull(message = "일정 시작 일시는 필수 입력입니다.") + @Schema(description = "일정 시작 일시", example = "2025.12.22 00:00:00", requiredMode = REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss") + LocalDateTime startedAt, + + @NotNull(message = "일정 종료 일시는 필수 입력입니다.") + @Schema(description = "일정 종료 일시", example = "2026.02.27 23:59:59", requiredMode = REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd HH:mm:ss") + LocalDateTime endedAt, + + @NotNull(message = "일정 종류는 필수 입력입니다.") + @Schema(description = "일정 종류", example = "UNIVERSITY", requiredMode = REQUIRED) + ScheduleType scheduleType +) { +} diff --git a/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertRequest.java b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertRequest.java new file mode 100644 index 00000000..e9e4191f --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/dto/AdminScheduleUpsertRequest.java @@ -0,0 +1,16 @@ +package gg.agit.konect.admin.schedule.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; + +public record AdminScheduleUpsertRequest( + @NotEmpty(message = "일정 목록은 필수 입력입니다.") + @Schema(description = "생성/수정할 일정 목록", requiredMode = REQUIRED) + List<@Valid AdminScheduleUpsertItemRequest> schedules +) { +} diff --git a/src/main/java/gg/agit/konect/admin/schedule/service/AdminScheduleService.java b/src/main/java/gg/agit/konect/admin/schedule/service/AdminScheduleService.java new file mode 100644 index 00000000..f4b77e32 --- /dev/null +++ b/src/main/java/gg/agit/konect/admin/schedule/service/AdminScheduleService.java @@ -0,0 +1,112 @@ +package gg.agit.konect.admin.schedule.service; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agit.konect.admin.schedule.dto.AdminScheduleCreateRequest; +import gg.agit.konect.admin.schedule.dto.AdminScheduleUpsertItemRequest; +import gg.agit.konect.admin.schedule.dto.AdminScheduleUpsertRequest; +import gg.agit.konect.domain.schedule.model.Schedule; +import gg.agit.konect.domain.schedule.model.ScheduleType; +import gg.agit.konect.domain.schedule.model.UniversitySchedule; +import gg.agit.konect.domain.schedule.repository.ScheduleRepository; +import gg.agit.konect.domain.schedule.repository.UniversityScheduleRepository; +import gg.agit.konect.domain.university.model.University; +import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminScheduleService { + + private final UserRepository userRepository; + private final ScheduleRepository scheduleRepository; + private final UniversityScheduleRepository universityScheduleRepository; + + @Transactional + public void createSchedule(AdminScheduleCreateRequest request, Integer userId) { + User user = userRepository.getById(userId); + University university = user.getUniversity(); + + createUniversitySchedule( + university, + request.title(), + request.startedAt(), + request.endedAt(), + request.scheduleType() + ); + } + + @Transactional + public void upsertSchedules(AdminScheduleUpsertRequest request, Integer userId) { + User user = userRepository.getById(userId); + University university = user.getUniversity(); + + for (AdminScheduleUpsertItemRequest item : request.schedules()) { + if (item.scheduleId() == null) { + createUniversitySchedule( + university, + item.title(), + item.startedAt(), + item.endedAt(), + item.scheduleType() + ); + continue; + } + + UniversitySchedule universitySchedule = universityScheduleRepository.getByIdAndUniversityId( + item.scheduleId(), + university.getId() + ); + + universitySchedule.getSchedule().update( + item.title(), + item.startedAt(), + item.endedAt(), + item.scheduleType() + ); + } + } + + @Transactional + public void deleteSchedule(Integer scheduleId, Integer userId) { + User user = userRepository.getById(userId); + University university = user.getUniversity(); + + UniversitySchedule universitySchedule = universityScheduleRepository.getByIdAndUniversityId( + scheduleId, + university.getId() + ); + + universityScheduleRepository.delete(universitySchedule); + scheduleRepository.delete(universitySchedule.getSchedule()); + } + + private void createUniversitySchedule( + University university, + String title, + LocalDateTime startedAt, + LocalDateTime endedAt, + ScheduleType scheduleType + ) { + Schedule schedule = Schedule.of( + title, + startedAt, + endedAt, + scheduleType + ); + + Schedule savedSchedule = scheduleRepository.save(schedule); + + UniversitySchedule universitySchedule = UniversitySchedule.of( + savedSchedule, + university + ); + + universityScheduleRepository.save(universitySchedule); + } +} diff --git a/src/main/java/gg/agit/konect/domain/schedule/model/Schedule.java b/src/main/java/gg/agit/konect/domain/schedule/model/Schedule.java index 169e9cb4..c35a0a06 100644 --- a/src/main/java/gg/agit/konect/domain/schedule/model/Schedule.java +++ b/src/main/java/gg/agit/konect/domain/schedule/model/Schedule.java @@ -9,6 +9,8 @@ import java.time.temporal.ChronoUnit; import gg.agit.konect.global.model.BaseEntity; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.exception.CustomException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Enumerated; @@ -61,6 +63,46 @@ private Schedule( this.startedAt = startedAt; this.endedAt = endedAt; this.scheduleType = scheduleType; + + validateDateTimeRange(startedAt, endedAt); + } + + public static Schedule of( + String title, + LocalDateTime startedAt, + LocalDateTime endedAt, + ScheduleType scheduleType + ) { + return Schedule.builder() + .title(title) + .startedAt(startedAt) + .endedAt(endedAt) + .scheduleType(scheduleType) + .build(); + } + + public void update( + String title, + LocalDateTime startedAt, + LocalDateTime endedAt, + ScheduleType scheduleType + ) { + validateDateTimeRange(startedAt, endedAt); + + this.title = title; + this.startedAt = startedAt; + this.endedAt = endedAt; + this.scheduleType = scheduleType; + } + + private void validateDateTimeRange(LocalDateTime startedAt, LocalDateTime endedAt) { + if (startedAt == null || endedAt == null) { + return; + } + + if (startedAt.isAfter(endedAt)) { + throw CustomException.of(ApiResponseCode.INVALID_DATE_TIME); + } } public Integer calculateDDay(LocalDate today) { diff --git a/src/main/java/gg/agit/konect/domain/schedule/model/UniversitySchedule.java b/src/main/java/gg/agit/konect/domain/schedule/model/UniversitySchedule.java index e6828393..3be3d3b7 100644 --- a/src/main/java/gg/agit/konect/domain/schedule/model/UniversitySchedule.java +++ b/src/main/java/gg/agit/konect/domain/schedule/model/UniversitySchedule.java @@ -42,4 +42,14 @@ private UniversitySchedule( this.schedule = schedule; this.university = university; } + + public static UniversitySchedule of( + Schedule schedule, + University university + ) { + return UniversitySchedule.builder() + .schedule(schedule) + .university(university) + .build(); + } } diff --git a/src/main/java/gg/agit/konect/domain/schedule/repository/ScheduleRepository.java b/src/main/java/gg/agit/konect/domain/schedule/repository/ScheduleRepository.java index 45ea1632..c65234ad 100644 --- a/src/main/java/gg/agit/konect/domain/schedule/repository/ScheduleRepository.java +++ b/src/main/java/gg/agit/konect/domain/schedule/repository/ScheduleRepository.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; @@ -12,6 +13,12 @@ public interface ScheduleRepository extends Repository { + Optional findById(Integer id); + + Schedule save(Schedule schedule); + + void delete(Schedule schedule); + @Query(""" SELECT s FROM Schedule s diff --git a/src/main/java/gg/agit/konect/domain/schedule/repository/UniversityScheduleRepository.java b/src/main/java/gg/agit/konect/domain/schedule/repository/UniversityScheduleRepository.java new file mode 100644 index 00000000..cae4d74b --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/schedule/repository/UniversityScheduleRepository.java @@ -0,0 +1,35 @@ +package gg.agit.konect.domain.schedule.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; + +import gg.agit.konect.domain.schedule.model.UniversitySchedule; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.exception.CustomException; + +public interface UniversityScheduleRepository extends Repository { + + void save(UniversitySchedule universitySchedule); + + void delete(UniversitySchedule universitySchedule); + + @Query(""" + SELECT us + FROM UniversitySchedule us + JOIN FETCH us.schedule s + WHERE us.id = :scheduleId + AND us.university.id = :universityId + """) + Optional findByIdAndUniversityId( + @Param("scheduleId") Integer scheduleId, + @Param("universityId") Integer universityId + ); + + default UniversitySchedule getByIdAndUniversityId(Integer scheduleId, Integer universityId) { + return findByIdAndUniversityId(scheduleId, universityId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_SCHEDULE)); + } +} diff --git a/src/main/java/gg/agit/konect/global/auth/annotation/Auth.java b/src/main/java/gg/agit/konect/global/auth/annotation/Auth.java new file mode 100644 index 00000000..d0f15727 --- /dev/null +++ b/src/main/java/gg/agit/konect/global/auth/annotation/Auth.java @@ -0,0 +1,17 @@ +package gg.agit.konect.global.auth.annotation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import gg.agit.konect.domain.user.enums.UserRole; + +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +public @interface Auth { + + UserRole[] roles(); +} diff --git a/src/main/java/gg/agit/konect/global/auth/interceptor/AuthorizationInterceptor.java b/src/main/java/gg/agit/konect/global/auth/interceptor/AuthorizationInterceptor.java new file mode 100644 index 00000000..0561585f --- /dev/null +++ b/src/main/java/gg/agit/konect/global/auth/interceptor/AuthorizationInterceptor.java @@ -0,0 +1,75 @@ +package gg.agit.konect.global.auth.interceptor; + +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import gg.agit.konect.global.auth.annotation.Auth; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.exception.CustomException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AuthorizationInterceptor implements HandlerInterceptor { + + private final UserRepository userRepository; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (HttpMethod.OPTIONS.matches(request.getMethod())) { + return true; + } + + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + + if (Boolean.TRUE.equals(request.getAttribute(LoginCheckInterceptor.PUBLIC_ENDPOINT_ATTRIBUTE))) { + return true; + } + + Auth auth = findAuthAnnotation(handlerMethod); + + if (auth == null) { + return true; + } + + Object userId = request.getAttribute(LoginCheckInterceptor.AUTHENTICATED_USER_ID_ATTRIBUTE); + + if (!(userId instanceof Integer id)) { + throw CustomException.of(ApiResponseCode.INVALID_SESSION); + } + + validateRole(id, auth); + + return true; + } + + private Auth findAuthAnnotation(HandlerMethod handlerMethod) { + Auth methodAnnotation = handlerMethod.getMethodAnnotation(Auth.class); + + if (methodAnnotation != null) { + return methodAnnotation; + } + + return handlerMethod.getBeanType().getAnnotation(Auth.class); + } + + private void validateRole(Integer userId, Auth auth) { + User user = userRepository.getById(userId); + + for (var allowedRole : auth.roles()) { + if (user.getRole() == allowedRole) { + return; + } + } + + throw CustomException.of(ApiResponseCode.FORBIDDEN_ROLE_ACCESS); + } +} diff --git a/src/main/java/gg/agit/konect/global/auth/interceptor/LoginCheckInterceptor.java b/src/main/java/gg/agit/konect/global/auth/interceptor/LoginCheckInterceptor.java index 03690b51..09a02140 100644 --- a/src/main/java/gg/agit/konect/global/auth/interceptor/LoginCheckInterceptor.java +++ b/src/main/java/gg/agit/konect/global/auth/interceptor/LoginCheckInterceptor.java @@ -15,6 +15,9 @@ @Component public class LoginCheckInterceptor implements HandlerInterceptor { + public static final String AUTHENTICATED_USER_ID_ATTRIBUTE = "authenticatedUserId"; + public static final String PUBLIC_ENDPOINT_ATTRIBUTE = "publicEndpoint"; + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (HttpMethod.OPTIONS.matches(request.getMethod())) { @@ -26,6 +29,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } if (isPublicEndpoint(handlerMethod)) { + request.setAttribute(PUBLIC_ENDPOINT_ATTRIBUTE, true); return true; } @@ -36,6 +40,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons throw CustomException.of(ApiResponseCode.INVALID_SESSION); } + request.setAttribute(AUTHENTICATED_USER_ID_ATTRIBUTE, userId); + return true; } diff --git a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java index 5338995d..9c9cfb3a 100644 --- a/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java +++ b/src/main/java/gg/agit/konect/global/code/ApiResponseCode.java @@ -50,6 +50,7 @@ public enum ApiResponseCode { FORBIDDEN_COUNCIL_NOTICE_ACCESS(HttpStatus.FORBIDDEN, "총동아리연합회 공지사항 조회 권한이 없습니다."), FORBIDDEN_MEMBER_POSITION_CHANGE(HttpStatus.FORBIDDEN, "회원 직책 변경 권한이 없습니다."), FORBIDDEN_POSITION_NAME_CHANGE(HttpStatus.FORBIDDEN, "해당 직책의 이름은 변경할 수 없습니다."), + FORBIDDEN_ROLE_ACCESS(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), // 404 Not Found (리소스를 찾을 수 없음) NO_HANDLER_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 API 경로입니다."), @@ -62,6 +63,7 @@ public enum ApiResponseCode { NOT_FOUND_COUNCIL(HttpStatus.NOT_FOUND, "총동아리연합회를 찾을 수 없습니다."), NOT_FOUND_COUNCIL_NOTICE(HttpStatus.NOT_FOUND, "총동아리연합회 공지사항을 찾을 수 없습니다."), NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다."), + NOT_FOUND_SCHEDULE(HttpStatus.NOT_FOUND, "일정을 찾을 수 없습니다."), NOT_FOUND_UNREGISTERED_USER(HttpStatus.NOT_FOUND, "임시 유저를 찾을 수 없습니다."), UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND, "대학교를 찾을 수 없습니다."), NOT_FOUND_CLUB_APPLY_QUESTION(HttpStatus.NOT_FOUND, "동아리 가입 문항을 찾을 수 없습니다."), diff --git a/src/main/java/gg/agit/konect/global/config/SwaggerConfig.java b/src/main/java/gg/agit/konect/global/config/SwaggerConfig.java index e5a5a69e..9c32f24b 100644 --- a/src/main/java/gg/agit/konect/global/config/SwaggerConfig.java +++ b/src/main/java/gg/agit/konect/global/config/SwaggerConfig.java @@ -6,6 +6,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springdoc.core.models.GroupedOpenApi; + import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.servers.Server; @@ -32,6 +34,23 @@ public OpenAPI openAPI() { .addServersItem(server); } + @Bean + public GroupedOpenApi publicApi() { + return GroupedOpenApi.builder() + .group("Public API") + .pathsToMatch("/**") + .pathsToExclude("/admin/**") + .build(); + } + + @Bean + public GroupedOpenApi adminApi() { + return GroupedOpenApi.builder() + .group("Admin API") + .pathsToMatch("/admin/**") + .build(); + } + private Info apiInfo() { return new Info() .title("KONECT API") diff --git a/src/main/java/gg/agit/konect/global/config/WebConfig.java b/src/main/java/gg/agit/konect/global/config/WebConfig.java index bab22442..7ceea7e4 100644 --- a/src/main/java/gg/agit/konect/global/config/WebConfig.java +++ b/src/main/java/gg/agit/konect/global/config/WebConfig.java @@ -10,6 +10,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import gg.agit.konect.global.auth.interceptor.LoginCheckInterceptor; +import gg.agit.konect.global.auth.interceptor.AuthorizationInterceptor; import gg.agit.konect.global.auth.resolver.LoginUserArgumentResolver; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ public class WebConfig implements WebMvcConfigurer { private final CorsProperties corsProperties; private final LoginCheckInterceptor loginCheckInterceptor; + private final AuthorizationInterceptor authorizationInterceptor; private final LoginUserArgumentResolver loginUserArgumentResolver; @Override @@ -43,6 +45,10 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor) .addPathPatterns("/**") .excludePathPatterns(SecurityPaths.PUBLIC_PATHS); + + registry.addInterceptor(authorizationInterceptor) + .addPathPatterns("/**") + .excludePathPatterns(SecurityPaths.PUBLIC_PATHS); } @Override