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