From 3aee1f5179228e845c8597a8489d4db0dcb46458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Fri, 26 Sep 2025 22:03:33 +0200 Subject: [PATCH 1/3] Replace subjects with custom --- .../pkwmtt/timetable/TimetableController.java | 23 +++- .../timetable/TimetableExceptionHandler.java | 17 +-- .../pkwmtt/timetable/TimetableService.java | 126 +++++++++++++++--- .../pkwmtt/timetable/dto/CustomSubject.java | 15 +++ .../timetable/dto/CustomSubjectFilterDTO.java | 14 ++ .../pkwmtt/timetable/dto/DayOfWeekDTO.java | 102 +++++++++++--- .../pkwmtt/timetable/enums/TypeOfWeek.java | 5 + .../timetable/TimetableServiceTest.java | 81 ++++++----- 8 files changed, 289 insertions(+), 94 deletions(-) create mode 100644 src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java create mode 100644 src/main/java/org/pkwmtt/timetable/dto/CustomSubjectFilterDTO.java create mode 100644 src/main/java/org/pkwmtt/timetable/enums/TypeOfWeek.java diff --git a/src/main/java/org/pkwmtt/timetable/TimetableController.java b/src/main/java/org/pkwmtt/timetable/TimetableController.java index f7490d6..e20ec85 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableController.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableController.java @@ -5,10 +5,12 @@ import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.pkwmtt.exceptions.WebPageContentNotAvailableException; +import org.pkwmtt.timetable.dto.CustomSubjectFilterDTO; import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; import static java.util.Objects.isNull; @@ -29,13 +31,26 @@ public class TimetableController { * @throws WebPageContentNotAvailableException . */ @GetMapping("/{generalGroupName}") - public ResponseEntity getGeneralGroupSchedule (@PathVariable String generalGroupName, @RequestParam(required = false, name = "sub") List subgroups) + public ResponseEntity getGeneralGroupSchedule (@PathVariable String generalGroupName, + @RequestParam(required = false, name = "sub") List subgroups, + @RequestBody(required = false) List customSubjects) throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, SpecifiedSubGroupDoesntExistsException, JsonProcessingException { + var areSubgroupsProvided = !(isNull(subgroups) || subgroups.isEmpty()); + var areCustomSubjectsProvided = !(isNull(customSubjects) || customSubjects.isEmpty()); - if (isNull(subgroups) || subgroups.isEmpty()) { - return ResponseEntity.ok(cachedService.getGeneralGroupSchedule(generalGroupName)); + if (areSubgroupsProvided) { + if (!areCustomSubjectsProvided) { + customSubjects = new ArrayList<>(); + } + + return ResponseEntity.ok(service.getFilteredGeneralGroupSchedule( + generalGroupName, + subgroups, + customSubjects + )); + } - return ResponseEntity.ok(service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups)); + return ResponseEntity.ok(cachedService.getGeneralGroupSchedule(generalGroupName)); } /** diff --git a/src/main/java/org/pkwmtt/timetable/TimetableExceptionHandler.java b/src/main/java/org/pkwmtt/timetable/TimetableExceptionHandler.java index bb92055..08944b3 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableExceptionHandler.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableExceptionHandler.java @@ -18,12 +18,10 @@ public class TimetableExceptionHandler { @ExceptionHandler(WebPageContentNotAvailableException.class) @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) - public ResponseEntity handleWebPageContentNotAvailableException (WebPageContentNotAvailableException e) { + public ResponseEntity handleWebPageContentNotAvailableException ( + WebPageContentNotAvailableException e) { log.error("SERVICE_UNAVAILABLE # " + e.getMessage()); - return new ResponseEntity<>( - new ErrorResponseDTO(e.getMessage()), - HttpStatus.SERVICE_UNAVAILABLE - ); + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.SERVICE_UNAVAILABLE); } @ExceptionHandler(JsonProcessingException.class) @@ -32,11 +30,11 @@ public ResponseEntity handleJsonProcessingException (JsonProce log.error("INTERNAL_SERVER_ERROR # " + e.getMessage()); return new ResponseEntity<>( new ErrorResponseDTO("Json Processing Failed"), - HttpStatus.INTERNAL_SERVER_ERROR + HttpStatus.INTERNAL_SERVER_ERROR ); } - @ExceptionHandler({SpecifiedGeneralGroupDoesntExistsException.class, SpecifiedSubGroupDoesntExistsException.class}) + @ExceptionHandler({SpecifiedGeneralGroupDoesntExistsException.class, SpecifiedSubGroupDoesntExistsException.class, IllegalArgumentException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseEntity handleSpecifiedGeneralGroupDoesntExistsException (Exception e) { return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.BAD_REQUEST); @@ -46,9 +44,6 @@ public ResponseEntity handleSpecifiedGeneralGroupDoesntExistsE @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseEntity handleIllegalAccessException (IllegalAccessException e) { log.error("INTERNAL_SERVER_ERROR # " + e.getMessage()); - return new ResponseEntity<>( - new ErrorResponseDTO(e.getMessage()), - HttpStatus.INTERNAL_SERVER_ERROR - ); + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index 55d0ce5..05a9768 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -6,12 +6,12 @@ import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.pkwmtt.exceptions.WebPageContentNotAvailableException; -import org.pkwmtt.timetable.dto.DayOfWeekDTO; -import org.pkwmtt.timetable.dto.SubjectDTO; -import org.pkwmtt.timetable.dto.TimetableDTO; +import org.pkwmtt.timetable.dto.*; +import org.pkwmtt.timetable.enums.TypeOfWeek; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -71,28 +71,110 @@ public List getAvailableSubGroups (String generalGroupName) * Retrieves timetable and filters entries based on subgroups parameters * * @param generalGroupName name of the general group - * @param sub subgroups list + * @param subgroup subgroups list * @return filtered timetable * @throws WebPageContentNotAvailableException if source data can't be retrieved */ - public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, List sub) + public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, + List subgroup, + List customSubjectFilters) throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, JsonProcessingException { - generalGroupName = generalGroupName.toUpperCase(); - //Check if specified subgroup is available for this generalGroup - var subgroups = getAvailableSubGroups(generalGroupName); - for (var group : sub) { - if (!subgroups.contains(group)) { - throw new SpecifiedSubGroupDoesntExistsException(group); - } - } + checkSubGroupAvailability(generalGroupName, subgroup); List schedule = cachedService.getGeneralGroupSchedule(generalGroupName).getData(); + List customSubjects = new ArrayList<>(); + //Mechatronika 13K2 K02 + String finalGeneralGroupName = generalGroupName; + customSubjectFilters.forEach(customFilter -> { + + //Get schedule for specified filter + List tempSchedule = customFilter + .getGeneralGroup() + .equals(finalGeneralGroupName) ? schedule : cachedService + .getGeneralGroupSchedule(customFilter.getGeneralGroup()) + .getData(); + + for (int i = 0; i < tempSchedule.size(); i++) { + int finalI = i; + + tempSchedule.get(i).getEven().forEach(subject -> { + var customSubject = createCustomSubject(TypeOfWeek.EVEN, subject, customFilter, finalI); + + if (customSubject != null) { + customSubjects.add(customSubject); + } + }); + + tempSchedule.get(i).getOdd().forEach(subject -> { + var customSubject = createCustomSubject(TypeOfWeek.ODD, subject, customFilter, finalI); + + if (customSubject != null) { + customSubjects.add(customSubject); + } + }); + } + + }); - for (var day : schedule) { - sub.forEach(day::filterByGroup); + return filterSchedule(schedule, subgroup, generalGroupName, customSubjects); + } + + private CustomSubject createCustomSubject (TypeOfWeek type, + SubjectDTO subject, + CustomSubjectFilterDTO customFilter, + int i) { + if (subject.getName().contains(customFilter.getName())) { + if (subject.getName().contains(customFilter.getSubGroup())) { + return new CustomSubject(subject, customFilter.getSubGroup(), i, type); + } + } + return null; + } + + private TimetableDTO filterSchedule (List schedule, + List subgroups, + String generalGroupName, + List customSubjects) { + for (int i = 0; i < schedule.size(); i++) { + var day = schedule.get(i); + + for (CustomSubject customSubject : customSubjects) { + customSubject.getSubject().deleteTypeAndUnnecessaryCharactersFromName(); + + day.setEven(day + .getEven() + .stream() + .filter( + subject -> !subject.getName().contains(customSubject.getSubject().getName())) + .toList()); + + day.setOdd(day + .getOdd() + .stream() + .filter(subject -> !subject.getName().contains(customSubject.getSubject().getName())) + .toList()); + + } + + int finalI = i; + subgroups.forEach(subgroup -> { + if (customSubjects.isEmpty()) { + day.filterByGroup(subgroup); + return; + } + + var customSubjectsByDay = customSubjects.stream() + //Compare day of week to get only matching days + .filter(subject -> subject.getSubGroup().charAt(0) == subgroup.charAt(0)) // match subgroup + .filter(subject -> subject.getDayOfWeekNumber() == finalI) // match day of week + .toList(); + + day.filterByGroup(subgroup, customSubjectsByDay); + }); + } schedule.forEach(DayOfWeekDTO::deleteSubjectTypesFromNames); @@ -100,9 +182,17 @@ public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, Li return new TimetableDTO(generalGroupName, schedule); } - /** - * @return List of general group's names - */ + private void checkSubGroupAvailability (String generalGroupName, List subgroup) + throws JsonProcessingException { + //Check if specified subgroup is available for this generalGroup + var subgroups = getAvailableSubGroups(generalGroupName); + for (var group : subgroup) { + if (!subgroups.contains(group)) { + throw new SpecifiedSubGroupDoesntExistsException(group); + } + } + } + public List getGeneralGroupList () throws WebPageContentNotAvailableException { return cachedService.getGeneralGroupsMap().keySet().stream().sorted().collect(Collectors.toList()); } diff --git a/src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java b/src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java new file mode 100644 index 0000000..27b37e9 --- /dev/null +++ b/src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java @@ -0,0 +1,15 @@ +package org.pkwmtt.timetable.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.pkwmtt.timetable.enums.TypeOfWeek; + +@Getter +@AllArgsConstructor +public class CustomSubject { + SubjectDTO subject; + String subGroup; + int dayOfWeekNumber; + TypeOfWeek typeOfWeek; + +} diff --git a/src/main/java/org/pkwmtt/timetable/dto/CustomSubjectFilterDTO.java b/src/main/java/org/pkwmtt/timetable/dto/CustomSubjectFilterDTO.java new file mode 100644 index 0000000..e024dc7 --- /dev/null +++ b/src/main/java/org/pkwmtt/timetable/dto/CustomSubjectFilterDTO.java @@ -0,0 +1,14 @@ +package org.pkwmtt.timetable.dto; + +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +@Getter +public class CustomSubjectFilterDTO { + private final String name; + private final String generalGroup; + private final String subGroup; +} diff --git a/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java b/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java index 53d581b..420b473 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java +++ b/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java @@ -1,16 +1,25 @@ package org.pkwmtt.timetable.dto; import lombok.Data; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.pkwmtt.timetable.enums.TypeOfWeek; +import org.springframework.data.util.Pair; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; +@Slf4j @Data public class DayOfWeekDTO { private final String name; + @Setter private List odd; + @Setter private List even; public DayOfWeekDTO (String name) { @@ -44,21 +53,46 @@ public void deleteSubjectTypesFromNames () { * and the last character is the subgroup number */ public void filterByGroup (String group) { + var groupCharAndTargetNumber = getGroupCharAndTargetNumber(group); + + // Apply the filter to both odd- and even-week lists + odd = filter(odd, groupCharAndTargetNumber.getFirst(), groupCharAndTargetNumber.getSecond()); + even = filter(even, groupCharAndTargetNumber.getFirst(), groupCharAndTargetNumber.getSecond()); + + } + + public void filterByGroup (String group, List customSubjects) { //K04 | Mech K05 13K3 + var groupCharAndTargetNumber = getGroupCharAndTargetNumber(group); + + // Apply the filter to both odd- and even-week lists + odd = filter( + odd, groupCharAndTargetNumber.getFirst(), groupCharAndTargetNumber.getSecond(), customSubjects + .stream() + .filter(customSubject -> customSubject.getTypeOfWeek().equals(TypeOfWeek.ODD)) + .toList() + ); + + even = filter( + even, groupCharAndTargetNumber.getFirst(), groupCharAndTargetNumber.getSecond(), customSubjects + .stream() + .filter(customSubject -> customSubject.getTypeOfWeek().equals(TypeOfWeek.EVEN)) + .toList() + ); + + } + + private Pair getGroupCharAndTargetNumber (String group) { // Delete first character if group starts 'G' if (group.charAt(0) == 'G' && group.length() > 3) { group = group.substring(1); } // Extract the group letter (e.g., "K" from "K03") - var groupName = String.valueOf(group.charAt(0)); + var groupChar = String.valueOf(group.charAt(0)); // Extract the subgroup digit (e.g., "3" from "K03") var targetNumber = String.valueOf(group.charAt(group.length() - 1)); - - // Apply the filter to both odd- and even-week lists - odd = filter(odd, groupName, targetNumber); - even = filter(even, groupName, targetNumber); - + return Pair.of(groupChar, targetNumber); } /** @@ -71,14 +105,51 @@ public void filterByGroup (String group) { * @return a filtered list of SubjectDTO */ private List filter (List list, String groupName, String targetNumber) { + return list.stream().filter(item -> hasOnlyTargetGroup(item.getName(), groupName, targetNumber)).toList(); + } + + /* + Student: Jacek, 13K1 K04 + Mechatroniki 13K3 K05 + */ + private List filter (List list, //Lista przedmiotów dla pon odd + String groupName,// K + String targetNumber, // 4 + List customSubjects) { // Mech K 5 13K3 - list = list.stream() - // Keep only items that have no other subgroup codes - .filter(item -> hasOnlyTargetGroup(item.getName(), groupName, targetNumber)) - .toList(); + + list = list + .stream() + .filter(item -> hasOnlyTargetGroup(item.getName(), groupName, targetNumber)) // K04 -> usun K != 4 + .collect(Collectors.toList()); + + for (var customSubject : customSubjects) { + list.add(customSubject.getSubject()); + } + + list.sort(Comparator.comparingInt(SubjectDTO::getRowId)); return list; } + /* + try { + CustomSubjectFilterDTO customSubjectMatchingName = customSubjects + .stream() + .filter(subject -> item.getName().contains(subject.getName())) + .toList() + .getFirst(); + + String customGroupName = String.valueOf(customSubjectMatchingName.getSubGroup().charAt(0)); //K + String customTargetNumber = String.valueOf(customSubjectMatchingName.getSubGroup().charAt(2)); //5 + + return hasOnlyTargetGroup(item.getName(), customGroupName, customTargetNumber); + + } catch (NoSuchElementException e) { + return hasOnlyTargetGroup(item.getName(), groupName, targetNumber); + } + }).toList(); + + */ /** * Checks if the given element string contains no other codes for the same group.* @@ -89,20 +160,13 @@ private List filter (List list, String groupName, String * @return true if no non-target subgroup codes are present */ private boolean hasOnlyTargetGroup (String element, String groupName, String targetNumber) { - var pattern = Pattern.compile(String.format( - "\\bG?[%s]0[1-9]\\b", - Pattern.quote(groupName) - )); + var pattern = Pattern.compile(String.format("\\bG?[%s]0[1-9]\\b", Pattern.quote(groupName))); var matcher = pattern.matcher(element); if (!matcher.find()) { return true; } - pattern = Pattern.compile(String.format( - "%s0%s", - Pattern.quote(groupName), - Pattern.quote(targetNumber) - )); + pattern = Pattern.compile(String.format("%s0%s", Pattern.quote(groupName), Pattern.quote(targetNumber))); matcher = pattern.matcher(element); return matcher.find(); } diff --git a/src/main/java/org/pkwmtt/timetable/enums/TypeOfWeek.java b/src/main/java/org/pkwmtt/timetable/enums/TypeOfWeek.java new file mode 100644 index 0000000..e09ea53 --- /dev/null +++ b/src/main/java/org/pkwmtt/timetable/enums/TypeOfWeek.java @@ -0,0 +1,5 @@ +package org.pkwmtt.timetable.enums; + +public enum TypeOfWeek { + ODD, EVEN, BOTH +} diff --git a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java index 357ccf2..2e048ae 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java @@ -1,11 +1,5 @@ package org.pkwmtt.timetable; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,31 +8,40 @@ import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import test.TestConfig; +import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + @SpringBootTest class TimetableServiceTest extends TestConfig { @Autowired private TimetableService service; - + @BeforeEach - public void initWireMock() { - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.timetableHTML))); - - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.listHTML))); + public void initWireMock () { + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")).willReturn(aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "text/*" + ) + .withBody( + ValuesForTest.timetableHTML))); + + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")).willReturn(aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "text/*" + ) + .withBody(ValuesForTest.listHTML))); } @Test @@ -54,14 +57,14 @@ public void shouldReturnAvailableSubGroups () throws JsonProcessingException { //then assertThat(result).isEqualTo(expectedResult); - + //I don't know why it is in test. I think it is for debug? -// result.forEach(item -> { -// Matcher matcher = pattern.matcher(item); -// if (!matcher.find()) { -// fail("Wrong subgroup format"); -// } -// }); + // result.forEach(item -> { + // Matcher matcher = pattern.matcher(item); + // if (!matcher.find()) { + // fail("Wrong subgroup format"); + // } + // }); } @@ -74,8 +77,8 @@ public void shouldThrow_SpecifiedGeneralGroupDoesntExistsException () { //then assertThrows( - SpecifiedGeneralGroupDoesntExistsException.class, - () -> service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups) + SpecifiedGeneralGroupDoesntExistsException.class, + () -> service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups, new ArrayList<>()) ); } @@ -88,29 +91,23 @@ public void shouldThrow_SpecifiedSubGroupDoesntExistsException () { //then assertThrows( - SpecifiedSubGroupDoesntExistsException.class, - () -> service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups) + SpecifiedSubGroupDoesntExistsException.class, + () -> service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups, new ArrayList<>()) ); } @Test public void shouldReturnSortedGeneralGroupList () { //given - var expectedResult = List.of( - "11A1", - "11K2", - "12K1", - "12K2", - "12K3" - ); + var expectedResult = List.of("11A1", "11K2", "12K1", "12K2", "12K3"); //when var result = service.getGeneralGroupList(); - + //then assertAll( - () -> assertNotNull(result), - () -> assertFalse(result.isEmpty()), - () -> assertEquals(expectedResult, result) + () -> assertNotNull(result), + () -> assertFalse(result.isEmpty()), + () -> assertEquals(expectedResult, result) ); } } \ No newline at end of file From 10e6b768b63d2261aa1488c0583c553c0d7bb2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 27 Sep 2025 18:44:38 +0200 Subject: [PATCH 2/3] Optimize code and add test --- .../pkwmtt/timetable/TimetableController.java | 20 +- .../pkwmtt/timetable/TimetableService.java | 166 +++++++++------- .../pkwmtt/timetable/dto/DayOfWeekDTO.java | 60 +++--- .../org/pkwmtt/timetable/dto/SubjectDTO.java | 2 +- .../CustomSubjectDetails.java} | 5 +- .../parser/TimetableParserService.java | 180 ++++++++++-------- .../timetable/TimetableCacheServiceTest.java | 2 +- .../timetable/TimetableControllerTest.java | 122 +++++++++--- .../timetable/TimetableServiceTest.java | 12 +- 9 files changed, 338 insertions(+), 231 deletions(-) rename src/main/java/org/pkwmtt/timetable/{dto/CustomSubject.java => objects/CustomSubjectDetails.java} (66%) diff --git a/src/main/java/org/pkwmtt/timetable/TimetableController.java b/src/main/java/org/pkwmtt/timetable/TimetableController.java index e20ec85..c5a1bfd 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableController.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableController.java @@ -32,8 +32,24 @@ public class TimetableController { */ @GetMapping("/{generalGroupName}") public ResponseEntity getGeneralGroupSchedule (@PathVariable String generalGroupName, - @RequestParam(required = false, name = "sub") List subgroups, - @RequestBody(required = false) List customSubjects) + @RequestParam(required = false, name = "sub") List subgroups) + throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, SpecifiedSubGroupDoesntExistsException, JsonProcessingException { + var areSubgroupsProvided = !(isNull(subgroups) || subgroups.isEmpty()); + + return + areSubgroupsProvided ? + ResponseEntity.ok(service.getFilteredGeneralGroupSchedule( + generalGroupName, + subgroups, + new ArrayList<>() + )) + : ResponseEntity.ok(cachedService.getGeneralGroupSchedule(generalGroupName)); + } + + @PostMapping(value = "/{generalGroupName}", consumes = "application/json", produces = "application/json") + public ResponseEntity getGeneralGroupScheduleWithCustomSubjects (@PathVariable String generalGroupName, + @RequestParam(required = false, name = "sub") List subgroups, + @RequestBody(required = false) List customSubjects) throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, SpecifiedSubGroupDoesntExistsException, JsonProcessingException { var areSubgroupsProvided = !(isNull(subgroups) || subgroups.isEmpty()); var areCustomSubjectsProvided = !(isNull(customSubjects) || customSubjects.isEmpty()); diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index 05a9768..dc862bc 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -8,6 +8,7 @@ import org.pkwmtt.exceptions.WebPageContentNotAvailableException; import org.pkwmtt.timetable.dto.*; import org.pkwmtt.timetable.enums.TypeOfWeek; +import org.pkwmtt.timetable.objects.CustomSubjectDetails; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -79,102 +80,86 @@ public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, List subgroup, List customSubjectFilters) throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, JsonProcessingException { + //Uppercase name to assure match generalGroupName = generalGroupName.toUpperCase(); + //Check if specified subgroup is available for general group or else throw checkSubGroupAvailability(generalGroupName, subgroup); + //Get user's schedule List schedule = cachedService.getGeneralGroupSchedule(generalGroupName).getData(); - List customSubjects = new ArrayList<>(); - //Mechatronika 13K2 K02 - String finalGeneralGroupName = generalGroupName; + //Get schedule to extract customSubject details + List customSubjects = + createListOfCustomSchedulesDetails(generalGroupName, customSubjectFilters, schedule); + + return filterSchedule(schedule, subgroup, generalGroupName, customSubjects); + } + + private List createListOfCustomSchedulesDetails (String generalGroupName, + List customSubjectFilters, + List schedule) { + List customSubjectsDetails = new ArrayList<>(); customSubjectFilters.forEach(customFilter -> { //Get schedule for specified filter - List tempSchedule = customFilter + List customSubjectSchedule = customFilter .getGeneralGroup() - .equals(finalGeneralGroupName) ? schedule : cachedService + .equals(generalGroupName) ? schedule : cachedService .getGeneralGroupSchedule(customFilter.getGeneralGroup()) .getData(); - for (int i = 0; i < tempSchedule.size(); i++) { - int finalI = i; + //Add detail like classroom and rowId + //Go by days: Monday, Tuesday etc... + for (int i = 0; i < customSubjectSchedule.size(); i++) { + //Find subjects matching filters + customSubjectsDetails.addAll( + searchDayOfWeekAndAddCustomSubjectsDetails( + customSubjectSchedule.get(i).getEven(), customFilter, i, + TypeOfWeek.EVEN + )); - tempSchedule.get(i).getEven().forEach(subject -> { - var customSubject = createCustomSubject(TypeOfWeek.EVEN, subject, customFilter, finalI); - - if (customSubject != null) { - customSubjects.add(customSubject); - } - }); - - tempSchedule.get(i).getOdd().forEach(subject -> { - var customSubject = createCustomSubject(TypeOfWeek.ODD, subject, customFilter, finalI); - - if (customSubject != null) { - customSubjects.add(customSubject); - } - }); + customSubjectsDetails.addAll( + searchDayOfWeekAndAddCustomSubjectsDetails( + customSubjectSchedule.get(i).getOdd(), customFilter, i, + TypeOfWeek.ODD + )); } - }); - - return filterSchedule(schedule, subgroup, generalGroupName, customSubjects); + return customSubjectsDetails; } - private CustomSubject createCustomSubject (TypeOfWeek type, - SubjectDTO subject, - CustomSubjectFilterDTO customFilter, - int i) { - if (subject.getName().contains(customFilter.getName())) { - if (subject.getName().contains(customFilter.getSubGroup())) { - return new CustomSubject(subject, customFilter.getSubGroup(), i, type); - } + private List searchDayOfWeekAndAddCustomSubjectsDetails (List day, + CustomSubjectFilterDTO customFilter, + int dayIndex, + TypeOfWeek typeOfWeek) { + var matches = day.stream() + //Filter by matching name and subgroup from customFilter + .filter(item -> (item.getName().contains(customFilter.getName()) && item + .getName() + .contains(customFilter.getSubGroup()))).toList(); + + if (!matches.isEmpty()) { + return matches + .stream() + .map((item) -> new CustomSubjectDetails(item, customFilter.getSubGroup(), dayIndex, typeOfWeek)) + .toList(); } - return null; + return new ArrayList<>(); } private TimetableDTO filterSchedule (List schedule, List subgroups, String generalGroupName, - List customSubjects) { + List customSubjectsDetails) { + //Go through user's schedule day by day for (int i = 0; i < schedule.size(); i++) { var day = schedule.get(i); - for (CustomSubject customSubject : customSubjects) { - customSubject.getSubject().deleteTypeAndUnnecessaryCharactersFromName(); - - day.setEven(day - .getEven() - .stream() - .filter( - subject -> !subject.getName().contains(customSubject.getSubject().getName())) - .toList()); - - day.setOdd(day - .getOdd() - .stream() - .filter(subject -> !subject.getName().contains(customSubject.getSubject().getName())) - .toList()); - - } - - int finalI = i; - subgroups.forEach(subgroup -> { - if (customSubjects.isEmpty()) { - day.filterByGroup(subgroup); - return; - } - - var customSubjectsByDay = customSubjects.stream() - //Compare day of week to get only matching days - .filter(subject -> subject.getSubGroup().charAt(0) == subgroup.charAt(0)) // match subgroup - .filter(subject -> subject.getDayOfWeekNumber() == finalI) // match day of week - .toList(); - - day.filterByGroup(subgroup, customSubjectsByDay); - }); - + //delete subjects colliding with custom subjects by name + deleteSubjectsCollidingWithCustomFilters(customSubjectsDetails, day); + //Filter by user's subgroups + filterDayByUsersSubgroups(subgroups, customSubjectsDetails, day, i); } schedule.forEach(DayOfWeekDTO::deleteSubjectTypesFromNames); @@ -182,6 +167,49 @@ private TimetableDTO filterSchedule (List schedule, return new TimetableDTO(generalGroupName, schedule); } + private void filterDayByUsersSubgroups (List subgroups, + List customSubjectsDetails, + DayOfWeekDTO day, int dayIndex) { + subgroups.forEach(subgroup -> { + if (customSubjectsDetails.isEmpty()) { + day.filterByGroup(subgroup); + return; + } + + var customSubjectsByDay = customSubjectsDetails.stream() + //Compare day of week and subgroup + .filter(subject -> subject.getSubGroup().charAt(0) == subgroup.charAt(0)) // match subgroup + .filter(subject -> subject.getDayOfWeekNumber() == dayIndex) // match day of week + .toList(); + + day.filterByGroup(subgroup, customSubjectsByDay); + }); + } + + private void deleteSubjectsCollidingWithCustomFilters (List customSubjectsDetails, + DayOfWeekDTO day) { + for (CustomSubjectDetails customSubjectDetail : customSubjectsDetails) { + customSubjectDetail.getSubject().deleteTypeAndUnnecessaryCharactersFromName(); + + day.setEven(day + .getEven() + .stream() + .filter( + subject -> !subject + .getName() + .contains(customSubjectDetail.getSubject().getName())) + .toList()); + + day.setOdd(day + .getOdd() + .stream() + .filter( + subject -> !subject.getName().contains(customSubjectDetail.getSubject().getName())) + .toList()); + + } + } + private void checkSubGroupAvailability (String generalGroupName, List subgroup) throws JsonProcessingException { //Check if specified subgroup is available for this generalGroup diff --git a/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java b/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java index 420b473..4770bdd 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java +++ b/src/main/java/org/pkwmtt/timetable/dto/DayOfWeekDTO.java @@ -4,6 +4,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.pkwmtt.timetable.enums.TypeOfWeek; +import org.pkwmtt.timetable.objects.CustomSubjectDetails; import org.springframework.data.util.Pair; import java.util.ArrayList; @@ -28,16 +29,19 @@ public DayOfWeekDTO (String name) { even = new ArrayList<>(); } - - public void add (SubjectDTO subjectDTO, boolean isNotOdd) { - if (isNotOdd) { - even.add(subjectDTO); - } else { - odd.add(subjectDTO); + /** + * Add subject by week Type + * + * @param subjectDTO - subject + * @param typeOfWeek - type of week + */ + public void add (SubjectDTO subjectDTO, TypeOfWeek typeOfWeek) { + switch (typeOfWeek) { + case EVEN -> this.even.add(subjectDTO); + case ODD -> this.odd.add(subjectDTO); } } - public void deleteSubjectTypesFromNames () { even.forEach(SubjectDTO::deleteTypeAndUnnecessaryCharactersFromName); odd.forEach(SubjectDTO::deleteTypeAndUnnecessaryCharactersFromName); @@ -61,7 +65,7 @@ public void filterByGroup (String group) { } - public void filterByGroup (String group, List customSubjects) { //K04 | Mech K05 13K3 + public void filterByGroup (String group, List customSubjects) { var groupCharAndTargetNumber = getGroupCharAndTargetNumber(group); // Apply the filter to both odd- and even-week lists @@ -108,14 +112,19 @@ private List filter (List list, String groupName, String return list.stream().filter(item -> hasOnlyTargetGroup(item.getName(), groupName, targetNumber)).toList(); } - /* - Student: Jacek, 13K1 K04 - Mechatroniki 13K3 K05 + /** + * Filter by subgroup char and number + * + * @param list list of subjects for specific day + * @param groupName - name of subgroup + * @param targetNumber - number fo subgroup + * @param customSubjects - custom subjects added by user + * @return modified list of subjects */ - private List filter (List list, //Lista przedmiotów dla pon odd - String groupName,// K - String targetNumber, // 4 - List customSubjects) { // Mech K 5 13K3 + private List filter (List list, + String groupName, + String targetNumber, + List customSubjects) { list = list @@ -124,32 +133,13 @@ private List filter (List list, //Lista przedmiotów dla .collect(Collectors.toList()); for (var customSubject : customSubjects) { - list.add(customSubject.getSubject()); + list.add(customSubject.getSubject().setCustom(true)); } list.sort(Comparator.comparingInt(SubjectDTO::getRowId)); return list; } - /* - try { - CustomSubjectFilterDTO customSubjectMatchingName = customSubjects - .stream() - .filter(subject -> item.getName().contains(subject.getName())) - .toList() - .getFirst(); - - String customGroupName = String.valueOf(customSubjectMatchingName.getSubGroup().charAt(0)); //K - String customTargetNumber = String.valueOf(customSubjectMatchingName.getSubGroup().charAt(2)); //5 - - return hasOnlyTargetGroup(item.getName(), customGroupName, customTargetNumber); - - } catch (NoSuchElementException e) { - return hasOnlyTargetGroup(item.getName(), groupName, targetNumber); - } - }).toList(); - - */ /** * Checks if the given element string contains no other codes for the same group.* diff --git a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java index 141c80a..97f7504 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java +++ b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java @@ -13,7 +13,7 @@ public class SubjectDTO { private String classroom; private int rowId; private SubjectType type; - + private Boolean custom = false; public void deleteTypeAndUnnecessaryCharactersFromName () { if (name.contains(" ")) { diff --git a/src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java b/src/main/java/org/pkwmtt/timetable/objects/CustomSubjectDetails.java similarity index 66% rename from src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java rename to src/main/java/org/pkwmtt/timetable/objects/CustomSubjectDetails.java index 27b37e9..8f5afef 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/CustomSubject.java +++ b/src/main/java/org/pkwmtt/timetable/objects/CustomSubjectDetails.java @@ -1,12 +1,13 @@ -package org.pkwmtt.timetable.dto; +package org.pkwmtt.timetable.objects; import lombok.AllArgsConstructor; import lombok.Getter; +import org.pkwmtt.timetable.dto.SubjectDTO; import org.pkwmtt.timetable.enums.TypeOfWeek; @Getter @AllArgsConstructor -public class CustomSubject { +public class CustomSubjectDetails { SubjectDTO subject; String subGroup; int dayOfWeekNumber; diff --git a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java index 501a9e8..0973935 100644 --- a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java +++ b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java @@ -8,6 +8,7 @@ import org.pkwmtt.timetable.dto.DayOfWeekDTO; import org.pkwmtt.timetable.dto.SubjectDTO; import org.pkwmtt.examCalendar.enums.SubjectType; +import org.pkwmtt.timetable.enums.TypeOfWeek; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -20,47 +21,48 @@ @Slf4j @Service public class TimetableParserService { - + /** * Alters html code for it to fit parsing process * * @param html webpage content * @return altered html code */ - private String clean(String html) { + private String clean (String html) { return html.replaceAll("
", " ") - .replaceAll(Pattern.quote("-(N"), "-(N") - .replaceAll(Pattern.quote("-(P"), "-(P") - .replaceAll(Pattern.quote("-(p"), "-(p") - .replaceAll(Pattern.quote("-(n"), "-(n") - .replaceAll(Pattern.quote(""), "") - .replaceAll(Pattern.quote(""), "") - .replaceAll(Pattern.quote("J ang"), "J_ang") - .replaceAll(Pattern.quote("J niemiecki"), "J_niemiecki") - .replaceAll(Pattern.quote("WF hala ("), "WF_hala_(") - .replaceAll(Pattern.quote(" "), ""); - + .replaceAll(Pattern.quote("-(N"), "-(N") + .replaceAll(Pattern.quote("-(P"), "-(P") + .replaceAll(Pattern.quote("-(p"), "-(p") + .replaceAll(Pattern.quote("-(n"), "-(n") + .replaceAll(Pattern.quote(""), "") + .replaceAll(Pattern.quote(""), "") + .replaceAll(Pattern.quote("J ang"), "J_ang") + .replaceAll(Pattern.quote("J niemiecki"), "J_niemiecki") + .replaceAll(Pattern.quote("WF hala ("), "WF_hala_(") + .replaceAll(Pattern.quote(" "), ""); + } - + /** * Extrude hours list from webpage * * @param html subpage of any general group * @return List of hours: List of Strings */ - public List parseHours(String html) { + public List parseHours (String html) { //Parse html code to Document object (allows to query elements) Document document = Jsoup.parse(clean(html)); - + List hours = new ArrayList<>(); - - for (Element item : document.select("td.g")) + + for (Element item : document.select("td.g")) { hours.add(item.text()); - + } + return hours; } - + /** * Parse webpage html to map of general groups containing name of a group and link * to subpage with its timetable @@ -68,58 +70,59 @@ public List parseHours(String html) { * @param html .../list containing list of general groups * @return map of general groups in format [GroupName: URL] */ - public Map parseGeneralGroups(String html) { + public Map parseGeneralGroups (String html) { Document document = Jsoup.parse(html); - + Map generalGroups = new HashMap<>(); - - for (Element item : document.select("#oddzialy .el a")) + + for (Element item : document.select("#oddzialy .el a")) { generalGroups.put(item.text(), item.attr("href")); - + } + return generalGroups; } - + /** * Parse html of specific General Group webpage to lists of subjects * * @param html of general group webpage * @return list of subjects sorted by day and odd or even type */ - public List parse(String html) { + public List parse (String html) { Document document = Jsoup.parse(clean(html)); Elements rows = extractRows(document); - + List days = parseHeaders(rows); - + //Remove header row rows.removeFirst(); - + //Go every row for (int rowId = 0; rowId < rows.size(); rowId++) { Element row = rows.get(rowId); Elements cell = row.select("td.l"); - + //Go every cell in a row for (int columnId = 0; columnId < cell.size(); columnId++) { Elements items = getValidItems(cell.get(columnId)); - + //Go every item in column for (int itemId = 0; itemId < items.size() - 1; itemId += 2) { boolean notOdd; String name = items.get(itemId).text(); String classroom = items.get(itemId + 1).text(); - + notOdd = isNameNotOdd(name); - + SubjectDTO subject = buildSubject(name, classroom, rowId); - - days.get(columnId).add(subject, notOdd); + + days.get(columnId).add(subject, notOdd ? TypeOfWeek.EVEN : TypeOfWeek.ODD); } } } return days; } - + /** * Cleans names from unnecessary and unwanted characters * @@ -128,78 +131,87 @@ public List parse(String html) { * @param rowId timetable row id * @return subject with cleaned data */ - private SubjectDTO buildSubject(String rawName, String rawClassroom, int rowId) { + private SubjectDTO buildSubject (String rawName, String rawClassroom, int rowId) { String name = cleanSubjectName(rawName); String classroom = cleanClassroomName(rawClassroom); SubjectType type = extractSubjectTypeFromName(name); - + return new SubjectDTO() - .setName(name) - .setClassroom(classroom) - .setRowId(rowId) - .setType(type); + .setName(name) + .setClassroom(classroom) + .setRowId(rowId) + .setType(type); } - + /** * Finds items containing data in cell * * @param cell from timetable * @return items from cell */ - private Elements getValidItems(Element cell) { + private Elements getValidItems (Element cell) { Elements items = cell.select("span"); items.removeIf(item -> item.text().contains("#") || item.text().length() == 2); return items; } - + /** * Extracts subject type from its name * * @param name subject name * @return subject type or empty string if there isn't any specified */ - private SubjectType extractSubjectTypeFromName(String name) { + private SubjectType extractSubjectTypeFromName (String name) { name = name.trim(); - if (name.endsWith("W")) return SubjectType.LECTURE; - if (name.endsWith("S")) return SubjectType.SEMINAR; - if (name.endsWith("Ć")) return SubjectType.EXERCISES; - + if (name.endsWith("W")) { + return SubjectType.LECTURE; + } + if (name.endsWith("S")) { + return SubjectType.SEMINAR; + } + if (name.endsWith("Ć")) { + return SubjectType.EXERCISES; + } + Pattern laboratoryPattern = Pattern.compile("(? parseHeaders(Elements rows) { + private List parseHeaders (Elements rows) { List days = new ArrayList<>(); Elements headers = rows.getFirst().select("th"); for (int i = 2; i < headers.size(); i++) { @@ -207,22 +219,24 @@ private List parseHeaders(Elements rows) { } return days; } - - private String cleanClassroomName(String text) { - if (text.contains("-p")) + + private String cleanClassroomName (String text) { + if (text.contains("-p")) { return text.replace("-p", ""); - if (text.contains("-n")) + } + if (text.contains("-n")) { return text.replace("-n", ""); + } return text; } - + /** * Deletes all unnecessary characters in subject name * * @param text subject name * @return cleaned name */ - private String cleanSubjectName(String text) { + private String cleanSubjectName (String text) { text = text.replaceAll("-", ""); text = deleteEvenMark(text); text = deleteOddMark(text); @@ -230,39 +244,43 @@ private String cleanSubjectName(String text) { text = text.replaceAll(Pattern.quote("."), ""); return text; } - + /** * Deletes marks of odd day * * @param text subject name * @return altered text */ - private String deleteEvenMark(String text) { - if (text.contains("(P")) + private String deleteEvenMark (String text) { + if (text.contains("(P")) { return text.replace("(P", ""); - if (text.contains("(p")) + } + if (text.contains("(p")) { return text.replace("(p", ""); - + } + return text; } - + /** * Deletes marks of even day * * @param text subject name * @return altered text */ - private String deleteOddMark(String text) { - + private String deleteOddMark (String text) { + text = text.replaceAll("-", ""); - - if (text.contains("(N")) + + if (text.contains("(N")) { return text.replace("(N", ""); - if (text.contains("(n")) + } + if (text.contains("(n")) { return text.replace("(n", ""); + } return text; } - + /** * Checks if subjects name isn't odd * @@ -270,7 +288,7 @@ private String deleteOddMark(String text) { * @return true if subject isn't odd and * false if subject is odd */ - private boolean isNameNotOdd(String name) { + private boolean isNameNotOdd (String name) { return !name.contains("(N") && !name.contains("-(n"); } } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java index c4c80a3..598ea30 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java @@ -40,7 +40,7 @@ public void initWireMock () { } @Test - @Disabled("hard coded values") + @Disabled("Values for hours are hard coded in endpoint for now") public void shouldHourListBePresentInCache () { //given var key = "hourList"; diff --git a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java index 2636a70..1c4d195 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java @@ -4,15 +4,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pkwmtt.ValuesForTest; +import org.pkwmtt.examCalendar.enums.SubjectType; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; +import org.pkwmtt.timetable.dto.CustomSubjectFilterDTO; +import org.pkwmtt.timetable.dto.SubjectDTO; import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import test.TestConfig; import java.util.Arrays; @@ -22,7 +23,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.*; @Slf4j @@ -33,27 +33,28 @@ class TimetableControllerTest extends TestConfig { @Autowired private TestRestTemplate restTemplate; - + @BeforeEach - public void initWireMock() { + public void initWireMock () { EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.timetableHTML))); - + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.timetableHTML))); + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.listHTML))); + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.listHTML))); } @Test public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { //given - var url = String.format("http://localhost:%s/pkwmtt/api/v1/timetables/12K1?sub=K01&sub=L01&sub=P01", - port + var url = String.format( + "http://localhost:%s/pkwmtt/api/v1/timetables/12K1?sub=K01&sub=L01&sub=P01", + port ); //when @@ -61,18 +62,75 @@ public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { //then assertAll( - () -> assertEquals(HttpStatus.OK, response.getStatusCode()), - () -> { - var responseBody = response.getBody(); - assertNotNull(responseBody); - }, - () -> { - assertNotNull(response.getBody()); - var responseData = response.getBody().getData(); - assertEquals(5, responseData.size()); - assertEquals(12, responseData.getFirst().getOdd().size()); - assertEquals(6, responseData.getFirst().getEven().size()); - } + () -> assertEquals(HttpStatus.OK, response.getStatusCode()), + () -> { + var responseBody = response.getBody(); + assertNotNull(responseBody); + }, + () -> { + assertNotNull(response.getBody()); + var responseData = response.getBody().getData(); + assertEquals(5, responseData.size()); + assertEquals(12, responseData.getFirst().getOdd().size()); + assertEquals(6, responseData.getFirst().getEven().size()); + } + ); + } + + @Test + public void testGetGeneralGroupScheduleFiltered_withOptionalParamsAndCustomSubjectsForSameGeneralGroup () { + //given + var url = String.format( + "http://localhost:%s/pkwmtt/api/v1/timetables/12K1?sub=K01&sub=L01&sub=P01", + port + ); + List payload = List.of(new CustomSubjectFilterDTO("PKM", "12K1", "K04")); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + var expectedObject = new SubjectDTO() + .setName("PKM") + .setType(SubjectType.COMPUTER_LABORATORY) + .setClassroom("A227") + .setRowId(8) + .setCustom(true); + + //when + + HttpEntity> request = new HttpEntity<>(payload, headers); + ResponseEntity response = restTemplate.postForEntity( + url, + request, + TimetableDTO.class + ); + //then + assertAll( + () -> assertEquals(HttpStatus.OK, response.getStatusCode()), + () -> { + var responseBody = response.getBody(); + assertNotNull(responseBody); + }, + () -> { + assertNotNull(response.getBody()); + var responseData = response.getBody().getData(); + var subject_Monday_Nr10_Odd_Row8 = responseData + .getFirst() + .getOdd() + .stream() + .filter(item -> item.getRowId() == 8).toList().getFirst(); + var subject_Monday_Nr11_Odd_Row9 = responseData + .getFirst() + .getOdd() + .stream() + .filter(item -> item.getRowId() == 9).toList().getFirst(); + assertEquals(subject_Monday_Nr10_Odd_Row8, expectedObject); + assertEquals(subject_Monday_Nr11_Odd_Row9, expectedObject.setRowId(9)); + var subject_Thursday_Nr3_Odd_Row2List = responseData + .get(3) + .getOdd() + .stream() + .filter(item -> item.getRowId() == 2).toList(); + assertEquals(0, subject_Thursday_Nr3_Odd_Row2List.size()); + } ); } @@ -181,7 +239,11 @@ public void shouldReturn_ListOfHours () { Pattern pattern = Pattern.compile(regex); Arrays.stream(response.getBody()).toList().forEach(item -> { Matcher matcher = pattern.matcher(item); - if(!matcher.find()) fail("Wrong hour format"); + if (!matcher.find()) { + fail("Wrong hour format"); + } }); } + + } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java index 2e048ae..09b53d7 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; @@ -48,8 +47,6 @@ public void initWireMock () { public void shouldReturnAvailableSubGroups () throws JsonProcessingException { //given var generalGroupName = "12K1"; - var regex = "^[A-Z]\\d{2}$"; - var pattern = Pattern.compile(regex); var expectedResult = List.of("K01", "K04", "L01", "L02", "L04", "P01", "P04"); //when @@ -58,13 +55,6 @@ public void shouldReturnAvailableSubGroups () throws JsonProcessingException { //then assertThat(result).isEqualTo(expectedResult); - //I don't know why it is in test. I think it is for debug? - // result.forEach(item -> { - // Matcher matcher = pattern.matcher(item); - // if (!matcher.find()) { - // fail("Wrong subgroup format"); - // } - // }); } @@ -110,4 +100,6 @@ public void shouldReturnSortedGeneralGroupList () { () -> assertEquals(expectedResult, result) ); } + + } \ No newline at end of file From 6b04aac56810e1d205bfc039c32144527da4367a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:12:22 +0200 Subject: [PATCH 3/3] Fix: Deleting different types of subjects --- .../pkwmtt/timetable/TimetableService.java | 37 +++++++++++++------ .../parser/TimetableParserService.java | 2 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index dc862bc..1be7e72 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -6,9 +6,13 @@ import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.pkwmtt.exceptions.WebPageContentNotAvailableException; -import org.pkwmtt.timetable.dto.*; +import org.pkwmtt.timetable.dto.CustomSubjectFilterDTO; +import org.pkwmtt.timetable.dto.DayOfWeekDTO; +import org.pkwmtt.timetable.dto.SubjectDTO; +import org.pkwmtt.timetable.dto.TimetableDTO; import org.pkwmtt.timetable.enums.TypeOfWeek; import org.pkwmtt.timetable.objects.CustomSubjectDetails; +import org.pkwmtt.timetable.parser.TimetableParserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -191,25 +195,36 @@ private void deleteSubjectsCollidingWithCustomFilters (List !subject - .getName() - .contains(customSubjectDetail.getSubject().getName())) - .toList()); + day.setEven( + day + .getEven() + .stream() + .filter( + subject -> !(subject + .getName() + .contains(customSubjectDetail.getSubject().getName()) + && subjectsAreSameType(subject, customSubjectDetail)) + ).toList()); day.setOdd(day .getOdd() .stream() .filter( - subject -> !subject.getName().contains(customSubjectDetail.getSubject().getName())) - .toList()); + subject -> !(subject.getName().contains(customSubjectDetail.getSubject().getName()) + && subjectsAreSameType(subject, customSubjectDetail)) + ).toList()); } } + private boolean subjectsAreSameType (SubjectDTO subject, CustomSubjectDetails customSubjectDetails) { + var subjectType = TimetableParserService.extractSubjectTypeFromName(subject.getName()); + var customSubjectType = TimetableParserService.extractSubjectTypeFromName( + customSubjectDetails.getSubGroup()); + return subjectType.equals(customSubjectType); + + } + private void checkSubGroupAvailability (String generalGroupName, List subgroup) throws JsonProcessingException { //Check if specified subgroup is available for this generalGroup diff --git a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java index 0973935..ee060b1 100644 --- a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java +++ b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java @@ -161,7 +161,7 @@ private Elements getValidItems (Element cell) { * @param name subject name * @return subject type or empty string if there isn't any specified */ - private SubjectType extractSubjectTypeFromName (String name) { + public static SubjectType extractSubjectTypeFromName (String name) { name = name.trim(); if (name.endsWith("W")) { return SubjectType.LECTURE;