diff --git a/bot/pom.xml b/bot/pom.xml
index ed1d026..21aab18 100644
--- a/bot/pom.xml
+++ b/bot/pom.xml
@@ -128,6 +128,11 @@
kafka
test
+
+ edu.java
+ dto
+ 0.1
+
diff --git a/bot/src/main/java/edu/java/bot/api/client/ScrapperClient.java b/bot/src/main/java/edu/java/bot/api/client/ScrapperClient.java
deleted file mode 100644
index d6dbbec..0000000
--- a/bot/src/main/java/edu/java/bot/api/client/ScrapperClient.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package edu.java.bot.api.client;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.NonNull;
-
-public class ScrapperClient extends AbstractClient {
- public ScrapperClient(@NonNull String baseUrl, ObjectMapper mapper) {
- super(baseUrl, mapper);
- }
-}
diff --git a/bot/src/main/java/edu/java/bot/api/controller/UpdateController.java b/bot/src/main/java/edu/java/bot/api/controller/UpdateController.java
index f30e7ea..e2fd998 100644
--- a/bot/src/main/java/edu/java/bot/api/controller/UpdateController.java
+++ b/bot/src/main/java/edu/java/bot/api/controller/UpdateController.java
@@ -1,7 +1,7 @@
package edu.java.bot.api.controller;
-import edu.java.bot.api.dto.ApiErrorResponse;
-import edu.java.bot.api.dto.LinkUpdateRequest;
+import edu.java.dto.api.bot.ApiErrorResponse;
+import edu.java.dto.api.bot.LinkUpdateRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -29,5 +29,4 @@ public class UpdateController {
public ResponseEntity sendUpdate(@Valid @RequestBody LinkUpdateRequest request) {
return ResponseEntity.ok().build();
}
-
}
diff --git a/bot/src/main/java/edu/java/bot/api/exception/ExceptionApiHandler.java b/bot/src/main/java/edu/java/bot/api/exception/ExceptionApiHandler.java
index 31c0eeb..57cfcb0 100644
--- a/bot/src/main/java/edu/java/bot/api/exception/ExceptionApiHandler.java
+++ b/bot/src/main/java/edu/java/bot/api/exception/ExceptionApiHandler.java
@@ -1,13 +1,13 @@
package edu.java.bot.api.exception;
-import edu.java.bot.api.dto.ApiErrorResponse;
+import edu.java.dto.api.bot.ApiErrorResponse;
+import java.util.Arrays;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
-@RestControllerAdvice
-public class ExceptionApiHandler {
+@RestControllerAdvice public class ExceptionApiHandler {
@ExceptionHandler(ChatNotExistException.class)
public ResponseEntity chatNotExistException(ChatNotExistException exception) {
@@ -16,24 +16,19 @@ public ResponseEntity chatNotExistException(ChatNotExistExcept
HttpStatus.NOT_FOUND.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace()).map(StackTraceElement::toString).toArray(String[]::new)
);
- return ResponseEntity
- .status(HttpStatus.NOT_FOUND)
- .body(error);
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
- @ExceptionHandler(Exception.class)
- public ResponseEntity anyException(Exception exception) {
+ @ExceptionHandler(Exception.class) public ResponseEntity anyException(Exception exception) {
ApiErrorResponse error = new ApiErrorResponse(
- "Неверные параметры запроса",
- HttpStatus.BAD_REQUEST.toString(),
+ "Произошла непредвиденная ошибка на стороне сервера",
+ HttpStatus.INTERNAL_SERVER_ERROR.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace()).map(StackTraceElement::toString).toArray(String[]::new)
);
- return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
- .body(error);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
diff --git a/scrapper/src/main/java/edu/java/client/AbstractClient.java b/bot/src/main/java/edu/java/bot/client/AbstractClient.java
similarity index 93%
rename from scrapper/src/main/java/edu/java/client/AbstractClient.java
rename to bot/src/main/java/edu/java/bot/client/AbstractClient.java
index 195245b..1c43082 100644
--- a/scrapper/src/main/java/edu/java/client/AbstractClient.java
+++ b/bot/src/main/java/edu/java/bot/client/AbstractClient.java
@@ -1,4 +1,4 @@
-package edu.java.client;
+package edu.java.bot.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.NonNull;
diff --git a/bot/src/main/java/edu/java/bot/client/ScrapperApiException.java b/bot/src/main/java/edu/java/bot/client/ScrapperApiException.java
new file mode 100644
index 0000000..cbb465a
--- /dev/null
+++ b/bot/src/main/java/edu/java/bot/client/ScrapperApiException.java
@@ -0,0 +1,15 @@
+package edu.java.bot.client;
+
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import lombok.Getter;
+
+@Getter
+public class ScrapperApiException extends Exception {
+ private final ApiErrorResponse error;
+
+ public ScrapperApiException(ApiErrorResponse error) {
+ super("%s: %s. %s - %s\n%s".formatted(error.code(), error.description(),
+ error.exceptionName(), error.exceptionMessage(), String.join("\n", error.stacktrace())));
+ this.error = error;
+ }
+}
diff --git a/bot/src/main/java/edu/java/bot/client/ScrapperClient.java b/bot/src/main/java/edu/java/bot/client/ScrapperClient.java
new file mode 100644
index 0000000..07037a7
--- /dev/null
+++ b/bot/src/main/java/edu/java/bot/client/ScrapperClient.java
@@ -0,0 +1,87 @@
+package edu.java.bot.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.java.dto.api.scrapper.AddLinkRequest;
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import edu.java.dto.api.scrapper.LinkResponse;
+import edu.java.dto.api.scrapper.ListLinksResponse;
+import edu.java.dto.api.scrapper.RemoveLinkRequest;
+import lombok.NonNull;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import reactor.core.publisher.Mono;
+
+public class ScrapperClient extends AbstractClient {
+ private static final String CHAT_BASE_URL = "/tg-chat";
+ private static final String LINK_BASE_URL = "/links";
+ private static final String ID_PATH = "/{id}";
+ private static final String CHAT_HEADER = "Tg-Chat-Id";
+
+ public ScrapperClient(@NonNull String baseUrl, ObjectMapper mapper) {
+ super(baseUrl, mapper);
+ }
+
+ public void registerChat(long id) {
+ client.post()
+ .uri(uriBuilder -> uriBuilder
+ .path(CHAT_BASE_URL + ID_PATH)
+ .build(id))
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(Void.class)
+ .block();
+ }
+
+ public void deleteChat(long id) {
+ client.delete()
+ .uri(uriBuilder -> uriBuilder
+ .path(CHAT_BASE_URL + ID_PATH)
+ .build(id))
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(Void.class)
+ .block();
+ }
+
+ public ListLinksResponse getLinks(long id) {
+ return client.get()
+ .uri(LINK_BASE_URL)
+ .header(CHAT_HEADER, String.valueOf(id))
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(ListLinksResponse.class)
+ .block();
+ }
+
+ public LinkResponse addLink(long id, AddLinkRequest request) {
+ return client.post()
+ .uri(LINK_BASE_URL)
+ .header(CHAT_HEADER, String.valueOf(id))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(Mono.just(request), AddLinkRequest.class)
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(LinkResponse.class)
+ .block();
+ }
+
+ public LinkResponse deleteLink(long id, RemoveLinkRequest request) {
+ return client.method(HttpMethod.DELETE)
+ .uri(LINK_BASE_URL)
+ .header(CHAT_HEADER, String.valueOf(id))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(Mono.just(request), RemoveLinkRequest.class)
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(LinkResponse.class)
+ .block();
+ }
+
+ private Mono getException(ClientResponse response) {
+ return response
+ .bodyToMono(ApiErrorResponse.class)
+ .map(ScrapperApiException::new);
+ }
+}
diff --git a/bot/src/main/java/edu/java/bot/service/ScrapperService.java b/bot/src/main/java/edu/java/bot/client/service/ScrapperService.java
similarity index 97%
rename from bot/src/main/java/edu/java/bot/service/ScrapperService.java
rename to bot/src/main/java/edu/java/bot/client/service/ScrapperService.java
index 1deaa2f..abbce8d 100644
--- a/bot/src/main/java/edu/java/bot/service/ScrapperService.java
+++ b/bot/src/main/java/edu/java/bot/client/service/ScrapperService.java
@@ -1,4 +1,4 @@
-package edu.java.bot.service;
+package edu.java.bot.client.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -15,7 +15,7 @@ public ScrapperService() {
public boolean isUserRegistered(Long userId) {
// TODO: добавить работу с БД через scrapper
log.info("%s. Проверка регистрации пользователя %d".formatted(LocalDateTime.now(), userId));
- return false;
+ return true;
}
public void registerUser(Long userId) {
diff --git a/bot/src/main/java/edu/java/bot/command/Command.java b/bot/src/main/java/edu/java/bot/command/Command.java
deleted file mode 100644
index 807b41a..0000000
--- a/bot/src/main/java/edu/java/bot/command/Command.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package edu.java.bot.command;
-
-import com.pengrad.telegrambot.model.Update;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.command.CommandException;
-import edu.java.bot.exception.link.LinkException;
-import edu.java.bot.exception.parameter.ParameterException;
-
-public interface Command {
-
- String getName();
-
- String getDescription();
-
- String execute(Update update) throws UnregisteredUserException, ParameterException, CommandException, LinkException;
-}
diff --git a/bot/src/main/java/edu/java/bot/configuration/ClientConfig.java b/bot/src/main/java/edu/java/bot/configuration/ClientConfig.java
index ccb6a51..0f74021 100644
--- a/bot/src/main/java/edu/java/bot/configuration/ClientConfig.java
+++ b/bot/src/main/java/edu/java/bot/configuration/ClientConfig.java
@@ -1,7 +1,7 @@
package edu.java.bot.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
-import edu.java.bot.api.client.ScrapperClient;
+import edu.java.bot.client.ScrapperClient;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
diff --git a/bot/src/main/java/edu/java/bot/configuration/CommandConfig.java b/bot/src/main/java/edu/java/bot/configuration/CommandConfig.java
new file mode 100644
index 0000000..94af187
--- /dev/null
+++ b/bot/src/main/java/edu/java/bot/configuration/CommandConfig.java
@@ -0,0 +1,21 @@
+package edu.java.bot.configuration;
+
+import edu.java.dto.utils.LinkParser;
+import edu.java.dto.utils.sources.parsers.GithubParser;
+import edu.java.dto.utils.sources.parsers.SourceParser;
+import edu.java.dto.utils.sources.parsers.StackoverflowParser;
+import java.util.Set;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class CommandConfig {
+
+ @Bean
+ public LinkParser linkParser() {
+ return new LinkParser(SourceParser.buildChain(Set.of(
+ new GithubParser(),
+ new StackoverflowParser()
+ )), true);
+ }
+}
diff --git a/bot/src/main/java/edu/java/bot/configuration/TelegramBotConfig.java b/bot/src/main/java/edu/java/bot/configuration/TelegramBotConfig.java
index c4e25ce..51f91c5 100644
--- a/bot/src/main/java/edu/java/bot/configuration/TelegramBotConfig.java
+++ b/bot/src/main/java/edu/java/bot/configuration/TelegramBotConfig.java
@@ -3,8 +3,8 @@
import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.model.BotCommand;
import com.pengrad.telegrambot.request.SetMyCommands;
-import edu.java.bot.command.Command;
-import edu.java.bot.command.components.HelpCommand;
+import edu.java.bot.telegram.command.Command;
+import edu.java.bot.telegram.command.components.HelpCommand;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
diff --git a/bot/src/main/java/edu/java/bot/controller/Controller.java b/bot/src/main/java/edu/java/bot/telegram/TgBotController.java
similarity index 89%
rename from bot/src/main/java/edu/java/bot/controller/Controller.java
rename to bot/src/main/java/edu/java/bot/telegram/TgBotController.java
index d9a1c1d..8b1753a 100644
--- a/bot/src/main/java/edu/java/bot/controller/Controller.java
+++ b/bot/src/main/java/edu/java/bot/telegram/TgBotController.java
@@ -1,4 +1,4 @@
-package edu.java.bot.controller;
+package edu.java.bot.telegram;
import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.TelegramException;
@@ -6,10 +6,10 @@
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendMessage;
import com.pengrad.telegrambot.response.SendResponse;
-import edu.java.bot.command.AbstractCommand;
-import edu.java.bot.command.Command;
import edu.java.bot.configuration.ApplicationConfig;
import edu.java.bot.configuration.TelegramBotConfig;
+import edu.java.bot.telegram.command.AbstractCommand;
+import edu.java.bot.telegram.command.Command;
import java.time.LocalDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@@ -18,12 +18,12 @@
@Component
@Slf4j
-public class Controller {
+public class TgBotController {
private final TelegramBot bot;
private final TelegramBotConfig botConfig;
@Autowired
- public Controller(ApplicationConfig config, TelegramBotConfig botConfig) {
+ public TgBotController(ApplicationConfig config, TelegramBotConfig botConfig) {
this.botConfig = botConfig;
bot = botConfig.telegramBot(config);
bot.setUpdatesListener(this::processUpdates, this::processException);
diff --git a/bot/src/main/java/edu/java/bot/command/AbstractCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/AbstractCommand.java
similarity index 74%
rename from bot/src/main/java/edu/java/bot/command/AbstractCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/AbstractCommand.java
index 6d1d6e6..9b4f36b 100644
--- a/bot/src/main/java/edu/java/bot/command/AbstractCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/AbstractCommand.java
@@ -1,12 +1,12 @@
-package edu.java.bot.command;
+package edu.java.bot.telegram.command;
import com.pengrad.telegrambot.model.Update;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.command.CommandException;
-import edu.java.bot.exception.command.CommandNotExistException;
-import edu.java.bot.exception.command.NotCommandException;
-import edu.java.bot.exception.link.LinkException;
-import edu.java.bot.exception.parameter.ParameterException;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.command.CommandException;
+import edu.java.bot.telegram.exception.command.CommandNotExistException;
+import edu.java.bot.telegram.exception.command.NotCommandException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
+import edu.java.dto.utils.exception.LinkException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
diff --git a/bot/src/main/java/edu/java/bot/command/AbstractServiceCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/AbstractServiceCommand.java
similarity index 77%
rename from bot/src/main/java/edu/java/bot/command/AbstractServiceCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/AbstractServiceCommand.java
index 028ee0b..a78798c 100644
--- a/bot/src/main/java/edu/java/bot/command/AbstractServiceCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/AbstractServiceCommand.java
@@ -1,6 +1,6 @@
-package edu.java.bot.command;
+package edu.java.bot.telegram.command;
-import edu.java.bot.service.ScrapperService;
+import edu.java.bot.client.service.ScrapperService;
public abstract class AbstractServiceCommand extends AbstractCommand {
protected ScrapperService service;
diff --git a/bot/src/main/java/edu/java/bot/telegram/command/Command.java b/bot/src/main/java/edu/java/bot/telegram/command/Command.java
new file mode 100644
index 0000000..874b01e
--- /dev/null
+++ b/bot/src/main/java/edu/java/bot/telegram/command/Command.java
@@ -0,0 +1,16 @@
+package edu.java.bot.telegram.command;
+
+import com.pengrad.telegrambot.model.Update;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.command.CommandException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
+import edu.java.dto.utils.exception.LinkException;
+
+public interface Command {
+
+ String getName();
+
+ String getDescription();
+
+ String execute(Update update) throws UnregisteredUserException, ParameterException, CommandException, LinkException;
+}
diff --git a/bot/src/main/java/edu/java/bot/command/CommandUtils.java b/bot/src/main/java/edu/java/bot/telegram/command/CommandUtils.java
similarity index 72%
rename from bot/src/main/java/edu/java/bot/command/CommandUtils.java
rename to bot/src/main/java/edu/java/bot/telegram/command/CommandUtils.java
index 666fc21..3eb2d27 100644
--- a/bot/src/main/java/edu/java/bot/command/CommandUtils.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/CommandUtils.java
@@ -1,8 +1,8 @@
-package edu.java.bot.command;
+package edu.java.bot.telegram.command;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.parameter.WrongNumberParametersException;
-import edu.java.bot.service.ScrapperService;
+import edu.java.bot.client.service.ScrapperService;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.parameter.WrongNumberParametersException;
public class CommandUtils {
diff --git a/bot/src/main/java/edu/java/bot/command/components/HelpCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/components/HelpCommand.java
similarity index 76%
rename from bot/src/main/java/edu/java/bot/command/components/HelpCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/components/HelpCommand.java
index 6360263..b0a1895 100644
--- a/bot/src/main/java/edu/java/bot/command/components/HelpCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/components/HelpCommand.java
@@ -1,9 +1,9 @@
-package edu.java.bot.command.components;
+package edu.java.bot.telegram.command.components;
-import edu.java.bot.command.AbstractCommand;
-import edu.java.bot.command.Command;
-import edu.java.bot.command.CommandUtils;
-import edu.java.bot.exception.parameter.ParameterException;
+import edu.java.bot.telegram.command.AbstractCommand;
+import edu.java.bot.telegram.command.Command;
+import edu.java.bot.telegram.command.CommandUtils;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.core.annotation.Order;
diff --git a/bot/src/main/java/edu/java/bot/command/components/ListCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/components/ListCommand.java
similarity index 77%
rename from bot/src/main/java/edu/java/bot/command/components/ListCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/components/ListCommand.java
index 1f7a377..1f6cd78 100644
--- a/bot/src/main/java/edu/java/bot/command/components/ListCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/components/ListCommand.java
@@ -1,10 +1,10 @@
-package edu.java.bot.command.components;
+package edu.java.bot.telegram.command.components;
-import edu.java.bot.command.AbstractServiceCommand;
-import edu.java.bot.command.CommandUtils;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.parameter.ParameterException;
-import edu.java.bot.service.ScrapperService;
+import edu.java.bot.client.service.ScrapperService;
+import edu.java.bot.telegram.command.AbstractServiceCommand;
+import edu.java.bot.telegram.command.CommandUtils;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.core.annotation.Order;
diff --git a/bot/src/main/java/edu/java/bot/command/components/StartCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/components/StartCommand.java
similarity index 77%
rename from bot/src/main/java/edu/java/bot/command/components/StartCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/components/StartCommand.java
index 120ae79..e4f0af0 100644
--- a/bot/src/main/java/edu/java/bot/command/components/StartCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/components/StartCommand.java
@@ -1,9 +1,9 @@
-package edu.java.bot.command.components;
+package edu.java.bot.telegram.command.components;
-import edu.java.bot.command.AbstractServiceCommand;
-import edu.java.bot.command.CommandUtils;
-import edu.java.bot.exception.parameter.ParameterException;
-import edu.java.bot.service.ScrapperService;
+import edu.java.bot.client.service.ScrapperService;
+import edu.java.bot.telegram.command.AbstractServiceCommand;
+import edu.java.bot.telegram.command.CommandUtils;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
diff --git a/bot/src/main/java/edu/java/bot/command/components/TrackCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/components/TrackCommand.java
similarity index 55%
rename from bot/src/main/java/edu/java/bot/command/components/TrackCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/components/TrackCommand.java
index 466f995..09a061c 100644
--- a/bot/src/main/java/edu/java/bot/command/components/TrackCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/components/TrackCommand.java
@@ -1,22 +1,24 @@
-package edu.java.bot.command.components;
+package edu.java.bot.telegram.command.components;
-import edu.java.bot.command.AbstractServiceCommand;
-import edu.java.bot.command.CommandUtils;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.link.LinkRegistrationException;
-import edu.java.bot.exception.link.NotLinkException;
-import edu.java.bot.exception.parameter.ParameterException;
-import edu.java.bot.service.ScrapperService;
-import edu.java.bot.utils.Link;
+import edu.java.bot.client.service.ScrapperService;
+import edu.java.bot.telegram.command.AbstractServiceCommand;
+import edu.java.bot.telegram.command.CommandUtils;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
+import edu.java.dto.utils.LinkParser;
+import edu.java.dto.utils.exception.LinkException;
+import edu.java.dto.utils.exception.LinkRegistrationException;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(3)
public class TrackCommand extends AbstractServiceCommand {
+ private final LinkParser parser;
- public TrackCommand(ScrapperService service) {
+ public TrackCommand(ScrapperService service, LinkParser parser) {
super(service);
+ this.parser = parser;
}
@Override
@@ -31,10 +33,10 @@ public String getDescription() {
@Override
protected String doAction(Long userId, String[] params)
- throws ParameterException, NotLinkException, UnregisteredUserException, LinkRegistrationException {
+ throws ParameterException, UnregisteredUserException, LinkException {
CommandUtils.checkParamsNumber(params, 1);
String link = params[0];
- Link.parse(link);
+ parser.parse(link);
CommandUtils.checkUserRegistration(userId, service);
if (service.isLinkRegistered(userId, link)) {
throw new LinkRegistrationException("Ссылка уже зарегистрирована");
diff --git a/bot/src/main/java/edu/java/bot/command/components/UntrackCommand.java b/bot/src/main/java/edu/java/bot/telegram/command/components/UntrackCommand.java
similarity index 56%
rename from bot/src/main/java/edu/java/bot/command/components/UntrackCommand.java
rename to bot/src/main/java/edu/java/bot/telegram/command/components/UntrackCommand.java
index 3e65102..c828b4f 100644
--- a/bot/src/main/java/edu/java/bot/command/components/UntrackCommand.java
+++ b/bot/src/main/java/edu/java/bot/telegram/command/components/UntrackCommand.java
@@ -1,22 +1,24 @@
-package edu.java.bot.command.components;
+package edu.java.bot.telegram.command.components;
-import edu.java.bot.command.AbstractServiceCommand;
-import edu.java.bot.command.CommandUtils;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.link.LinkRegistrationException;
-import edu.java.bot.exception.link.NotLinkException;
-import edu.java.bot.exception.parameter.ParameterException;
-import edu.java.bot.service.ScrapperService;
-import edu.java.bot.utils.Link;
+import edu.java.bot.client.service.ScrapperService;
+import edu.java.bot.telegram.command.AbstractServiceCommand;
+import edu.java.bot.telegram.command.CommandUtils;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
+import edu.java.dto.utils.LinkParser;
+import edu.java.dto.utils.exception.LinkException;
+import edu.java.dto.utils.exception.LinkRegistrationException;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(4)
public class UntrackCommand extends AbstractServiceCommand {
+ private final LinkParser parser;
- public UntrackCommand(ScrapperService service) {
+ public UntrackCommand(ScrapperService service, LinkParser parser) {
super(service);
+ this.parser = parser;
}
@Override
@@ -31,10 +33,10 @@ public String getDescription() {
@Override
protected String doAction(Long userId, String[] params)
- throws ParameterException, NotLinkException, UnregisteredUserException, LinkRegistrationException {
+ throws ParameterException, UnregisteredUserException, LinkException {
CommandUtils.checkParamsNumber(params, 1);
String link = params[0];
- Link.parse(link);
+ parser.parse(link);
CommandUtils.checkUserRegistration(userId, service);
if (!service.isLinkRegistered(userId, link)) {
throw new LinkRegistrationException("Ссылка не была зарегистрирована");
diff --git a/bot/src/main/java/edu/java/bot/exception/UnregisteredUserException.java b/bot/src/main/java/edu/java/bot/telegram/exception/UnregisteredUserException.java
similarity index 84%
rename from bot/src/main/java/edu/java/bot/exception/UnregisteredUserException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/UnregisteredUserException.java
index c8760df..3f94199 100644
--- a/bot/src/main/java/edu/java/bot/exception/UnregisteredUserException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/UnregisteredUserException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception;
+package edu.java.bot.telegram.exception;
public class UnregisteredUserException extends Exception {
diff --git a/bot/src/main/java/edu/java/bot/exception/command/CommandException.java b/bot/src/main/java/edu/java/bot/telegram/exception/command/CommandException.java
similarity index 73%
rename from bot/src/main/java/edu/java/bot/exception/command/CommandException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/command/CommandException.java
index 2bc19fe..25f1725 100644
--- a/bot/src/main/java/edu/java/bot/exception/command/CommandException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/command/CommandException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception.command;
+package edu.java.bot.telegram.exception.command;
public abstract class CommandException extends Exception {
diff --git a/bot/src/main/java/edu/java/bot/exception/command/CommandNotExistException.java b/bot/src/main/java/edu/java/bot/telegram/exception/command/CommandNotExistException.java
similarity index 77%
rename from bot/src/main/java/edu/java/bot/exception/command/CommandNotExistException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/command/CommandNotExistException.java
index 6e6c488..e73c5b9 100644
--- a/bot/src/main/java/edu/java/bot/exception/command/CommandNotExistException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/command/CommandNotExistException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception.command;
+package edu.java.bot.telegram.exception.command;
public class CommandNotExistException extends CommandException {
public CommandNotExistException() {
diff --git a/bot/src/main/java/edu/java/bot/exception/command/NotCommandException.java b/bot/src/main/java/edu/java/bot/telegram/exception/command/NotCommandException.java
similarity index 76%
rename from bot/src/main/java/edu/java/bot/exception/command/NotCommandException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/command/NotCommandException.java
index 65def43..e1c5919 100644
--- a/bot/src/main/java/edu/java/bot/exception/command/NotCommandException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/command/NotCommandException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception.command;
+package edu.java.bot.telegram.exception.command;
public class NotCommandException extends CommandException {
public NotCommandException() {
diff --git a/bot/src/main/java/edu/java/bot/exception/parameter/ParameterException.java b/bot/src/main/java/edu/java/bot/telegram/exception/parameter/ParameterException.java
similarity index 73%
rename from bot/src/main/java/edu/java/bot/exception/parameter/ParameterException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/parameter/ParameterException.java
index 017af07..5cb975c 100644
--- a/bot/src/main/java/edu/java/bot/exception/parameter/ParameterException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/parameter/ParameterException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception.parameter;
+package edu.java.bot.telegram.exception.parameter;
public abstract class ParameterException extends Exception {
diff --git a/bot/src/main/java/edu/java/bot/exception/parameter/WrongNumberParametersException.java b/bot/src/main/java/edu/java/bot/telegram/exception/parameter/WrongNumberParametersException.java
similarity index 76%
rename from bot/src/main/java/edu/java/bot/exception/parameter/WrongNumberParametersException.java
rename to bot/src/main/java/edu/java/bot/telegram/exception/parameter/WrongNumberParametersException.java
index f9c4147..9b4417f 100644
--- a/bot/src/main/java/edu/java/bot/exception/parameter/WrongNumberParametersException.java
+++ b/bot/src/main/java/edu/java/bot/telegram/exception/parameter/WrongNumberParametersException.java
@@ -1,4 +1,4 @@
-package edu.java.bot.exception.parameter;
+package edu.java.bot.telegram.exception.parameter;
public class WrongNumberParametersException extends ParameterException {
diff --git a/bot/src/main/java/edu/java/bot/utils/Link.java b/bot/src/main/java/edu/java/bot/utils/Link.java
deleted file mode 100644
index 0421d92..0000000
--- a/bot/src/main/java/edu/java/bot/utils/Link.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package edu.java.bot.utils;
-
-import edu.java.bot.exception.link.NotLinkException;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-public class Link {
- private Link() {
- }
-
- public static URL parse(String link) throws NotLinkException {
- String url = link.toLowerCase();
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
- throw new NotLinkException("У ссылки отсутствует протокол");
- }
- try {
- return new URI(link).toURL();
- } catch (MalformedURLException | URISyntaxException e) {
- throw new NotLinkException("Неверная ссылка");
- }
- }
-}
diff --git a/bot/src/test/java/edu/java/bot/BotApplicationTest.java b/bot/src/test/java/edu/java/bot/BotApplicationTest.java
index 65f3332..51bea96 100644
--- a/bot/src/test/java/edu/java/bot/BotApplicationTest.java
+++ b/bot/src/test/java/edu/java/bot/BotApplicationTest.java
@@ -3,20 +3,22 @@
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.User;
-import edu.java.bot.command.AbstractCommand;
-import edu.java.bot.command.AbstractServiceCommand;
-import edu.java.bot.command.Command;
+import edu.java.bot.telegram.command.AbstractCommand;
+import edu.java.bot.telegram.command.AbstractServiceCommand;
+import edu.java.bot.telegram.command.Command;
import edu.java.bot.configuration.TelegramBotConfig;
-import edu.java.bot.exception.UnregisteredUserException;
-import edu.java.bot.exception.command.CommandException;
-import edu.java.bot.exception.command.CommandNotExistException;
-import edu.java.bot.exception.command.NotCommandException;
-import edu.java.bot.exception.link.LinkException;
-import edu.java.bot.exception.link.LinkRegistrationException;
-import edu.java.bot.exception.link.NotLinkException;
-import edu.java.bot.exception.parameter.ParameterException;
-import edu.java.bot.exception.parameter.WrongNumberParametersException;
+import edu.java.bot.telegram.exception.UnregisteredUserException;
+import edu.java.bot.telegram.exception.command.CommandException;
+import edu.java.bot.telegram.exception.command.CommandNotExistException;
+import edu.java.bot.telegram.exception.command.NotCommandException;
+import edu.java.dto.utils.exception.BadSourceUrlException;
+import edu.java.dto.utils.exception.LinkException;
+import edu.java.dto.utils.exception.LinkRegistrationException;
+import edu.java.dto.utils.exception.NotLinkException;
+import edu.java.bot.telegram.exception.parameter.ParameterException;
+import edu.java.bot.telegram.exception.parameter.WrongNumberParametersException;
import java.util.List;
+import edu.java.dto.utils.exception.SourceNotSupportedException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -122,10 +124,10 @@ void helpTest() throws UnregisteredUserException, ParameterException, CommandExc
void trackTest() throws UnregisteredUserException, ParameterException, CommandException, LinkException {
service.setUserRegistered(true);
service.setLinks(List.of());
- setMessage("/track https://github.com/cyberpanncake");
+ setMessage("/track https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring");
Command command = AbstractCommand.parse(updateMock, config.commands());
String expected = "Ссылка добавлена в отслеживаемые";
- String actual = command.execute(updateMock);
+ String actual = command.execute(updateMock);
Assertions.assertEquals(expected, actual);
}
@@ -138,18 +140,36 @@ void notLinkExceptionTest() throws CommandException {
Assertions.assertThrows(NotLinkException.class, () -> command.execute(updateMock));
}
+ @Test
+ void sourceNotSupportedExceptionTest() throws CommandException {
+ service.setUserRegistered(true);
+ service.setLinks(List.of());
+ setMessage("/track https://google.com");
+ Command command = AbstractCommand.parse(updateMock, config.commands());
+ Assertions.assertThrows(SourceNotSupportedException.class, () -> command.execute(updateMock));
+ }
+
+ @Test
+ void badSourceUrlExceptionTest() throws CommandException {
+ service.setUserRegistered(true);
+ service.setLinks(List.of());
+ setMessage("/track https://github.com/cyberpanncake");
+ Command command = AbstractCommand.parse(updateMock, config.commands());
+ Assertions.assertThrows(BadSourceUrlException.class, () -> command.execute(updateMock));
+ }
+
@Test
void unregisteredUserExceptionTest() throws CommandException {
service.setUserRegistered(false);
service.setLinks(List.of());
- setMessage("/track https://github.com/cyberpanncake");
+ setMessage("/track https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring");
Command command = AbstractCommand.parse(updateMock, config.commands());
Assertions.assertThrows(UnregisteredUserException.class, () -> command.execute(updateMock));
}
@Test
void linkRegisteredExceptionTest() throws CommandException {
- String link = "https://github.com/cyberpanncake";
+ String link = "https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring";
service.setUserRegistered(true);
service.setLinks(List.of(link));
setMessage("/track " + link);
@@ -159,7 +179,7 @@ void linkRegisteredExceptionTest() throws CommandException {
@Test
void untrackTest() throws UnregisteredUserException, ParameterException, CommandException, LinkException {
- String link = "https://github.com/cyberpanncake";
+ String link = "https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring";
service.setUserRegistered(true);
service.setLinks(List.of(link));
setMessage("/untrack " + link);
@@ -173,7 +193,7 @@ void untrackTest() throws UnregisteredUserException, ParameterException, Command
void linkNotRegisteredExceptionTest() throws CommandException {
service.setUserRegistered(true);
service.setLinks(List.of());
- setMessage("/untrack https://github.com/cyberpanncake");
+ setMessage("/untrack https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring");
Command command = AbstractCommand.parse(updateMock, config.commands());
Assertions.assertThrows(LinkRegistrationException.class, () -> command.execute(updateMock));
}
diff --git a/bot/src/test/java/edu/java/bot/TestScrapperService.java b/bot/src/test/java/edu/java/bot/TestScrapperService.java
index 6eb3f3c..b5a827c 100644
--- a/bot/src/test/java/edu/java/bot/TestScrapperService.java
+++ b/bot/src/test/java/edu/java/bot/TestScrapperService.java
@@ -1,6 +1,6 @@
package edu.java.bot;
-import edu.java.bot.service.ScrapperService;
+import edu.java.bot.client.service.ScrapperService;
import java.util.List;
import lombok.Setter;
diff --git a/dto/pom.xml b/dto/pom.xml
new file mode 100644
index 0000000..a9c9b8f
--- /dev/null
+++ b/dto/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ edu.java
+ root
+ ${revision}
+
+
+ dto
+ ${revision}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.3
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ 21
+ 21
+ UTF-8
+
+
+
diff --git a/scrapper/src/main/java/edu/java/api/dto/ApiErrorResponse.java b/dto/src/main/java/edu/java/dto/api/bot/ApiErrorResponse.java
similarity index 79%
rename from scrapper/src/main/java/edu/java/api/dto/ApiErrorResponse.java
rename to dto/src/main/java/edu/java/dto/api/bot/ApiErrorResponse.java
index e04ccab..c760ecd 100644
--- a/scrapper/src/main/java/edu/java/api/dto/ApiErrorResponse.java
+++ b/dto/src/main/java/edu/java/dto/api/bot/ApiErrorResponse.java
@@ -1,4 +1,4 @@
-package edu.java.api.dto;
+package edu.java.dto.api.bot;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -8,7 +8,6 @@ public record ApiErrorResponse(
String code,
String exceptionName,
String exceptionMessage,
-
- StackTraceElement[] stacktrace
+ String[] stacktrace
) {
}
diff --git a/bot/src/main/java/edu/java/bot/api/dto/LinkUpdateRequest.java b/dto/src/main/java/edu/java/dto/api/bot/LinkUpdateRequest.java
similarity index 55%
rename from bot/src/main/java/edu/java/bot/api/dto/LinkUpdateRequest.java
rename to dto/src/main/java/edu/java/dto/api/bot/LinkUpdateRequest.java
index 3598f86..410fa0d 100644
--- a/bot/src/main/java/edu/java/bot/api/dto/LinkUpdateRequest.java
+++ b/dto/src/main/java/edu/java/dto/api/bot/LinkUpdateRequest.java
@@ -1,14 +1,14 @@
-package edu.java.bot.api.dto;
+package edu.java.dto.api.bot;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import org.hibernate.validator.constraints.URL;
+import java.net.URI;
@JsonIgnoreProperties(ignoreUnknown = true)
public record LinkUpdateRequest(
- long id,
- @URL
- String url,
+ Long id,
+ URI url,
String description,
- long[] tgChatIds
+ Long[] tgChatIds
) {
}
+
diff --git a/scrapper/src/main/java/edu/java/api/dto/AddLinkRequest.java b/dto/src/main/java/edu/java/dto/api/scrapper/AddLinkRequest.java
similarity index 86%
rename from scrapper/src/main/java/edu/java/api/dto/AddLinkRequest.java
rename to dto/src/main/java/edu/java/dto/api/scrapper/AddLinkRequest.java
index 35e5038..a45ba0c 100644
--- a/scrapper/src/main/java/edu/java/api/dto/AddLinkRequest.java
+++ b/dto/src/main/java/edu/java/dto/api/scrapper/AddLinkRequest.java
@@ -1,4 +1,4 @@
-package edu.java.api.dto;
+package edu.java.dto.api.scrapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.hibernate.validator.constraints.URL;
diff --git a/bot/src/main/java/edu/java/bot/api/dto/ApiErrorResponse.java b/dto/src/main/java/edu/java/dto/api/scrapper/ApiErrorResponse.java
similarity index 78%
rename from bot/src/main/java/edu/java/bot/api/dto/ApiErrorResponse.java
rename to dto/src/main/java/edu/java/dto/api/scrapper/ApiErrorResponse.java
index 364072c..47752f1 100644
--- a/bot/src/main/java/edu/java/bot/api/dto/ApiErrorResponse.java
+++ b/dto/src/main/java/edu/java/dto/api/scrapper/ApiErrorResponse.java
@@ -1,4 +1,4 @@
-package edu.java.bot.api.dto;
+package edu.java.dto.api.scrapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -8,7 +8,6 @@ public record ApiErrorResponse(
String code,
String exceptionName,
String exceptionMessage,
-
- StackTraceElement[] stacktrace
+ String[] stacktrace
) {
}
diff --git a/scrapper/src/main/java/edu/java/api/dto/LinkResponse.java b/dto/src/main/java/edu/java/dto/api/scrapper/LinkResponse.java
similarity index 56%
rename from scrapper/src/main/java/edu/java/api/dto/LinkResponse.java
rename to dto/src/main/java/edu/java/dto/api/scrapper/LinkResponse.java
index ce13424..bf3d094 100644
--- a/scrapper/src/main/java/edu/java/api/dto/LinkResponse.java
+++ b/dto/src/main/java/edu/java/dto/api/scrapper/LinkResponse.java
@@ -1,12 +1,11 @@
-package edu.java.api.dto;
+package edu.java.dto.api.scrapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import org.hibernate.validator.constraints.URL;
+import java.net.URI;
@JsonIgnoreProperties(ignoreUnknown = true)
public record LinkResponse(
- long id,
- @URL
- String url
+ Long id,
+ URI url
) {
}
diff --git a/scrapper/src/main/java/edu/java/api/dto/ListLinksResponse.java b/dto/src/main/java/edu/java/dto/api/scrapper/ListLinksResponse.java
similarity index 76%
rename from scrapper/src/main/java/edu/java/api/dto/ListLinksResponse.java
rename to dto/src/main/java/edu/java/dto/api/scrapper/ListLinksResponse.java
index a6707c5..6393dce 100644
--- a/scrapper/src/main/java/edu/java/api/dto/ListLinksResponse.java
+++ b/dto/src/main/java/edu/java/dto/api/scrapper/ListLinksResponse.java
@@ -1,10 +1,10 @@
-package edu.java.api.dto;
+package edu.java.dto.api.scrapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record ListLinksResponse(
LinkResponse[] links,
- int size
+ Integer size
) {
}
diff --git a/scrapper/src/main/java/edu/java/api/dto/RemoveLinkRequest.java b/dto/src/main/java/edu/java/dto/api/scrapper/RemoveLinkRequest.java
similarity index 86%
rename from scrapper/src/main/java/edu/java/api/dto/RemoveLinkRequest.java
rename to dto/src/main/java/edu/java/dto/api/scrapper/RemoveLinkRequest.java
index 318f8f2..1ed7b78 100644
--- a/scrapper/src/main/java/edu/java/api/dto/RemoveLinkRequest.java
+++ b/dto/src/main/java/edu/java/dto/api/scrapper/RemoveLinkRequest.java
@@ -1,4 +1,4 @@
-package edu.java.api.dto;
+package edu.java.dto.api.scrapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.hibernate.validator.constraints.URL;
diff --git a/dto/src/main/java/edu/java/dto/utils/LinkInfo.java b/dto/src/main/java/edu/java/dto/utils/LinkInfo.java
new file mode 100644
index 0000000..f024812
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/LinkInfo.java
@@ -0,0 +1,7 @@
+package edu.java.dto.utils;
+
+import edu.java.dto.utils.sources.info.SourceInfo;
+import java.net.URI;
+
+public record LinkInfo(URI url, SourceInfo source) {
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/LinkParser.java b/dto/src/main/java/edu/java/dto/utils/LinkParser.java
new file mode 100644
index 0000000..b1e2eef
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/LinkParser.java
@@ -0,0 +1,73 @@
+package edu.java.dto.utils;
+
+import edu.java.dto.utils.exception.NotLinkException;
+import edu.java.dto.utils.exception.SourceException;
+import edu.java.dto.utils.exception.SourceNotSupportedException;
+import edu.java.dto.utils.sources.info.SourceInfo;
+import edu.java.dto.utils.sources.parsers.SourceParser;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class LinkParser {
+ private static final String NOT_LINK_MESSAGE = "Неверная ссылка";
+ private final SourceParser parser;
+ private final boolean checkIsLinkAvailable;
+
+ public LinkParser(boolean checkLinkAvailable) {
+ this.checkIsLinkAvailable = checkLinkAvailable;
+ parser = null;
+ }
+
+ public LinkParser(SourceParser parser, boolean checkIsLinkAvailable) {
+ this.parser = parser;
+ this.checkIsLinkAvailable = checkIsLinkAvailable;
+ }
+
+ public LinkInfo parse(String link) throws NotLinkException, SourceException {
+ URI uri = tryMakeURI(link);
+ if (checkIsLinkAvailable) {
+ checkIsLinkAvailable(uri);
+ }
+ SourceInfo sourceInfo = tryParseSource(uri);
+ return new LinkInfo(uri, sourceInfo);
+ }
+
+ private static URI tryMakeURI(String link) throws NotLinkException {
+ try {
+ return new URI(link);
+ } catch (URISyntaxException e) {
+ throw new NotLinkException(NOT_LINK_MESSAGE);
+ }
+ }
+
+ private static void checkIsLinkAvailable(URI uri) throws NotLinkException {
+ HttpURLConnection connection = null;
+ try {
+ URL url = uri.toURL();
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("HEAD");
+ int response = connection.getResponseCode();
+ if (response != HttpURLConnection.HTTP_OK) {
+ throw new NotLinkException("Ссылка не существует или недоступна");
+ }
+ } catch (NotLinkException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new NotLinkException(NOT_LINK_MESSAGE);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ private SourceInfo tryParseSource(URI uri) throws SourceException {
+ try {
+ return parser.parse(uri);
+ } catch (NullPointerException | SourceNotSupportedException e) {
+ throw new SourceNotSupportedException("Данный ресурс не поддерживается");
+ }
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/exception/BadSourceUrlException.java b/dto/src/main/java/edu/java/dto/utils/exception/BadSourceUrlException.java
new file mode 100644
index 0000000..21bc38a
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/exception/BadSourceUrlException.java
@@ -0,0 +1,10 @@
+package edu.java.dto.utils.exception;
+
+public class BadSourceUrlException extends SourceException {
+ public BadSourceUrlException() {
+ }
+
+ public BadSourceUrlException(String message) {
+ super(message);
+ }
+}
diff --git a/bot/src/main/java/edu/java/bot/exception/link/LinkException.java b/dto/src/main/java/edu/java/dto/utils/exception/LinkException.java
similarity index 64%
rename from bot/src/main/java/edu/java/bot/exception/link/LinkException.java
rename to dto/src/main/java/edu/java/dto/utils/exception/LinkException.java
index 483556e..7a005b5 100644
--- a/bot/src/main/java/edu/java/bot/exception/link/LinkException.java
+++ b/dto/src/main/java/edu/java/dto/utils/exception/LinkException.java
@@ -1,6 +1,8 @@
-package edu.java.bot.exception.link;
+package edu.java.dto.utils.exception;
public abstract class LinkException extends Exception {
+ public LinkException() {
+ }
public LinkException(String message) {
super(message);
diff --git a/bot/src/main/java/edu/java/bot/exception/link/LinkRegistrationException.java b/dto/src/main/java/edu/java/dto/utils/exception/LinkRegistrationException.java
similarity index 63%
rename from bot/src/main/java/edu/java/bot/exception/link/LinkRegistrationException.java
rename to dto/src/main/java/edu/java/dto/utils/exception/LinkRegistrationException.java
index 2c3b5ff..ce9eca7 100644
--- a/bot/src/main/java/edu/java/bot/exception/link/LinkRegistrationException.java
+++ b/dto/src/main/java/edu/java/dto/utils/exception/LinkRegistrationException.java
@@ -1,6 +1,9 @@
-package edu.java.bot.exception.link;
+package edu.java.dto.utils.exception;
public class LinkRegistrationException extends LinkException {
+ public LinkRegistrationException() {
+ }
+
public LinkRegistrationException(String message) {
super(message);
}
diff --git a/bot/src/main/java/edu/java/bot/exception/link/NotLinkException.java b/dto/src/main/java/edu/java/dto/utils/exception/NotLinkException.java
similarity index 63%
rename from bot/src/main/java/edu/java/bot/exception/link/NotLinkException.java
rename to dto/src/main/java/edu/java/dto/utils/exception/NotLinkException.java
index b3124f9..8205cc3 100644
--- a/bot/src/main/java/edu/java/bot/exception/link/NotLinkException.java
+++ b/dto/src/main/java/edu/java/dto/utils/exception/NotLinkException.java
@@ -1,6 +1,9 @@
-package edu.java.bot.exception.link;
+package edu.java.dto.utils.exception;
public class NotLinkException extends LinkException {
+ public NotLinkException() {
+ }
+
public NotLinkException(String message) {
super(message);
}
diff --git a/dto/src/main/java/edu/java/dto/utils/exception/SourceException.java b/dto/src/main/java/edu/java/dto/utils/exception/SourceException.java
new file mode 100644
index 0000000..6a1b06f
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/exception/SourceException.java
@@ -0,0 +1,10 @@
+package edu.java.dto.utils.exception;
+
+public abstract class SourceException extends LinkException {
+ public SourceException() {
+ }
+
+ public SourceException(String message) {
+ super(message);
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/exception/SourceNotSupportedException.java b/dto/src/main/java/edu/java/dto/utils/exception/SourceNotSupportedException.java
new file mode 100644
index 0000000..7dc2801
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/exception/SourceNotSupportedException.java
@@ -0,0 +1,10 @@
+package edu.java.dto.utils.exception;
+
+public class SourceNotSupportedException extends SourceException {
+ public SourceNotSupportedException() {
+ }
+
+ public SourceNotSupportedException(String message) {
+ super(message);
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/info/GithubInfo.java b/dto/src/main/java/edu/java/dto/utils/sources/info/GithubInfo.java
new file mode 100644
index 0000000..9262dfb
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/info/GithubInfo.java
@@ -0,0 +1,19 @@
+package edu.java.dto.utils.sources.info;
+
+public class GithubInfo extends SourceInfo {
+ private final String owner;
+ private final String repo;
+
+ public GithubInfo(String owner, String repo) {
+ this.owner = owner;
+ this.repo = repo;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public String getRepo() {
+ return repo;
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/info/SourceInfo.java b/dto/src/main/java/edu/java/dto/utils/sources/info/SourceInfo.java
new file mode 100644
index 0000000..aeeff17
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/info/SourceInfo.java
@@ -0,0 +1,5 @@
+package edu.java.dto.utils.sources.info;
+
+public abstract class SourceInfo {
+
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/info/StackoverflowInfo.java b/dto/src/main/java/edu/java/dto/utils/sources/info/StackoverflowInfo.java
new file mode 100644
index 0000000..b9269f8
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/info/StackoverflowInfo.java
@@ -0,0 +1,13 @@
+package edu.java.dto.utils.sources.info;
+
+public class StackoverflowInfo extends SourceInfo {
+ private final long questionId;
+
+ public StackoverflowInfo(long questionId) {
+ this.questionId = questionId;
+ }
+
+ public long getQuestionId() {
+ return questionId;
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/parsers/GithubParser.java b/dto/src/main/java/edu/java/dto/utils/sources/parsers/GithubParser.java
new file mode 100644
index 0000000..6e344c7
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/parsers/GithubParser.java
@@ -0,0 +1,31 @@
+package edu.java.dto.utils.sources.parsers;
+
+import edu.java.dto.utils.exception.BadSourceUrlException;
+import edu.java.dto.utils.exception.SourceException;
+import edu.java.dto.utils.exception.SourceNotSupportedException;
+import edu.java.dto.utils.sources.info.GithubInfo;
+import java.net.URI;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GithubParser extends SourceParser {
+ private static final String BASE_PATTERN = "^https?://github.com";
+ private static final String LINK_PATTERN = BASE_PATTERN + "/(.+?)/(.+?)(/.*)?$";
+
+ @Override
+ GithubInfo parseSource(URI uri) throws SourceException {
+ String link = uri.toString();
+ Pattern pattern = Pattern.compile(LINK_PATTERN);
+ Matcher matcher = pattern.matcher(link);
+ if (matcher.find()) {
+ String owner = matcher.group(1);
+ String repo = matcher.group(2);
+ return new GithubInfo(owner, repo);
+ }
+ if (link.matches(BASE_PATTERN + ".*")) {
+ throw new BadSourceUrlException("Некорретная ссылка для GitHub, требуется формат\n"
+ + "\"http(-s)://github.com/{owner}/{repo}[/...]\"");
+ }
+ throw new SourceNotSupportedException();
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/parsers/SourceParser.java b/dto/src/main/java/edu/java/dto/utils/sources/parsers/SourceParser.java
new file mode 100644
index 0000000..9a72fcf
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/parsers/SourceParser.java
@@ -0,0 +1,37 @@
+package edu.java.dto.utils.sources.parsers;
+
+import edu.java.dto.utils.exception.SourceException;
+import edu.java.dto.utils.exception.SourceNotSupportedException;
+import edu.java.dto.utils.sources.info.SourceInfo;
+import java.net.URI;
+import java.util.Set;
+
+public abstract class SourceParser {
+ protected SourceParser next;
+
+ public void setNext(SourceParser next) {
+ this.next = next;
+ }
+
+ public SourceInfo parse(URI uri) throws SourceException {
+ try {
+ return parseSource(uri);
+ } catch (SourceNotSupportedException e) {
+ if (next != null) {
+ return next.parse(uri);
+ }
+ throw e;
+ }
+ }
+
+ abstract SourceInfo parseSource(URI uri) throws SourceException;
+
+ public static SourceParser buildChain(Set parsers) {
+ SourceParser first = null;
+ for (SourceParser parser : parsers) {
+ parser.setNext(first);
+ first = parser;
+ }
+ return first;
+ }
+}
diff --git a/dto/src/main/java/edu/java/dto/utils/sources/parsers/StackoverflowParser.java b/dto/src/main/java/edu/java/dto/utils/sources/parsers/StackoverflowParser.java
new file mode 100644
index 0000000..3cd5935
--- /dev/null
+++ b/dto/src/main/java/edu/java/dto/utils/sources/parsers/StackoverflowParser.java
@@ -0,0 +1,30 @@
+package edu.java.dto.utils.sources.parsers;
+
+import edu.java.dto.utils.exception.BadSourceUrlException;
+import edu.java.dto.utils.exception.SourceException;
+import edu.java.dto.utils.exception.SourceNotSupportedException;
+import edu.java.dto.utils.sources.info.StackoverflowInfo;
+import java.net.URI;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class StackoverflowParser extends SourceParser {
+ private static final String BASE_PATTERN = "^https?://stackoverflow.com/questions";
+ private static final String LINK_PATTERN = BASE_PATTERN + "/([0-9]+?)(/.*)?$";
+
+ @Override
+ StackoverflowInfo parseSource(URI uri) throws SourceException {
+ String link = uri.toString();
+ Pattern pattern = Pattern.compile(LINK_PATTERN);
+ Matcher matcher = pattern.matcher(link);
+ if (matcher.find()) {
+ long questionId = Long.parseLong(matcher.group(1));
+ return new StackoverflowInfo(questionId);
+ }
+ if (link.matches(BASE_PATTERN + ".*")) {
+ throw new BadSourceUrlException("Некорретная ссылка для Stackoverflow, требуется формат\n"
+ + "\"http(-s)://stackoverflow.com/{questionId}[/...]\"");
+ }
+ throw new SourceNotSupportedException();
+ }
+}
diff --git a/pom.xml b/pom.xml
index e813daf..02e04cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
bot
scrapper
+ dto
diff --git a/scrapper/pom.xml b/scrapper/pom.xml
index 3370247..b8833da 100644
--- a/scrapper/pom.xml
+++ b/scrapper/pom.xml
@@ -138,6 +138,12 @@
kafka
test
+
+ edu.java
+ dto
+ 0.1
+ compile
+
diff --git a/scrapper/src/main/java/edu/java/api/client/BotClient.java b/scrapper/src/main/java/edu/java/api/client/BotClient.java
deleted file mode 100644
index 023466f..0000000
--- a/scrapper/src/main/java/edu/java/api/client/BotClient.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package edu.java.api.client;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import edu.java.client.AbstractClient;
-import lombok.NonNull;
-
-public class BotClient extends AbstractClient {
-
- public BotClient(@NonNull String baseUrl, ObjectMapper mapper) {
- super(baseUrl, mapper);
- }
-}
diff --git a/scrapper/src/main/java/edu/java/api/exception/chat/ChatRegistrationException.java b/scrapper/src/main/java/edu/java/api/exception/chat/ChatRegistrationException.java
deleted file mode 100644
index d804ca0..0000000
--- a/scrapper/src/main/java/edu/java/api/exception/chat/ChatRegistrationException.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package edu.java.api.exception.chat;
-
-public class ChatRegistrationException extends Exception {
-}
diff --git a/scrapper/src/main/java/edu/java/ScrapperApplication.java b/scrapper/src/main/java/edu/java/scrapper/ScrapperApplication.java
similarity index 85%
rename from scrapper/src/main/java/edu/java/ScrapperApplication.java
rename to scrapper/src/main/java/edu/java/scrapper/ScrapperApplication.java
index fadceb1..22cb3aa 100644
--- a/scrapper/src/main/java/edu/java/ScrapperApplication.java
+++ b/scrapper/src/main/java/edu/java/scrapper/ScrapperApplication.java
@@ -1,6 +1,6 @@
-package edu.java;
+package edu.java.scrapper;
-import edu.java.configuration.ApplicationConfig;
+import edu.java.scrapper.configuration.ApplicationConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
diff --git a/scrapper/src/main/java/edu/java/api/controller/ChatController.java b/scrapper/src/main/java/edu/java/scrapper/api/controller/ChatController.java
similarity index 79%
rename from scrapper/src/main/java/edu/java/api/controller/ChatController.java
rename to scrapper/src/main/java/edu/java/scrapper/api/controller/ChatController.java
index 92849c1..25fff0f 100644
--- a/scrapper/src/main/java/edu/java/api/controller/ChatController.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/controller/ChatController.java
@@ -1,6 +1,9 @@
-package edu.java.api.controller;
+package edu.java.scrapper.api.controller;
-import edu.java.api.dto.ApiErrorResponse;
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import edu.java.scrapper.api.exception.chat.ChatAlreadyRegisteredException;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+import edu.java.scrapper.api.service.ChatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -17,6 +20,11 @@
@RestController
@RequestMapping("/tg-chat")
public class ChatController {
+ private final ChatService service;
+
+ public ChatController(ChatService service) {
+ this.service = service;
+ }
@Operation(summary = "Зарегистрировать чат")
@ApiResponses(value = {
@@ -26,11 +34,8 @@ public class ChatController {
schema = @Schema(implementation = ApiErrorResponse.class))})
})
@PostMapping("/{id}")
- public ResponseEntity registerChat(@PathVariable @Positive long id) {
- /*
- TODO: регистрация чата
- может быть брошено ChatRegistrationException
- */
+ public ResponseEntity registerChat(@PathVariable @Positive long id) throws ChatAlreadyRegisteredException {
+ service.register(id);
return ResponseEntity.ok().build();
}
@@ -45,11 +50,8 @@ public ResponseEntity registerChat(@PathVariable @Positive long id) {
schema = @Schema(implementation = ApiErrorResponse.class))})
})
@DeleteMapping("/{id}")
- public ResponseEntity deleteChat(@PathVariable @Positive long id) {
- /*
- TODO: удаление чата
- может быть брошено ChatNotFoundException
- */
+ public ResponseEntity deleteChat(@PathVariable @Positive long id) throws ChatNotFoundException {
+ service.unregister(id);
return ResponseEntity.ok().build();
}
}
diff --git a/scrapper/src/main/java/edu/java/api/controller/LinkController.java b/scrapper/src/main/java/edu/java/scrapper/api/controller/LinkController.java
similarity index 70%
rename from scrapper/src/main/java/edu/java/api/controller/LinkController.java
rename to scrapper/src/main/java/edu/java/scrapper/api/controller/LinkController.java
index 5829aed..6f40192 100644
--- a/scrapper/src/main/java/edu/java/api/controller/LinkController.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/controller/LinkController.java
@@ -1,16 +1,23 @@
-package edu.java.api.controller;
+package edu.java.scrapper.api.controller;
-import edu.java.api.dto.AddLinkRequest;
-import edu.java.api.dto.ApiErrorResponse;
-import edu.java.api.dto.LinkResponse;
-import edu.java.api.dto.ListLinksResponse;
-import edu.java.api.dto.RemoveLinkRequest;
+import edu.java.dto.api.scrapper.AddLinkRequest;
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import edu.java.dto.api.scrapper.LinkResponse;
+import edu.java.dto.api.scrapper.ListLinksResponse;
+import edu.java.dto.api.scrapper.RemoveLinkRequest;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+import edu.java.scrapper.api.exception.link.LinkAdditionException;
+import edu.java.scrapper.api.exception.link.LinkNotFoundException;
+import edu.java.scrapper.api.service.LinkService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
+import java.net.URI;
+import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -23,6 +30,11 @@
@RestController
@RequestMapping("/links")
public class LinkController {
+ private final LinkService service;
+
+ public LinkController(LinkService service) {
+ this.service = service;
+ }
@Operation(summary = "Получить все отслеживаемые ссылки")
@ApiResponses(value = {
@@ -34,11 +46,15 @@ public class LinkController {
schema = @Schema(implementation = ApiErrorResponse.class))})
})
@GetMapping
- public ResponseEntity getLinks(@RequestHeader(name = "Tg-Chat-Id") long chatId) {
- /*
- TODO: получение списка ссылок
- */
- return ResponseEntity.ok(new ListLinksResponse(new LinkResponse[0], 1));
+ public ResponseEntity getLinks(@RequestHeader(name = "Tg-Chat-Id") long chatId)
+ throws ChatNotFoundException {
+ List links = service.listAll(chatId);
+ return ResponseEntity.ok(new ListLinksResponse(
+ links.stream()
+ .map(l -> new LinkResponse(l.id(), l.url()))
+ .toArray(LinkResponse[]::new),
+ links.size()
+ ));
}
@Operation(summary = "Добавить отслеживание ссылки")
@@ -54,12 +70,9 @@ public ResponseEntity getLinks(@RequestHeader(name = "Tg-Chat
public ResponseEntity addLink(
@RequestHeader(name = "Tg-Chat-Id") long chatId,
@Valid @RequestBody AddLinkRequest request
- ) {
- /*
- TODO: добавление ссылки
- может быть брошено LinkAdditionException
- */
- return ResponseEntity.ok(new LinkResponse(0, ""));
+ ) throws LinkAdditionException, ChatNotFoundException {
+ Link link = service.add(chatId, URI.create(request.link()));
+ return ResponseEntity.ok(new LinkResponse(link.id(), link.url()));
}
@Operation(summary = "Убрать отслеживание ссылки")
@@ -78,11 +91,8 @@ public ResponseEntity addLink(
public ResponseEntity deleteLink(
@RequestHeader(name = "Tg-Chat-Id") long chatId,
@Valid @RequestBody RemoveLinkRequest request
- ) {
- /*
- TODO: удаление ссылки
- может быть брошено LinkNotFoundException
- */
- return ResponseEntity.ok(new LinkResponse(0, ""));
+ ) throws ChatNotFoundException, LinkNotFoundException {
+ Link link = service.remove(chatId, URI.create(request.link()));
+ return ResponseEntity.ok(new LinkResponse(link.id(), link.url()));
}
}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Chat.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Chat.java
new file mode 100644
index 0000000..92db131
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Chat.java
@@ -0,0 +1,4 @@
+package edu.java.scrapper.api.domain.dto;
+
+public record Chat(Long id, long tgId) {
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Link.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Link.java
new file mode 100644
index 0000000..f96249d
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Link.java
@@ -0,0 +1,7 @@
+package edu.java.scrapper.api.domain.dto;
+
+import java.net.URI;
+import java.time.OffsetDateTime;
+
+public record Link(Long id, URI url, OffsetDateTime lastUpdate, OffsetDateTime lastCheck) {
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Subscription.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Subscription.java
new file mode 100644
index 0000000..8ce08d3
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/dto/Subscription.java
@@ -0,0 +1,4 @@
+package edu.java.scrapper.api.domain.dto;
+
+public record Subscription(long chatId, long linkId) {
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/ChatRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/ChatRepository.java
new file mode 100644
index 0000000..61c36ff
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/ChatRepository.java
@@ -0,0 +1,15 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import java.util.List;
+import java.util.Optional;
+
+public interface ChatRepository {
+ Optional findByTgId(long tgId);
+
+ Chat add(long tgId);
+
+ void remove(long tgId);
+
+ List findAll();
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/LinkRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/LinkRepository.java
new file mode 100644
index 0000000..d9a323f
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/LinkRepository.java
@@ -0,0 +1,25 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.api.domain.dto.Link;
+import java.net.URI;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Optional;
+
+public interface LinkRepository {
+ Optional findById(long id);
+
+ Optional findByUrl(URI url);
+
+ Link add(Link link);
+
+ void remove(URI url);
+
+ void remove(long id);
+
+ List findAll();
+
+ List findAllWithLastCheckOlderThan(OffsetDateTime time);
+
+ void update(Link link);
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/SubscriptionRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/SubscriptionRepository.java
new file mode 100644
index 0000000..63c98d9
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/SubscriptionRepository.java
@@ -0,0 +1,28 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+
+public interface SubscriptionRepository {
+ Optional find(long tgId, URI url);
+
+ Subscription add(Subscription subscription);
+
+ void remove(Subscription subscription);
+
+ List findAll();
+
+ List findAllByChat(Chat chat);
+
+ List findAllByLink(Link link);
+
+ boolean linkNotFollowedByAnyone(long linkId);
+
+ List findAllLinksByChat(Chat chat);
+
+ List findAllChatsByLink(Link link);
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/AbstractJdbcRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/AbstractJdbcRepository.java
new file mode 100644
index 0000000..2820fe6
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/AbstractJdbcRepository.java
@@ -0,0 +1,11 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import org.springframework.jdbc.core.simple.JdbcClient;
+
+public abstract class AbstractJdbcRepository {
+ protected final JdbcClient client;
+
+ public AbstractJdbcRepository(JdbcClient client) {
+ this.client = client;
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepository.java
new file mode 100644
index 0000000..d1a1000
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepository.java
@@ -0,0 +1,51 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.repository.ChatRepository;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.jdbc.core.simple.JdbcClient;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional
+public class JdbcChatRepository extends AbstractJdbcRepository implements ChatRepository {
+
+ public JdbcChatRepository(JdbcClient client) {
+ super(client);
+ }
+
+ @Override
+ public Optional findByTgId(long tgId) {
+ return client.sql("select * from chat where tg_id = ?")
+ .param(tgId)
+ .query(Chat.class)
+ .optional();
+ }
+
+ @Override
+ public Chat add(long tgId) {
+ GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
+ client.sql("insert into chat (tg_id) values (?)")
+ .param(tgId)
+ .update(keyHolder, "id");
+ long id = keyHolder.getKey().longValue();
+ return new Chat(id, tgId);
+ }
+
+ @Override
+ public void remove(long tgId) {
+ client.sql("delete from chat where tg_id = ?")
+ .param(tgId)
+ .update();
+ }
+
+ @Override
+ public List findAll() {
+ return client.sql("select * from chat")
+ .query(Chat.class)
+ .list();
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepository.java
new file mode 100644
index 0000000..7601691
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepository.java
@@ -0,0 +1,87 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.repository.LinkRepository;
+import java.net.URI;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.jdbc.core.simple.JdbcClient;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional
+public class JdbcLinkRepository extends AbstractJdbcRepository implements LinkRepository {
+
+ public JdbcLinkRepository(JdbcClient client) {
+ super(client);
+ }
+
+ @Override
+ public Optional findById(long id) {
+ return client.sql("select * from link where id = ?")
+ .param(id)
+ .query(Link.class)
+ .optional();
+ }
+
+ @Override
+ public Optional findByUrl(URI url) {
+ return client.sql("select * from link where url = ?")
+ .param(url.toString())
+ .query(Link.class)
+ .optional();
+ }
+
+ @Override
+ public Link add(Link link) {
+ GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
+ client.sql("insert into link (url, last_update, last_check) values (?, ?, ?)")
+ .param(link.url().toString())
+ .param(link.lastUpdate())
+ .param(link.lastCheck())
+ .update(keyHolder, "id");
+ long id = keyHolder.getKey().longValue();
+ return new Link(id, link.url(), link.lastUpdate(), link.lastCheck());
+ }
+
+ @Override
+ public void remove(URI url) {
+ client.sql("delete from link where url = ?")
+ .param(url.toString())
+ .update();
+ }
+
+ @Override
+ public void remove(long id) {
+ client.sql("delete from link where id = ?")
+ .param(id)
+ .update();
+ }
+
+ @Override
+ public List findAll() {
+ return client.sql("select * from link")
+ .query(Link.class)
+ .list();
+ }
+
+ @Override
+ public List findAllWithLastCheckOlderThan(OffsetDateTime time) {
+ return client.sql("select * from link where link.last_check < ?")
+ .param(time)
+ .query(Link.class)
+ .list();
+ }
+
+ @Override
+ public void update(Link link) {
+ client.sql("update link set last_update = ?, last_check = ? where id = ?")
+ .param(link.lastUpdate())
+ .param(link.lastCheck())
+ .param(link.id())
+ .update();
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepository.java b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepository.java
new file mode 100644
index 0000000..91b9f27
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepository.java
@@ -0,0 +1,114 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import edu.java.scrapper.api.domain.repository.SubscriptionRepository;
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.jdbc.core.simple.JdbcClient;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional
+public class JdbcSubscriptionRepository extends AbstractJdbcRepository implements SubscriptionRepository {
+
+ public JdbcSubscriptionRepository(JdbcClient client) {
+ super(client);
+ }
+
+ @Override
+ public Optional find(long tgId, URI url) {
+ return client.sql("""
+ select subscription.*
+ from subscription
+ inner join chat on subscription.chat_id = chat.id
+ inner join link on subscription.link_id = link.id
+ where
+ chat.tg_id = ?
+ and link.url = ?""")
+ .param(tgId)
+ .param(url.toString())
+ .query(Subscription.class)
+ .optional();
+ }
+
+ @Override
+ public Subscription add(Subscription subscription) {
+ client.sql("insert into subscription (chat_id, link_id) values (?, ?)")
+ .param(subscription.chatId())
+ .param(subscription.linkId())
+ .update();
+ return subscription;
+ }
+
+ @Override
+ public void remove(Subscription subscription) {
+ client.sql("delete from subscription where chat_id = ? and link_id = ?")
+ .param(subscription.chatId())
+ .param(subscription.linkId())
+ .update();
+ }
+
+ @Override
+ public List findAll() {
+ return client.sql("select * from subscription")
+ .query(Subscription.class)
+ .list();
+ }
+
+ @Override
+ public List findAllByChat(Chat chat) {
+ return client.sql("select * from subscription where chat_id = ?")
+ .param(chat.id())
+ .query(Subscription.class)
+ .list();
+ }
+
+ @SuppressWarnings("MultipleStringLiterals")
+ @Override
+ public List findAllByLink(Link link) {
+ return client.sql("select * from subscription where link_id = ?")
+ .param(link.id())
+ .query(Subscription.class)
+ .list();
+ }
+
+ @SuppressWarnings("MultipleStringLiterals")
+ @Override
+ public boolean linkNotFollowedByAnyone(long linkId) {
+ List subscriptions = client.sql("select * from subscription where link_id = ?")
+ .param(linkId)
+ .query(Subscription.class)
+ .list();
+ return subscriptions.isEmpty();
+ }
+
+ @Override
+ public List findAllLinksByChat(Chat chat) {
+ return client.sql("""
+ select link.*
+ from link
+ inner join subscription on link.id = subscription.link_id
+ where
+ subscription.chat_id = ?""")
+ .param(chat.id())
+ .query(Link.class)
+ .list();
+ }
+
+ @Override
+ public List findAllChatsByLink(Link link) {
+ return client.sql("""
+ select chat.*
+ from chat
+ inner join subscription on chat.id = subscription.chat_id
+ where
+ subscription.link_id = ?""")
+ .param(link.id())
+ .query(Chat.class)
+ .list();
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatAlreadyRegisteredException.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatAlreadyRegisteredException.java
new file mode 100644
index 0000000..f0333fc
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatAlreadyRegisteredException.java
@@ -0,0 +1,4 @@
+package edu.java.scrapper.api.exception.chat;
+
+public class ChatAlreadyRegisteredException extends Exception {
+}
diff --git a/scrapper/src/main/java/edu/java/api/exception/chat/ChatExceptionApiHandler.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatExceptionApiHandler.java
similarity index 59%
rename from scrapper/src/main/java/edu/java/api/exception/chat/ChatExceptionApiHandler.java
rename to scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatExceptionApiHandler.java
index 4483f07..3a14e19 100644
--- a/scrapper/src/main/java/edu/java/api/exception/chat/ChatExceptionApiHandler.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatExceptionApiHandler.java
@@ -1,6 +1,7 @@
-package edu.java.api.exception.chat;
+package edu.java.scrapper.api.exception.chat;
-import edu.java.api.dto.ApiErrorResponse;
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import java.util.Arrays;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -9,14 +10,16 @@
@RestControllerAdvice
public class ChatExceptionApiHandler {
- @ExceptionHandler(ChatRegistrationException.class)
- public ResponseEntity chatRegistrationException(ChatRegistrationException exception) {
+ @ExceptionHandler(ChatAlreadyRegisteredException.class)
+ public ResponseEntity chatRegistrationException(ChatAlreadyRegisteredException exception) {
ApiErrorResponse error = new ApiErrorResponse(
- "Чат с уже зарегистрирован",
+ "Чат уже зарегистрирован",
HttpStatus.BAD_REQUEST.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
@@ -26,11 +29,13 @@ public ResponseEntity chatRegistrationException(ChatRegistrati
@ExceptionHandler(ChatNotFoundException.class)
public ResponseEntity chatNotFoundException(ChatNotFoundException exception) {
ApiErrorResponse error = new ApiErrorResponse(
- "Чат с id не найден",
+ "Чат не найден",
HttpStatus.NOT_FOUND.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
@@ -40,14 +45,16 @@ public ResponseEntity chatNotFoundException(ChatNotFoundExcept
@ExceptionHandler(Exception.class)
public ResponseEntity anyException(Exception exception) {
ApiErrorResponse error = new ApiErrorResponse(
- "Неверные параметры запроса",
- HttpStatus.BAD_REQUEST.toString(),
+ "Произошла непредвиденная ошибка на стороне сервера",
+ HttpStatus.INTERNAL_SERVER_ERROR.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
diff --git a/scrapper/src/main/java/edu/java/api/exception/chat/ChatNotFoundException.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatNotFoundException.java
similarity index 55%
rename from scrapper/src/main/java/edu/java/api/exception/chat/ChatNotFoundException.java
rename to scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatNotFoundException.java
index 35cf0c7..647066d 100644
--- a/scrapper/src/main/java/edu/java/api/exception/chat/ChatNotFoundException.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/chat/ChatNotFoundException.java
@@ -1,4 +1,4 @@
-package edu.java.api.exception.chat;
+package edu.java.scrapper.api.exception.chat;
public class ChatNotFoundException extends Exception {
}
diff --git a/scrapper/src/main/java/edu/java/api/exception/link/LinkAdditionException.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkAdditionException.java
similarity index 55%
rename from scrapper/src/main/java/edu/java/api/exception/link/LinkAdditionException.java
rename to scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkAdditionException.java
index f5c22de..dbe67a6 100644
--- a/scrapper/src/main/java/edu/java/api/exception/link/LinkAdditionException.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkAdditionException.java
@@ -1,4 +1,4 @@
-package edu.java.api.exception.link;
+package edu.java.scrapper.api.exception.link;
public class LinkAdditionException extends Exception {
}
diff --git a/scrapper/src/main/java/edu/java/api/exception/link/LinkExceptionApiHandler.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkExceptionApiHandler.java
similarity index 67%
rename from scrapper/src/main/java/edu/java/api/exception/link/LinkExceptionApiHandler.java
rename to scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkExceptionApiHandler.java
index 7be1029..d17e833 100644
--- a/scrapper/src/main/java/edu/java/api/exception/link/LinkExceptionApiHandler.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkExceptionApiHandler.java
@@ -1,6 +1,7 @@
-package edu.java.api.exception.link;
+package edu.java.scrapper.api.exception.link;
-import edu.java.api.dto.ApiErrorResponse;
+import edu.java.dto.api.scrapper.ApiErrorResponse;
+import java.util.Arrays;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -16,7 +17,9 @@ public ResponseEntity linkAdditionException(LinkAdditionExcept
HttpStatus.BAD_REQUEST.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
@@ -30,7 +33,9 @@ public ResponseEntity linkNotFoundException(LinkNotFoundExcept
HttpStatus.NOT_FOUND.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
@@ -40,14 +45,16 @@ public ResponseEntity linkNotFoundException(LinkNotFoundExcept
@ExceptionHandler(Exception.class)
public ResponseEntity anyException(Exception exception) {
ApiErrorResponse error = new ApiErrorResponse(
- "Неверные параметры запроса",
- HttpStatus.BAD_REQUEST.toString(),
+ "Произошла непредвиденная ошибка на стороне сервера",
+ HttpStatus.INTERNAL_SERVER_ERROR.toString(),
exception.getClass().getName(),
exception.getMessage(),
- exception.getStackTrace()
+ Arrays.stream(exception.getStackTrace())
+ .map(StackTraceElement::toString)
+ .toArray(String[]::new)
);
return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
diff --git a/scrapper/src/main/java/edu/java/api/exception/link/LinkNotFoundException.java b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkNotFoundException.java
similarity index 55%
rename from scrapper/src/main/java/edu/java/api/exception/link/LinkNotFoundException.java
rename to scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkNotFoundException.java
index 879f43e..655dc36 100644
--- a/scrapper/src/main/java/edu/java/api/exception/link/LinkNotFoundException.java
+++ b/scrapper/src/main/java/edu/java/scrapper/api/exception/link/LinkNotFoundException.java
@@ -1,4 +1,4 @@
-package edu.java.api.exception.link;
+package edu.java.scrapper.api.exception.link;
public class LinkNotFoundException extends Exception {
}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/ChatService.java b/scrapper/src/main/java/edu/java/scrapper/api/service/ChatService.java
new file mode 100644
index 0000000..1eb5ee4
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/ChatService.java
@@ -0,0 +1,10 @@
+package edu.java.scrapper.api.service;
+
+import edu.java.scrapper.api.exception.chat.ChatAlreadyRegisteredException;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+
+public interface ChatService {
+ void register(long tgId) throws ChatAlreadyRegisteredException;
+
+ void unregister(long tgId) throws ChatNotFoundException;
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/LinkService.java b/scrapper/src/main/java/edu/java/scrapper/api/service/LinkService.java
new file mode 100644
index 0000000..fd58f07
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/LinkService.java
@@ -0,0 +1,16 @@
+package edu.java.scrapper.api.service;
+
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+import edu.java.scrapper.api.exception.link.LinkAdditionException;
+import edu.java.scrapper.api.exception.link.LinkNotFoundException;
+import java.net.URI;
+import java.util.List;
+
+public interface LinkService {
+ Link add(long tgId, URI url) throws ChatNotFoundException, LinkAdditionException;
+
+ Link remove(long tgId, URI url) throws LinkNotFoundException, ChatNotFoundException;
+
+ List listAll(long tgId) throws ChatNotFoundException;
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/LinkUpdater.java b/scrapper/src/main/java/edu/java/scrapper/api/service/LinkUpdater.java
new file mode 100644
index 0000000..502bf89
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/LinkUpdater.java
@@ -0,0 +1,5 @@
+package edu.java.scrapper.api.service;
+
+public interface LinkUpdater {
+ int update();
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/ScrapperService.java b/scrapper/src/main/java/edu/java/scrapper/api/service/ScrapperService.java
new file mode 100644
index 0000000..3e79981
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/ScrapperService.java
@@ -0,0 +1,24 @@
+package edu.java.scrapper.api.service;
+
+import edu.java.scrapper.api.domain.repository.ChatRepository;
+import edu.java.scrapper.api.domain.repository.LinkRepository;
+import edu.java.scrapper.api.domain.repository.SubscriptionRepository;
+import edu.java.scrapper.configuration.ApplicationConfig;
+
+public abstract class ScrapperService {
+ protected final ApplicationConfig config;
+ protected final ChatRepository chatRepo;
+ protected final LinkRepository linkRepo;
+ protected final SubscriptionRepository subscriptionRepo;
+
+ protected ScrapperService(
+ ApplicationConfig config, ChatRepository chatRepo,
+ LinkRepository linkRepo,
+ SubscriptionRepository subscriptionRepo
+ ) {
+ this.config = config;
+ this.chatRepo = chatRepo;
+ this.linkRepo = linkRepo;
+ this.subscriptionRepo = subscriptionRepo;
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcChatService.java b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcChatService.java
new file mode 100644
index 0000000..47a2b31
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcChatService.java
@@ -0,0 +1,56 @@
+package edu.java.scrapper.api.service.jdbc;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcChatRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcLinkRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcSubscriptionRepository;
+import edu.java.scrapper.api.exception.chat.ChatAlreadyRegisteredException;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+import edu.java.scrapper.api.service.ChatService;
+import edu.java.scrapper.api.service.ScrapperService;
+import edu.java.scrapper.configuration.ApplicationConfig;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@Service
+public class JdbcChatService extends ScrapperService implements ChatService {
+
+ @Autowired
+ protected JdbcChatService(
+ ApplicationConfig config,
+ JdbcChatRepository chatRepo, JdbcLinkRepository linkRepo,
+ JdbcSubscriptionRepository subscriptionRepo
+ ) {
+ super(config, chatRepo, linkRepo, subscriptionRepo);
+ }
+
+ @Override
+ public void register(long tgId) throws ChatAlreadyRegisteredException {
+ Optional chat = chatRepo.findByTgId(tgId);
+ if (chat.isPresent()) {
+ throw new ChatAlreadyRegisteredException();
+ }
+ chatRepo.add(tgId);
+ }
+
+ @Override
+ public void unregister(long tgId) throws ChatNotFoundException {
+ Optional chat = chatRepo.findByTgId(tgId);
+ if (chat.isEmpty()) {
+ throw new ChatNotFoundException();
+ }
+ List subscriptions = subscriptionRepo.findAllByChat(chat.get());
+ for (Subscription subscription : subscriptions) {
+ subscriptionRepo.remove(subscription);
+ if (subscriptionRepo.linkNotFollowedByAnyone(subscription.linkId())) {
+ linkRepo.remove(subscription.linkId());
+ }
+ }
+ chatRepo.remove(tgId);
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkService.java b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkService.java
new file mode 100644
index 0000000..ce6a854
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkService.java
@@ -0,0 +1,83 @@
+package edu.java.scrapper.api.service.jdbc;
+
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcChatRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcLinkRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcSubscriptionRepository;
+import edu.java.scrapper.api.exception.chat.ChatNotFoundException;
+import edu.java.scrapper.api.exception.link.LinkAdditionException;
+import edu.java.scrapper.api.exception.link.LinkNotFoundException;
+import edu.java.scrapper.api.service.LinkService;
+import edu.java.scrapper.api.service.ScrapperService;
+import edu.java.scrapper.configuration.ApplicationConfig;
+import java.net.URI;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@Service
+public class JdbcLinkService extends ScrapperService implements LinkService {
+
+ @Autowired
+ protected JdbcLinkService(
+ ApplicationConfig config,
+ JdbcChatRepository chatRepo, JdbcLinkRepository linkRepo,
+ JdbcSubscriptionRepository subscriptionRepo
+ ) {
+ super(config, chatRepo, linkRepo, subscriptionRepo);
+ }
+
+ @Override
+ public Link add(long tgId, URI url) throws ChatNotFoundException, LinkAdditionException {
+ Optional subscription = subscriptionRepo.find(tgId, url);
+ if (subscription.isPresent()) {
+ throw new LinkAdditionException();
+ }
+ Optional chat = chatRepo.findByTgId(tgId);
+ if (chat.isEmpty()) {
+ throw new ChatNotFoundException();
+ }
+ Optional link = linkRepo.findByUrl(url);
+ if (link.isEmpty()) {
+ OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
+ link = Optional.of(linkRepo.add(new Link(null, url, now, now)));
+ }
+ subscriptionRepo.add(new Subscription(chat.get().id(), link.get().id()));
+ return link.get();
+ }
+
+ @Override
+ public Link remove(long tgId, URI url) throws LinkNotFoundException, ChatNotFoundException {
+ Optional chat = chatRepo.findByTgId(tgId);
+ if (chat.isEmpty()) {
+ throw new ChatNotFoundException();
+ }
+ Optional subscription = subscriptionRepo.find(tgId, url);
+ if (subscription.isEmpty()) {
+ throw new LinkNotFoundException();
+ }
+ Link link = linkRepo.findById(subscription.get().linkId()).get();
+ subscriptionRepo.remove(subscription.get());
+ if (subscriptionRepo.linkNotFollowedByAnyone(link.id())) {
+ linkRepo.remove(link.id());
+ }
+ return link;
+ }
+
+ @Override
+ public List listAll(long tgId) throws ChatNotFoundException {
+ Optional chat = chatRepo.findByTgId(tgId);
+ if (chat.isEmpty()) {
+ throw new ChatNotFoundException();
+ }
+ return subscriptionRepo.findAllLinksByChat(chat.get());
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkUpdater.java b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkUpdater.java
new file mode 100644
index 0000000..7342648
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/api/service/jdbc/JdbcLinkUpdater.java
@@ -0,0 +1,109 @@
+package edu.java.scrapper.api.service.jdbc;
+
+import edu.java.dto.api.bot.LinkUpdateRequest;
+import edu.java.dto.utils.LinkInfo;
+import edu.java.dto.utils.exception.NotLinkException;
+import edu.java.dto.utils.exception.SourceException;
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcChatRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcLinkRepository;
+import edu.java.scrapper.api.domain.repository.jdbc.JdbcSubscriptionRepository;
+import edu.java.scrapper.api.service.LinkUpdater;
+import edu.java.scrapper.api.service.ScrapperService;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.configuration.ApplicationConfig;
+import edu.java.scrapper.configuration.ClientConfig;
+import edu.java.scrapper.configuration.LinkParserConfig;
+import edu.java.scrapper.shedule.update.dto.Update;
+import edu.java.scrapper.shedule.update.sources.SourceUpdater;
+import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@Service
+public class JdbcLinkUpdater extends ScrapperService implements LinkUpdater {
+ private final ClientConfig clientConfig;
+ private final LinkParserConfig parserConfig;
+
+ @Autowired
+ protected JdbcLinkUpdater(
+ ApplicationConfig config, LinkParserConfig parserConfig,
+ ClientConfig clientConfig,
+ JdbcChatRepository chatRepo, JdbcLinkRepository linkRepo,
+ JdbcSubscriptionRepository subscriptionRepo
+ ) {
+ super(config, chatRepo, linkRepo, subscriptionRepo);
+ this.parserConfig = parserConfig;
+ this.clientConfig = clientConfig;
+ }
+
+ @Override
+ public int update() {
+ Duration checkDelay = config.scheduler().forceCheckDelay();
+ OffsetDateTime lastCheck = OffsetDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
+ List linksNeedToCheck = linkRepo.findAllWithLastCheckOlderThan(lastCheck.minus(checkDelay));
+ int countUpdates = 0;
+ for (Link link : linksNeedToCheck) {
+ linkRepo.update(new Link(link.id(), link.url(), link.lastUpdate(), lastCheck));
+ try {
+ LinkInfo linkInfo = parserConfig.linkParser().parse(link.url().toString());
+ Optional update = getUpdateFromSource(linkInfo);
+ if (update.isPresent() && update.get().getCreatedAt().isAfter(link.lastUpdate())) {
+ countUpdates++;
+ linkRepo.update(new Link(link.id(), link.url(), update.get().getCreatedAt(), lastCheck));
+ processLinkUpdate(update.get(), link);
+ }
+ } catch (NotLinkException | SourceException | ResponseException e) {
+ removeCorruptedLinkWithSubscriptions(link);
+ }
+ }
+ return countUpdates;
+ }
+
+ private void processLinkUpdate(Update update, Link link) throws SourceException, NotLinkException {
+ List subscribers = subscriptionRepo.findAllChatsByLink(link);
+ if (subscribers.isEmpty()) {
+ linkRepo.remove(link.url());
+ return;
+ }
+ clientConfig.botClient().sendUpdate(new LinkUpdateRequest(
+ link.id(),
+ link.url(),
+ "Новое обновление по ссылке\n%s\nСоздано в %s"
+ .formatted(link.url(), update.getCreatedAt()),
+ subscribers.stream().map(Chat::tgId).toArray(Long[]::new)
+ ));
+ }
+
+ private Optional getUpdateFromSource(LinkInfo linkInfo) throws ResponseException {
+ SourceUpdater updater = SourceUpdater.getUpdaterForSource(clientConfig, linkInfo.source());
+ if (updater != null) {
+ return updater.getUpdate(linkInfo.source());
+ }
+ return Optional.empty();
+ }
+
+ private void removeCorruptedLinkWithSubscriptions(Link link) {
+ List subscribers = subscriptionRepo.findAllChatsByLink(link);
+ for (Chat subscriber : subscribers) {
+ Subscription subscription = new Subscription(subscriber.id(), link.id());
+ subscriptionRepo.remove(subscription);
+ }
+ linkRepo.remove(link.url());
+ clientConfig.botClient().sendUpdate(new LinkUpdateRequest(
+ link.id(),
+ link.url(),
+ "Ссылка\n" + link.url() + "\n больше не доступна, поэтому она удалена из Ваших подписок",
+ subscribers.stream().map(Chat::tgId).toArray(Long[]::new)
+ ));
+ }
+}
diff --git a/bot/src/main/java/edu/java/bot/api/client/AbstractClient.java b/scrapper/src/main/java/edu/java/scrapper/client/AbstractClient.java
similarity index 92%
rename from bot/src/main/java/edu/java/bot/api/client/AbstractClient.java
rename to scrapper/src/main/java/edu/java/scrapper/client/AbstractClient.java
index 5ebef97..e73da2f 100644
--- a/bot/src/main/java/edu/java/bot/api/client/AbstractClient.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/AbstractClient.java
@@ -1,4 +1,4 @@
-package edu.java.bot.api.client;
+package edu.java.scrapper.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.NonNull;
diff --git a/scrapper/src/main/java/edu/java/scrapper/client/bot/BotApiException.java b/scrapper/src/main/java/edu/java/scrapper/client/bot/BotApiException.java
new file mode 100644
index 0000000..378c9be
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/client/bot/BotApiException.java
@@ -0,0 +1,15 @@
+package edu.java.scrapper.client.bot;
+
+import edu.java.dto.api.bot.ApiErrorResponse;
+import lombok.Getter;
+
+@Getter
+public class BotApiException extends Exception {
+ private final ApiErrorResponse error;
+
+ public BotApiException(ApiErrorResponse error) {
+ super("%s: %s. %s - %s\n%s".formatted(error.code(), error.description(),
+ error.exceptionName(), error.exceptionMessage(), String.join("\n", error.stacktrace())));
+ this.error = error;
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/client/bot/BotClient.java b/scrapper/src/main/java/edu/java/scrapper/client/bot/BotClient.java
new file mode 100644
index 0000000..6cba112
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/client/bot/BotClient.java
@@ -0,0 +1,35 @@
+package edu.java.scrapper.client.bot;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.java.dto.api.bot.ApiErrorResponse;
+import edu.java.dto.api.bot.LinkUpdateRequest;
+import edu.java.scrapper.client.AbstractClient;
+import lombok.NonNull;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import reactor.core.publisher.Mono;
+
+public class BotClient extends AbstractClient {
+
+ public BotClient(@NonNull String baseUrl, ObjectMapper mapper) {
+ super(baseUrl, mapper);
+ }
+
+ public void sendUpdate(LinkUpdateRequest request) {
+ client.post()
+ .uri("/update")
+ .accept(MediaType.APPLICATION_JSON)
+ .body(Mono.just(request), LinkUpdateRequest.class)
+ .retrieve()
+ .onStatus(HttpStatusCode::isError, this::getException)
+ .bodyToMono(Void.class)
+ .block();
+ }
+
+ private Mono getException(ClientResponse response) {
+ return response
+ .bodyToMono(ApiErrorResponse.class)
+ .map(BotApiException::new);
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/client/GithubClient.java b/scrapper/src/main/java/edu/java/scrapper/client/sources/GithubClient.java
similarity index 74%
rename from scrapper/src/main/java/edu/java/client/GithubClient.java
rename to scrapper/src/main/java/edu/java/scrapper/client/sources/GithubClient.java
index 007f4cf..365e794 100644
--- a/scrapper/src/main/java/edu/java/client/GithubClient.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/sources/GithubClient.java
@@ -1,9 +1,10 @@
-package edu.java.client;
+package edu.java.scrapper.client.sources;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import edu.java.dto.GithubResponse;
+import edu.java.scrapper.client.AbstractClient;
+import edu.java.scrapper.client.sources.dto.GithubResponse;
import java.util.Optional;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
@@ -14,12 +15,12 @@ public GithubClient(String baseUrl, ObjectMapper mapper) {
super(baseUrl, mapper);
}
- public Optional getUpdate(String author, String repository) throws ResponseException {
+ public Optional getUpdate(String owner, String repo) throws ResponseException {
Mono jsonNodeMono = client.get()
.uri(uriBuilder -> uriBuilder
- .path("/repos/{author}/{repo}/events")
+ .path("/repos/{owner}/{repo}/events")
.queryParam("per_page", 1)
- .build(author, repository))
+ .build(owner, repo))
.retrieve()
.bodyToMono(JsonNode.class);
try {
diff --git a/scrapper/src/main/java/edu/java/client/ResponseException.java b/scrapper/src/main/java/edu/java/scrapper/client/sources/ResponseException.java
similarity index 56%
rename from scrapper/src/main/java/edu/java/client/ResponseException.java
rename to scrapper/src/main/java/edu/java/scrapper/client/sources/ResponseException.java
index c72808b..c28d33c 100644
--- a/scrapper/src/main/java/edu/java/client/ResponseException.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/sources/ResponseException.java
@@ -1,4 +1,4 @@
-package edu.java.client;
+package edu.java.scrapper.client.sources;
public class ResponseException extends Exception {
}
diff --git a/scrapper/src/main/java/edu/java/client/StackoverflowClient.java b/scrapper/src/main/java/edu/java/scrapper/client/sources/StackoverflowClient.java
similarity index 89%
rename from scrapper/src/main/java/edu/java/client/StackoverflowClient.java
rename to scrapper/src/main/java/edu/java/scrapper/client/sources/StackoverflowClient.java
index f9afbe5..7625599 100644
--- a/scrapper/src/main/java/edu/java/client/StackoverflowClient.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/sources/StackoverflowClient.java
@@ -1,9 +1,10 @@
-package edu.java.client;
+package edu.java.scrapper.client.sources;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import edu.java.dto.StackoverflowResponse;
+import edu.java.scrapper.client.AbstractClient;
+import edu.java.scrapper.client.sources.dto.StackoverflowResponse;
import java.util.Optional;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
diff --git a/scrapper/src/main/java/edu/java/dto/GithubResponse.java b/scrapper/src/main/java/edu/java/scrapper/client/sources/dto/GithubResponse.java
similarity index 91%
rename from scrapper/src/main/java/edu/java/dto/GithubResponse.java
rename to scrapper/src/main/java/edu/java/scrapper/client/sources/dto/GithubResponse.java
index b122ce0..2ddb643 100644
--- a/scrapper/src/main/java/edu/java/dto/GithubResponse.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/sources/dto/GithubResponse.java
@@ -1,4 +1,4 @@
-package edu.java.dto;
+package edu.java.scrapper.client.sources.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.time.OffsetDateTime;
diff --git a/scrapper/src/main/java/edu/java/dto/StackoverflowResponse.java b/scrapper/src/main/java/edu/java/scrapper/client/sources/dto/StackoverflowResponse.java
similarity index 89%
rename from scrapper/src/main/java/edu/java/dto/StackoverflowResponse.java
rename to scrapper/src/main/java/edu/java/scrapper/client/sources/dto/StackoverflowResponse.java
index 4a0a8d0..b3ac47e 100644
--- a/scrapper/src/main/java/edu/java/dto/StackoverflowResponse.java
+++ b/scrapper/src/main/java/edu/java/scrapper/client/sources/dto/StackoverflowResponse.java
@@ -1,4 +1,4 @@
-package edu.java.dto;
+package edu.java.scrapper.client.sources.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.time.OffsetDateTime;
diff --git a/scrapper/src/main/java/edu/java/configuration/ApplicationConfig.java b/scrapper/src/main/java/edu/java/scrapper/configuration/ApplicationConfig.java
similarity index 92%
rename from scrapper/src/main/java/edu/java/configuration/ApplicationConfig.java
rename to scrapper/src/main/java/edu/java/scrapper/configuration/ApplicationConfig.java
index 1760267..7574e4e 100644
--- a/scrapper/src/main/java/edu/java/configuration/ApplicationConfig.java
+++ b/scrapper/src/main/java/edu/java/scrapper/configuration/ApplicationConfig.java
@@ -1,4 +1,4 @@
-package edu.java.configuration;
+package edu.java.scrapper.configuration;
import jakarta.validation.constraints.NotNull;
import java.time.Duration;
diff --git a/scrapper/src/main/java/edu/java/configuration/ClientConfig.java b/scrapper/src/main/java/edu/java/scrapper/configuration/ClientConfig.java
similarity index 83%
rename from scrapper/src/main/java/edu/java/configuration/ClientConfig.java
rename to scrapper/src/main/java/edu/java/scrapper/configuration/ClientConfig.java
index 7d36ea2..63595c0 100644
--- a/scrapper/src/main/java/edu/java/configuration/ClientConfig.java
+++ b/scrapper/src/main/java/edu/java/scrapper/configuration/ClientConfig.java
@@ -1,9 +1,9 @@
-package edu.java.configuration;
+package edu.java.scrapper.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
-import edu.java.api.client.BotClient;
-import edu.java.client.GithubClient;
-import edu.java.client.StackoverflowClient;
+import edu.java.scrapper.client.bot.BotClient;
+import edu.java.scrapper.client.sources.GithubClient;
+import edu.java.scrapper.client.sources.StackoverflowClient;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
@@ -41,9 +41,6 @@ public StackoverflowClient stackoverflowClient() {
@Bean
public BotClient botClient() {
- return new BotClient(
- botUrl,
- objectMapper
- );
+ return new BotClient(botUrl, objectMapper);
}
}
diff --git a/scrapper/src/main/java/edu/java/scrapper/configuration/LinkParserConfig.java b/scrapper/src/main/java/edu/java/scrapper/configuration/LinkParserConfig.java
new file mode 100644
index 0000000..852f10f
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/configuration/LinkParserConfig.java
@@ -0,0 +1,21 @@
+package edu.java.scrapper.configuration;
+
+import edu.java.dto.utils.LinkParser;
+import edu.java.dto.utils.sources.parsers.GithubParser;
+import edu.java.dto.utils.sources.parsers.SourceParser;
+import edu.java.dto.utils.sources.parsers.StackoverflowParser;
+import java.util.Set;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LinkParserConfig {
+
+ @Bean
+ public LinkParser linkParser() {
+ return new LinkParser(SourceParser.buildChain(Set.of(
+ new GithubParser(),
+ new StackoverflowParser()
+ )), false);
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/configuration/ObjectMapperConfig.java b/scrapper/src/main/java/edu/java/scrapper/configuration/ObjectMapperConfig.java
similarity index 94%
rename from scrapper/src/main/java/edu/java/configuration/ObjectMapperConfig.java
rename to scrapper/src/main/java/edu/java/scrapper/configuration/ObjectMapperConfig.java
index 6d2f3be..daac430 100644
--- a/scrapper/src/main/java/edu/java/configuration/ObjectMapperConfig.java
+++ b/scrapper/src/main/java/edu/java/scrapper/configuration/ObjectMapperConfig.java
@@ -1,4 +1,4 @@
-package edu.java.configuration;
+package edu.java.scrapper.configuration;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/scrapper/src/main/java/edu/java/shedule/LinkUpdaterScheduler.java b/scrapper/src/main/java/edu/java/scrapper/shedule/LinkUpdaterScheduler.java
similarity index 58%
rename from scrapper/src/main/java/edu/java/shedule/LinkUpdaterScheduler.java
rename to scrapper/src/main/java/edu/java/scrapper/shedule/LinkUpdaterScheduler.java
index b04b193..b399eaf 100644
--- a/scrapper/src/main/java/edu/java/shedule/LinkUpdaterScheduler.java
+++ b/scrapper/src/main/java/edu/java/scrapper/shedule/LinkUpdaterScheduler.java
@@ -1,6 +1,8 @@
-package edu.java.shedule;
+package edu.java.scrapper.shedule;
+import edu.java.scrapper.api.service.LinkUpdater;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -9,9 +11,15 @@
@ConditionalOnProperty(value = "app.scheduler.enable", matchIfMissing = true)
@Slf4j
public class LinkUpdaterScheduler {
+ private final LinkUpdater updater;
+
+ @Autowired
+ public LinkUpdaterScheduler(LinkUpdater updater) {
+ this.updater = updater;
+ }
@Scheduled(fixedDelayString = "${app.scheduler.interval}")
public void update() {
- log.info("Обновление");
+ updater.update();
}
}
diff --git a/scrapper/src/main/java/edu/java/scrapper/shedule/update/dto/Update.java b/scrapper/src/main/java/edu/java/scrapper/shedule/update/dto/Update.java
new file mode 100644
index 0000000..fee75d9
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/shedule/update/dto/Update.java
@@ -0,0 +1,13 @@
+package edu.java.scrapper.shedule.update.dto;
+
+import java.time.OffsetDateTime;
+import lombok.Getter;
+
+@Getter
+public class Update {
+ protected final OffsetDateTime createdAt;
+
+ public Update(OffsetDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/GithubUpdater.java b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/GithubUpdater.java
new file mode 100644
index 0000000..dd1e1b3
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/GithubUpdater.java
@@ -0,0 +1,22 @@
+package edu.java.scrapper.shedule.update.sources;
+
+import edu.java.dto.utils.sources.info.GithubInfo;
+import edu.java.dto.utils.sources.info.SourceInfo;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.client.sources.dto.GithubResponse;
+import edu.java.scrapper.configuration.ClientConfig;
+import edu.java.scrapper.shedule.update.dto.Update;
+import java.util.Optional;
+
+public class GithubUpdater extends SourceUpdater {
+ public GithubUpdater(ClientConfig config) {
+ super(config);
+ }
+
+ @Override
+ public Optional getUpdate(SourceInfo sourceInfo) throws ResponseException {
+ GithubInfo info = (GithubInfo) sourceInfo;
+ Optional response = config.githubClient().getUpdate(info.getOwner(), info.getRepo());
+ return response.map(stackoverflowResponse -> new Update(stackoverflowResponse.createdAt()));
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/SourceUpdater.java b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/SourceUpdater.java
new file mode 100644
index 0000000..a3c5e57
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/SourceUpdater.java
@@ -0,0 +1,28 @@
+package edu.java.scrapper.shedule.update.sources;
+
+import edu.java.dto.utils.sources.info.GithubInfo;
+import edu.java.dto.utils.sources.info.SourceInfo;
+import edu.java.dto.utils.sources.info.StackoverflowInfo;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.configuration.ClientConfig;
+import edu.java.scrapper.shedule.update.dto.Update;
+import java.util.Optional;
+
+public abstract class SourceUpdater {
+ protected final ClientConfig config;
+
+ protected SourceUpdater(ClientConfig config) {
+ this.config = config;
+ }
+
+ public abstract Optional getUpdate(SourceInfo sourceInfo) throws ResponseException;
+
+ public static SourceUpdater getUpdaterForSource(ClientConfig config, SourceInfo sourceInfo) {
+ if (sourceInfo instanceof GithubInfo) {
+ return new GithubUpdater(config);
+ } else if (sourceInfo instanceof StackoverflowInfo) {
+ return new StackoverflowUpdater(config);
+ }
+ return null;
+ }
+}
diff --git a/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/StackoverflowUpdater.java b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/StackoverflowUpdater.java
new file mode 100644
index 0000000..b4b6a19
--- /dev/null
+++ b/scrapper/src/main/java/edu/java/scrapper/shedule/update/sources/StackoverflowUpdater.java
@@ -0,0 +1,22 @@
+package edu.java.scrapper.shedule.update.sources;
+
+import edu.java.dto.utils.sources.info.SourceInfo;
+import edu.java.dto.utils.sources.info.StackoverflowInfo;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.client.sources.dto.StackoverflowResponse;
+import edu.java.scrapper.configuration.ClientConfig;
+import edu.java.scrapper.shedule.update.dto.Update;
+import java.util.Optional;
+
+public class StackoverflowUpdater extends SourceUpdater {
+ public StackoverflowUpdater(ClientConfig config) {
+ super(config);
+ }
+
+ @Override
+ public Optional getUpdate(SourceInfo sourceInfo) throws ResponseException {
+ StackoverflowInfo info = (StackoverflowInfo) sourceInfo;
+ Optional response = config.stackoverflowClient().getUpdate(info.getQuestionId());
+ return response.map(stackoverflowResponse -> new Update(stackoverflowResponse.lastActivityDate()));
+ }
+}
diff --git a/scrapper/src/main/resources/application.yml b/scrapper/src/main/resources/application.yml
index 8fa269c..170d0be 100644
--- a/scrapper/src/main/resources/application.yml
+++ b/scrapper/src/main/resources/application.yml
@@ -2,7 +2,7 @@ app:
scheduler:
enable: true
interval: 10000
- force-check-delay: 10000
+ force-check-delay: 300000
resources:
base-url:
@@ -14,9 +14,12 @@ spring:
application:
name: scrapper
liquibase:
- enabled: true
- autoconfigure:
- exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
+ enabled: false
+ datasource:
+ driver-class-name: org.postgresql.Driver
+ url: "jdbc:postgresql://localhost:5433/scrapper"
+ username: "postgres"
+ password: "postgres"
jackson:
property-naming-strategy: SNAKE_CASE
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/ChatRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/ChatRepositoryTest.java
new file mode 100644
index 0000000..77c65db
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/ChatRepositoryTest.java
@@ -0,0 +1,75 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.IntegrationTest;
+import edu.java.scrapper.api.domain.dto.Chat;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public abstract class ChatRepositoryTest extends IntegrationTest {
+ private static final long TG_ID = 11111;
+ private final ChatRepository repo;
+
+ protected ChatRepositoryTest(ChatRepository repo) {
+ this.repo = repo;
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addTest() {
+ Chat actual = repo.add(TG_ID);
+ Assertions.assertEquals(TG_ID, actual.tgId());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addDuplicateExceptionTest() {
+ repo.add(TG_ID);
+ Assertions.assertThrows(DuplicateKeyException.class, () -> repo.add(TG_ID));
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findByTgIdTest() {
+ Chat expected = repo.add(TG_ID);
+ Chat actual = repo.findByTgId(TG_ID).get();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findByTgIdNotExistTest() {
+ Optional actual = repo.findByTgId(TG_ID);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void deleteTest() {
+ repo.add(TG_ID);
+ repo.remove(TG_ID);
+ Optional actual = repo.findByTgId(TG_ID);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findAllTest() {
+ List expected = new ArrayList<>();
+ expected.add(repo.add(TG_ID));
+ expected.add(repo.add(22222));
+ List actual = repo.findAll();
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/LinkRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/LinkRepositoryTest.java
new file mode 100644
index 0000000..457c069
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/LinkRepositoryTest.java
@@ -0,0 +1,88 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.IntegrationTest;
+import edu.java.scrapper.api.domain.dto.Link;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.transaction.annotation.Transactional;
+import java.net.URI;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public abstract class LinkRepositoryTest extends IntegrationTest {
+ private static final URI URL =
+ URI.create("https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring/");
+ private static final OffsetDateTime NOW = OffsetDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
+ private final LinkRepository repo;
+
+ protected LinkRepositoryTest(LinkRepository repo) {
+ this.repo = repo;
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addTest() {
+ Link expected = new Link(null, URL, NOW, NOW);
+ Link actual = repo.add(expected);
+ Assertions.assertEquals(expected.url(), actual.url());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addDuplicateExceptionTest() {
+ Link link = new Link(null, URL, NOW, NOW);
+ repo.add(link);
+ Assertions.assertThrows(DuplicateKeyException.class, () -> repo.add(link));
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findByUrlTest() {
+ Link link = new Link(null, URL, NOW, NOW);
+ Link expected = repo.add(link);
+ Link actual = repo.findByUrl(URL).get();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findByUrlNotExistTest() {
+ Optional actual = repo.findByUrl(URL);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void deleteTest() {
+ Link link = new Link(null, URL, NOW, NOW);
+ repo.add(link);
+ repo.remove(URL);
+ Optional actual = repo.findByUrl(URL);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findAllTest() {
+ Link link1 = new Link(null, URL, NOW, NOW);
+ URI url2 = URI.create("https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse/");
+ Link link2 = new Link(null, url2, NOW, NOW);
+ List expected = new ArrayList<>();
+ expected.add(repo.add(link1));
+ expected.add(repo.add(link2));
+ List actual = repo.findAll();
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/SubscriptionRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/SubscriptionRepositoryTest.java
new file mode 100644
index 0000000..30ca69d
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/SubscriptionRepositoryTest.java
@@ -0,0 +1,109 @@
+package edu.java.scrapper.api.domain.repository;
+
+import edu.java.scrapper.IntegrationTest;
+import edu.java.scrapper.api.domain.dto.Chat;
+import edu.java.scrapper.api.domain.dto.Link;
+import edu.java.scrapper.api.domain.dto.Subscription;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.transaction.annotation.Transactional;
+import java.net.URI;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public abstract class SubscriptionRepositoryTest extends IntegrationTest {
+ private static final long TG_ID = 11111;
+ private static final URI URL =
+ URI.create("https://github.com/cyberpanncake/Torzhkova-Tinkoff-JavaBackendCourse-Spring/");
+ private static final OffsetDateTime NOW = OffsetDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
+ private final ChatRepository chatRepo;
+ private final LinkRepository linkRepo;
+ private final SubscriptionRepository repo;
+
+ protected SubscriptionRepositoryTest(ChatRepository chatRepo,
+ LinkRepository linkRepo, SubscriptionRepository repo) {
+ this.chatRepo = chatRepo;
+ this.linkRepo = linkRepo;
+ this.repo = repo;
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addTest() {
+ Chat chat = chatRepo.add(TG_ID);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription expected = new Subscription(chat.id(), link.id());
+ Subscription actual = repo.add(expected);
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void addDuplicateExceptionTest() {
+ Chat chat = chatRepo.add(TG_ID);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription subscription = new Subscription(chat.id(), link.id());
+ repo.add(subscription);
+ Assertions.assertThrows(DuplicateKeyException.class, () -> repo.add(subscription));
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findTest() {
+ Chat chat = chatRepo.add(TG_ID);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription subscription = new Subscription(chat.id(), link.id());
+ Subscription expected = repo.add(subscription);
+ Subscription actual = repo.find(TG_ID, URL).get();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findNotExistTest() {
+ Chat chat = chatRepo.add(TG_ID);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription subscription = new Subscription(chat.id(), link.id());
+ Optional actual = repo.find(TG_ID, URL);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void deleteTest() {
+ Chat chat = chatRepo.add(TG_ID);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription subscription = new Subscription(chat.id(), link.id());
+ repo.add(subscription);
+ repo.remove(subscription);
+ Optional actual = repo.find(TG_ID, URL);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ @Transactional
+ @Rollback
+ void findAllTest() {
+ Chat chat1 = chatRepo.add(TG_ID);
+ Chat chat2 = chatRepo.add(22222);
+ Link link = linkRepo.add(new Link(null, URL, NOW, NOW));
+ Subscription subscription1 = new Subscription(chat1.id(), link.id());
+ Subscription subscription2 = new Subscription(chat2.id(), link.id());
+ List expected = new ArrayList<>();
+ expected.add(repo.add(subscription1));
+ expected.add(repo.add(subscription2));
+ List actual = repo.findAll();
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepositoryTest.java
new file mode 100644
index 0000000..82f2c30
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcChatRepositoryTest.java
@@ -0,0 +1,14 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.repository.ChatRepositoryTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class JdbcChatRepositoryTest extends ChatRepositoryTest {
+
+ @Autowired
+ protected JdbcChatRepositoryTest(JdbcChatRepository repo) {
+ super(repo);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepositoryTest.java
new file mode 100644
index 0000000..367d6fc
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcLinkRepositoryTest.java
@@ -0,0 +1,14 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.repository.LinkRepositoryTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class JdbcLinkRepositoryTest extends LinkRepositoryTest {
+
+ @Autowired
+ protected JdbcLinkRepositoryTest(JdbcLinkRepository repo) {
+ super(repo);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepositoryTest.java b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepositoryTest.java
new file mode 100644
index 0000000..9f95afa
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/api/domain/repository/jdbc/JdbcSubscriptionRepositoryTest.java
@@ -0,0 +1,15 @@
+package edu.java.scrapper.api.domain.repository.jdbc;
+
+import edu.java.scrapper.api.domain.repository.SubscriptionRepositoryTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class JdbcSubscriptionRepositoryTest extends SubscriptionRepositoryTest {
+
+ @Autowired
+ protected JdbcSubscriptionRepositoryTest(JdbcSubscriptionRepository repo, JdbcChatRepository chatRepo,
+ JdbcLinkRepository linkRepo) {
+ super(chatRepo, linkRepo, repo);
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/client/GithubClientTest.java b/scrapper/src/test/java/edu/java/scrapper/client/GithubClientTest.java
similarity index 94%
rename from scrapper/src/test/java/edu/java/client/GithubClientTest.java
rename to scrapper/src/test/java/edu/java/scrapper/client/GithubClientTest.java
index ded166b..277b85e 100644
--- a/scrapper/src/test/java/edu/java/client/GithubClientTest.java
+++ b/scrapper/src/test/java/edu/java/scrapper/client/GithubClientTest.java
@@ -1,10 +1,12 @@
-package edu.java.client;
+package edu.java.scrapper.client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.WireMockServer;
-import edu.java.configuration.ObjectMapperConfig;
-import edu.java.dto.GithubResponse;
+import edu.java.scrapper.client.sources.GithubClient;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.configuration.ObjectMapperConfig;
+import edu.java.scrapper.client.sources.dto.GithubResponse;
import java.util.List;
import java.util.Map;
import java.util.Optional;
diff --git a/scrapper/src/test/java/edu/java/scrapper/client/ObjectMapperTest.java b/scrapper/src/test/java/edu/java/scrapper/client/ObjectMapperTest.java
new file mode 100644
index 0000000..6af27ca
--- /dev/null
+++ b/scrapper/src/test/java/edu/java/scrapper/client/ObjectMapperTest.java
@@ -0,0 +1,28 @@
+package edu.java.scrapper.client;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.java.scrapper.configuration.ObjectMapperConfig;
+import edu.java.scrapper.client.sources.dto.GithubResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import java.time.OffsetDateTime;
+
+public class ObjectMapperTest {
+
+ private final ObjectMapper mapper;
+
+ public ObjectMapperTest() {
+ this.mapper = new ObjectMapperConfig().objectMapper();
+ }
+
+ @Test
+ void objectMapperTest() throws JsonProcessingException {
+ GithubResponse object = new GithubResponse(0L, "example",
+ new GithubResponse.Actor("cyberpanncake"),
+ new GithubResponse.Repository("Torzhkova-Tinkoff-JavaBackendCourse-Spring"),
+ OffsetDateTime.now());
+ String json = mapper.writeValueAsString(object);
+ Assertions.assertTrue(json.contains("created_at"));
+ }
+}
diff --git a/scrapper/src/test/java/edu/java/client/StackoverflowClientTest.java b/scrapper/src/test/java/edu/java/scrapper/client/StackoverflowClientTest.java
similarity index 94%
rename from scrapper/src/test/java/edu/java/client/StackoverflowClientTest.java
rename to scrapper/src/test/java/edu/java/scrapper/client/StackoverflowClientTest.java
index 95e2757..de5909d 100644
--- a/scrapper/src/test/java/edu/java/client/StackoverflowClientTest.java
+++ b/scrapper/src/test/java/edu/java/scrapper/client/StackoverflowClientTest.java
@@ -1,10 +1,12 @@
-package edu.java.client;
+package edu.java.scrapper.client;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.WireMockServer;
-import edu.java.configuration.ObjectMapperConfig;
-import edu.java.dto.StackoverflowResponse;
+import edu.java.scrapper.client.sources.ResponseException;
+import edu.java.scrapper.client.sources.StackoverflowClient;
+import edu.java.scrapper.configuration.ObjectMapperConfig;
+import edu.java.scrapper.client.sources.dto.StackoverflowResponse;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;