diff --git a/README.md b/README.md index 7e3b975..427bf84 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Academ (Back-end) -Academ Back-end repository 입니다. +Academ Back-end repository입니다. --- diff --git a/src/main/java/com/example/Devkor_project/controller/AdminController.java b/src/main/java/com/example/Devkor_project/controller/AdminController.java index f7c4fd3..a6169f6 100644 --- a/src/main/java/com/example/Devkor_project/controller/AdminController.java +++ b/src/main/java/com/example/Devkor_project/controller/AdminController.java @@ -207,4 +207,27 @@ public ResponseEntity createTestAccount(@Valid @RequestBody ); } + /* 강의 시간 및 장소 정보 동기화 컨트톨러 */ + @PostMapping("/api/admin/course-time-location-synchronization") + @Operation(summary = "강의 시간 및 장소 정보 동기화") + @Parameters(value = { + @Parameter(in = ParameterIn.HEADER, name = "Authorization", description = "Bearer {access token}"), + }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200"), + }) + public ResponseEntity checkTimeLocationSynchronization(@Valid @RequestBody CrawlingDto.Synchronization dto) + { + adminService.checkTimeLocationSynchronization(dto); + + return ResponseEntity.status(HttpStatus.OK) + .body( + ResponseDto.Success.builder() + .message("강의 시간 및 장소 동기화를 성공적으로 수행하였습니다.") + .data(null) + .version(versionProvider.getVersion()) + .build() + ); + } + } diff --git a/src/main/java/com/example/Devkor_project/dto/CourseDto.java b/src/main/java/com/example/Devkor_project/dto/CourseDto.java index 5c1e8bf..b8da0a7 100644 --- a/src/main/java/com/example/Devkor_project/dto/CourseDto.java +++ b/src/main/java/com/example/Devkor_project/dto/CourseDto.java @@ -307,6 +307,23 @@ public static class CheckSynchronization private int delete_count; } + @AllArgsConstructor + @NoArgsConstructor + @Getter + @ToString + @Builder + public static class TimeLocation + { + @Schema(description = "요일") + private String day; + @Schema(description = "시작 교시") + private Integer startPeriod; + @Schema(description = "끝 교시") + private Integer endPeriod; + @Schema(description = "강의실") + private String location; + } + public static CourseDto.Basic entityToBasic(Course course, CourseRating courseRating, Boolean isBookmark) { return Basic.builder() .course_id(course.getCourse_id()) diff --git a/src/main/java/com/example/Devkor_project/entity/TimeLocation.java b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java new file mode 100644 index 0000000..bf0a8c7 --- /dev/null +++ b/src/main/java/com/example/Devkor_project/entity/TimeLocation.java @@ -0,0 +1,29 @@ +package com.example.Devkor_project.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@ToString +public class TimeLocation +{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long timeLocation_id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "course_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) + private Course course_id; + + @Column(nullable = true) private String day; + @Column(nullable = true) private Integer startPeriod; + @Column(nullable = true) private Integer endPeriod; + @Column(nullable = true) private String location; +} diff --git a/src/main/java/com/example/Devkor_project/exception/ErrorCode.java b/src/main/java/com/example/Devkor_project/exception/ErrorCode.java index f24c171..9493bd3 100644 --- a/src/main/java/com/example/Devkor_project/exception/ErrorCode.java +++ b/src/main/java/com/example/Devkor_project/exception/ErrorCode.java @@ -40,7 +40,9 @@ public enum ErrorCode NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "포인트가 부족합니다."), WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 틀렸습니다."), TRAFFIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 기간 동안 요청이 들어오지 않았습니다."), - UNKNOWN_NOT_FOUND(HttpStatus.NOT_FOUND, "알 수 없음 계정이 존재하지 않습니다."); + UNKNOWN_NOT_FOUND(HttpStatus.NOT_FOUND, "알 수 없음 계정이 존재하지 않습니다."), + INVALID_PERIOD(HttpStatus.BAD_REQUEST, "해당 교시는 유효하지 않습니다."), + INVALID_TIME_LOCATION(HttpStatus.BAD_REQUEST, "해당 시간 및 장소 정보는 유효하지 않습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java b/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java new file mode 100644 index 0000000..3c3e04d --- /dev/null +++ b/src/main/java/com/example/Devkor_project/repository/TimeLocationRepository.java @@ -0,0 +1,7 @@ +package com.example.Devkor_project.repository; + +import com.example.Devkor_project.entity.TimeLocation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TimeLocationRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/Devkor_project/service/AdminService.java b/src/main/java/com/example/Devkor_project/service/AdminService.java index 89da76a..f8501f8 100644 --- a/src/main/java/com/example/Devkor_project/service/AdminService.java +++ b/src/main/java/com/example/Devkor_project/service/AdminService.java @@ -21,9 +21,12 @@ import org.springframework.web.reactive.function.client.WebClient; import java.security.Principal; +import java.sql.Time; import java.time.LocalDate; import java.util.*; +import static com.example.Devkor_project.util.TimeLocationParser.parseTimeLocation; + @Service @RequiredArgsConstructor @Slf4j @@ -37,6 +40,7 @@ public class AdminService private final CommentReportRepository commentReportRepository; private final ProfileRepository profileRepository; private final TrafficRepository trafficRepository; + private final TimeLocationRepository timeLocationRepository; private final CourseService courseService; @@ -51,7 +55,7 @@ public CourseDto.CheckSynchronization checkCourseSynchronization(CrawlingDto.Syn int delete_count = 0; // 삭제한 강의 정보 개수 // 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보 - // 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하지 위함 + // 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하기 위함 // 즉, 삭제할 강의 리스트 List allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester()); @@ -399,4 +403,33 @@ public void createTestAccount(ProfileDto.CreateTestAccount dto) // 해당 엔티티를 데이터베이스에 저장 profileRepository.save(profile); } + + /* 강의 시간 및 장소 동기화 서비스 */ + @Transactional + public void checkTimeLocationSynchronization(CrawlingDto.Synchronization dto) + { + // 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보 + List allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester()); + + for(Course course:allCourseInDatabase) + { + // 해당 강의의 time_location 정보를 CourseDto.TimeLocation 리스트로 변환 + List timeLocations = parseTimeLocation(course.getTime_location()); + + // TimeLocation 엔티티 추가 + if (timeLocations != null && !timeLocations.isEmpty()) { + for (CourseDto.TimeLocation timeLocationDto : timeLocations) { + TimeLocation timeLocation = TimeLocation.builder() + .course_id(course) + .day(timeLocationDto.getDay()) + .startPeriod(timeLocationDto.getStartPeriod()) + .endPeriod(timeLocationDto.getEndPeriod()) + .location(timeLocationDto.getLocation()) + .build(); + + timeLocationRepository.save(timeLocation); + } + } + } + } } diff --git a/src/main/java/com/example/Devkor_project/util/ClassTime.java b/src/main/java/com/example/Devkor_project/util/ClassTime.java new file mode 100644 index 0000000..0c7f642 --- /dev/null +++ b/src/main/java/com/example/Devkor_project/util/ClassTime.java @@ -0,0 +1,36 @@ +package com.example.Devkor_project.util; + +import com.example.Devkor_project.exception.AppException; +import com.example.Devkor_project.exception.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalTime; + +@AllArgsConstructor +@Getter +public enum ClassTime +{ + PERIOD_1(1, LocalTime.of(9, 0), LocalTime.of(10, 15)), + PERIOD_2(2, LocalTime.of(10, 30), LocalTime.of(11, 45)), + PERIOD_3(3, LocalTime.of(12, 0), LocalTime.of(13, 15)), + PERIOD_4(4, LocalTime.of(13, 30), LocalTime.of(14, 45)), + PERIOD_5(5, LocalTime.of(15, 0), LocalTime.of(16, 15)), + PERIOD_6(6, LocalTime.of(16, 30), LocalTime.of(17, 45)), + PERIOD_7(7, LocalTime.of(18, 0), LocalTime.of(19, 15)), + PERIOD_8(8, LocalTime.of(19, 30), LocalTime.of(20, 45)); + + private final int period; + private final LocalTime startTime; + private final LocalTime endTime; + + public static ClassTime fromPeriod(int period) { + for (ClassTime classTime : values()) { + if (classTime.getPeriod() == period) { + return classTime; + } + } + throw new AppException(ErrorCode.INVALID_PERIOD, period); + } +} + diff --git a/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java new file mode 100644 index 0000000..87c1e82 --- /dev/null +++ b/src/main/java/com/example/Devkor_project/util/TimeLocationParser.java @@ -0,0 +1,80 @@ +package com.example.Devkor_project.util; + +import com.example.Devkor_project.dto.CourseDto; +import com.example.Devkor_project.exception.AppException; +import com.example.Devkor_project.exception.ErrorCode; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TimeLocationParser { + + /** + * Parses a time_location string to List. + * For example: "화(6-8) 로봇융합관 301호\n목(6)" -> [ + * { + * day: "화" + * startPeriod: 6 + * endPeriod: 8 + * location: "로봇융합관 301호" + * }, + * { + * day: "목" + * startPeriod: 6 + * endPeriod: 6 + * location: null + * } + * ] + * + * @param timeLocations The time_location string to parse. + * @return List representing List of { day, startPeriod, endPeriod, location }. + */ + public static List parseTimeLocation(String timeLocations) + { + if (timeLocations == null || timeLocations.isBlank()) + return null; + + List timeLocationList = new ArrayList<>(); + String[] lines = timeLocations.split("\\n"); + + // Regular expression to extract start and end periods + Pattern pattern = Pattern.compile("([가-힣])\\((\\d+)(?:-(\\d+))?\\)(?:\\s(.+))?|([가-힣])\\s?(\\S.*)?|\\((\\d+)-(\\d+)\\)"); + + for (String line : lines) { + Matcher matcher = pattern.matcher(line.trim()); + + if (matcher.find()) { + String day = matcher.group(1) != null ? matcher.group(1) : matcher.group(5); // 요일 추출 + String start = matcher.group(2); + String end = matcher.group(3); + String location = matcher.group(4) != null ? matcher.group(4) : matcher.group(6); // 강의실 추출 + + // "요일"과 "시작교시-끝교시"가 모두 없는 경우 처리 (예: (8-10) 형태) + if (matcher.group(7) != null) { + start = matcher.group(7); + end = matcher.group(8); + day = null; + location = null; + } + + Integer startPeriod = (start != null) ? Integer.parseInt(start) : null; + Integer endPeriod = (start != null && end != null) ? Integer.valueOf(Integer.parseInt(end)) : startPeriod; + + CourseDto.TimeLocation timeLocation = CourseDto.TimeLocation.builder() + .day(day) + .startPeriod(startPeriod) + .endPeriod(endPeriod) + .location(location) + .build(); + + timeLocationList.add(timeLocation); + } else { + throw new AppException(ErrorCode.INVALID_TIME_LOCATION, "Invalid format: " + timeLocations); + } + } + + return timeLocationList; + } +}