diff --git a/.gitignore b/.gitignore index 810ebf3..862eb15 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,5 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -# IJ IDEA # +# idea .idea \ No newline at end of file diff --git a/FP/bot/pom.xml b/FP/bot/pom.xml new file mode 100644 index 0000000..60f49bd --- /dev/null +++ b/FP/bot/pom.xml @@ -0,0 +1,88 @@ + + + + org.example + FP + 1.0 + + + 4.0.0 + + bot + + + 3.0.1 + 2.7.6 + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.0.2 + + + 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 + + + \ 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..312857e --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java @@ -0,0 +1,21 @@ +package ru.tinkoff.edu.java.bot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import ru.tinkoff.edu.java.bot.configuration.ApplicationConfig; +import ru.tinkoff.edu.java.bot.firstBot.BotMain; + + +@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); + + BotMain bot = new BotMain(config.bot().token()); + 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..25186cf --- /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.api.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..732a56e --- /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.api.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/api/model/ApiErrorResponse.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/ApiErrorResponse.java new file mode 100644 index 0000000..f43001e --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/ApiErrorResponse.java @@ -0,0 +1,11 @@ +package ru.tinkoff.edu.java.bot.api.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/api/model/LinkUpdate.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/LinkUpdate.java new file mode 100644 index 0000000..0f44607 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/LinkUpdate.java @@ -0,0 +1,6 @@ +package ru.tinkoff.edu.java.bot.api.model; + +import java.util.List; + +public record LinkUpdate(long id, String url, String description, List tgChatIds) { +} \ No newline at end of file diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java new file mode 100644 index 0000000..49ce5e3 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java @@ -0,0 +1,18 @@ +package ru.tinkoff.edu.java.bot.configuration; + +import jakarta.validation.constraints.NotNull; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.validation.annotation.Validated; +import ru.tinkoff.edu.java.bot.configuration.configRecords.Bot; +import ru.tinkoff.edu.java.bot.configuration.configRecords.Scheduler; + +@Validated +@EnableScheduling +@ConfigurationProperties(prefix = "app", ignoreUnknownFields = false) +public record ApplicationConfig( + @NotNull String test, + @NotNull Scheduler scheduler, + Bot bot +) { +} \ No newline at end of file diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Bot.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Bot.java new file mode 100644 index 0000000..37f4842 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Bot.java @@ -0,0 +1,4 @@ +package ru.tinkoff.edu.java.bot.configuration.configRecords; + +public record Bot(String token, String name) { +} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Scheduler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Scheduler.java new file mode 100644 index 0000000..08278f6 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/configRecords/Scheduler.java @@ -0,0 +1,5 @@ +package ru.tinkoff.edu.java.bot.configuration.configRecords; + +import java.time.Duration; + +public record Scheduler(Duration interval) {} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/BotMain.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/BotMain.java new file mode 100644 index 0000000..f758472 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/BotMain.java @@ -0,0 +1,36 @@ +package ru.tinkoff.edu.java.bot.firstBot; + +import com.pengrad.telegrambot.TelegramBot; +import com.pengrad.telegrambot.request.SendMessage; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import ru.tinkoff.edu.java.bot.configuration.ApplicationConfig; + + +@EnableConfigurationProperties(ApplicationConfig.class) +public class BotMain { + + String token; + static TelegramBot bot; + + public BotMain(String token) { + this.token = token; + } + + public void start() { + bot = new TelegramBot(token); + bot.setUpdatesListener(new Updater(bot)); + } + + public void end() { + bot.removeGetUpdatesListener(); + } + + public static void apiCommand(long tgChatId, String command) { + + MessageHandler handler = new MessageHandler(); + String[] parse = command.split(" "); + if(parse.length > 1) command = handler.call_command(parse[0], parse[1]); + else command = handler.call_command(parse[0]); + bot.execute(new SendMessage(tgChatId, command)); + } +} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/DB.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/DB.java new file mode 100644 index 0000000..486a886 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/DB.java @@ -0,0 +1,42 @@ +package ru.tinkoff.edu.java.bot.firstBot; + +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/firstBot/MessageHandler.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/MessageHandler.java new file mode 100644 index 0000000..f003c84 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/MessageHandler.java @@ -0,0 +1,30 @@ +package ru.tinkoff.edu.java.bot.firstBot; + +import ru.tinkoff.edu.java.bot.firstBot.commands.All; + +public class MessageHandler extends All { + + public boolean is_command(String message) { + return message.startsWith("/"); + } + + public String call_command(String command, String arg) { + return switch (command) { + case "/start" -> start(); + case "/help" -> help(); + case "/track" -> track(arg); + case "/list" -> list(); + case "/untrack" -> untrack(arg); + default -> unknow(); + }; + } + + public String call_command(String command) { + return switch (command) { + case "/start" -> start(); + case "/help" -> help(); + case "/list" -> list(); + default -> unknow(); + }; + } +} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/Updater.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/Updater.java new file mode 100644 index 0000000..a01e4d9 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/Updater.java @@ -0,0 +1,39 @@ +package ru.tinkoff.edu.java.bot.firstBot; + +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 java.util.List; + +public class Updater implements UpdatesListener { + + MessageHandler handler = new MessageHandler(); + String command; + TelegramBot bot; + + public Updater(TelegramBot bot) { + this.bot = bot; + } + + @Override + public int process(List updates) { + Update update = updates.get(0); + 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/firstBot/commands/All.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/All.java new file mode 100644 index 0000000..991b177 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/All.java @@ -0,0 +1,7 @@ +package ru.tinkoff.edu.java.bot.firstBot.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/firstBot/commands/Help.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Help.java new file mode 100644 index 0000000..0761e05 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Help.java @@ -0,0 +1,11 @@ +package ru.tinkoff.edu.java.bot.firstBot.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/firstBot/commands/List.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/List.java new file mode 100644 index 0000000..8673887 --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/List.java @@ -0,0 +1,10 @@ +package ru.tinkoff.edu.java.bot.firstBot.commands; + +import ru.tinkoff.edu.java.bot.firstBot.DB; + +public interface List { + default String list() { + if(DB.listIsEmpty()) return "list пустой"; + return "Отслеживаемые ссылки: " + DB.getListParse() + ""; + } +} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Start.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Start.java new file mode 100644 index 0000000..d58c89c --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Start.java @@ -0,0 +1,9 @@ +package ru.tinkoff.edu.java.bot.firstBot.commands; + +public interface Start { + + default String start() { + return "Бот начал работу"; + } +} + diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Track.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Track.java new file mode 100644 index 0000000..0f4669e --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Track.java @@ -0,0 +1,13 @@ +package ru.tinkoff.edu.java.bot.firstBot.commands; + +import ru.tinkoff.edu.java.bot.firstBot.DB; + +public interface Track { + default String track(String link) { + link = link.trim(); + if(link.equals("")) return "ведите ссылку"; + if(DB.linkContain(link)) return "Ссылка уже есть"; + DB.addLink(link); + return "ссылка " + link + " добавлена"; + } +} diff --git a/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Untrack.java b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Untrack.java new file mode 100644 index 0000000..6550daf --- /dev/null +++ b/FP/bot/src/main/java/ru/tinkoff/edu/java/bot/firstBot/commands/Untrack.java @@ -0,0 +1,14 @@ +package ru.tinkoff.edu.java.bot.firstBot.commands; + +import ru.tinkoff.edu.java.bot.firstBot.DB; + +public interface Untrack { + default String untrack(String link) { + if(link.equals("")) return "ведите ссылку"; + if(DB.linkContain(link)) { + DB.rmLink(link); + return "ссылка " + link + " удалена"; + } + return "ссылки " + link + " нет в пуле"; + } +} 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..a54b385 --- /dev/null +++ b/FP/bot/src/main/resources/application.properties @@ -0,0 +1,5 @@ +app.test=123 +springdoc.swagger-ui.path=/swagger-ui +app.scheduler.interval=50000 +app.bot.token=5805337447:AAGnmh2isW2115L7tJWFojbpmSjNrarTvxQ +app.bot.name=@SFRETbot diff --git a/FP/bot/src/main/resources/botapi.yml b/FP/bot/src/main/resources/botapi.yml new file mode 100644 index 0000000..489441e --- /dev/null +++ b/FP/bot/src/main/resources/botapi.yml @@ -0,0 +1,59 @@ +openapi: 3.0.1 +info: + title: Bot API + version: 1.0.0 + contact: + name: Alexander Biryukov + url: https://github.com +paths: + /updates: + post: + summary: Отправить обновление + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LinkUpdate' + required: true + responses: + '200': + description: Обновление обработано + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' +components: + schemas: + ApiErrorResponse: + type: object + properties: + description: + type: string + code: + type: string + exceptionName: + type: string + exceptionMessage: + type: string + stacktrace: + type: array + items: + type: string + LinkUpdate: + type: object + properties: + id: + type: integer + format: int64 + url: + type: string + format: uri + description: + type: string + tgChatIds: + type: array + items: + type: integer + format: int64 diff --git a/FP/bot/src/test/java/BotTest.java b/FP/bot/src/test/java/BotTest.java new file mode 100644 index 0000000..1548bc9 --- /dev/null +++ b/FP/bot/src/test/java/BotTest.java @@ -0,0 +1,24 @@ +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.tinkoff.edu.java.bot.firstBot.DB; +import ru.tinkoff.edu.java.bot.firstBot.MessageHandler; + + +public class BotTest { + + String except_empty_list = "list пустой"; + String except_unknow_command = "Неизвестная команда"; + MessageHandler handler = new MessageHandler(); + + @Test + public void listTest() { + Assertions.assertEquals(handler.call_command("/list"), except_empty_list); + DB.addLink("link"); + Assertions.assertNotEquals(handler.call_command("/list"), except_empty_list); + } + + @Test + public void unknowCommandAndFormatTest() { + Assertions.assertEquals(handler.call_command("/uno"), except_unknow_command); + } +} diff --git a/FP/docker-compose.yml b/FP/docker-compose.yml new file mode 100644 index 0000000..4829d18 --- /dev/null +++ b/FP/docker-compose.yml @@ -0,0 +1,42 @@ +version: '1.-' + +services: + postgres: + container_name: postgres + image: postgres + environment: + POSTGRES_DB: scrapper + 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 + 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/scrapper + - --username=scrap_user + - --password=hard_password + - --logLevel=debug + - update + volumes: + - ./migrations:/liquibase/changelog + networks: + - backend + +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..a73b23b --- /dev/null +++ b/FP/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java @@ -0,0 +1,20 @@ +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 (!Objects.equals(parsed[2], "stackoverflow.com")) return null; + if (!Objects.equals(parsed[3], "questions")) return null; + + + if (parsed.length > 4) return parsed[4]; + + return null; + } +} 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..7de0483 --- /dev/null +++ b/FP/migrations/chats_links_scheme.sql @@ -0,0 +1,10 @@ +CREATE TABLE links (id INTEGER PRIMARY KEY, link VARCHAR(128) NOT NULL); + +CREATE TABLE tgChats (id INTEGER PRIMARY KEY, tg_chat_id BIGINT NOT NULL); + +CREATE TABLE links_tgChats ( + LinkID INTEGER, + FOREIGN KEY (LinkID) REFERENCES links (id), + ChatID INTEGER, + FOREIGN KEY (ChatID) REFERENCES tgChats (id) +); \ 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..bbd37fb --- /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.example + bot + 1.0 + compile + + + 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 + + + \ 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..55f205a --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java @@ -0,0 +1,20 @@ +package ru.tinkoff.edu.java.scrapper; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import ru.tinkoff.edu.java.scrapper.client.ClientConfiguration; +import ru.tinkoff.edu.java.scrapper.configuration.ApplicationConfig; + +@SpringBootApplication +@EnableConfigurationProperties(ApplicationConfig.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("----------------------------------------------------------------"); + ClientConfiguration cls = ctx.getBean(ClientConfiguration.class); + System.out.println(cls.gitHubClient()); + System.out.println(cls.stackOverflowClient()); + } +} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/DB.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/DB.java new file mode 100644 index 0000000..392e291 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/DB.java @@ -0,0 +1,42 @@ +package ru.tinkoff.edu.java.scrapper.api; + +import java.util.ArrayList; + +public class DB { + + static ArrayList tgChatIDList = new ArrayList(); + + public static void addId(Long id) { + tgChatIDList.add(id); + } + + public static void rmId(Long id) { + tgChatIDList.remove(id); + } + + public static String getListParse() { + + int i = 1; + + String out_list = ""; + + for(Long element: tgChatIDList){ + + out_list += element; + + if(i != tgChatIDList.size()) { + out_list += ", "; + } + i++; + } + return out_list; + } + + public static boolean listIsEmpty() { + return tgChatIDList.isEmpty(); + } + + public static boolean linkContain(Long id) { + return tgChatIDList.contains(id); + } +} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java new file mode 100644 index 0000000..1b4c616 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java @@ -0,0 +1,36 @@ +package ru.tinkoff.edu.java.scrapper.api; + +import org.springframework.http.HttpStatus; +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.bot.firstBot.BotMain; +import ru.tinkoff.edu.java.bot.firstBot.DB; +import ru.tinkoff.edu.java.scrapper.api.model.*; + +@RequestMapping("/links") +@RestController +public class ScrapperControllerLink { + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping + public void linksDelete( + @RequestHeader("Tg-Chat-Id") Long tgChatId, + @RequestBody RemoveLinkRequest removeLinkRequest + ) { + BotMain.apiCommand(tgChatId, "/untrack" + " " + removeLinkRequest.link()); + } + + @GetMapping + public void linksGet(@RequestHeader("Tg-Chat-Id") Long tgChatId) { + BotMain.apiCommand(tgChatId, "/list"); + } + + @PostMapping + public void linksPost( + @RequestHeader("Tg-Chat-Id") Long tgChatId, + @RequestBody AddLinkRequest addLinkRequest + ) { + BotMain.apiCommand(tgChatId, "/track" + " " + addLinkRequest.link()); + } +} 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..5968bd0 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java @@ -0,0 +1,20 @@ +package ru.tinkoff.edu.java.scrapper.api; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("/tg-chat") +@RestController +public class ScrapperControllerTg { + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{id}") + public void tgChatIdDelete(@PathVariable Long id) { + DB.addId(id); + } + + @PostMapping("/{id}") + public void tgChatIdPost(@PathVariable Long id) { + DB.rmId(id); + } +} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/exceptionHandler/ScrapperExceptionHandler.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/exceptionHandler/ScrapperExceptionHandler.java new file mode 100644 index 0000000..f6f64c9 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/exceptionHandler/ScrapperExceptionHandler.java @@ -0,0 +1,50 @@ +package ru.tinkoff.edu.java.scrapper.api.exceptionHandler; + +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.api.model.ApiErrorResponse; + +@RestControllerAdvice +public class ScrapperExceptionHandler { + + 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("Метод не разрешен!"); + } + + @ExceptionHandler(MissingRequestHeaderException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public String MissingRequestHeader(MissingRequestHeaderException Exception) { + return getDescription("Нет свойства в Headers!"); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public String ArgumentTypeMismatch(MethodArgumentTypeMismatchException Exception) { + return getDescription("Неверный тип свойства в Headers!"); + } +} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/AddLinkRequest.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/AddLinkRequest.java new file mode 100644 index 0000000..7282291 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/AddLinkRequest.java @@ -0,0 +1,3 @@ +package ru.tinkoff.edu.java.scrapper.api.model; + +public record AddLinkRequest(String link) {} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ApiErrorResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ApiErrorResponse.java new file mode 100644 index 0000000..42e9be3 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ApiErrorResponse.java @@ -0,0 +1,11 @@ +package ru.tinkoff.edu.java.scrapper.api.model; + +import java.util.List; + +public record ApiErrorResponse( + String description, + String code, + String ExceptionName, + String exceptionMessage, + List stacktrace +) {} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/LinkResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/LinkResponse.java new file mode 100644 index 0000000..023172d --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/LinkResponse.java @@ -0,0 +1,3 @@ +package ru.tinkoff.edu.java.scrapper.api.model; + +public record LinkResponse(String url, int id) {} diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ListLinksResponse.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ListLinksResponse.java new file mode 100644 index 0000000..9c9ed5b --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ListLinksResponse.java @@ -0,0 +1,6 @@ +package ru.tinkoff.edu.java.scrapper.api.model; + +import java.util.*; + +public record ListLinksResponse(List links, int size) {} + diff --git a/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/RemoveLinkRequest.java b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/RemoveLinkRequest.java new file mode 100644 index 0000000..3e2a8f0 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/RemoveLinkRequest.java @@ -0,0 +1,3 @@ +package ru.tinkoff.edu.java.scrapper.api.model; + +public record RemoveLinkRequest(String link) {} 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..7bb87b7 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java @@ -0,0 +1,66 @@ +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; + } + + @Bean + public GitHubRecord gitHubClient() { + + if(Objects.equals(gitLink, "")) gitLink = BASE_GIT_URL; + + gitLink = pars.getLink(gitLink); + GitHubRecord gitHubResponse = builder.build() + .get() + .uri("https://api.github.com/repos/" + gitLink) + .retrieve() + .bodyToMono(GitHubRecord.class) + .block(); + return gitHubResponse; + } + + @Bean + public StackOverflowRecord stackOverflowClient() { + + + if(Objects.equals(stackLink, "")) stackLink = BASE_STACK_URL; + + String params = "?order=desc&sort=activity&site=stackoverflow"; + stackLink = pars.getLink(stackLink); + StackOverflowRecord stackOverflowResponse = builder.build() + .get() + .uri("https://api.stackexchange.com/2.3/questions/" + stackLink + params) + .retrieve() + .bodyToMono(StackOverflowRecord.class) + .block(); + return stackOverflowResponse; + } +} 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..3a82d60 --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java @@ -0,0 +1,15 @@ +package ru.tinkoff.edu.java.scrapper.configuration; + +import jakarta.validation.constraints.NotNull; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.validation.annotation.Validated; +import ru.tinkoff.edu.java.scrapper.schedule.Scheduler; + +@Validated +@ConfigurationProperties(prefix = "app", ignoreUnknownFields = false) +@EnableScheduling +public record ApplicationConfig( + @NotNull String test, + @NotNull Scheduler scheduler +) {} 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..37ca0de --- /dev/null +++ b/FP/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java @@ -0,0 +1,17 @@ +package ru.tinkoff.edu.java.scrapper.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 scrapper called"); + } +} 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..b9ad588 --- /dev/null +++ b/FP/scrapper/src/main/resources/application.properties @@ -0,0 +1,5 @@ +app.test=123 +springdoc.swagger-ui.path=/swagger-ui +app.scheduler.interval=5000 +git.link= +stack.link= diff --git a/FP/scrapper/src/main/resources/scrapperapi.yml b/FP/scrapper/src/main/resources/scrapperapi.yml new file mode 100644 index 0000000..c98cf7d --- /dev/null +++ b/FP/scrapper/src/main/resources/scrapperapi.yml @@ -0,0 +1,184 @@ +openapi: 3.0.1 +info: + title: Scrapper API + version: 1.0.0 + contact: + name: Alexander Biryukov + url: https://github.com +paths: + /tg-chat/{id}: + post: + summary: Зарегистрировать чат + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Чат зарегистрирован + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + delete: + summary: Удалить чат + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Чат успешно удалён + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: Чат не существует + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + /links: + get: + summary: Получить все отслеживаемые ссылки + parameters: + - name: Tg-Chat-Id + in: header + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Ссылки успешно получены + content: + application/json: + schema: + $ref: '#/components/schemas/ListLinksResponse' + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + post: + summary: Добавить отслеживание ссылки + parameters: + - name: Tg-Chat-Id + in: header + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AddLinkRequest' + required: true + responses: + '200': + description: Ссылка успешно добавлена + content: + application/json: + schema: + $ref: '#/components/schemas/LinkResponse' + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + delete: + summary: Убрать отслеживание ссылки + parameters: + - name: Tg-Chat-Id + in: header + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveLinkRequest' + required: true + responses: + '200': + description: Ссылка успешно убрана + content: + application/json: + schema: + $ref: '#/components/schemas/LinkResponse' + '400': + description: Некорректные параметры запроса + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' + '404': + description: Ссылка не найдена + content: + application/json: + schema: + $ref: '#/components/schemas/ApiErrorResponse' +components: + schemas: + LinkResponse: + type: object + properties: + id: + type: integer + format: int64 + url: + type: string + format: uri + ApiErrorResponse: + type: object + properties: + description: + type: string + code: + type: string + exceptionName: + type: string + exceptionMessage: + type: string + stacktrace: + type: array + items: + type: string + AddLinkRequest: + type: object + properties: + link: + type: string + format: uri + ListLinksResponse: + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/LinkResponse' + size: + type: integer + format: int32 + RemoveLinkRequest: + type: object + properties: + link: + type: string + format: uri 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..675b2c1 --- /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; + +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); + } + } +}