From 4a3adfa7b6503226c4d5bec50808b71fb59bf166 Mon Sep 17 00:00:00 2001 From: Nastasiya Trusova Date: Fri, 20 Oct 2023 21:31:15 +0300 Subject: [PATCH] add new check --- app/build.gradle.kts | 4 +- app/src/main/java/hexlet/code/App.java | 2 + .../code/controller/UrlChecksController.java | 56 +++++++++++ .../code/controller/UrlsController.java | 46 +++++---- .../java/hexlet/code/dto/urls/UrlPage.java | 4 + .../java/hexlet/code/dto/urls/UrlsPage.java | 3 + app/src/main/java/hexlet/code/model/Url.java | 8 +- .../main/java/hexlet/code/model/UrlCheck.java | 30 ++++++ .../code/repository/UrlCheckRepository.java | 97 +++++++++++++++++++ .../hexlet/code/repository/UrlRepository.java | 3 + .../java/hexlet/code/util/NamedRoutes.java | 8 ++ app/src/main/java/hexlet/code/util/Utils.java | 29 +++++- app/src/main/resources/schema.sql | 11 +++ .../main/resources/templates/layout/page.jte | 2 +- .../main/resources/templates/urls/build.jte | 2 +- .../main/resources/templates/urls/index.jte | 86 +++++++++------- .../main/resources/templates/urls/show.jte | 48 ++++++++- app/src/test/java/hexlet/code/AppTest.java | 37 ++++++- 18 files changed, 398 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/hexlet/code/controller/UrlChecksController.java create mode 100644 app/src/main/java/hexlet/code/model/UrlCheck.java create mode 100644 app/src/main/java/hexlet/code/repository/UrlCheckRepository.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ee437ff..499280a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,14 +37,14 @@ dependencies { implementation("io.javalin:javalin-bundle:5.6.1") implementation("io.javalin:javalin-rendering:5.6.0") implementation("gg.jte:jte:3.0.1") + implementation("com.konghq:unirest-java:4.0.0-RC2") implementation("org.apache.commons:commons-lang3:3.12.0") - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.1") - implementation("org.mockito:mockito-core:5.4.0") testImplementation("org.assertj:assertj-core:3.24.2") testImplementation(platform("org.junit:junit-bom:5.9.2")) testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + testImplementation("com.squareup.okhttp3:mockwebserver:4.11.0") } tasks.test { diff --git a/app/src/main/java/hexlet/code/App.java b/app/src/main/java/hexlet/code/App.java index 394da4b..02cffba 100644 --- a/app/src/main/java/hexlet/code/App.java +++ b/app/src/main/java/hexlet/code/App.java @@ -5,6 +5,7 @@ import gg.jte.ContentType; import gg.jte.TemplateEngine; import gg.jte.resolve.ResourceCodeResolver; +import hexlet.code.controller.UrlChecksController; import hexlet.code.controller.UrlsController; import hexlet.code.repository.BaseRepository; import hexlet.code.util.NamedRoutes; @@ -82,6 +83,7 @@ public static Javalin getApp() throws IOException, SQLException { app.post(NamedRoutes.rootPath(), UrlsController::create); app.get(NamedRoutes.urlsPath(), UrlsController::index); app.get(NamedRoutes.urlPath("{id}"), UrlsController::show); + app.post(NamedRoutes.urlCheckPath("{id}"), UrlChecksController::check); return app; } diff --git a/app/src/main/java/hexlet/code/controller/UrlChecksController.java b/app/src/main/java/hexlet/code/controller/UrlChecksController.java new file mode 100644 index 0000000..8b1cab7 --- /dev/null +++ b/app/src/main/java/hexlet/code/controller/UrlChecksController.java @@ -0,0 +1,56 @@ +package hexlet.code.controller; + +import hexlet.code.model.UrlCheck; +import hexlet.code.repository.UrlCheckRepository; +import hexlet.code.repository.UrlRepository; +import hexlet.code.util.NamedRoutes; +import io.javalin.http.Context; +import io.javalin.http.NotFoundResponse; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UrlChecksController { + public static void check(Context ctx) throws SQLException { + var urlId = ctx.pathParamAsClass("id", Long.class).get(); + + var url = UrlRepository.find(urlId) + .orElseThrow(() -> new NotFoundResponse("Запись с таким ID не найдена")); + var name = url.getName(); + + try { + HttpResponse response = Unirest.get(name).asString(); + String responseBody = response.getBody(); + + int statusCode = response.getStatus(); + + Pattern patternH1 = Pattern.compile("

([^<]*)

", Pattern.CASE_INSENSITIVE); + Matcher matcherH1 = patternH1.matcher(responseBody); + String h1 = matcherH1.find() ? matcherH1.group(1) : ""; + + Pattern patternTitle = Pattern.compile("([^<]*)", Pattern.CASE_INSENSITIVE); + Matcher matcherTitle = patternTitle.matcher(responseBody); + String title = matcherTitle.find() ? matcherTitle.group(1) : ""; + + Pattern patternDescription = Pattern.compile("", + Pattern.CASE_INSENSITIVE); + Matcher matcherDescription = patternDescription.matcher(responseBody); + String description = matcherDescription.find() ? matcherDescription.group(1) : ""; + + var createdAt = new Timestamp(System.currentTimeMillis()); + + var urlCheck = new UrlCheck(statusCode, h1, title, description, createdAt); + urlCheck.setUrlId(urlId); + UrlCheckRepository.save(urlCheck); + + } catch (Exception e) { + throw new RuntimeException(e); + } + + ctx.redirect(NamedRoutes.urlPath(urlId)); + } +} diff --git a/app/src/main/java/hexlet/code/controller/UrlsController.java b/app/src/main/java/hexlet/code/controller/UrlsController.java index dd40b29..a383346 100644 --- a/app/src/main/java/hexlet/code/controller/UrlsController.java +++ b/app/src/main/java/hexlet/code/controller/UrlsController.java @@ -4,6 +4,8 @@ import hexlet.code.dto.urls.UrlPage; import hexlet.code.dto.urls.UrlsPage; import hexlet.code.model.Url; +import hexlet.code.model.UrlCheck; +import hexlet.code.repository.UrlCheckRepository; import hexlet.code.repository.UrlRepository; import hexlet.code.util.NamedRoutes; import hexlet.code.util.Utils; @@ -14,7 +16,10 @@ import java.net.URL; import java.sql.SQLException; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Map; public class UrlsController { @@ -46,28 +51,27 @@ public static void create(Context ctx) throws SQLException { public static void index(Context ctx) throws SQLException { var urls = UrlRepository.getEntities(); + final int itemsPerPage = 10; + int pageCount = (urls.size() % itemsPerPage == 0) + ? (urls.size() / itemsPerPage) : (urls.size() / itemsPerPage + 1); + int pageNumber = ctx.queryParamAsClass("page", Integer.class).getOrDefault(pageCount); + List urlsPerPage = new ArrayList<>(); - if (!urls.isEmpty()) { - final int itemsPerPage = 10; - int pageCount = (urls.size() % itemsPerPage == 0) - ? (urls.size() / itemsPerPage) : (urls.size() / itemsPerPage + 1); - int pageNumber = ctx.queryParamAsClass("page", Integer.class).getOrDefault(pageCount); - - if (pageNumber <= pageCount) { - var urlsPerPage = Utils.getItemsPerPage(urls, pageNumber, itemsPerPage); - var page1 = new UrlsPage(urlsPerPage, pageNumber); - String message = ctx.consumeSessionAttribute("message"); - page1.setMessage(message); - ctx.render("urls/index.jte", Collections.singletonMap("page", page1)); - } else { - throw new NotFoundResponse("Страница не найдена"); - } + if (urls.isEmpty()) { + var pageEmpty = new UrlsPage(urlsPerPage); + String message = ctx.consumeSessionAttribute("message"); + pageEmpty.setMessage(message); + ctx.render("urls/index.jte", Collections.singletonMap("page", pageEmpty)); + } else if (pageNumber <= pageCount) { + urlsPerPage = Utils.getItemsPerPage(urls, pageNumber, itemsPerPage); + Map> checks = Utils.getItemsPerPageWithChecks(urlsPerPage); - } else { - var page2 = new UrlsPage(urls); + var page = new UrlsPage(urlsPerPage, checks, pageNumber); String message = ctx.consumeSessionAttribute("message"); - page2.setMessage(message); - ctx.render("urls/index.jte", Collections.singletonMap("page", page2)); + page.setMessage(message); + ctx.render("urls/index.jte", Collections.singletonMap("page", page)); + } else { + throw new NotFoundResponse("Страница не найдена"); } } @@ -75,7 +79,9 @@ public static void show(Context ctx) throws SQLException { var id = ctx.pathParamAsClass("id", Long.class).get(); var url = UrlRepository.find(id) .orElseThrow(() -> new NotFoundResponse("Запись с таким ID не найдена")); - var page = new UrlPage(url); + var checks = UrlCheckRepository.find(id) + .orElse(null); + var page = new UrlPage(url, checks); ctx.render("urls/show.jte", Collections.singletonMap("page", page)); } } diff --git a/app/src/main/java/hexlet/code/dto/urls/UrlPage.java b/app/src/main/java/hexlet/code/dto/urls/UrlPage.java index dcbd009..74405f7 100644 --- a/app/src/main/java/hexlet/code/dto/urls/UrlPage.java +++ b/app/src/main/java/hexlet/code/dto/urls/UrlPage.java @@ -1,11 +1,15 @@ package hexlet.code.dto.urls; import hexlet.code.model.Url; +import hexlet.code.model.UrlCheck; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.List; + @AllArgsConstructor @Getter public class UrlPage { private Url url; + private List checks; } diff --git a/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java b/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java index 3a32898..ec9bb3a 100644 --- a/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java +++ b/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java @@ -2,15 +2,18 @@ import hexlet.code.dto.BasePage; import hexlet.code.model.Url; +import hexlet.code.model.UrlCheck; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; +import java.util.Map; @AllArgsConstructor @Getter public class UrlsPage extends BasePage { private List urls; + private Map> checks; private int pageNumber; public UrlsPage(List urls) { diff --git a/app/src/main/java/hexlet/code/model/Url.java b/app/src/main/java/hexlet/code/model/Url.java index 2e2e5e1..80bd6bd 100644 --- a/app/src/main/java/hexlet/code/model/Url.java +++ b/app/src/main/java/hexlet/code/model/Url.java @@ -1,11 +1,13 @@ package hexlet.code.model; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.sql.Timestamp; +@AllArgsConstructor @NoArgsConstructor @Getter @Setter @@ -18,10 +20,4 @@ public Url(String name, Timestamp createdAt) { this.name = name; this.createdAt = createdAt; } - - public Url(Long id, String name, Timestamp createdAt) { - this.name = name; - this.createdAt = createdAt; - this.id = id; - } } diff --git a/app/src/main/java/hexlet/code/model/UrlCheck.java b/app/src/main/java/hexlet/code/model/UrlCheck.java new file mode 100644 index 0000000..9fe2448 --- /dev/null +++ b/app/src/main/java/hexlet/code/model/UrlCheck.java @@ -0,0 +1,30 @@ +package hexlet.code.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.sql.Timestamp; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class UrlCheck { + private Long id; + private Long urlId; + private int statusCode; + private String h1; + private String title; + private String description; + private Timestamp createdAt; + + public UrlCheck(int statusCode, String h1, String title, String description, Timestamp createdAt) { + this.statusCode = statusCode; + this.h1 = h1; + this.title = title; + this.description = description; + this.createdAt = createdAt; + } +} diff --git a/app/src/main/java/hexlet/code/repository/UrlCheckRepository.java b/app/src/main/java/hexlet/code/repository/UrlCheckRepository.java new file mode 100644 index 0000000..2a2144d --- /dev/null +++ b/app/src/main/java/hexlet/code/repository/UrlCheckRepository.java @@ -0,0 +1,97 @@ +package hexlet.code.repository; + +import hexlet.code.model.UrlCheck; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +public class UrlCheckRepository extends BaseRepository { + + public static void save(UrlCheck urlCheck) throws SQLException { + var sql = "INSERT INTO url_checks (url_id, status_code, h1, title, description, created_at) " + + "VALUES (?, ?, ?, ?, ?, ?)"; + try (var conn = dataSource.getConnection(); + var preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + preparedStatement.setLong(1, urlCheck.getUrlId()); + preparedStatement.setInt(2, urlCheck.getStatusCode()); + preparedStatement.setString(3, urlCheck.getH1()); + preparedStatement.setString(4, urlCheck.getTitle()); + preparedStatement.setString(5, urlCheck.getDescription()); + preparedStatement.setTimestamp(6, urlCheck.getCreatedAt()); + preparedStatement.executeUpdate(); + + var generatedKeys = preparedStatement.getGeneratedKeys(); + if (generatedKeys.next()) { + urlCheck.setId(generatedKeys.getLong(1)); + } else { + throw new SQLException("Не сформирован ID"); + } + } + } + + public static Optional> find(Long urlId) throws SQLException { + var sql = "SELECT * FROM url_checks WHERE url_id = ?"; + try (var conn = dataSource.getConnection(); + var stmt = conn.prepareStatement(sql)) { + + stmt.setLong(1, urlId); + var resultSet = stmt.executeQuery(); + + var results = new ArrayList(); + var currentListId = 1L; + + while (resultSet.next()) { + var statusCode = resultSet.getInt("status_code"); + var h1 = resultSet.getString("h1"); + var title = resultSet.getString("title"); + var description = resultSet.getString("description"); + var createdAt = resultSet.getTimestamp("created_at"); + var urlCheck = new UrlCheck(statusCode, h1, title, description, createdAt); + urlCheck.setUrlId(urlId); + urlCheck.setId(currentListId); + + currentListId++; + + results.add(urlCheck); + } + + if (!results.isEmpty()) { + return Optional.of(results); + } else { + return Optional.empty(); + } + } + } + + public static List getEntities() throws SQLException { + var sql = "SELECT * FROM url_checks"; + try (var conn = dataSource.getConnection(); + var stmt = conn.prepareStatement(sql)) { + + var resultSet = stmt.executeQuery(); + var result = new ArrayList(); + + while (resultSet.next()) { + var id = resultSet.getLong("id"); + var urlId = resultSet.getLong("url_id"); + var statusCode = resultSet.getInt("status_code"); + var h1 = resultSet.getString("h1"); + var title = resultSet.getString("title"); + var description = resultSet.getString("description"); + var createdAt = resultSet.getTimestamp("created_at"); + var urlCheck = new UrlCheck(statusCode, h1, title, description, createdAt); + urlCheck.setId(id); + urlCheck.setUrlId(urlId); + result.add(urlCheck); + } + result.sort(Comparator.comparing(UrlCheck::getId)); + + return result; + } + } +} diff --git a/app/src/main/java/hexlet/code/repository/UrlRepository.java b/app/src/main/java/hexlet/code/repository/UrlRepository.java index b8b28c0..8f2ec90 100644 --- a/app/src/main/java/hexlet/code/repository/UrlRepository.java +++ b/app/src/main/java/hexlet/code/repository/UrlRepository.java @@ -5,6 +5,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -73,6 +74,8 @@ public static List getEntities() throws SQLException { url.setId(id); result.add(url); } + result.sort(Comparator.comparing(Url::getId)); + return result; } } diff --git a/app/src/main/java/hexlet/code/util/NamedRoutes.java b/app/src/main/java/hexlet/code/util/NamedRoutes.java index 77dbd75..2b4bdde 100644 --- a/app/src/main/java/hexlet/code/util/NamedRoutes.java +++ b/app/src/main/java/hexlet/code/util/NamedRoutes.java @@ -21,4 +21,12 @@ public static String urlsPath() { public static String urlsPath(int page) { return "/urls?page=" + page; } + + public static String urlCheckPath(Long id) { + return urlCheckPath(String.valueOf(id)); + } + + public static String urlCheckPath(String id) { + return "/urls/" + id + "/checks"; + } } diff --git a/app/src/main/java/hexlet/code/util/Utils.java b/app/src/main/java/hexlet/code/util/Utils.java index 37a3e68..758aa3e 100644 --- a/app/src/main/java/hexlet/code/util/Utils.java +++ b/app/src/main/java/hexlet/code/util/Utils.java @@ -1,21 +1,42 @@ package hexlet.code.util; import hexlet.code.model.Url; +import hexlet.code.model.UrlCheck; +import hexlet.code.repository.UrlCheckRepository; import java.sql.SQLException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public final class Utils { - public static List getItemsPerPage(List urls, int pageNumber, int itemsPerPage) throws SQLException { + + public static List getItemsPerPage(List urls, int pageNumber, int itemsPerPage) { + var start = itemsPerPage * (pageNumber - 1); var end = Math.min(urls.size() - 1, itemsPerPage * pageNumber - 1); - var newUrls = new ArrayList(); + var results = new ArrayList(); for (int i = start; i <= end; i++) { - newUrls.add(urls.get(i)); + results.add(urls.get(i)); + } + + return results; + } + + public static Map> getItemsPerPageWithChecks(List urls) + throws SQLException { + + var results = new HashMap>(); + + for (Url item : urls) { + var urlId = item.getId(); + var checks = UrlCheckRepository.find(urlId) + .orElse(null); + results.put(item, checks); } - return newUrls; + return results; } } diff --git a/app/src/main/resources/schema.sql b/app/src/main/resources/schema.sql index 2c9ee9c..15dc2eb 100644 --- a/app/src/main/resources/schema.sql +++ b/app/src/main/resources/schema.sql @@ -1,3 +1,4 @@ +DROP TABLE IF EXISTS url_checks; DROP TABLE IF EXISTS urls; CREATE TABLE urls ( @@ -5,3 +6,13 @@ CREATE TABLE urls ( name VARCHAR(255) NOT NULL, created_at TIMESTAMP ); + +CREATE TABLE url_checks ( + id SERIAL PRIMARY KEY, + url_id INTEGER REFERENCES urls(id), + status_code INTEGER, + h1 VARCHAR(255) NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP +) \ No newline at end of file diff --git a/app/src/main/resources/templates/layout/page.jte b/app/src/main/resources/templates/layout/page.jte index b8e50ad..8ccdce7 100644 --- a/app/src/main/resources/templates/layout/page.jte +++ b/app/src/main/resources/templates/layout/page.jte @@ -45,7 +45,7 @@
${content}
-