Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Academ (Back-end)
Academ Back-end repository 입니다.
Academ Back-end repository입니다.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,27 @@ public ResponseEntity<ResponseDto.Success> 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<ResponseDto.Success> 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()
);
}

}
17 changes: 17 additions & 0 deletions src/main/java/com/example/Devkor_project/dto/CourseDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/example/Devkor_project/entity/TimeLocation.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TimeLocation, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand All @@ -51,7 +55,7 @@ public CourseDto.CheckSynchronization checkCourseSynchronization(CrawlingDto.Syn
int delete_count = 0; // 삭제한 강의 정보 개수

// 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보
// 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하지 위함
// 현재 데이터베이스에는 존재하지만, 크롤링한 데이터에는 존재하지 않는 강의 정보를 데이터베이스에서 삭제하기 위함
// 즉, 삭제할 강의 리스트
List<Course> allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester());

Expand Down Expand Up @@ -399,4 +403,33 @@ public void createTestAccount(ProfileDto.CreateTestAccount dto)
// 해당 엔티티를 데이터베이스에 저장
profileRepository.save(profile);
}

/* 강의 시간 및 장소 동기화 서비스 */
@Transactional
public void checkTimeLocationSynchronization(CrawlingDto.Synchronization dto)
{
// 현재 데이터베이스에 존재하는 해당 연도와 학기의 모든 강의 정보
List<Course> allCourseInDatabase = courseRepository.findCourseByYearAndSemester(dto.getYear(), dto.getSemester());

for(Course course:allCourseInDatabase)
{
// 해당 강의의 time_location 정보를 CourseDto.TimeLocation 리스트로 변환
List<CourseDto.TimeLocation> 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);
}
}
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/com/example/Devkor_project/util/ClassTime.java
Original file line number Diff line number Diff line change
@@ -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);
}
}

Original file line number Diff line number Diff line change
@@ -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<CourseDto.TimeLocation>.
* 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<CourseDto.TimeLocation> representing List of { day, startPeriod, endPeriod, location }.
*/
public static List<CourseDto.TimeLocation> parseTimeLocation(String timeLocations)
{
if (timeLocations == null || timeLocations.isBlank())
return null;

List<CourseDto.TimeLocation> 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;
}
}
Loading