diff --git a/.gitignore b/.gitignore index 29fa0bd..95d875a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ **/gradlew **/gradlew.bat **/gradle/** +**/build/** # Compiled class file *.class diff --git a/node/src/main/java/com/bipbup/dao/AppUserConfigDAO.java b/node/src/main/java/com/bipbup/dao/AppUserConfigDAO.java index b50c105..d9b43ec 100644 --- a/node/src/main/java/com/bipbup/dao/AppUserConfigDAO.java +++ b/node/src/main/java/com/bipbup/dao/AppUserConfigDAO.java @@ -2,11 +2,10 @@ import com.bipbup.entity.AppUser; import com.bipbup.entity.AppUserConfig; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public interface AppUserConfigDAO extends JpaRepository { diff --git a/node/src/main/java/com/bipbup/entity/AppUser.java b/node/src/main/java/com/bipbup/entity/AppUser.java index fadc379..05fcc18 100644 --- a/node/src/main/java/com/bipbup/entity/AppUser.java +++ b/node/src/main/java/com/bipbup/entity/AppUser.java @@ -17,7 +17,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.CreationTimestamp; @Getter @@ -53,8 +52,8 @@ public class AppUser { private String username; @NotNull - @ColumnDefault("'USER'") + @Builder.Default @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false, length = 32) - private Role role; + private Role role = Role.USER; } diff --git a/node/src/main/java/com/bipbup/entity/AppUserConfig.java b/node/src/main/java/com/bipbup/entity/AppUserConfig.java index 8d80e6b..e49b7d3 100644 --- a/node/src/main/java/com/bipbup/entity/AppUserConfig.java +++ b/node/src/main/java/com/bipbup/entity/AppUserConfig.java @@ -1,6 +1,7 @@ package com.bipbup.entity; import com.bipbup.enums.impl.ExperienceParam; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -64,10 +65,10 @@ public class AppUserConfig { private AppUser appUser; @Builder.Default - @OneToMany(mappedBy = "config", fetch = FetchType.EAGER) + @OneToMany(mappedBy = "config", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private List educationLevels = new ArrayList<>(); @Builder.Default - @OneToMany(mappedBy = "config", fetch = FetchType.EAGER) + @OneToMany(mappedBy = "config", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private List scheduleTypes = new ArrayList<>(); } diff --git a/node/src/main/java/com/bipbup/handlers/impl/callback/QueryMenuStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/callback/QueryMenuStateHandler.java index 6af3029..500052e 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/callback/QueryMenuStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/callback/QueryMenuStateHandler.java @@ -6,14 +6,22 @@ import com.bipbup.entity.EducationLevel; import com.bipbup.entity.ScheduleType; import com.bipbup.enums.AppUserState; -import static com.bipbup.enums.AppUserState.QUERY_DELETE_STATE; -import static com.bipbup.enums.AppUserState.QUERY_MENU_STATE; -import static com.bipbup.enums.AppUserState.QUERY_UPDATE_STATE; import com.bipbup.enums.EnumParam; import com.bipbup.handlers.StateHandler; import com.bipbup.handlers.impl.message.BasicStateHandler; -import com.bipbup.service.db.ConfigService; import com.bipbup.service.cache.UserStateCacheService; +import com.bipbup.service.db.ConfigService; +import com.bipbup.utils.Decoder; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + + +import static com.bipbup.enums.AppUserState.QUERY_DELETE_STATE; +import static com.bipbup.enums.AppUserState.QUERY_MENU_STATE; +import static com.bipbup.enums.AppUserState.QUERY_UPDATE_STATE; import static com.bipbup.utils.CommandMessageConstants.BotCommand.MYQUERIES; import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.CONFIG_NOT_FOUND; import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.DELETE_CONFIRMATION; @@ -24,12 +32,6 @@ import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.MENU_QUERY; import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.MENU_SCHEDULE; import static com.bipbup.utils.CommandMessageConstants.Prefix; -import com.bipbup.utils.Decoder; -import java.util.List; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; @Slf4j @Component @@ -37,97 +39,103 @@ @RequiredArgsConstructor public class QueryMenuStateHandler implements StateHandler { - private final UserStateCacheService userStateCacheService; - - private final ConfigService configService; - - private final Decoder decoder; - - private final BasicStateHandler basicStateHandler; - - @Override - public String process(AppUser user, String input) { - if (isBackToQueryListCommand(input)) { - return processBackToQueryListCommand(user); - } - if (hasDeletePrefix(input)) { - return processConfigActionCommand(user, input, QUERY_DELETE_STATE); - } - if (hasUpdatePrefix(input)) { - return processConfigActionCommand(user, input, QUERY_UPDATE_STATE); - } - - return ""; - } - - @Override - public AppUserState state() { - return QUERY_MENU_STATE; - } - - private String processBackToQueryListCommand(AppUser user) { - return basicStateHandler.process(user, MYQUERIES.getCommand()); - } - - private boolean isBackToQueryListCommand(String input) { - return MYQUERIES.getCommand().equals(input); - } - - private boolean hasUpdatePrefix(String input) { - return input.startsWith(Prefix.UPDATE); - } - - private boolean hasDeletePrefix(String input) { - return input.startsWith(Prefix.DELETE); - } - - private void appendEnumParams(StringBuilder output, List values, String prefix) { - if (!values.isEmpty()) { - output.append('\n').append(prefix).append('\n'); - - String paramNames = - values.stream().map(param -> " - " + param.getDescription()) - .sorted() - .collect(Collectors.joining("\n")); - - output.append(paramNames); - } - } - - private String showDetailedQueryOutput(AppUserConfig config) { - StringBuilder output = - new StringBuilder().append(MENU_CONFIG_NAME.getTemplate()).append(config.getConfigName()).append("\n") - .append(MENU_QUERY.getTemplate()).append(config.getQueryText()).append("\n") - .append(MENU_AREA.getTemplate()).append(config.getArea() == null ? "Любой" : config.getArea()) - .append("\n").append(MENU_EXPERIENCE.getTemplate()) - .append(config.getExperience().getDescription()); - - var eduParams = config.getEducationLevels().stream().map(EducationLevel::getParam).toList(); - var scheduleParams = config.getScheduleTypes().stream().map(ScheduleType::getParam).toList(); - - appendEnumParams(output, eduParams, MENU_EDUCATION.getTemplate()); - appendEnumParams(output, scheduleParams, MENU_SCHEDULE.getTemplate()); - - return output.toString(); - } - - private String processConfigActionCommand(AppUser user, String input, AppUserState state) { - var configId = decoder.parseIdFromCallback(input); - var optionalConfig = configService.getConfigById(configId); - - if (optionalConfig.isPresent()) { - var config = optionalConfig.get(); - userStateCacheService.putUserState(user.getTelegramId(), state); - log.info("User {} selected menu action and state set to {}", user.getFirstName(), state); - - if (state == QUERY_UPDATE_STATE) { - return showDetailedQueryOutput(config); - } - - return String.format(DELETE_CONFIRMATION.getTemplate(), config.getConfigName()); - } else { - log.debug("Configuration with id {} not found for user {}", configId, user.getFirstName()); - return CONFIG_NOT_FOUND.getTemplate(); - } - } + private final UserStateCacheService userStateCacheService; + + private final ConfigService configService; + + private final Decoder decoder; + + private final BasicStateHandler basicStateHandler; + + @Override + public String process(AppUser user, String input) { + if (isBackToQueryListCommand(input)) + return processBackToQueryListCommand(user); + if (hasDeletePrefix(input)) + return processConfigActionCommand(user, input, QUERY_DELETE_STATE); + if (hasUpdatePrefix(input)) + return processConfigActionCommand(user, input, QUERY_UPDATE_STATE); + + return ""; + } + + @Override + public AppUserState state() { + return QUERY_MENU_STATE; + } + + private String processBackToQueryListCommand(AppUser user) { + return basicStateHandler.process(user, MYQUERIES.getCommand()); + } + + private boolean isBackToQueryListCommand(String input) { + return MYQUERIES.getCommand().equals(input); + } + + private boolean hasUpdatePrefix(String input) { + return input.startsWith(Prefix.UPDATE); + } + + private boolean hasDeletePrefix(String input) { + return input.startsWith(Prefix.DELETE); + } + + private void appendEnumParams(StringBuilder output, List values, String prefix) { + if (!values.isEmpty()) { + output.append('\n') + .append(prefix) + .append('\n'); + + String paramNames = values.stream() + .map(param -> " - " + param.getDescription()) + .sorted() + .collect(Collectors.joining("\n")); + + output.append(paramNames); + } + } + + private String showDetailedQueryOutput(AppUserConfig config) { + StringBuilder output = new StringBuilder().append(MENU_CONFIG_NAME.getTemplate()) + .append(config.getConfigName()).append("\n") + .append(MENU_QUERY.getTemplate()) + .append(config.getQueryText()).append("\n") + .append(MENU_AREA.getTemplate()) + .append(config.getArea() == null ? "Любой" : config.getArea()).append("\n") + .append(MENU_EXPERIENCE.getTemplate()) + .append(config.getExperience().getDescription()); + var eduParams = config.getEducationLevels() + .stream() + .map(EducationLevel::getParam) + .toList(); + + var scheduleParams = config.getScheduleTypes() + .stream() + .map(ScheduleType::getParam) + .toList(); + + appendEnumParams(output, eduParams, MENU_EDUCATION.getTemplate()); + appendEnumParams(output, scheduleParams, MENU_SCHEDULE.getTemplate()); + + return output.toString(); + } + + private String processConfigActionCommand(AppUser user, String input, AppUserState state) { + var configId = decoder.parseIdFromCallback(input); + var optionalConfig = configService.getConfigById(configId); + + if (optionalConfig.isPresent()) { + var config = optionalConfig.get(); + userStateCacheService.putUserState(user.getTelegramId(), state); + log.info("User {} selected menu action and state set to {}", user.getFirstName(), state); + + if (state == QUERY_UPDATE_STATE) + return showDetailedQueryOutput(config); + + return String.format(DELETE_CONFIRMATION.getTemplate(), config.getConfigName()); + } else { + log.debug("Configuration with id {} not found for user {}", configId, user.getFirstName()); + return CONFIG_NOT_FOUND.getTemplate(); + } + } } diff --git a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitEducationStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitEducationStateHandler.java index 462964b..8c19576 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitEducationStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitEducationStateHandler.java @@ -4,21 +4,23 @@ import com.bipbup.entity.AppUser; import com.bipbup.entity.EducationLevel; import com.bipbup.enums.AppUserState; -import static com.bipbup.enums.AppUserState.WAIT_EDUCATION_STATE; import com.bipbup.enums.impl.EducationLevelParam; import com.bipbup.handlers.StateHandler; -import com.bipbup.service.db.ConfigService; import com.bipbup.service.cache.EducationLevelCacheService; import com.bipbup.service.cache.UserStateCacheService; -import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.CONFIG_NOT_FOUND; -import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.EDU_SAVE; -import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.SELECT_EDUCATION; -import static com.bipbup.utils.CommandMessageConstants.Prefix; +import com.bipbup.service.db.ConfigService; import com.bipbup.utils.Decoder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; + +import static com.bipbup.enums.AppUserState.WAIT_EDUCATION_STATE; +import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.CONFIG_NOT_FOUND; +import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.EDU_SAVE; +import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.SELECT_EDUCATION; +import static com.bipbup.utils.CommandMessageConstants.Prefix; + @Slf4j @Component @CallbackQualifier @@ -69,7 +71,8 @@ private String processSaveEducationLevelsCommand(AppUser user, String input) { .toList(); config.setEducationLevels(educationLevels); - configService.saveConfig(config, true); + + configService.saveConfig(config); educationLevelCacheService.clearEducationLevels(user.getTelegramId()); userStateCacheService.clearUserState(user.getTelegramId()); diff --git a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitExperienceStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitExperienceStateHandler.java index eca1bd1..d461d6b 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitExperienceStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitExperienceStateHandler.java @@ -56,7 +56,7 @@ private String processSetExperienceCommand(AppUser user, String input) { var config = optionalConfig.get(); var experience = ExperienceParam.valueOfPrefix(prefix); config.setExperience(experience); - configService.saveConfig(config, false); + configService.saveConfig(config); log.info("User {} selected experience level and state set to BASIC_STATE", user.getFirstName()); return String.format(EXP_SET.getTemplate(), experience.getDescription(), config.getConfigName()); diff --git a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitScheduleStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitScheduleStateHandler.java index c67b5a3..50e12b5 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/callback/WaitScheduleStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/callback/WaitScheduleStateHandler.java @@ -79,7 +79,8 @@ protected String processSaveScheduleTypesCommand(AppUser user, String input) { .toList(); config.setScheduleTypes(scheduleTypes); - configService.saveConfig(config, true); + + configService.saveConfig(config); scheduleTypeCacheService.clearScheduleTypes(telegramId); userStateCacheService.clearUserState(telegramId); diff --git a/node/src/main/java/com/bipbup/handlers/impl/message/WaitAreaStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/message/WaitAreaStateHandler.java index 8bfa8f0..6adf880 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/message/WaitAreaStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/message/WaitAreaStateHandler.java @@ -80,7 +80,7 @@ private String processValidAreaName(AppUser user, var area = normalizeAreaName(input); var output = getOutputAndSetArea(config, input, area); - configService.saveConfig(config, false); + configService.saveConfig(config); userStateCacheService.clearUserState(user.getTelegramId()); log.info("User {} set area '{}' in configuration '{}'", user.getFirstName(), area, config.getConfigName()); diff --git a/node/src/main/java/com/bipbup/handlers/impl/message/WaitConfigNameStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/message/WaitConfigNameStateHandler.java index 3069796..bd4e8d1 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/message/WaitConfigNameStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/message/WaitConfigNameStateHandler.java @@ -83,7 +83,7 @@ private String updateConfigName(AppUser user, String newConfigName) { var oldConfigName = config.getConfigName(); config.setConfigName(newConfigName); - configService.saveConfig(config, false); + configService.saveConfig(config); userStateCacheService.clearUserState(user.getTelegramId()); log.info("User {} updated name of config \"{}\" and state set to BASIC_STATE", user.getFirstName(), oldConfigName); @@ -115,7 +115,7 @@ private String processUpdatingConfig(AppUser user, String configName) { private String processNewConfig(AppUser user, String configName) { var telegramId = user.getTelegramId(); var newConfig = createConfigWithOnlyName(user, configName); - configService.saveConfig(newConfig, false); + configService.saveConfig(newConfig); configCacheService.clearConfigId(telegramId); userStateCacheService.putUserState(telegramId, WAIT_QUERY_STATE); log.info("User {} created config \"{}\" and state set to WAIT_QUERY_STATE", user.getFirstName(), configName); diff --git a/node/src/main/java/com/bipbup/handlers/impl/message/WaitQueryStateHandler.java b/node/src/main/java/com/bipbup/handlers/impl/message/WaitQueryStateHandler.java index 9b51b91..14359f1 100644 --- a/node/src/main/java/com/bipbup/handlers/impl/message/WaitQueryStateHandler.java +++ b/node/src/main/java/com/bipbup/handlers/impl/message/WaitQueryStateHandler.java @@ -57,7 +57,7 @@ private String processValidQuery(AppUser user, AppUserConfig config, String input) { config.setQueryText(input); - configService.saveConfig(config, false); + configService.saveConfig(config); userStateCacheService.clearUserState(user.getTelegramId()); log.info("User {} set query '{}' in configuration '{}'", user.getFirstName(), input, config.getConfigName()); return String.format(QUERY_SET.getTemplate(), input, config.getConfigName()); diff --git a/node/src/main/java/com/bipbup/service/bot/impl/NotifierServiceImpl.java b/node/src/main/java/com/bipbup/service/bot/impl/NotifierServiceImpl.java index e92969a..0a35e05 100644 --- a/node/src/main/java/com/bipbup/service/bot/impl/NotifierServiceImpl.java +++ b/node/src/main/java/com/bipbup/service/bot/impl/NotifierServiceImpl.java @@ -2,19 +2,19 @@ import com.bipbup.dto.VacancyDTO; import com.bipbup.entity.AppUserConfig; -import com.bipbup.service.net.APIHandler; -import com.bipbup.service.kafka.AnswerProducer; -import com.bipbup.service.db.ConfigService; import com.bipbup.service.bot.NotifierService; +import com.bipbup.service.db.ConfigService; +import com.bipbup.service.kafka.AnswerProducer; +import com.bipbup.service.net.APIHandler; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Locale; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; -import java.time.format.DateTimeFormatter; -import java.util.Collections; -import java.util.Locale; import static com.bipbup.utils.CommandMessageConstants.MessageTemplate.VACANCY; @@ -25,7 +25,7 @@ public class NotifierServiceImpl implements NotifierService { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("d MMMM yyyy", new Locale("ru")); - private static final int SIZE_OF_PAGE = 500; + private static final int SIZE_OF_PAGE = 1000; private final APIHandler apiHandler; @@ -41,7 +41,7 @@ public void searchNewVacancies() { var configs = configService.getConfigsFromPage(page++, SIZE_OF_PAGE); while (!configs.isEmpty()) { - configs.stream() + configs.parallelStream() .filter(this::isPresentQuery) .forEach(this::processNewVacancies); @@ -62,10 +62,13 @@ private void processNewVacancies(AppUserConfig config) { var lastNotificationTime = newVacancies.get(0).getPublishedAt().plusMinutes(1); Collections.reverse(newVacancies); - newVacancies.forEach(v -> sendVacancyMessage(v, config)); + log.debug("Sending {} vacancies to user {} with config {}", + newVacancies.size(), user.getFirstName(), config.getConfigName()); + + newVacancies.forEach(vacancy -> sendVacancyMessage(vacancy, config)); config.setLastNotificationTime(lastNotificationTime); - configService.saveConfig(config, false); + configService.saveConfig(config); } log.info("For user {} find {} vacancies with config {}", diff --git a/node/src/main/java/com/bipbup/service/db/ConfigService.java b/node/src/main/java/com/bipbup/service/db/ConfigService.java index 6351b5d..0793b1e 100644 --- a/node/src/main/java/com/bipbup/service/db/ConfigService.java +++ b/node/src/main/java/com/bipbup/service/db/ConfigService.java @@ -9,7 +9,7 @@ public interface ConfigService { List getConfigsFromPage(int numOfPage, int sizeOfPage); - AppUserConfig saveConfig(AppUserConfig config, boolean updateParams); + AppUserConfig saveConfig(AppUserConfig config); void deleteConfig(AppUserConfig config); diff --git a/node/src/main/java/com/bipbup/service/db/impl/ConfigServiceImpl.java b/node/src/main/java/com/bipbup/service/db/impl/ConfigServiceImpl.java index ca051d4..c73d472 100644 --- a/node/src/main/java/com/bipbup/service/db/impl/ConfigServiceImpl.java +++ b/node/src/main/java/com/bipbup/service/db/impl/ConfigServiceImpl.java @@ -1,8 +1,6 @@ package com.bipbup.service.db.impl; import com.bipbup.dao.AppUserConfigDAO; -import com.bipbup.dao.EducationLevelDao; -import com.bipbup.dao.ScheduleParamEntityDAO; import com.bipbup.entity.AppUser; import com.bipbup.entity.AppUserConfig; import com.bipbup.service.db.ConfigService; @@ -17,53 +15,38 @@ @Service public class ConfigServiceImpl implements ConfigService { - private final AppUserConfigDAO appUserConfigDAO; - - private final EducationLevelDao educationLevelDao; - - private final ScheduleParamEntityDAO scheduleParamEntityDAO; - - @Override - public List getConfigsFromPage(int numOfPage, int sizeOfPage) { - var pageRequest = PageRequest.of(numOfPage, sizeOfPage); - var pageResult = appUserConfigDAO.findAll(pageRequest); - return pageResult.toList(); - } - - // было saveAndFlush() - @Override - @Transactional - public AppUserConfig saveConfig(AppUserConfig config, boolean updateParams) { - if (updateParams) { - scheduleParamEntityDAO.deleteAllByConfig(config); - educationLevelDao.deleteAllByConfig(config); - educationLevelDao.saveAll(config.getEducationLevels()); - scheduleParamEntityDAO.saveAll(config.getScheduleTypes()); - } - - return appUserConfigDAO.save(config); - } - - @Override - @Transactional - public void deleteConfig(AppUserConfig config) { - scheduleParamEntityDAO.deleteAllByConfig(config); - educationLevelDao.deleteAllByConfig(config); - appUserConfigDAO.delete(config); - } - - @Override - public Optional getConfigById(long id) { - return appUserConfigDAO.findById(id); - } - - @Override - public List getConfigByUser(AppUser user) { - return appUserConfigDAO.findByAppUser(user); - } - - @Override - public Long countOfConfigs(AppUser user) { - return appUserConfigDAO.countAppUserConfigByAppUser(user); - } + private final AppUserConfigDAO appUserConfigDAO; + + @Override + public List getConfigsFromPage(int numOfPage, int sizeOfPage) { + var pageRequest = PageRequest.of(numOfPage, sizeOfPage); + var pageResult = appUserConfigDAO.findAll(pageRequest); + return pageResult.toList(); + } + + @Override + public AppUserConfig saveConfig(AppUserConfig config) { + return appUserConfigDAO.save(config); + } + + @Override + @Transactional + public void deleteConfig(AppUserConfig config) { + appUserConfigDAO.delete(config); + } + + @Override + public Optional getConfigById(long id) { + return appUserConfigDAO.findById(id); + } + + @Override + public List getConfigByUser(AppUser user) { + return appUserConfigDAO.findByAppUser(user); + } + + @Override + public Long countOfConfigs(AppUser user) { + return appUserConfigDAO.countAppUserConfigByAppUser(user); + } } diff --git a/node/src/main/java/com/bipbup/service/net/impl/APIHandlerImpl.java b/node/src/main/java/com/bipbup/service/net/impl/APIHandlerImpl.java index d60127d..ced7c2e 100644 --- a/node/src/main/java/com/bipbup/service/net/impl/APIHandlerImpl.java +++ b/node/src/main/java/com/bipbup/service/net/impl/APIHandlerImpl.java @@ -10,8 +10,6 @@ import com.bipbup.service.net.APIConnection; import com.bipbup.service.net.APIHandler; import com.bipbup.service.net.AreaService; -import static com.bipbup.utils.factory.VacancyFactory.createVacancyDTO; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URLEncoder; @@ -21,7 +19,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; @@ -31,158 +31,160 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; + +import static com.bipbup.utils.factory.VacancyFactory.createVacancyDTO; + +/** + * This service handles fetching vacancy data from the API. It uses the user configuration to query for new vacancies + * and processes the results. + */ @Slf4j -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class APIHandlerImpl implements APIHandler { - public static final int COUNT_OF_DAYS = 2; - - private static final int COUNT_OF_VACANCIES_IN_PAGE = 100; - - private static final int TIMESTAMP_FULL_LENGTH = 24; - - private static final int TIMESTAMP_TRIMMED_LENGTH = 19; + public static final int COUNT_OF_DAYS = 2; - private static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + private static final int COUNT_OF_VACANCIES_IN_PAGE = 100; - private final APIConnection apiConnection; + private static final int TIMESTAMP_FULL_LENGTH = 24; - private final ObjectMapper objectMapper; + private static final int TIMESTAMP_TRIMMED_LENGTH = 19; - private final RestTemplate restTemplate; + private static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; - private final AreaService areaService; + private final APIConnection apiConnection; - @Value("${headhunter.endpoint.searchForVacancy}") - private String searchForVacancyURI; + private final ObjectMapper objectMapper; - private static boolean isPresentJson(JsonNode jsonNode) { - return jsonNode != null && !jsonNode.isEmpty(); - } + private final RestTemplate restTemplate; - private static void addVacanciesFromPage(AppUserConfig config, - List vacancyList, - JsonNode vacanciesOnPage) { - vacanciesOnPage.forEach(v -> { - var publishedAt = getPublishedAtFromJson(v); + private final AreaService areaService; - if (publishedAt.isBefore(config.getLastNotificationTime())) - return; + @Value("${headhunter.endpoint.searchForVacancy}") + private String searchForVacancyURI; - vacancyList.add(createVacancyDTO(v, publishedAt)); - }); - } + /** + * Fetches new vacancies based on user configuration. + * + * @param config The user configuration containing query parameters. + * + * @return A list of new vacancies. + */ + @Override + public List fetchNewVacancies(AppUserConfig config) { + var request = apiConnection.createRequestWithHeaders(); + var pageCount = fetchPageCount(request, config); + var vacancyList = new ArrayList(); - private static LocalDateTime getPublishedAtFromJson(JsonNode vacancy) { - var timestamp = vacancy.get("published_at").asText(); + IntStream.range(0, pageCount) + .forEach(i -> processVacancyPage(config, request, i, vacancyList)); - if (timestamp.length() == TIMESTAMP_FULL_LENGTH) - timestamp = timestamp.substring(0, TIMESTAMP_TRIMMED_LENGTH); + return vacancyList; + } - return LocalDateTime.parse(timestamp, - DateTimeFormatter.ofPattern(TIMESTAMP_PATTERN)); - } + private int fetchPageCount(HttpEntity request, AppUserConfig config) { + var firstPage = fetchVacancyPage(request, 0, config); - @Override - public List fetchNewVacancies(AppUserConfig config) { - var request = apiConnection.createRequestWithHeaders(); - var pageCount = fetchPageCount(request, config); - List vacancyList = new ArrayList<>(); + if (firstPage.isEmpty()) + return 0; - for (int i = 0; i <= pageCount; i++) { - processVacancyPage(config, request, i, vacancyList); - } + var totalCount = firstPage.get("found") + .asInt(0); + return (int) Math.ceil((double) totalCount / COUNT_OF_VACANCIES_IN_PAGE); + } - return vacancyList; - } + @SneakyThrows + private JsonNode fetchVacancyPage(HttpEntity request, int pageNumber, AppUserConfig config) { + var vacancySearchUri = generateVacancySearchUri(pageNumber, config); + var response = restTemplate.exchange(vacancySearchUri, HttpMethod.GET, request, String.class) + .getBody(); + return objectMapper.readTree(response); + } - private String encodeQueryText(String queryText) { - return URLEncoder.encode(queryText, StandardCharsets.UTF_8); - } + private String generateVacancySearchUri(int pageNumber, AppUserConfig config) { + var builder = UriComponentsBuilder.fromUriString(searchForVacancyURI) + .queryParam("page", pageNumber) + .queryParam("per_page", COUNT_OF_VACANCIES_IN_PAGE) + .queryParam("text", encodeQueryText(config.getQueryText())) + .queryParam("search_field", "name") + .queryParam("period", COUNT_OF_DAYS) + .queryParam("order_by", "publication_time"); - private JsonNode fetchVacancyPage(HttpEntity request, - int pageNumber, - AppUserConfig config) { - var vacancySearchUri = generateVacancySearchUri(pageNumber, config); - var response = restTemplate.exchange(vacancySearchUri, HttpMethod.GET, request, String.class).getBody(); + addAreaParam(builder, config); + addExperienceParam(builder, config); + addEducationParam(builder, config); + addScheduleParam(builder, config); - try { - return objectMapper.readTree(response); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - return objectMapper.createObjectNode(); - } - } + return builder.build() + .toUriString(); + } - private int fetchPageCount(HttpEntity request, - AppUserConfig config) { - var firstPage = fetchVacancyPage(request, 0, config); + private String encodeQueryText(String queryText) { + return URLEncoder.encode(queryText, StandardCharsets.UTF_8); + } - if (isPresentJson(firstPage)) { - var totalCount = firstPage.get("found").asInt(0); - return (int) Math.ceil((double) totalCount / COUNT_OF_VACANCIES_IN_PAGE); - } + private void addExperienceParam(UriComponentsBuilder builder, AppUserConfig config) { + var experience = config.getExperience(); - return 0; - } + if (ExperienceParam.NO_MATTER != experience) + builder.queryParam("experience", experience.getParam()); + } - private void processVacancyPage(AppUserConfig config, - HttpEntity request, - int pageNum, - List vacancyList) { - var jsonPage = fetchVacancyPage(request, pageNum, config); + private void addAreaParam(UriComponentsBuilder builder, AppUserConfig config) { + Optional.ofNullable(areaService.getAreaIdByName(config.getArea())) + .ifPresent(areaId -> builder.queryParam("area", areaId)); + } - if (isPresentJson(jsonPage)) - addVacanciesFromPage(config, vacancyList, jsonPage.get("items")); - } + public void addEducationParam(UriComponentsBuilder builder, AppUserConfig config) { + var levels = config.getEducationLevels() + .stream() + .map(EducationLevel::getParam) + .map(EducationLevelParam::getParam) + .toList(); - private void addAreaParam(UriComponentsBuilder builder, AppUserConfig config) { - Optional.ofNullable(areaService.getAreaIdByName(config.getArea())) - .ifPresent(areaId -> builder.queryParam("area", areaId)); - } - - private void addExperienceParam(UriComponentsBuilder builder, AppUserConfig config) { - var experience = config.getExperience(); - - if (ExperienceParam.NO_MATTER != experience) - builder.queryParam("experience", experience.getParam()); - } - - private void addEducationParam(UriComponentsBuilder builder, AppUserConfig config) { - var levels = config.getEducationLevels().stream() - .map(EducationLevel::getParam) - .map(EducationLevelParam::getParam) - .toList(); - - if (!levels.isEmpty()) - builder.queryParam("education", levels); - } - - private void addScheduleParam(UriComponentsBuilder builder, AppUserConfig config) { - var types = config.getScheduleTypes().stream() - .map(ScheduleType::getParam) - .map(ScheduleTypeParam::getParam) - .toList(); - - if (!types.isEmpty()) - builder.queryParam("schedule", types); - } - - private String generateVacancySearchUri(int pageNumber, AppUserConfig config) { - var builder = UriComponentsBuilder.fromUriString(searchForVacancyURI) - .queryParam("page", pageNumber) - .queryParam("per_page", COUNT_OF_VACANCIES_IN_PAGE) - .queryParam("text", encodeQueryText(config.getQueryText())) - .queryParam("search_field", "name") - .queryParam("period", COUNT_OF_DAYS) - .queryParam("order_by", "publication_time"); - - addAreaParam(builder, config); - addExperienceParam(builder, config); - addEducationParam(builder, config); - addScheduleParam(builder, config); - - return builder.build().toUriString(); - } + if (!levels.isEmpty()) + builder.queryParam("education", levels); + } + + private void addScheduleParam(UriComponentsBuilder builder, AppUserConfig config) { + var types = config.getScheduleTypes() + .stream() + .map(ScheduleType::getParam) + .map(ScheduleTypeParam::getParam) + .toList(); + + if (!types.isEmpty()) + builder.queryParam("schedule", types); + } + + private void processVacancyPage( + AppUserConfig config, HttpEntity request, int pageNum, List vacancyList + ) { + var jsonPage = fetchVacancyPage(request, pageNum, config); + + if (!jsonPage.isEmpty()) + addVacanciesFromPage(config, vacancyList, jsonPage.get("items")); + } + + private void addVacanciesFromPage(AppUserConfig config, List vacancyList, JsonNode vacanciesOnPage) { + vacanciesOnPage.forEach(vacancy -> { + var publishedAt = getPublishedAtFromJson(vacancy); + + if (publishedAt.isBefore(config.getLastNotificationTime())) + return; + + vacancyList.add(createVacancyDTO(vacancy, publishedAt)); + }); + } + + private LocalDateTime getPublishedAtFromJson(JsonNode vacancy) { + var timestamp = vacancy.get("published_at") + .asText(); + + if (timestamp.length() == TIMESTAMP_FULL_LENGTH) + timestamp = timestamp.substring(0, TIMESTAMP_TRIMMED_LENGTH); + + return LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern(TIMESTAMP_PATTERN)); + } } diff --git a/node/src/main/java/com/bipbup/utils/AreaUtil.java b/node/src/main/java/com/bipbup/utils/AreaUtil.java index 8968a71..cc8b6bf 100644 --- a/node/src/main/java/com/bipbup/utils/AreaUtil.java +++ b/node/src/main/java/com/bipbup/utils/AreaUtil.java @@ -1,6 +1,6 @@ package com.bipbup.utils; -import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; @@ -8,8 +8,6 @@ import static java.net.http.HttpResponse.BodyHandlers.ofString; import java.util.Deque; import java.util.LinkedList; -import java.util.List; -import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -36,7 +34,8 @@ public Integer getAreaIdFromApi(String areaName) { var response = client.send(request, ofString()); if (response.statusCode() == HttpStatus.OK.value()) { - var areas = objectMapper.readValue(response.body(), new TypeReference>>() {}); + var areas = objectMapper.readTree(response.body()); + return Optional.ofNullable(findAreaId(areas, areaName)) .map(Integer::valueOf) .orElse(null); @@ -46,22 +45,19 @@ public Integer getAreaIdFromApi(String areaName) { return null; } - private String findAreaId(List> areas, String name) { - Deque>> stack = new LinkedList<>(); + private String findAreaId(JsonNode areas, String name) { + Deque stack = new LinkedList<>(); stack.push(areas); while (!stack.isEmpty()) { var first = stack.pop(); - if (first == null) continue; for (var area : first) { - String areaName = (String) area.get("name"); + var areaName = area.get("name").asText(); if (name.equalsIgnoreCase(areaName)) - return (String) area.get("id"); + return area.get("id").asText(); Optional.ofNullable(area.get("areas")) - .filter(List.class::isInstance) - .map(List.class::cast) .ifPresent(stack::push); } } diff --git a/node/src/test/java/com/bipbup/handlers/impl/WaitAreaStateHandlerTest.java b/node/src/test/java/com/bipbup/handlers/impl/WaitAreaStateHandlerTest.java index f959b88..00ea049 100644 --- a/node/src/test/java/com/bipbup/handlers/impl/WaitAreaStateHandlerTest.java +++ b/node/src/test/java/com/bipbup/handlers/impl/WaitAreaStateHandlerTest.java @@ -67,7 +67,7 @@ void testProcessValidAreaName() { String result = waitAreaStateHandler.process(appUser, input); - verify(configService).saveConfig(config, false); + verify(configService).saveConfig(config); verify(userStateCacheService).clearUserState(appUser.getTelegramId()); assertEquals(String.format(AREA_SET.getTemplate(), "New Area", config.getConfigName()), result); } @@ -84,7 +84,7 @@ void testProcessAnyInput() { String result = waitAreaStateHandler.process(appUser, ANY); - verify(configService).saveConfig(config, false); + verify(configService).saveConfig(config); verify(userStateCacheService).clearUserState(appUser.getTelegramId()); assertEquals(String.format(ANY_AREA_SET.getTemplate(), config.getConfigName()), result); }