diff --git a/FP_re/bot/pom.xml b/FP_re/bot/pom.xml
new file mode 100644
index 0000000..ad944c9
--- /dev/null
+++ b/FP_re/bot/pom.xml
@@ -0,0 +1,65 @@
+
+
+
+ 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.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
+
+
+
\ No newline at end of file
diff --git a/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java
new file mode 100644
index 0000000..7e836be
--- /dev/null
+++ b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/BotApplication.java
@@ -0,0 +1,15 @@
+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;
+
+@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);
+ }
+}
diff --git a/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/BotController.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/BotController.java
new file mode 100644
index 0000000..25186cf
--- /dev/null
+++ b/FP_re/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_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/exceptionHandler/BotExceptionHandler.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/exceptionHandler/BotExceptionHandler.java
new file mode 100644
index 0000000..732a56e
--- /dev/null
+++ b/FP_re/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_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/ApiErrorResponse.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/ApiErrorResponse.java
new file mode 100644
index 0000000..f43001e
--- /dev/null
+++ b/FP_re/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_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/LinkUpdate.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/api/model/LinkUpdate.java
new file mode 100644
index 0000000..0f44607
--- /dev/null
+++ b/FP_re/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_re/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java
new file mode 100644
index 0000000..deb3dee
--- /dev/null
+++ b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/configuration/ApplicationConfig.java
@@ -0,0 +1,12 @@
+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.schedule.Scheduler;
+
+@Validated
+@EnableScheduling
+@ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
+public record ApplicationConfig(@NotNull String test, @NotNull Scheduler scheduler) {}
\ No newline at end of file
diff --git a/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/LinkUpdaterScheduler.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/LinkUpdaterScheduler.java
new file mode 100644
index 0000000..1b2ff0f
--- /dev/null
+++ b/FP_re/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_re/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/Scheduler.java b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/Scheduler.java
new file mode 100644
index 0000000..01d1766
--- /dev/null
+++ b/FP_re/bot/src/main/java/ru/tinkoff/edu/java/bot/schedule/Scheduler.java
@@ -0,0 +1,5 @@
+package ru.tinkoff.edu.java.bot.schedule;
+
+import java.time.Duration;
+
+public record Scheduler(Duration interval) {}
diff --git a/FP_re/bot/src/main/resources/application.properties b/FP_re/bot/src/main/resources/application.properties
new file mode 100644
index 0000000..bef1ef2
--- /dev/null
+++ b/FP_re/bot/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+app.test=123
+springdoc.swagger-ui.path=/swagger-ui
+app.scheduler.interval=5000
\ No newline at end of file
diff --git a/FP_re/bot/src/main/resources/botapi.yml b/FP_re/bot/src/main/resources/botapi.yml
new file mode 100644
index 0000000..489441e
--- /dev/null
+++ b/FP_re/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_re/link-parser/pom.xml b/FP_re/link-parser/pom.xml
new file mode 100644
index 0000000..1a9e456
--- /dev/null
+++ b/FP_re/link-parser/pom.xml
@@ -0,0 +1,14 @@
+
+
+
+ org.example
+ FP
+ 1.0
+
+
+ 4.0.0
+
+ link-parser
+
\ No newline at end of file
diff --git a/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/LinkParser.java b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/LinkParser.java
new file mode 100644
index 0000000..1f217ea
--- /dev/null
+++ b/FP_re/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_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/AbstractParser.java b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/AbstractParser.java
new file mode 100644
index 0000000..503e413
--- /dev/null
+++ b/FP_re/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_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java
new file mode 100644
index 0000000..c30cc33
--- /dev/null
+++ b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/GitParser.java
@@ -0,0 +1,18 @@
+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(!Objects.equals(parsed[2], "github.com")) return null;
+
+ if (parsed.length > 4) return parsed[3] + "/" + parsed[4];
+
+ return null;
+ }
+}
diff --git a/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/OtherParser.java b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/OtherParser.java
new file mode 100644
index 0000000..f3746fe
--- /dev/null
+++ b/FP_re/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_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java b/FP_re/link-parser/src/main/java/ru/tinkoff/edu/java/linkparser/absracts/StackParser.java
new file mode 100644
index 0000000..a73b23b
--- /dev/null
+++ b/FP_re/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_re/pom.xml b/FP_re/pom.xml
new file mode 100644
index 0000000..302e82c
--- /dev/null
+++ b/FP_re/pom.xml
@@ -0,0 +1,95 @@
+
+
+ 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
+
+
+
+
+
+
+ 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
+
+
+
+
\ No newline at end of file
diff --git a/FP_re/scrapper/pom.xml b/FP_re/scrapper/pom.xml
new file mode 100644
index 0000000..a373157
--- /dev/null
+++ b/FP_re/scrapper/pom.xml
@@ -0,0 +1,75 @@
+
+
+
+ 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
+
+
+
+
\ No newline at end of file
diff --git a/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java
new file mode 100644
index 0000000..4c7f816
--- /dev/null
+++ b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/ScrapperApplication.java
@@ -0,0 +1,29 @@
+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;
+import ru.tinkoff.edu.java.scrapper.linkHandler.ParamsConverter;
+
+import java.util.HashMap;
+
+@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.weatherClient());
+ System.out.println(cls.gitHubClient());
+ System.out.println(cls.stackOverflowClient());
+// HashMap trt = new HashMap();
+// trt.put("order", "desc");
+// trt.put("sort", "activity");
+// trt.put("site", "stackoverflow");
+// System.out.println(ParamsConverter.setParamsLink("https://api.stackexchange.com/2.3/questions/552659", trt));
+ }
+}
diff --git a/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java
new file mode 100644
index 0000000..b443033
--- /dev/null
+++ b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerLink.java
@@ -0,0 +1,32 @@
+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.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
+ ) {}
+
+ @GetMapping
+ public String linksGet(@RequestHeader("Tg-Chat-Id") Long tgChatId) {
+ return tgChatId + "";
+ }
+
+ @PostMapping
+ public String linksPost(
+ @RequestHeader("Tg-Chat-Id") Long tgChatId,
+ @RequestBody AddLinkRequest addLinkRequest
+ ) {
+ return addLinkRequest.link();
+ }
+}
diff --git a/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java
new file mode 100644
index 0000000..be74b4f
--- /dev/null
+++ b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/ScrapperControllerTg.java
@@ -0,0 +1,19 @@
+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) {}
+
+ @PostMapping("/{id}")
+ public String tgChatIdPost(@PathVariable Long id) {
+ if(id < 0) return "Аргумент отрицательный";
+ return id + "";
+ }
+}
diff --git a/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/exceptionHandler/ScrapperExceptionHandler.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/exceptionHandler/ScrapperExceptionHandler.java
new file mode 100644
index 0000000..f6f64c9
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/AddLinkRequest.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/AddLinkRequest.java
new file mode 100644
index 0000000..7282291
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ApiErrorResponse.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ApiErrorResponse.java
new file mode 100644
index 0000000..42e9be3
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/LinkResponse.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/LinkResponse.java
new file mode 100644
index 0000000..023172d
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ListLinksResponse.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/ListLinksResponse.java
new file mode 100644
index 0000000..9c9ed5b
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/RemoveLinkRequest.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/api/model/RemoveLinkRequest.java
new file mode 100644
index 0000000..3e2a8f0
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/ClientConfiguration.java
new file mode 100644
index 0000000..7bb87b7
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/GitHubRecord.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/GitHubRecord.java
new file mode 100644
index 0000000..97ef980
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/StackOverflowRecord.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/StackOverflowRecord.java
new file mode 100644
index 0000000..6013400
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/WeatherRecord.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/client/WeatherRecord.java
new file mode 100644
index 0000000..5ee9adc
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/configuration/ApplicationConfig.java
new file mode 100644
index 0000000..3a82d60
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/LinkUpdaterScheduler.java
new file mode 100644
index 0000000..37ca0de
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/Scheduler.java b/FP_re/scrapper/src/main/java/ru/tinkoff/edu/java/scrapper/schedule/Scheduler.java
new file mode 100644
index 0000000..d060c22
--- /dev/null
+++ b/FP_re/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_re/scrapper/src/main/resources/application.properties b/FP_re/scrapper/src/main/resources/application.properties
new file mode 100644
index 0000000..a476623
--- /dev/null
+++ b/FP_re/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=
\ No newline at end of file
diff --git a/FP_re/scrapper/src/main/resources/scrapperapi.yml b/FP_re/scrapper/src/main/resources/scrapperapi.yml
new file mode 100644
index 0000000..c98cf7d
--- /dev/null
+++ b/FP_re/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