diff --git a/.gitignore b/.gitignore
index 862eb15..f87b265 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Compiled class file
*.class
+
# Log file
*.log
@@ -23,4 +24,8 @@
hs_err_pid*
# idea
-.idea
\ No newline at end of file
+FP/.idea/
+
+# volumes
+FP/migrations/postgres_data/
+FP/rabbitmq/
\ No newline at end of file
diff --git a/FP/bot/pom.xml b/FP/bot/pom.xml
new file mode 100644
index 0000000..96dea0d
--- /dev/null
+++ b/FP/bot/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+ org.example
+ FP
+ 1.0
+
+
+ 4.0.0
+
+ bot
+
+
+ 3.0.1
+ 2.7.6
+
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
+ org.apache.commons
+ commons-lang3
+ 3.10
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ ${starter-validation.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${starter-web.version}
+
+
+ org.apache.logging.log4j
+ log4j-to-slf4j
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework
+ spring-context-indexer
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ com.github.pengrad
+ java-telegram-bot-api
+ 6.6.0
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+ 3.0.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.example
+ scrapper
+ 1.0
+ compile
+
+
+
\ No newline at end of file
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java
new file mode 100644
index 0000000..92ae210
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java
@@ -0,0 +1,26 @@
+package ru.tinkoff.edu.java.bot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.bot.configuration.records.ApplicationConfig;
+import ru.tinkoff.edu.java.bot.handler.BotMain;
+
+import javax.sql.DataSource;
+
+
+@SpringBootApplication
+@EnableConfigurationProperties(ApplicationConfig.class)
+public class BotApplication
+{
+ public static void main(String[] args){
+ var ctx = SpringApplication.run(BotApplication.class, args);
+ ApplicationConfig config = ctx.getBean(ApplicationConfig.class);
+ JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
+ BotMain bot = new BotMain(config.bot().token(), jdbcTemplate);
+ bot.start();
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/BotController.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/BotController.java
new file mode 100644
index 0000000..6a559cf
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/BotController.java
@@ -0,0 +1,14 @@
+package ru.tinkoff.edu.java.bot.api;
+
+import org.springframework.web.bind.annotation.*;
+import ru.tinkoff.edu.java.bot.model.LinkUpdate;
+
+@RestController
+@RequestMapping("/update")
+public class BotController {
+
+ @PostMapping
+ public String updateChat(@RequestBody LinkUpdate update) {
+ return update.toString();
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/exceptionHandler/BotExceptionHandler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/exceptionHandler/BotExceptionHandler.java
new file mode 100644
index 0000000..5beb1bd
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/exceptionHandler/BotExceptionHandler.java
@@ -0,0 +1,36 @@
+package ru.tinkoff.edu.java.bot.api.exceptionHandler;
+
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import ru.tinkoff.edu.java.bot.model.ApiErrorResponse;
+
+@RestControllerAdvice
+public class BotExceptionHandler {
+
+ private String getDescription(String message) {
+ ApiErrorResponse errorObj = new ApiErrorResponse(
+ message,
+ null,
+ null,
+ null,
+ null
+ );
+ return errorObj.description();
+ }
+
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public String MessageNotReadable(HttpMessageNotReadableException Exception) {
+ return getDescription("Некорректные значения параметров или их нет!");
+ }
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public String MethodNotSupported(HttpRequestMethodNotSupportedException Exception) {
+ return getDescription("Метод не разрешен!");
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/RabbitMQConfiguration.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/RabbitMQConfiguration.java
new file mode 100644
index 0000000..1616669
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/RabbitMQConfiguration.java
@@ -0,0 +1,56 @@
+package ru.tinkoff.edu.java.bot.configuration;
+
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import ru.tinkoff.edu.java.bot.configuration.records.ApplicationConfig;
+
+import java.util.Collections;
+
+@Configuration
+public class RabbitMQConfiguration {
+
+ private final ApplicationConfig config;
+
+ public RabbitMQConfiguration(ApplicationConfig config) {
+ this.config = config;
+ }
+
+ @Bean
+ public DirectExchange exchange() {
+ return new DirectExchange(config.exchange());
+ }
+
+ @Bean
+ public Queue queue() {
+ return QueueBuilder.durable(config.queue())
+ .withArgument("x-dead-letter-exchange",config.queue() + ".dlq")
+ .build();
+ }
+
+ @Bean
+ public Binding binding(Queue queue, DirectExchange exchange) {
+ return BindingBuilder.bind(queue)
+ .to(exchange)
+ .with(config.routingKey());
+ }
+
+ @Bean
+ public DirectExchange dlqExchange() {
+ return new DirectExchange(config.exchange() + ".dlq");
+ }
+
+ @Bean
+ public Queue dlqQueue() {
+ return QueueBuilder.durable(config.queue() + ".dlq").build();
+ }
+
+ @Bean
+ public Binding dlqBinding() {
+ return BindingBuilder.bind(dlqQueue())
+ .to(dlqExchange())
+ .with(config.routingKey());
+ }
+}
\ No newline at end of file
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/jdbcBean.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/jdbcBean.java
new file mode 100644
index 0000000..e768de5
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/jdbcBean.java
@@ -0,0 +1,26 @@
+package ru.tinkoff.edu.java.bot.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class jdbcBean {
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.postgresql.Driver");
+ dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
+ dataSource.setUsername("scrap_user");
+ dataSource.setPassword("hard_password");
+
+ return dataSource;
+ }
+ @Bean
+ public JdbcTemplate jdbcTemplate () {
+ return new JdbcTemplate (dataSource());
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/ApplicationConfig.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/ApplicationConfig.java
new file mode 100644
index 0000000..c23d07b
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/ApplicationConfig.java
@@ -0,0 +1,22 @@
+package ru.tinkoff.edu.java.bot.configuration.records;
+
+import jakarta.validation.constraints.NotNull;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+@EnableScheduling
+@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
+@ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
+public record ApplicationConfig(
+ @NotNull String test,
+ @NotNull Scheduler scheduler,
+ String exchange,
+ String routingKey,
+ String queue,
+ Bot bot
+) {
+}
\ No newline at end of file
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Bot.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Bot.java
new file mode 100644
index 0000000..0a1fff4
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Bot.java
@@ -0,0 +1,4 @@
+package ru.tinkoff.edu.java.bot.configuration.records;
+
+public record Bot(String token, String name) {
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Scheduler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Scheduler.java
new file mode 100644
index 0000000..daed115
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/records/Scheduler.java
@@ -0,0 +1,5 @@
+package ru.tinkoff.edu.java.bot.configuration.records;
+
+import java.time.Duration;
+
+public record Scheduler(Duration interval) {}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/BotMain.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/BotMain.java
new file mode 100644
index 0000000..13d6b65
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/BotMain.java
@@ -0,0 +1,33 @@
+package ru.tinkoff.edu.java.bot.handler;
+
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.request.SendMessage;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.bot.configuration.records.ApplicationConfig;
+
+
+@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
+@EnableConfigurationProperties(ApplicationConfig.class)
+public class BotMain {
+
+ String token;
+ static TelegramBot bot;
+ private JdbcTemplate jdbcTemplate;
+
+ public BotMain(String token, JdbcTemplate jdbcTemplate) {
+ this.token = token;
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ public void start() {
+ bot = new TelegramBot(token);
+ bot.setUpdatesListener(new Updater(bot, jdbcTemplate));
+ }
+
+ public void end() {
+ bot.removeGetUpdatesListener();
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/DB.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/DB.java
new file mode 100644
index 0000000..6b57338
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/DB.java
@@ -0,0 +1,42 @@
+package ru.tinkoff.edu.java.bot.handler;
+
+import java.util.ArrayList;
+
+public class DB {
+
+ static ArrayList list = new ArrayList();
+
+ public static void addLink(String link) {
+ list.add(link);
+ }
+
+ public static void rmLink(String link) {
+ list.remove(link);
+ }
+
+ public static String getListParse() {
+
+ int i = 1;
+
+ String out_list = "";
+
+ for(String element: list){
+
+ out_list += element;
+
+ if(i != list.size()) {
+ out_list += ", ";
+ }
+ i++;
+ }
+ return out_list;
+ }
+
+ public static boolean listIsEmpty() {
+ return list.isEmpty();
+ }
+
+ public static boolean linkContain(String link) {
+ return list.contains(link);
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/MessageHandler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/MessageHandler.java
new file mode 100644
index 0000000..87f2ab0
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/MessageHandler.java
@@ -0,0 +1,39 @@
+package ru.tinkoff.edu.java.bot.handler;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.bot.handler.commands.All;
+
+public class MessageHandler extends All {
+
+ private final JdbcTemplate jdbcTemplate;
+ private final long chatid;
+
+ public MessageHandler(JdbcTemplate jdbcTemplate, long chatid) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.chatid = chatid;
+ }
+
+ public boolean is_command(String message) {
+ return message.startsWith("/");
+ }
+
+ public String call_command(String command, String arg) {
+ return switch (command) {
+ case "/start" -> start(jdbcTemplate, chatid);
+ case "/help" -> help();
+ case "/track" -> track(jdbcTemplate, arg, chatid);
+ case "/list" -> list(jdbcTemplate, chatid);
+ case "/untrack" -> untrack(jdbcTemplate, arg, chatid);
+ default -> unknow();
+ };
+ }
+
+ public String call_command(String command) {
+ return switch (command) {
+ case "/start" -> start(jdbcTemplate, chatid);
+ case "/help" -> help();
+ case "/list" -> list(jdbcTemplate, chatid);
+ default -> unknow();
+ };
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/Updater.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/Updater.java
new file mode 100644
index 0000000..7965170
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/Updater.java
@@ -0,0 +1,44 @@
+package ru.tinkoff.edu.java.bot.handler;
+
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.UpdatesListener;
+import com.pengrad.telegrambot.model.Update;
+import com.pengrad.telegrambot.model.request.*;
+import com.pengrad.telegrambot.request.SendMessage;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.List;
+
+public class Updater implements UpdatesListener {
+
+ MessageHandler handler;
+ String command;
+ TelegramBot bot;
+ private JdbcTemplate jdbcTemplate;
+ private long chatid;
+
+ public Updater(TelegramBot bot, JdbcTemplate jdbcTemplate) {
+ this.bot = bot;
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @Override
+ public int process(List updates) {
+ Update update = updates.get(0);
+ handler = new MessageHandler(jdbcTemplate, update.message().chat().id());
+ if(handler.is_command(update.message().text())) {
+ String[] parse = update.message().text().split(" ");
+ if(parse.length > 1) command = handler.call_command(parse[0], parse[1]);
+ else command = handler.call_command(parse[0]);
+ bot.execute(
+ new SendMessage(update.message().chat().id(), command)
+ .replyMarkup(new ReplyKeyboardMarkup(new String[][]{
+ {"/start", "/help"},
+ {"/track testlink", "/untrack testlink", "/list"}
+ }).resizeKeyboard(true)
+ ).parseMode(ParseMode.HTML)
+ );
+ }
+ return UpdatesListener.CONFIRMED_UPDATES_ALL;
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/All.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/All.java
new file mode 100644
index 0000000..5f63723
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/All.java
@@ -0,0 +1,7 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+public class All implements List, Start, Track, Untrack, Help {
+ protected String unknow() {
+ return "Неизвестная команда";
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Help.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Help.java
new file mode 100644
index 0000000..4d0da5a
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Help.java
@@ -0,0 +1,11 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+public interface Help {
+ default String help() {
+ return "/start -- зарегистрировать пользователя\n" +
+ "/help -- вывести окно с командами\n" +
+ "/track [link] -- начать отслеживание ссылки\n" +
+ "/untrack [link] -- прекратить отслеживание ссылки\n" +
+ "/list -- показать список отслеживаемых ссылок";
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/List.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/List.java
new file mode 100644
index 0000000..280f80a
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/List.java
@@ -0,0 +1,37 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+import com.google.gson.JsonObject;
+import okhttp3.Interceptor;
+import org.springframework.jdbc.BadSqlGrammarException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.bot.handler.DB;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.UnauthorizationException;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+
+import java.util.ArrayList;
+
+public interface List extends LinkOperations, ChatOperations, LinkChatOperations {
+
+ default String list(JdbcTemplate jdbc, long chat) {
+ int i;
+ String links = "";
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ return "Перед использованием нужно зарегистрироваться";
+ }
+ ArrayList link_list = new ArrayList<>();
+ try {
+ for (i = 0; i < i_get_all_links_for_chat(jdbc, chat_id).size(); i++) {
+ link_list.add(i_get_all_links_for_chat(jdbc, chat_id).get(i).linkid());
+ }
+ for (i = 0; i < i_findAllLink(jdbc, link_list).size(); i++) {
+ links += i_findAllLink(jdbc, link_list).get(i).url() + "\n";
+ }
+ return links;
+ } catch (BadSqlGrammarException e) {
+ return "У вас нет ссылок";
+ }
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Start.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Start.java
new file mode 100644
index 0000000..d2d5459
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Start.java
@@ -0,0 +1,19 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.EntryExsistException;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+
+public interface Start extends ChatOperations {
+
+ default String start(JdbcTemplate jdbc, long chat) {
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id != 0) {
+ return "Вы уже зарегистрированы";
+ } else {
+ i_addChat(jdbc, chat);
+ return "Регистрация успешна, ваш tg_id = " + chat;
+ }
+ }
+}
+
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Track.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Track.java
new file mode 100644
index 0000000..9080b48
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Track.java
@@ -0,0 +1,47 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import ru.tinkoff.edu.java.bot.handler.DB;
+import ru.tinkoff.edu.java.linkparser.LinkParser;
+import ru.tinkoff.edu.java.scrapper.client.ClientConfiguration;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.EntryExsistException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.EntryNotExsistException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.NullLinkException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.UnauthorizationException;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+import ru.tinkoff.edu.java.scrapper.model.RemoveLinkRequest;
+
+public interface Track extends LinkOperations, LinkChatOperations, ChatOperations {
+ default String track(JdbcTemplate jdbc, String link, long chat) {
+ ClientConfiguration client = new ClientConfiguration();
+ LinkParser parser = new LinkParser();
+ if (parser.getLink(link) == null) {
+ return "Ссылка невалидна";
+ }
+ try {
+ client.gitHubClient(parser.getLink(link));
+ } catch (WebClientResponseException e) {
+ try {
+ client.stackOverflowClient(parser.getLink(link));
+ } catch (WebClientResponseException ex) {
+ return "Ссылка не поддерживается, доступны: Git, StackOverFlow";
+ }
+ }
+ int link_id = i_findLink(jdbc, link);
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ return "Перед использованием нужно зарегистрироваться";
+ }
+ if (link_id == 0) {
+ i_addLink(jdbc, link);
+ link_id = i_findLink(jdbc, link);
+ }
+ if (i_findLinkChat(jdbc, link_id, chat_id)) {
+ addLinkChat(jdbc, link_id, chat_id);
+ return "Ссылка успешно добавлена!";
+ }else return"Ссылка уже добавлена!";
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Untrack.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Untrack.java
new file mode 100644
index 0000000..6615feb
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/handler/commands/Untrack.java
@@ -0,0 +1,24 @@
+package ru.tinkoff.edu.java.bot.handler.commands;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.bot.handler.DB;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+
+public interface Untrack extends LinkOperations, LinkChatOperations, ChatOperations {
+ default String untrack(JdbcTemplate jdbc, String link, long chat) {
+ int link_id = i_findLink(jdbc, link);
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ return "Перед использованием нужно зарегистрироваться";
+ }
+ if (link_id != 0) {
+ i_removeLink(jdbc, link);
+ if (!i_findLinkChat(jdbc, link_id, chat_id)) {
+ i_removeLinkChat(jdbc, link_id, chat_id);
+ }
+ return "Ссылка удалена";
+ } else return "Ссылки не существует";
+ }
+}
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/ApiErrorResponse.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/ApiErrorResponse.java
new file mode 100644
index 0000000..57e1c06
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/ApiErrorResponse.java
@@ -0,0 +1,11 @@
+package ru.tinkoff.edu.java.bot.model;
+
+import java.util.List;
+
+public record ApiErrorResponse(
+ String description,
+ String code,
+ String exceptionName,
+ String exceptionMessage,
+ List stacktrace
+) {}
\ No newline at end of file
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/LinkUpdate.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/LinkUpdate.java
new file mode 100644
index 0000000..d6b250b
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/model/LinkUpdate.java
@@ -0,0 +1,6 @@
+package ru.tinkoff.edu.java.bot.model;
+
+import java.util.List;
+
+public record LinkUpdate(String url) {
+}
\ No newline at end of file
diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/LinkUpdaterScheduler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/LinkUpdaterScheduler.java
new file mode 100644
index 0000000..1b2ff0f
--- /dev/null
+++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/LinkUpdaterScheduler.java
@@ -0,0 +1,17 @@
+package ru.tinkoff.edu.java.bot.schedule;
+
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Service
+public class LinkUpdaterScheduler {
+ private final static Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+
+ @Scheduled(fixedDelayString = "${app.scheduler.interval}")
+ public void update() {
+ LOGGER.log(Level.INFO, "Info bot called");
+ }
+}
diff --git a/FP/bot/src/main/resources/application.properties b/FP/bot/src/main/resources/application.properties
new file mode 100644
index 0000000..6600aaa
--- /dev/null
+++ b/FP/bot/src/main/resources/application.properties
@@ -0,0 +1,9 @@
+app.test=123
+springdoc.swagger-ui.path=/swagger-ui
+app.scheduler.interval=50000
+app.bot.token=5805337447:AAGnmh2isW2115L7tJWFojbpmSjNrarTvxQ
+app.bot.name=@SFRETbot
+server.port=8081
+app.exchange=my-direct-exchange
+app.queue=my-queue
+app.routingKey=my-routing-key
\ No newline at end of file
diff --git a/FP/docker-compose.yml b/FP/docker-compose.yml
new file mode 100644
index 0000000..4195e6e
--- /dev/null
+++ b/FP/docker-compose.yml
@@ -0,0 +1,54 @@
+version: '1.-'
+
+services:
+ postgres:
+ container_name: postgres
+ image: postgres:15.2
+ environment:
+ POSTGRES_DB: postgres
+ POSTGRES_USER: scrap_user
+ POSTGRES_PASSWORD: hard_password
+ volumes:
+ - ./migrations/postgres_data:/var/lib/postgresql/data
+ ports:
+ - "5432:5432"
+ networks:
+ - backend
+
+ liquibase-migrations:
+ container_name: liquibase
+ image: liquibase/liquibase:4.18
+ deploy:
+ restart_policy:
+ condition: on-failure
+ delay: 10s
+ max_attempts: 10
+ command:
+ - --hub-mode=off
+ - --changelog-file=master.xml
+ - --driver=org.postgresql.Driver
+ - --url=jdbc:postgresql://postgres:5432/postgres
+ - --username=scrap_user
+ - --password=hard_password
+ - --logLevel=debug
+ - update
+ volumes:
+ - ./migrations:/liquibase/changelog
+ networks:
+ - backend
+
+ rabbitmq:
+ image: rabbitmq:3-management-alpine
+ container_name: rabbitmq
+ ports:
+ - 5672:5672 # Порт для AMQP
+ - 15672:15672 # Порт для RabbitMQ Management UI
+ volumes:
+ - ./rabbitmq:/var/lib/rabbitmq
+ environment:
+ - RABBITMQ_DEFAULT_USER=guest
+ - RABBITMQ_DEFAULT_PASS=guest
+
+networks:
+ backend:
+ driver: bridge
\ No newline at end of file
diff --git a/FP/link-parser/pom.xml b/FP/link-parser/pom.xml
new file mode 100644
index 0000000..088df38
--- /dev/null
+++ b/FP/link-parser/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ org.example
+ FP
+ 1.0
+
+
+ 4.0.0
+
+ link-parser
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
\ No newline at end of file
diff --git a/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/LinkParser.java b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/LinkParser.java
new file mode 100644
index 0000000..1f217ea
--- /dev/null
+++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/LinkParser.java
@@ -0,0 +1,18 @@
+package ru.tinkoff.edu.java.linkparser;
+
+import ru.tinkoff.edu.java.linkparser.absracts.*;
+
+public class LinkParser {
+
+ public String getLink(String link) {
+
+ AbstractParser gitParser = new GitParser();
+ AbstractParser stackParser = new StackParser();
+ AbstractParser otherParser = new OtherParser();
+
+ gitParser.setNextParser(stackParser);
+ stackParser.setNextParser(otherParser);
+
+ return gitParser.logParser(link);
+ }
+}
diff --git a/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/AbstractParser.java b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/AbstractParser.java
new file mode 100644
index 0000000..503e413
--- /dev/null
+++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/AbstractParser.java
@@ -0,0 +1,20 @@
+package ru.tinkoff.edu.java.linkparser.absracts;
+
+public abstract class AbstractParser {
+
+ private AbstractParser nextParser;
+
+ public void setNextParser(AbstractParser nextParser) {
+ this.nextParser = nextParser;
+ }
+
+ public String logParser (String link) {
+ if(nextParser != null) {
+ if(this.parsAbstract(link) == null) return nextParser.logParser(link);
+ }
+ return this.parsAbstract(link);
+ }
+
+ abstract protected String parsAbstract(String link);
+
+}
diff --git a/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java
new file mode 100644
index 0000000..9525306
--- /dev/null
+++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java
@@ -0,0 +1,20 @@
+package ru.tinkoff.edu.java.linkparser.absracts;
+
+import java.util.Objects;
+
+public class GitParser extends AbstractParser {
+
+ @Override
+ protected String parsAbstract(String link) {
+
+ String[] parsed = link.split("/");
+
+ if (parsed.length < 2) return null;
+
+ if (!Objects.equals(parsed[2], "github.com")) return null;
+
+ if (parsed.length > 4) return parsed[3] + "/" + parsed[4];
+
+ return null;
+ }
+}
diff --git a/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/OtherParser.java b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/OtherParser.java
new file mode 100644
index 0000000..f3746fe
--- /dev/null
+++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/OtherParser.java
@@ -0,0 +1,9 @@
+package ru.tinkoff.edu.java.linkparser.absracts;
+
+public class OtherParser extends AbstractParser {
+
+ @Override
+ protected String parsAbstract(String link) {
+ return null;
+ }
+}
diff --git a/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java
new file mode 100644
index 0000000..571ea58
--- /dev/null
+++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java
@@ -0,0 +1,19 @@
+package ru.tinkoff.edu.java.linkparser.absracts;
+
+import java.util.Objects;
+
+public class StackParser extends AbstractParser {
+
+ @Override
+ protected String parsAbstract(String link) {
+
+ String[] parsed = link.split("/");
+
+ if (parsed.length < 5) return null;
+ if (!Objects.equals(parsed[2], "stackoverflow.com")) return null;
+ if (!Objects.equals(parsed[3], "questions")) return null;
+
+
+ return parsed[4];
+ }
+}
diff --git a/FP/link-parser/src/test/java/LinkParserTest.java b/FP/link-parser/src/test/java/LinkParserTest.java
new file mode 100644
index 0000000..7bd8349
--- /dev/null
+++ b/FP/link-parser/src/test/java/LinkParserTest.java
@@ -0,0 +1,24 @@
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import ru.tinkoff.edu.java.linkparser.LinkParser;
+
+public class LinkParserTest {
+
+ String gitLink = "https://github.com/Ray-Not/JavaGuava";
+ String invalidLink = "https://gitrub.com/";
+ String invalidLink2 = "https://github.com/Ray-Not";
+ String expectGitLink = "Ray-Not/JavaGuava";
+ String stackLink = "https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c";
+ String expectStackLink = "1642028";
+
+ LinkParser pars = new LinkParser();
+
+ @Test
+ public void validGitPars(){
+ Assertions.assertNull(pars.getLink(invalidLink));
+ Assertions.assertNull(pars.getLink(invalidLink2));
+ Assertions.assertNotNull(pars.getLink(gitLink));
+ Assertions.assertEquals(pars.getLink(gitLink), expectGitLink);
+ Assertions.assertEquals(pars.getLink(stackLink), expectStackLink);
+ }
+}
diff --git a/FP/migrations/chats_links_scheme.sql b/FP/migrations/chats_links_scheme.sql
new file mode 100644
index 0000000..63882e8
--- /dev/null
+++ b/FP/migrations/chats_links_scheme.sql
@@ -0,0 +1,8 @@
+CREATE TABLE links (id INTEGER, link VARCHAR(128) NOT NULL);
+
+CREATE TABLE tgChats (id INTEGER, tg_chat_id BIGINT NOT NULL);
+
+CREATE TABLE links_tgChats (
+ linkid INTEGER,
+ chatid INTEGER
+);
\ No newline at end of file
diff --git a/FP/migrations/master.xml b/FP/migrations/master.xml
new file mode 100644
index 0000000..5d643de
--- /dev/null
+++ b/FP/migrations/master.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FP/pom.xml b/FP/pom.xml
new file mode 100644
index 0000000..e38a124
--- /dev/null
+++ b/FP/pom.xml
@@ -0,0 +1,116 @@
+
+
+ 4.0.0
+
+ org.example
+
+ FP
+
+ pom
+
+ 1.0
+
+
+ bot
+ link-parser
+ scrapper
+
+
+
+ UTF-8
+ 17
+ 17
+ 3.8.1
+ 3.0.1
+ 2022.0.0
+ 23.1.0
+ 5.8.1
+ 1.18.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+ false
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version
+
+
+ true
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+ repackage
+ build-info
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ 17
+ true
+
+
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+ org.jetbrains
+ annotations
+ ${annotations.version}
+ provided
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/FP/scrapper/pom.xml b/FP/scrapper/pom.xml
new file mode 100644
index 0000000..fad5590
--- /dev/null
+++ b/FP/scrapper/pom.xml
@@ -0,0 +1,116 @@
+
+
+
+ org.example
+ FP
+ 1.0
+
+
+ 4.0.0
+
+ scrapper
+
+
+ 3.0.1
+ 2.7.6
+
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.0.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ ${starter-validation.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${starter-web.version}
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework
+ spring-context-indexer
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.example
+ link-parser
+ 1.0
+ compile
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ 3.0.5
+
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+ org.liquibase
+ liquibase-core
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+ 3.0.6
+
+
+
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java
new file mode 100644
index 0000000..1ef6a07
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java
@@ -0,0 +1,28 @@
+package ru.tinkoff.edu.java.scrapper;
+
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import ru.tinkoff.edu.java.scrapper.configuration.ApplicationConfig;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.ScrapperQueueProducer;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.SendNoticeServiceQueue;
+
+@SpringBootApplication
+@EnableConfigurationProperties(ApplicationConfig.class)
+@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
+public class ScrapperApplication {
+
+ public static void main(String[] args) {
+ var ctx = SpringApplication.run(ScrapperApplication.class, args);
+ ApplicationConfig config = ctx.getBean(ApplicationConfig.class);
+ System.out.println("----------------------------------------------------------------");
+// SendNoticeServiceQueue notificationService = new SendNoticeServiceQueue(new ScrapperQueueProducer(
+// new RabbitTemplate(),
+// config
+// ));
+// notificationService.sendNotice("Вот-вот");
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLinks.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLinks.java
new file mode 100644
index 0000000..47540df
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLinks.java
@@ -0,0 +1,46 @@
+package ru.tinkoff.edu.java.scrapper.api;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import ru.tinkoff.edu.java.scrapper.jdbc.JdbcLinkService;
+import ru.tinkoff.edu.java.scrapper.model.AddLinkRequest;
+import ru.tinkoff.edu.java.scrapper.model.LinkResponse;
+import ru.tinkoff.edu.java.scrapper.model.RemoveLinkRequest;
+
+import java.util.List;
+
+@RequestMapping("/links")
+@RestController
+public class ScrapperControllerLinks {
+ private final JdbcTemplate jdbcTemplate;
+ JdbcLinkService linkService = new JdbcLinkService();
+
+ public ScrapperControllerLinks(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @DeleteMapping
+ public void linksDelete(
+ @RequestHeader("Tg-Chat-Id") Long tgChatId,
+ @RequestBody RemoveLinkRequest removeLinkRequest
+ ) {
+ linkService.removeLink(jdbcTemplate, removeLinkRequest, tgChatId);
+ }
+
+ @GetMapping
+ public List linksGet(@RequestHeader("Tg-Chat-Id") Long tgChatId) {
+ return linkService.getLinks(jdbcTemplate, tgChatId);
+ }
+
+ @PostMapping
+ public void linksPost(
+ @RequestHeader("Tg-Chat-Id") Long tgChatId,
+ @RequestBody AddLinkRequest addLinkRequest
+ ) {
+ linkService.addLink(jdbcTemplate, addLinkRequest, tgChatId);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java
new file mode 100644
index 0000000..cc96bfb
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java
@@ -0,0 +1,29 @@
+package ru.tinkoff.edu.java.scrapper.api;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+import ru.tinkoff.edu.java.scrapper.jdbc.JdbcChatService;
+import ru.tinkoff.edu.java.scrapper.jdbc.JdbcLinkService;
+
+@RequestMapping("/tg-chat")
+@RestController
+public class ScrapperControllerTg {
+
+ private final JdbcTemplate jdbcTemplate;
+ JdbcChatService chatService = new JdbcChatService();
+ public ScrapperControllerTg(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @DeleteMapping("/{id}")
+ public void tgChatIdDelete(@PathVariable Long id) {
+ chatService.removeChat(jdbcTemplate, id);
+ }
+
+ @PostMapping("/{id}")
+ public void tgChatIdPost(@PathVariable long id) {
+ chatService.addChat(jdbcTemplate, id);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java
new file mode 100644
index 0000000..ee3e6b1
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java
@@ -0,0 +1,57 @@
+package ru.tinkoff.edu.java.scrapper.client;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.client.WebClient;
+import ru.tinkoff.edu.java.linkparser.LinkParser;
+
+import java.util.Objects;
+
+
+@Configuration
+public class ClientConfiguration {
+
+ private final String BASE_GIT_URL = "https://github.com/Ray-Not/JavaGuava";
+ private final String BASE_STACK_URL = "https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c";
+ @Value("${git.link}")
+ String gitLink;
+ @Value("${stack.link}")
+ String stackLink;
+ static WebClient.Builder builder = WebClient.builder();
+ LinkParser pars = new LinkParser();
+
+ @Bean
+ public WeatherRecord weatherClient() {
+ WeatherRecord weatherResponse = builder.build()
+ .get()
+ .uri("http://api.weatherapi.com/v1/current.json?key=3ff5d13401e44f30a14170938230204&q=Russia&aqi=no")
+ .retrieve()
+ .bodyToMono(WeatherRecord.class)
+ .block();
+ return weatherResponse;
+ }
+
+ public GitHubRecord gitHubClient(
+ String link
+ ) {
+ return builder.build()
+ .get()
+ .uri("https://api.github.com/repos/" + link)
+ .retrieve()
+ .bodyToMono(GitHubRecord.class)
+ .block();
+ }
+
+ public StackOverflowRecord stackOverflowClient(
+ String link
+ ) {
+ String params = "?order=desc&sort=activity&site=stackoverflow";
+ return builder.build()
+ .get()
+ .uri("https://api.stackexchange.com/2.3/questions/" + link + params)
+ .retrieve()
+ .bodyToMono(StackOverflowRecord.class)
+ .block();
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/GitHubRecord.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/GitHubRecord.java
new file mode 100644
index 0000000..97ef980
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/GitHubRecord.java
@@ -0,0 +1,19 @@
+package ru.tinkoff.edu.java.scrapper.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.OffsetDateTime;
+import java.util.HashMap;
+
+public record GitHubRecord(
+ @JsonProperty("full_name") String name,
+ @JsonProperty("owner") HashMap owner,
+ @JsonProperty("private") boolean is_private,
+ @JsonProperty("node_id") String node_id,
+ @JsonProperty("html_url") String url,
+ @JsonProperty("description") String description,
+ @JsonProperty("created_at") OffsetDateTime createdAt,
+ @JsonProperty("updated_at") OffsetDateTime updatedAt,
+ @JsonProperty("pushed_at") OffsetDateTime pushedAt
+
+) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/StackOverflowRecord.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/StackOverflowRecord.java
new file mode 100644
index 0000000..6013400
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/StackOverflowRecord.java
@@ -0,0 +1,10 @@
+package ru.tinkoff.edu.java.scrapper.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+public record StackOverflowRecord(
+ @JsonProperty("has_more") boolean has_more,
+ @JsonProperty("items") List owner
+) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/WeatherRecord.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/WeatherRecord.java
new file mode 100644
index 0000000..5ee9adc
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/WeatherRecord.java
@@ -0,0 +1,11 @@
+package ru.tinkoff.edu.java.scrapper.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.HashMap;
+import java.util.List;
+
+public record WeatherRecord(
+ @JsonProperty("location") HashMap location,
+ @JsonProperty("current") HashMap current
+ ) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java
new file mode 100644
index 0000000..2651ea3
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java
@@ -0,0 +1,39 @@
+package ru.tinkoff.edu.java.scrapper.configuration;
+
+import jakarta.validation.constraints.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.validation.annotation.Validated;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.ScrapperQueueProducer;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.SendNoticeServiceHttp;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.SendNoticeServiceImplement;
+import ru.tinkoff.edu.java.scrapper.rabbitmq.SendNoticeServiceQueue;
+import ru.tinkoff.edu.java.scrapper.schedule.Scheduler;
+
+@Validated
+@ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
+@EnableScheduling
+@Import(RabbitMQConfiguration.class)
+public record ApplicationConfig(
+ @NotNull String test,
+ @NotNull Scheduler scheduler,
+ Boolean useQueue,
+ String exchange,
+ String routingKey,
+ String queue
+) {
+@Bean
+public SendNoticeServiceImplement notificationService(ScrapperQueueProducer queueProducer/*, BotClient botClient */) {
+ if (useQueue) {
+ return new SendNoticeServiceQueue(queueProducer);
+ } else {
+ /* Заглушка */
+ return new SendNoticeServiceHttp();
+ }
+}
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/RabbitMQConfiguration.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/RabbitMQConfiguration.java
new file mode 100644
index 0000000..fc60661
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/RabbitMQConfiguration.java
@@ -0,0 +1,71 @@
+package ru.tinkoff.edu.java.scrapper.configuration;
+
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.support.converter.ClassMapper;
+import org.springframework.amqp.support.converter.DefaultClassMapper;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import ru.tinkoff.edu.java.scrapper.model.LinkUpdate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class RabbitMQConfiguration {
+
+ private final ApplicationConfig config;
+
+ public RabbitMQConfiguration(ApplicationConfig config) {
+ this.config = config;
+ }
+
+ @Bean
+ public DirectExchange exchange() {
+ return new DirectExchange(config.exchange());
+ }
+
+ @Bean
+ public Queue queue() {
+ return QueueBuilder.durable(config.queue())
+ .withArgument("x-dead-letter-exchange", config.queue() + ".dlq")
+ .build();
+ }
+
+ @Bean
+ public Binding binding(Queue queue, DirectExchange exchange) {
+ return BindingBuilder.bind(queue).to(exchange).with(config.routingKey());
+ }
+
+ @Bean
+ public ClassMapper classMapper(){
+ Map> mappings = new HashMap<>();
+ mappings.put("ru.tinkoff.edu.java.scrapper.model.LinkUpdate", LinkUpdate.class);
+
+ DefaultClassMapper classMapper = new DefaultClassMapper();
+ classMapper.setTrustedPackages("ru.tinkoff.edu.java.scrapper.model.*");
+ classMapper.setIdClassMapping(mappings);
+ return classMapper;
+ }
+
+ @Bean
+ public MessageConverter jsonMessageConverter(ClassMapper classMapper){
+ Jackson2JsonMessageConverter jsonConverter=new Jackson2JsonMessageConverter();
+ jsonConverter.setClassMapper(classMapper);
+ return jsonConverter;
+ }
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ CachingConnectionFactory connectionFactory = new CachingConnectionFactory("rabbitmq", 5672);
+ connectionFactory.setUsername("guest");
+ connectionFactory.setPassword("guest");
+ // Другие настройки, если необходимо
+
+ return connectionFactory;
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/ScrapperExceptionHandler.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/ScrapperExceptionHandler.java
new file mode 100644
index 0000000..55fc0c9
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/ScrapperExceptionHandler.java
@@ -0,0 +1,78 @@
+package ru.tinkoff.edu.java.scrapper.exceptions;
+
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MissingRequestHeaderException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.EntryExsistException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.EntryNotExsistException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.NullLinkException;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.UnauthorizationException;
+import ru.tinkoff.edu.java.scrapper.exceptions.model.ApiErrorResponse;
+
+@RestControllerAdvice
+public class ScrapperExceptionHandler {
+
+ private ApiErrorResponse setException(Exception exception) {
+ String[] parsing_class = exception.getClass().toString().split("\\.");
+ String class_name = parsing_class[parsing_class.length - 1];
+ ApiErrorResponse errorObj = new ApiErrorResponse(
+ exception.getMessage(),
+ class_name,
+ exception.toString()
+ );
+ return errorObj;
+ }
+
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ApiErrorResponse MessageNotReadable(HttpMessageNotReadableException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ApiErrorResponse MethodNotSupported(HttpRequestMethodNotSupportedException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(MissingRequestHeaderException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ApiErrorResponse MissingRequestHeader(MissingRequestHeaderException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ApiErrorResponse ArgumentTypeMismatch(MethodArgumentTypeMismatchException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(NullLinkException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ApiErrorResponse NullLink(NullLinkException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(UnauthorizationException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public ApiErrorResponse Unauthorization(UnauthorizationException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(EntryExsistException.class)
+ @ResponseStatus(HttpStatus.FOUND)
+ public ApiErrorResponse EntryExsist(EntryExsistException e) {
+ return setException(e);
+ }
+
+ @ExceptionHandler(EntryNotExsistException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ApiErrorResponse EntryNotExsist(EntryNotExsistException e) {
+ return setException(e);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryExsistException.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryExsistException.java
new file mode 100644
index 0000000..fca436d
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryExsistException.java
@@ -0,0 +1,12 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.customExceptions;
+
+public class EntryExsistException extends RuntimeException {
+
+ public EntryExsistException() {
+ super();
+ }
+
+ public EntryExsistException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryNotExsistException.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryNotExsistException.java
new file mode 100644
index 0000000..e25ba25
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/EntryNotExsistException.java
@@ -0,0 +1,12 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.customExceptions;
+
+public class EntryNotExsistException extends RuntimeException {
+
+ public EntryNotExsistException() {
+ super();
+ }
+
+ public EntryNotExsistException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullDBException.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullDBException.java
new file mode 100644
index 0000000..cd7baa2
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullDBException.java
@@ -0,0 +1,12 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.customExceptions;
+
+public class NullDBException extends RuntimeException {
+
+ public NullDBException() {
+ super();
+ }
+
+ public NullDBException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullLinkException.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullLinkException.java
new file mode 100644
index 0000000..00ddf4e
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/NullLinkException.java
@@ -0,0 +1,12 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.customExceptions;
+
+public class NullLinkException extends RuntimeException {
+
+ public NullLinkException() {
+ super();
+ }
+
+ public NullLinkException(String s) {
+ super(s);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/UnauthorizationException.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/UnauthorizationException.java
new file mode 100644
index 0000000..2135ddc
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/customExceptions/UnauthorizationException.java
@@ -0,0 +1,12 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.customExceptions;
+
+public class UnauthorizationException extends RuntimeException {
+
+ public UnauthorizationException() {
+ super();
+ }
+
+ public UnauthorizationException(String s) {
+ super(s);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/model/ApiErrorResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/model/ApiErrorResponse.java
new file mode 100644
index 0000000..0a8994e
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/exceptions/model/ApiErrorResponse.java
@@ -0,0 +1,7 @@
+package ru.tinkoff.edu.java.scrapper.exceptions.model;
+
+public record ApiErrorResponse(
+ String exceptionMessage,
+ String description,
+ String exceptionName
+) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcChatService.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcChatService.java
new file mode 100644
index 0000000..c8757c8
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcChatService.java
@@ -0,0 +1,26 @@
+package ru.tinkoff.edu.java.scrapper.jdbc;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.*;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+
+public class JdbcChatService implements LinkOperations, ChatOperations, LinkChatOperations {
+ public void addChat(JdbcTemplate jdbc, long chat) {
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id != 0) {
+ throw new EntryExsistException("Вы уже зарегистрированы");
+ } else {
+ i_addChat(jdbc, chat);
+ }
+ }
+
+ public void removeChat(JdbcTemplate jdbc, long chat) {
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id != 0) {
+ i_removeChat(jdbc, chat);
+ i_removeLinkChatAllChat(jdbc, chat_id);
+ } else throw new EntryNotExsistException("Пользователь не найден");
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcLinkService.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcLinkService.java
new file mode 100644
index 0000000..2d62856
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/JdbcLinkService.java
@@ -0,0 +1,81 @@
+package ru.tinkoff.edu.java.scrapper.jdbc;
+
+import org.springframework.jdbc.BadSqlGrammarException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import ru.tinkoff.edu.java.linkparser.LinkParser;
+import ru.tinkoff.edu.java.scrapper.model.AddLinkRequest;
+import ru.tinkoff.edu.java.scrapper.model.LinkResponse;
+import ru.tinkoff.edu.java.scrapper.model.RemoveLinkRequest;
+import ru.tinkoff.edu.java.scrapper.client.ClientConfiguration;
+import ru.tinkoff.edu.java.scrapper.exceptions.customExceptions.*;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.ChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkChatOperations;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class JdbcLinkService implements LinkOperations, ChatOperations, LinkChatOperations {
+ public void addLink(JdbcTemplate jdbc, AddLinkRequest link, Long chat) {
+ ClientConfiguration client = new ClientConfiguration();
+ LinkParser parser = new LinkParser();
+ if (parser.getLink(link.link()) == null) {
+ throw new NullLinkException("Ссылка невалидна");
+ }
+ try {
+ client.gitHubClient(parser.getLink(link.link()));
+ } catch (WebClientResponseException e) {
+ try {
+ client.stackOverflowClient(parser.getLink(link.link()));
+ } catch (WebClientResponseException ex) {
+ throw new NullLinkException("Ссылка не поддерживается, доступны: Git, StackOverFlow");
+ }
+ }
+ int link_id = i_findLink(jdbc, link.link());
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ throw new UnauthorizationException("Перед использованием нужно зарегистрироваться");
+ }
+ if (link_id == 0) {
+ i_addLink(jdbc, link.link());
+ link_id = i_findLink(jdbc, link.link());
+ }
+ if (i_findLinkChat(jdbc, link_id, chat_id)) {
+ addLinkChat(jdbc, link_id, chat_id);
+ }else throw new EntryExsistException("Ссылка уже добавлена!");
+ }
+
+ public void removeLink(JdbcTemplate jdbc, RemoveLinkRequest link, long chat) {
+ int link_id = i_findLink(jdbc, link.link());
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ throw new UnauthorizationException("Перед использованием нужно зарегистрироваться");
+ }
+ if (link_id != 0) {
+ i_removeLink(jdbc, link.link());
+ } else throw new EntryNotExsistException("Ссылки не существует");
+ if (!i_findLinkChat(jdbc, link_id, chat_id)) {
+ i_removeLinkChat(jdbc, link_id, chat_id);
+ }
+ }
+
+ public List getLinks(JdbcTemplate jdbc, long chat) {
+ int i;
+ int chat_id = i_findChat(jdbc, chat);
+ if (chat_id == 0) {
+ throw new UnauthorizationException("Перед использованием нужно зарегистрироваться");
+ }
+ ArrayList link_list = new ArrayList<>();
+ try {
+ for (i = 0; i < i_get_all_links_for_chat(jdbc, chat_id).size(); i++) {
+ link_list.add(i_get_all_links_for_chat(jdbc, chat_id).get(i).linkid());
+ }
+ return i_findAllLink(jdbc, link_list);
+ } catch (BadSqlGrammarException e) {
+ throw new EntryNotExsistException("У вас нет ссылок");
+ }
+
+ }
+
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/config/jdbcBean.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/config/jdbcBean.java
new file mode 100644
index 0000000..74a1fbe
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/config/jdbcBean.java
@@ -0,0 +1,25 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import javax.sql.DataSource;
+@Configuration
+public class jdbcBean {
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.postgresql.Driver");
+ dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
+ dataSource.setUsername("scrap_user");
+ dataSource.setPassword("hard_password");
+
+ return dataSource;
+ }
+ @Bean
+ public JdbcTemplate jdbcTemplate () {
+ return new JdbcTemplate (dataSource());
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/ChatMapper.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/ChatMapper.java
new file mode 100644
index 0000000..026139a
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/ChatMapper.java
@@ -0,0 +1,14 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.mappers;
+
+import org.springframework.jdbc.core.RowMapper;
+import ru.tinkoff.edu.java.scrapper.model.ChatResponse;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ChatMapper implements RowMapper {
+ @Override
+ public ChatResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new ChatResponse(rs.getLong("tg_chat_id"), rs.getInt("id"));
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkChatMapper.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkChatMapper.java
new file mode 100644
index 0000000..4b52ef9
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkChatMapper.java
@@ -0,0 +1,14 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.mappers;
+
+import org.springframework.jdbc.core.RowMapper;
+import ru.tinkoff.edu.java.scrapper.model.LinkChatResponse;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class LinkChatMapper implements RowMapper {
+ @Override
+ public LinkChatResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new LinkChatResponse(rs.getInt("linkid"), rs.getInt("chatid"));
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkMapper.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkMapper.java
new file mode 100644
index 0000000..914d113
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/mappers/LinkMapper.java
@@ -0,0 +1,15 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.mappers;
+
+
+import org.springframework.jdbc.core.RowMapper;
+import ru.tinkoff.edu.java.scrapper.model.LinkResponse;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class LinkMapper implements RowMapper {
+ @Override
+ public LinkResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new LinkResponse(rs.getString("link"), rs.getInt("id"));
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/ChatOperations.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/ChatOperations.java
new file mode 100644
index 0000000..3c36c88
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/ChatOperations.java
@@ -0,0 +1,50 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.operations;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.scrapper.jdbc.mappers.ChatMapper;
+import ru.tinkoff.edu.java.scrapper.jdbc.mappers.LinkMapper;
+
+public interface ChatOperations {
+ default void i_addChat(
+ JdbcTemplate jdbcTemplate,
+ long chat
+ ) {
+ int chat_id;
+
+ try {
+ chat_id = jdbcTemplate.query(
+ "SELECT * FROM tgchats WHERE id=(SELECT MAX(id) FROM tgchats)",
+ new ChatMapper()
+ ).get(0).id() + 1;
+ } catch (IndexOutOfBoundsException e) {
+ chat_id = 1;
+ }
+
+ jdbcTemplate.update("INSERT INTO tgchats VALUES(?, ?)", chat_id, chat);
+ }
+
+ default void i_removeChat(
+ JdbcTemplate jdbcTemplate,
+ long chat
+ ){
+ String query = "DELETE FROM tgchats WHERE tg_chat_id=(%d)";
+ query = query.formatted(chat);
+ jdbcTemplate.update(query);
+ }
+
+ default int i_findChat(
+ JdbcTemplate jdbcTemplate,
+ long chat
+ ){
+ try {
+ String query = "SELECT * FROM tgchats WHERE tg_chat_id=(%d)";
+ query = query.formatted(chat);
+ return jdbcTemplate.query(
+ query,
+ new ChatMapper()
+ ).get(0).id();
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return 0;
+ }
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkChatOperations.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkChatOperations.java
new file mode 100644
index 0000000..e40b248
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkChatOperations.java
@@ -0,0 +1,61 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.operations;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.scrapper.model.LinkChatResponse;
+import ru.tinkoff.edu.java.scrapper.jdbc.mappers.LinkChatMapper;
+
+import java.util.List;
+
+public interface LinkChatOperations {
+ default void addLinkChat(
+ JdbcTemplate jdbcTemplate,
+ int link_id,
+ int chat_id
+ ) {
+ jdbcTemplate.update("INSERT INTO links_tgchats VALUES(?, ?)", link_id, chat_id);
+ }
+
+ default void i_removeLinkChat(
+ JdbcTemplate jdbcTemplate,
+ int link_id,
+ int chat_id
+ ) {
+ String query = "DELETE FROM links_tgchats WHERE linkid=(%d) AND chatid=(%d)";
+ query = query.formatted(link_id, chat_id);
+ jdbcTemplate.update(query);
+ }
+
+ default void i_removeLinkChatAllChat(
+ JdbcTemplate jdbcTemplate,
+ int chat_id
+ ) {
+ String query = "DELETE FROM links_tgchats WHERE chatid=(%d)";
+ query = query.formatted(chat_id);
+ jdbcTemplate.update(query);
+ }
+
+ default boolean i_findLinkChat(
+ JdbcTemplate jdbcTemplate,
+ int link_id,
+ int chat_id
+ ){
+ try {
+ String query = "SELECT * FROM links_tgchats WHERE linkid=(%d) AND chatid=(%d)";
+ query = query.formatted(link_id, chat_id);
+ jdbcTemplate.query(query, new LinkChatMapper()).get(0);
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return true;
+ }
+ return false;
+ }
+
+ default List i_get_all_links_for_chat(JdbcTemplate jdbcTemplate, int chat_id) {
+ try {
+ String query = "SELECT * FROM links_tgchats WHERE chatid=(%d)";
+ query = query.formatted(chat_id);
+ return jdbcTemplate.query(query, new LinkChatMapper());
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return null;
+ }
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkOperations.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkOperations.java
new file mode 100644
index 0000000..dfdbe71
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/jdbc/operations/LinkOperations.java
@@ -0,0 +1,83 @@
+package ru.tinkoff.edu.java.scrapper.jdbc.operations;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import ru.tinkoff.edu.java.scrapper.model.AddLinkRequest;
+import ru.tinkoff.edu.java.scrapper.model.LinkResponse;
+import ru.tinkoff.edu.java.scrapper.model.RemoveLinkRequest;
+import ru.tinkoff.edu.java.scrapper.jdbc.mappers.LinkMapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public interface LinkOperations {
+ default void i_addLink(
+ JdbcTemplate jdbcTemplate,
+ String link
+ ) {
+ int link_id;
+
+ try {
+ link_id = jdbcTemplate.query(
+ "SELECT * FROM links WHERE id=(SELECT MAX(id) FROM links)",
+ new LinkMapper()
+ ).get(0).id() + 1;
+ } catch (IndexOutOfBoundsException e) {
+ link_id = 1;
+ }
+
+ jdbcTemplate.update("INSERT INTO links VALUES(?, ?)", link_id, link);
+ }
+
+ default void i_removeLink(
+ JdbcTemplate jdbcTemplate,
+ String link
+ ){
+ String query = "DELETE FROM links where link IN ('%s')";
+ query = query.formatted(link);
+ jdbcTemplate.update(query);
+ }
+
+ default List i_findAllLink(
+ JdbcTemplate jdbcTemplate,
+ ArrayList links_ids
+ ){
+ try {
+ String ids = Arrays.toString(links_ids.toArray());
+ ids = ids.substring(1, ids.length() - 1);
+ String query = "SELECT * FROM links WHERE id IN (%s)";
+ query = query.formatted(ids);
+ return jdbcTemplate.query(
+ query,
+ new LinkMapper()
+ );
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return null;
+ }
+ }
+
+ default int i_findLink(
+ JdbcTemplate jdbcTemplate,
+ String link
+ ){
+ try {
+ String query = "SELECT * FROM links WHERE link IN ('%s')";
+ query = query.formatted(link);
+ return jdbcTemplate.query(
+ query,
+ new LinkMapper()
+ ).get(0).id();
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return 0;
+ }
+ }
+
+ default List i_getAllIds(JdbcTemplate jdbcTemplate) {
+ try {
+ String query = "SELECT * FROM links";
+ return jdbcTemplate.query(query, new LinkMapper());
+ } catch (IndexOutOfBoundsException e) { // Если значение не нашлось
+ return null;
+ }
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/AddLinkRequest.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/AddLinkRequest.java
new file mode 100644
index 0000000..e7f8aa4
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/AddLinkRequest.java
@@ -0,0 +1,3 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record AddLinkRequest(String link) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ChatResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ChatResponse.java
new file mode 100644
index 0000000..d1e1893
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ChatResponse.java
@@ -0,0 +1,3 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record ChatResponse(long chat_id, int id) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkChatResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkChatResponse.java
new file mode 100644
index 0000000..6ef6a2c
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkChatResponse.java
@@ -0,0 +1,3 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record LinkChatResponse(int linkid, int chatid) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkResponse.java
new file mode 100644
index 0000000..d5192ab
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkResponse.java
@@ -0,0 +1,3 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record LinkResponse(String url, int id) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkUpdate.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkUpdate.java
new file mode 100644
index 0000000..ba13a15
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/LinkUpdate.java
@@ -0,0 +1,4 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record LinkUpdate(String url) {
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ListLinksResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ListLinksResponse.java
new file mode 100644
index 0000000..6a04698
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/ListLinksResponse.java
@@ -0,0 +1,6 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+import java.util.*;
+
+public record ListLinksResponse(List links, int size) {}
+
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/RemoveLinkRequest.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/RemoveLinkRequest.java
new file mode 100644
index 0000000..1f9b6ee
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/RemoveLinkRequest.java
@@ -0,0 +1,3 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public record RemoveLinkRequest(String link) {}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/UpdateService.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/UpdateService.java
new file mode 100644
index 0000000..4d53819
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/model/UpdateService.java
@@ -0,0 +1,4 @@
+package ru.tinkoff.edu.java.scrapper.model;
+
+public class UpdateService {
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceImplement.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceImplement.java
new file mode 100644
index 0000000..8432dad
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceImplement.java
@@ -0,0 +1,5 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+public interface HandleNoticeServiceImplement {
+ void handleNotification(String message);
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceQueue.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceQueue.java
new file mode 100644
index 0000000..deb53c3
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/HandleNoticeServiceQueue.java
@@ -0,0 +1,16 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HandleNoticeServiceQueue implements HandleNoticeServiceImplement {
+
+ Logger log = LoggerFactory.getLogger(HandleNoticeServiceQueue.class);
+ @Override
+ public void handleNotification(String message) {
+ log.info("Пришло уведомление с Rabbit очереди: " + message);
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueListener.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueListener.java
new file mode 100644
index 0000000..1d587b6
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueListener.java
@@ -0,0 +1,19 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+import org.springframework.amqp.rabbit.annotation.RabbitHandler;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import ru.tinkoff.edu.java.scrapper.model.LinkUpdate;
+
+@RabbitListener(queues = "${app.queue}")
+public class ScrapperQueueListener {
+ private final HandleNoticeServiceQueue handleNoticeServiceQueue;
+
+ public ScrapperQueueListener(HandleNoticeServiceQueue notificationHandler) {
+ this.handleNoticeServiceQueue = notificationHandler;
+ }
+
+ @RabbitHandler
+ public void receiver(LinkUpdate update) {
+ handleNoticeServiceQueue.handleNotification(update.url());
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueProducer.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueProducer.java
new file mode 100644
index 0000000..c592025
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/ScrapperQueueProducer.java
@@ -0,0 +1,28 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import ru.tinkoff.edu.java.scrapper.configuration.ApplicationConfig;
+import ru.tinkoff.edu.java.scrapper.model.LinkUpdate;
+
+@Service
+
+public class ScrapperQueueProducer {
+ private final RabbitTemplate rabbitTemplate;
+
+ private final ApplicationConfig config;
+
+ public ScrapperQueueProducer(RabbitTemplate rabbitTemplate, ApplicationConfig config) {
+ this.rabbitTemplate = rabbitTemplate;
+ this.config = config;
+ }
+
+ public void send(LinkUpdate update) {
+ String exchange = config.exchange(); // Имя обмена
+ String routingKey = config.routingKey(); // Маршрутный ключ
+
+ rabbitTemplate.convertAndSend(exchange, routingKey, update.url());
+ }
+}
+
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceHttp.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceHttp.java
new file mode 100644
index 0000000..7067329
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceHttp.java
@@ -0,0 +1,11 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class SendNoticeServiceHttp implements SendNoticeServiceImplement {
+ @Override
+ public void sendNotice(String message) {
+ // TO DO WHERE CLIENT
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceImplement.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceImplement.java
new file mode 100644
index 0000000..680f7fe
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceImplement.java
@@ -0,0 +1,5 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+public interface SendNoticeServiceImplement {
+ void sendNotice(String notice);
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceQueue.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceQueue.java
new file mode 100644
index 0000000..70eed14
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/rabbitmq/SendNoticeServiceQueue.java
@@ -0,0 +1,20 @@
+package ru.tinkoff.edu.java.scrapper.rabbitmq;
+
+import org.springframework.stereotype.Service;
+import ru.tinkoff.edu.java.scrapper.model.LinkUpdate;
+
+@Service
+public class SendNoticeServiceQueue implements SendNoticeServiceImplement {
+ private final ScrapperQueueProducer queueProducer;
+
+ public SendNoticeServiceQueue(ScrapperQueueProducer queueProducer) {
+ this.queueProducer = queueProducer;
+ }
+
+ @Override
+ public void sendNotice(String message) {
+ // Отправка сообщения в очередь через ScrapperQueueProducer
+ LinkUpdate update = new LinkUpdate(message);
+ queueProducer.send(update);
+ }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java
new file mode 100644
index 0000000..63acc43
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java
@@ -0,0 +1,63 @@
+package ru.tinkoff.edu.java.scrapper.schedule;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import ru.tinkoff.edu.java.linkparser.LinkParser;
+import ru.tinkoff.edu.java.scrapper.client.ClientConfiguration;
+import ru.tinkoff.edu.java.scrapper.jdbc.JdbcLinkService;
+import ru.tinkoff.edu.java.scrapper.jdbc.operations.LinkOperations;
+
+import java.lang.ref.Cleaner;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Service
+public class LinkUpdaterScheduler implements LinkOperations {
+// private final static Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+//
+// private final JdbcTemplate jdbcTemplate;
+// ArrayList link_list = new ArrayList<>();
+// static int ix = 0;
+// ClientConfiguration client = new ClientConfiguration();
+// LinkParser pars = new LinkParser();
+// boolean git_link_is_activity;
+// boolean stack_link_is_activity;
+//
+// public LinkUpdaterScheduler(JdbcTemplate jdbcTemplate) {
+// this.jdbcTemplate = jdbcTemplate;
+// }
+//
+// @Scheduled(fixedDelayString = "${app.scheduler.interval}")
+// public void update() {
+// int i;
+// for (i = 0; i < i_getAllIds(jdbcTemplate).size(); i++){
+// if (!link_list.contains(i_getAllIds(jdbcTemplate).get(i).url())) {
+// link_list.add(i_getAllIds(jdbcTemplate).get(i).url());
+// }
+// }
+// try {
+// client.gitHubClient(pars.getLink(link_list.get(ix)));
+// git_link_is_activity = true;
+// } catch (WebClientResponseException e) {
+// git_link_is_activity = false;
+// }
+// try {
+// client.stackOverflowClient(pars.getLink(link_list.get(ix)));
+// stack_link_is_activity = true;
+// } catch (WebClientResponseException ignored) {
+// stack_link_is_activity = false;
+// }
+// if (!(git_link_is_activity || stack_link_is_activity)) {
+// String link = "Ссылка %s устарела";
+// link = link.formatted(i_getAllIds(jdbcTemplate).get(ix).url());
+// LOGGER.log(Level.INFO, link);
+// }
+// ix++;
+// if (ix == link_list.size()) ix = 0;
+// }
+}
diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/Scheduler.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/Scheduler.java
new file mode 100644
index 0000000..d060c22
--- /dev/null
+++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/Scheduler.java
@@ -0,0 +1,5 @@
+package ru.tinkoff.edu.java.scrapper.schedule;
+
+import java.time.Duration;
+
+public record Scheduler(Duration interval) {}
diff --git a/FP/scrapper/src/main/resources/application.properties b/FP/scrapper/src/main/resources/application.properties
new file mode 100644
index 0000000..d4267e9
--- /dev/null
+++ b/FP/scrapper/src/main/resources/application.properties
@@ -0,0 +1,9 @@
+app.test=123
+springdoc.swagger-ui.path=/swagger-ui
+app.scheduler.interval=5000
+git.link=https://github.com/Ray-Not/JavaGuava
+stack.link=https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c
+app.exchange=my-direct-exchange
+app.queue=my-queue
+app.routingKey=my-routing-key
+app.useQueue=True
\ No newline at end of file
diff --git a/FP/scrapper/src/test/java/BaseTest.java b/FP/scrapper/src/test/java/BaseTest.java
new file mode 100644
index 0000000..447e5c5
--- /dev/null
+++ b/FP/scrapper/src/test/java/BaseTest.java
@@ -0,0 +1,11 @@
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class BaseTest extends IntegrationEnvironment {
+
+ @Test
+ void create_test() {
+ Assertions.assertTrue(Postgre_container.isCreated());
+ System.out.println(Postgre_container.getImage());
+ }
+}
\ No newline at end of file
diff --git a/FP/scrapper/src/test/java/IntegrationEnvironment.java b/FP/scrapper/src/test/java/IntegrationEnvironment.java
new file mode 100644
index 0000000..7fef825
--- /dev/null
+++ b/FP/scrapper/src/test/java/IntegrationEnvironment.java
@@ -0,0 +1,45 @@
+import liquibase.Contexts;
+import liquibase.LabelExpression;
+import liquibase.Liquibase;
+import liquibase.database.Database;
+import liquibase.database.DatabaseFactory;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.LiquibaseException;
+import liquibase.resource.DirectoryResourceAccessor;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import java.io.FileNotFoundException;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public abstract class IntegrationEnvironment {
+
+ static final protected PostgreSQLContainer Postgre_container;
+
+ static {
+ Postgre_container = new PostgreSQLContainer<>("postgres:14");
+ Postgre_container.start();
+
+ try {
+ Connection connection = DriverManager.getConnection(
+ Postgre_container.getJdbcUrl(),
+ Postgre_container.getUsername(),
+ Postgre_container.getPassword());
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+ Liquibase liquibase = new liquibase.Liquibase(
+ "master.xml",
+ new DirectoryResourceAccessor(Path.of(".")
+ .toAbsolutePath()
+ .getParent()
+ .getParent()
+ .resolve("migrations")),
+ database);
+ liquibase.update(new Contexts(), new LabelExpression());
+ } catch (SQLException | LiquibaseException | FileNotFoundException e) {
+ System.out.println(e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/FP/scrapper/src/test/java/JdbcAddTest.java b/FP/scrapper/src/test/java/JdbcAddTest.java
new file mode 100644
index 0000000..d5de9ec
--- /dev/null
+++ b/FP/scrapper/src/test/java/JdbcAddTest.java
@@ -0,0 +1,9 @@
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class JdbcAddTest extends IntegrationEnvironment {
+ @Test
+ void create_test() {
+// Postgre_container.
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29