diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 54deed8..292e171 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,12 +44,19 @@ dependencies { implementation("com.h2database:h2:2.3.232") implementation("com.zaxxer:HikariCP:6.3.0") + // Unirest Java + implementation("com.konghq:unirest-java:3.14.5") + // Tests testImplementation("org.junit.jupiter:junit-jupiter:6.0.0-M1") testImplementation(platform("org.junit:junit-bom:6.0.0-M1")) testImplementation("io.javalin:javalin-testtools:6.7.0") testImplementation("org.assertj:assertj-core:4.0.0-M1") testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.0-M1") + + // MockWebServer » 5.1.0 +// testImplementation("com.squareup.okhttp3:mockwebserver:5.1.0") + testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") } checkstyle { diff --git a/app/src/main/java/hexlet/code/App.java b/app/src/main/java/hexlet/code/App.java index 95cc815..fb9d8b0 100644 --- a/app/src/main/java/hexlet/code/App.java +++ b/app/src/main/java/hexlet/code/App.java @@ -1,5 +1,6 @@ package hexlet.code; +import hexlet.code.controller.UrlCheckController; import hexlet.code.repository.BaseRepository; import hexlet.code.controller.UrlController; import hexlet.code.util.NamedRoutes; @@ -64,7 +65,7 @@ public static Javalin getApp() throws IOException, SQLException { // Statement statement = connection.createStatement()) { statement.execute(sql); } - BaseRepository.dataSource = dataSource; + BaseRepository.setDataSource(dataSource); Javalin app = Javalin.create(config -> { config.bundledPlugins.enableDevLogging(); @@ -78,6 +79,8 @@ public static Javalin getApp() throws IOException, SQLException { // app.get(NamedRoutes.buildPath(), UrlController::build); app.get(NamedRoutes.urlsPath(), UrlController::index); app.get(NamedRoutes.urlPath("{id}"), UrlController::show); + // checks + app.post(NamedRoutes.urlPathForChecks("{id}"), UrlCheckController::check); return app; } diff --git a/app/src/main/java/hexlet/code/controller/UrlCheckController.java b/app/src/main/java/hexlet/code/controller/UrlCheckController.java new file mode 100644 index 0000000..d7b7fa1 --- /dev/null +++ b/app/src/main/java/hexlet/code/controller/UrlCheckController.java @@ -0,0 +1,61 @@ +package hexlet.code.controller; + +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 static hexlet.code.util.AppSettings.FLASH_TYPE; +import static hexlet.code.util.AppSettings.FLASH_DANGER; +import static hexlet.code.util.AppSettings.FLASH_SUCCESS; + +import static hexlet.code.util.AppSettings.FLASH; +import static hexlet.code.util.AppSettings.CHECK_ERROR; +import static hexlet.code.util.AppSettings.PAGE_OK; +import static hexlet.code.util.AppSettings.URL_BAD; + +import io.javalin.http.Context; +import io.javalin.http.NotFoundResponse; + +import java.sql.SQLException; + +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import kong.unirest.UnirestException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class UrlCheckController { + private UrlCheckController() { + // Prevent instantiation - Sonar Warning + } + + public static void check(Context ctx) throws SQLException { + Long urlId = ctx.pathParamAsClass("id", Long.class).get(); + Url url = UrlRepository.find(urlId) + .orElseThrow(() -> new NotFoundResponse("Entity with id = " + urlId + " not found")); + log.info("Получен ID: {}", urlId); + try { + HttpResponse response = Unirest.get(url.getName()).asString(); + int statusCode = response.getStatus(); + + UrlCheck urlCheck = new UrlCheck(urlId, statusCode, "title", "h1", "descrip"); + UrlCheckRepository.save(urlCheck); + + log.info("check saved"); + } catch (UnirestException e) { + ctx.sessionAttribute(FLASH, URL_BAD); + ctx.sessionAttribute(FLASH_TYPE, FLASH_DANGER); + ctx.redirect(NamedRoutes.urlPath(urlId)); + + } catch (Exception e) { + ctx.sessionAttribute(FLASH, CHECK_ERROR + e.getMessage()); + ctx.sessionAttribute(FLASH_TYPE, FLASH_DANGER); + ctx.redirect(NamedRoutes.urlPath(urlId)); + } + ctx.sessionAttribute(FLASH, PAGE_OK); + ctx.sessionAttribute(FLASH_TYPE, FLASH_SUCCESS); + ctx.redirect(NamedRoutes.urlPath(urlId)); + } +} diff --git a/app/src/main/java/hexlet/code/controller/UrlController.java b/app/src/main/java/hexlet/code/controller/UrlController.java index f89f9e9..59100b8 100644 --- a/app/src/main/java/hexlet/code/controller/UrlController.java +++ b/app/src/main/java/hexlet/code/controller/UrlController.java @@ -4,9 +4,22 @@ 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 static hexlet.code.util.AppSettings.FLASH; +import static hexlet.code.util.AppSettings.FLASH_DANGER; +import static hexlet.code.util.AppSettings.FLASH_INFO; +import static hexlet.code.util.AppSettings.FLASH_SUCCESS; +import static hexlet.code.util.AppSettings.FLASH_TYPE; +import static hexlet.code.util.AppSettings.FLASH_WARNING; +import static hexlet.code.util.AppSettings.PAGE_ADDED; +import static hexlet.code.util.AppSettings.PAGE_EXIST; +import static hexlet.code.util.AppSettings.URL_BAD; +import static hexlet.code.util.AppSettings.URL_EMPTY; + import io.javalin.http.Context; import io.javalin.http.NotFoundResponse; @@ -20,34 +33,40 @@ import java.sql.SQLException; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.utils.URIBuilder; @Slf4j -public class UrlController { +public final class UrlController { + private UrlController() { + // Sonar warning + // Prevent instantiation + } + public static void build(Context ctx) { BasePage page = new BasePage(); - page.setFlash(ctx.consumeSessionAttribute("flash")); - page.setFlashType(ctx.consumeSessionAttribute("flash-type")); + page.setFlash(ctx.consumeSessionAttribute(FLASH)); + page.setFlashType(ctx.consumeSessionAttribute(FLASH_TYPE)); ctx.render("urls/build.jte", model("page", page)); } public static void create(Context ctx) throws SQLException, URISyntaxException, MalformedURLException { String name = ctx.formParamAsClass("url", String.class).get(); - URL unifmResourceId = null; + URL unifmResourceId; try { unifmResourceId = new URI(name).toURL(); } catch (Exception e) { - ctx.sessionAttribute("flash", "Некорректный URL"); - ctx.sessionAttribute("flash-type", "danger"); + ctx.sessionAttribute(FLASH, URL_BAD); + ctx.sessionAttribute(FLASH_TYPE, FLASH_DANGER); ctx.redirect(NamedRoutes.buildPath()); return; } - if (name == null || name.isEmpty()) { - ctx.sessionAttribute("flash", "Поле URL не должно быть пустым"); - ctx.sessionAttribute("flash-type", "warning"); + if (name.isEmpty()) { + ctx.sessionAttribute(FLASH, URL_EMPTY); + ctx.sessionAttribute(FLASH_TYPE, FLASH_WARNING); ctx.redirect(NamedRoutes.buildPath()); return; } @@ -62,22 +81,24 @@ public static void create(Context ctx) throws SQLException, URISyntaxException, Url newUrl = new Url(String.valueOf(url), LocalDateTime.now()); UrlRepository.save(newUrl); } else { - ctx.sessionAttribute("flash", "Страница уже существует"); - ctx.sessionAttribute("flash-type", "info"); + ctx.sessionAttribute(FLASH, PAGE_EXIST); + ctx.sessionAttribute(FLASH_TYPE, FLASH_INFO); ctx.redirect(NamedRoutes.urlsPath()); return; } - ctx.sessionAttribute("flash", "Страница успешно добавлена"); - ctx.sessionAttribute("flash-type", "success"); + ctx.sessionAttribute(FLASH, PAGE_ADDED); + ctx.sessionAttribute(FLASH_TYPE, FLASH_SUCCESS); ctx.redirect(NamedRoutes.urlsPath()); } public static void index(Context ctx) throws SQLException { List urls = UrlRepository.getEntities(); - UrlsPage page = new UrlsPage(urls); - page.setFlash(ctx.consumeSessionAttribute("flash")); - page.setFlashType(ctx.consumeSessionAttribute("flash-type")); + Map latestChecks = UrlCheckRepository.getLastestChecks(); + + UrlsPage page = new UrlsPage(urls, latestChecks); + page.setFlash(ctx.consumeSessionAttribute(FLASH)); + page.setFlashType(ctx.consumeSessionAttribute(FLASH_TYPE)); ctx.render("urls/indexList.jte", model("page", page)); } @@ -85,9 +106,13 @@ public static void show(Context ctx) throws SQLException { Long id = ctx.pathParamAsClass("id", Long.class).get(); Url url = UrlRepository.find(id) .orElseThrow(() -> new NotFoundResponse("Entity with id = " + id + " not found")); + List urlCheckList = UrlCheckRepository.findById(id); + if (!urlCheckList.isEmpty()) { + url.setUrlCheckList(urlCheckList); + } UrlPage page = new UrlPage(url); - page.setFlash(ctx.consumeSessionAttribute("flash")); - page.setFlashType(ctx.consumeSessionAttribute("flash-type")); + page.setFlash(ctx.consumeSessionAttribute(FLASH)); + page.setFlashType(ctx.consumeSessionAttribute(FLASH_TYPE)); ctx.render("urls/show.jte", model("page", page)); } } 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 4dd77f2..025a554 100644 --- a/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java +++ b/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java @@ -4,7 +4,9 @@ import hexlet.code.model.Url; import java.util.List; +import java.util.Map; +import hexlet.code.model.UrlCheck; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,4 +14,5 @@ @Getter public class UrlsPage extends BasePage { private List urls; + private Map latestChecks; } diff --git a/app/src/main/java/hexlet/code/model/Url.java b/app/src/main/java/hexlet/code/model/Url.java index c32515c..9bdca59 100644 --- a/app/src/main/java/hexlet/code/model/Url.java +++ b/app/src/main/java/hexlet/code/model/Url.java @@ -5,6 +5,8 @@ import lombok.ToString; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Getter @Setter @@ -13,9 +15,11 @@ public class Url { private Long id; private String name; private LocalDateTime createdAt; + private List urlCheckList; public Url(String name, LocalDateTime createdAt) { this.name = name; this.createdAt = createdAt; + this.urlCheckList = new ArrayList<>(); } } 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..676a35b --- /dev/null +++ b/app/src/main/java/hexlet/code/model/UrlCheck.java @@ -0,0 +1,25 @@ +package hexlet.code.model; + +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UrlCheck { + private Long id; + private Long urlId; + private int statusCode; + private String title; + private String h1; + private String description; + private LocalDateTime createdAt; + + public UrlCheck(Long urlId, int statusCode, String title, String h1, String description) { + this.urlId = urlId; + this.title = title; + this.h1 = h1; + this.description = description; + this.statusCode = statusCode; + } +} diff --git a/app/src/main/java/hexlet/code/repository/BaseRepository.java b/app/src/main/java/hexlet/code/repository/BaseRepository.java index 9ecf5f2..a13078b 100644 --- a/app/src/main/java/hexlet/code/repository/BaseRepository.java +++ b/app/src/main/java/hexlet/code/repository/BaseRepository.java @@ -3,5 +3,13 @@ import com.zaxxer.hikari.HikariDataSource; public class BaseRepository { - public static HikariDataSource dataSource; + private static HikariDataSource dataSource; + + public static HikariDataSource getDataSource() { + return dataSource; + } + + public static void setDataSource(HikariDataSource dataSource) { + BaseRepository.dataSource = dataSource; + } } 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..123d510 --- /dev/null +++ b/app/src/main/java/hexlet/code/repository/UrlCheckRepository.java @@ -0,0 +1,99 @@ +package hexlet.code.repository; + +import hexlet.code.model.UrlCheck; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public final class UrlCheckRepository { + private UrlCheckRepository() { + // for Sonar Warning + throw new IllegalStateException("Utility class"); + } + + public static void save(UrlCheck urlCheck) throws SQLException { + String sql = "INSERT INTO url_checks (url_id, status_code, title, h1, description) VALUES (?, ?, ?, ?, ?)"; + try (Connection conn = BaseRepository.getDataSource().getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + preparedStatement.setLong(1, urlCheck.getUrlId()); + preparedStatement.setInt(2, urlCheck.getStatusCode()); + preparedStatement.setString(3, urlCheck.getTitle()); + preparedStatement.setString(4, urlCheck.getH1()); + preparedStatement.setString(5, urlCheck.getDescription()); + + preparedStatement.executeUpdate(); + var generatedKeys = preparedStatement.getGeneratedKeys(); + if (generatedKeys.next()) { + urlCheck.setId(generatedKeys.getLong(1)); + LocalDateTime createdAt = LocalDateTime.now(); + urlCheck.setCreatedAt(createdAt); + } else { + throw new SQLException("DB have not returned an id after saving an entity"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static List findById(Long urlId) throws SQLException { + List result = new ArrayList<>(); +// String sql = "SELECT * FROM url_checks WHERE url_id = ?" + // Sonar warning " * " + String sql = "SELECT id, url_id, status_code, title, h1, description, created_at" + + " FROM url_checks WHERE url_id = ?"; + try (Connection conn = BaseRepository.getDataSource().getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setLong(1, urlId); + ResultSet resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + Long id = resultSet.getLong("id"); + int statusCode = resultSet.getInt("status_code"); + String title = resultSet.getString("title"); + String h1 = resultSet.getString("h1"); + String description = resultSet.getString("description"); + LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + UrlCheck urlCheck = new UrlCheck(urlId, statusCode, title, h1, description); + urlCheck.setId(id); + urlCheck.setCreatedAt(createdAt); + result.add(urlCheck); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return result; + } + + public static Map getLastestChecks() throws SQLException { + Map result = new HashMap<>(); + String sql = "SELECT DISTINCT ON (url_id) * from url_checks order by url_id DESC, id DESC"; + try (Connection conn = BaseRepository.getDataSource().getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + ResultSet resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + Long id = resultSet.getLong("id"); + Long urlId = resultSet.getLong("url_id"); + int statusCode = resultSet.getInt("status_code"); + String title = resultSet.getString("title"); + String h1 = resultSet.getString("h1"); + String description = resultSet.getString("description"); + LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + UrlCheck urlCheck = new UrlCheck(urlId, statusCode, title, h1, description); + urlCheck.setId(id); + urlCheck.setCreatedAt(createdAt); + result.put(urlId, urlCheck); + } + } catch (SQLException e) { + e.printStackTrace(); + } + 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 51255f2..f28a461 100644 --- a/app/src/main/java/hexlet/code/repository/UrlRepository.java +++ b/app/src/main/java/hexlet/code/repository/UrlRepository.java @@ -16,10 +16,12 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class UrlRepository extends BaseRepository { +public final class UrlRepository extends BaseRepository { + public static final String FIELD_CREATED_AT = "created_at"; + public static void save(Url url) throws SQLException { String sql = "INSERT INTO urls (name, created_at) VALUES (?, ?)"; - try (Connection conn = dataSource.getConnection(); + try (Connection conn = BaseRepository.getDataSource().getConnection(); PreparedStatement preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { preparedStatement.setString(1, url.getName()); LocalDateTime createdAt = LocalDateTime.now(); @@ -36,14 +38,14 @@ public static void save(Url url) throws SQLException { } public static Optional find(Long id) throws SQLException { - String sql = "SELECT * FROM urls WHERE id = ?"; - try (Connection conn = dataSource.getConnection(); + String sql = "SELECT id, name, created_at FROM urls WHERE id = ?"; + try (Connection conn = BaseRepository.getDataSource().getConnection(); PreparedStatement preparedStatement = conn.prepareStatement(sql)) { preparedStatement.setLong(1, id); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { String name = resultSet.getString("name"); - LocalDateTime createAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime createAt = resultSet.getTimestamp(FIELD_CREATED_AT).toLocalDateTime(); Url url = new Url(name, createAt); url.setCreatedAt(createAt); url.setId(id); @@ -54,13 +56,13 @@ public static Optional find(Long id) throws SQLException { } public static Optional findByName(String name) throws SQLException { - String sql = "SELECT * FROM urls WHERE name = ?"; - try (Connection connection = dataSource.getConnection(); + String sql = "SELECT id, name, created_at FROM urls WHERE name = ?"; + try (Connection connection = BaseRepository.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, name); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { - LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime createdAt = resultSet.getTimestamp(FIELD_CREATED_AT).toLocalDateTime(); Long id = resultSet.getLong("id"); Url url = new Url(name, createdAt); url.setId(id); @@ -71,16 +73,16 @@ public static Optional findByName(String name) throws SQLException { } public static List getEntities() throws SQLException { - String sql = "SELECT * FROM urls"; - try (Connection conn = dataSource.getConnection(); + // String sql = "SELECT * FROM urls" - Sonar warning + String sql = "SELECT id, name, created_at FROM urls"; + try (Connection conn = BaseRepository.getDataSource().getConnection(); PreparedStatement preparedStatement = conn.prepareStatement(sql)) { ResultSet resultSet = preparedStatement.executeQuery(); - List result = new ArrayList(); + List result = new ArrayList<>(); while (resultSet.next()) { Long id = resultSet.getLong("id"); String name = resultSet.getString("name"); - LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); - + LocalDateTime createdAt = resultSet.getTimestamp(FIELD_CREATED_AT).toLocalDateTime(); Url url = new Url(name, createdAt); url.setId(id); result.add(url); @@ -91,12 +93,12 @@ public static List getEntities() throws SQLException { public static void clear() { String sql = "DELETE FROM urls;"; - try (Connection conn = dataSource.getConnection(); - PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + try (Connection conn = BaseRepository.getDataSource().getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql)) { preparedStatement.executeUpdate(); - } catch (SQLException e) { -// throw new RuntimeException("Failed to clear the Url database", e); - System.out.println("Failed to clear the Url database" + e); + } catch (SQLException e) { + // throw new RuntimeException("Failed to clear the Url database", e) + log.info("Failed to clear the Url database", e); // Compliant, output via logger } } } diff --git a/app/src/main/java/hexlet/code/util/AppSettings.java b/app/src/main/java/hexlet/code/util/AppSettings.java new file mode 100644 index 0000000..737b60d --- /dev/null +++ b/app/src/main/java/hexlet/code/util/AppSettings.java @@ -0,0 +1,26 @@ +package hexlet.code.util; + +public final class AppSettings { + private AppSettings() { + //for SONAR warning + throw new IllegalStateException("Utility class"); + } + + // Flash constant + public static final String FLASH_DANGER = "danger"; + public static final String FLASH_SUCCESS = "success"; + public static final String FLASH_TYPE = "flash-type"; + public static final String FLASH_INFO = "info"; + public static final String FLASH_WARNING = "warning"; + + + public static final String FLASH = "flash"; + public static final String PAGE_OK = "Страница успешно проверена"; + public static final String PAGE_EXIST = "Страница уже существует"; + public static final String PAGE_ADDED = "Страница успешно добавлена"; + + public static final String URL_BAD = "Некорректный адрес URL"; // Некорректный URL" + public static final String URL_EMPTY = "Поле URL не должно быть пустым"; + + public static final String CHECK_ERROR = "Ошибка при проверке страницы: "; +} diff --git a/app/src/main/java/hexlet/code/util/NamedRoutes.java b/app/src/main/java/hexlet/code/util/NamedRoutes.java index c6b06ba..951f8b8 100644 --- a/app/src/main/java/hexlet/code/util/NamedRoutes.java +++ b/app/src/main/java/hexlet/code/util/NamedRoutes.java @@ -1,24 +1,40 @@ package hexlet.code.util; -public class NamedRoutes { +public final class NamedRoutes { + static final String PATH_MAIN_PAGE = "/"; + static final String PATH_BUILD_PAGE = "/urls/build"; + static final String PATH_SITES_PAGE = "/urls"; + + private NamedRoutes() { + // for SONAR warning + throw new IllegalStateException("Utility class"); + } + public static String mainPage() { - return "/"; + return PATH_MAIN_PAGE; } public static String buildPath() { - return "/urls/build"; + return PATH_BUILD_PAGE; } public static String urlsPath() { - return "/urls"; + return PATH_SITES_PAGE; } public static String urlPath(String id) { - return "/urls/" + id; + return PATH_SITES_PAGE + "/" + id; } public static String urlPath(Long id) { return urlPath(String.valueOf(id)); } + public static String urlPathForChecks(String id) { + return PATH_SITES_PAGE + "/" + id + "/checks"; + } + + public static String urlPathForChecks(Long id) { + return urlPathForChecks(String.valueOf(id)); + } } diff --git a/app/src/main/resources/schema.sql b/app/src/main/resources/schema.sql index 2c9ee9c..ac89e54 100644 --- a/app/src/main/resources/schema.sql +++ b/app/src/main/resources/schema.sql @@ -1,7 +1,20 @@ +DROP TABLE IF EXISTS url_checks; DROP TABLE IF EXISTS urls; + CREATE TABLE urls ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, - created_at TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +CREATE TABLE url_checks ( + id SERIAL PRIMARY KEY, + url_id INT NOT NULL, + status_code INT, + title VARCHAR(100), + h1 VARCHAR(100), + description VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (url_id) REFERENCES urls(id) ON DELETE CASCADE -- foreign key with cascade delete +); \ No newline at end of file diff --git a/app/src/main/resources/templates/urls/indexList.jte b/app/src/main/resources/templates/urls/indexList.jte index f0e0e2b..334ace7 100644 --- a/app/src/main/resources/templates/urls/indexList.jte +++ b/app/src/main/resources/templates/urls/indexList.jte @@ -10,6 +10,8 @@ content = @` ID Имя + Последняя проверка + Код ответа @@ -18,6 +20,18 @@ content = @` ${url.getId()} ${url.getName()} + + @if(page.getLatestChecks().containsKey(url.getId())) + ${page.getLatestChecks().get(url.getId()).getCreatedAt().format(page.getFormatter())} + @endif + + + @if(page.getLatestChecks().containsKey(url.getId())) + ${page.getLatestChecks().get(url.getId()).getStatusCode()} + @endif + + + @endfor @endif diff --git a/app/src/main/resources/templates/urls/show.jte b/app/src/main/resources/templates/urls/show.jte index 2eabc96..d158434 100644 --- a/app/src/main/resources/templates/urls/show.jte +++ b/app/src/main/resources/templates/urls/show.jte @@ -21,5 +21,40 @@ content = @` + +

Проверки

+
+ +
+ + + + + + + + + + + + + @if(!page.getUrl().getUrlCheckList().isEmpty()) + @for(var check : page.getUrl().getUrlCheckList()) + + + + + + + + + + + @endfor + + @endif + <%--checks here--%> + +
IDКод ответаtitleh1descriptionДата проверки
${check.getId()}${check.getStatusCode()}${check.getTitle()}${check.getH1()}${check.getDescription()}${check.getCreatedAt().format(page.getFormatter())}
`) diff --git a/app/src/test/java/hexlet/code/AppTest.java b/app/src/test/java/hexlet/code/AppTest.java index e7852b5..082edaf 100644 --- a/app/src/test/java/hexlet/code/AppTest.java +++ b/app/src/test/java/hexlet/code/AppTest.java @@ -3,43 +3,74 @@ import hexlet.code.model.Url; import hexlet.code.repository.UrlRepository; +import hexlet.code.util.NamedRoutes; import io.javalin.Javalin; import io.javalin.testtools.JavalinTest; +import okhttp3.Response; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.sql.SQLException; import java.time.LocalDateTime; +import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; class AppTest { Javalin app; + private static MockWebServer mockServer; + + public static String readFixture(String fileName) throws IOException { + Path filePath = Paths.get("src/test/resources/fixtures", fileName); + return new String(Files.readAllBytes(filePath)); + } + + @BeforeAll + static void beforeAll() throws IOException { + mockServer = new MockWebServer(); + MockResponse mockedResponse = new MockResponse().setBody(readFixture("test.html")); + mockServer.enqueue(mockedResponse); + mockServer.start(); + } @BeforeEach - final void setUp() throws SQLException, IOException { + final void beforeEach() throws SQLException, IOException { app = App.getApp(); // Очистка базы данных перед каждым тестом UrlRepository.clear(); } + @AfterAll + static void afterAll() throws IOException { + mockServer.shutdown(); + } + + + // Main page @Test void testMainPage() { JavalinTest.test(app, (server, client) -> { - var response = client.get("/"); - assertThat(response.code()).isEqualTo(200); - assertThat(response.body().string()).contains("Анализатор страниц"); + // Response response = client.get("/") + assertThat(client.get("/").code()).isEqualTo(200); + assertThat(client.get("/").body().string()).contains("Анализатор страниц"); }); } //save URL in BD @Test void testUrlSave() throws SQLException { - var url = new Url("https://mail.ru/", LocalDateTime.now()); + Url url = new Url("https://mail.ru/", LocalDateTime.now()); UrlRepository.save(url); JavalinTest.test(app, (server, client) -> { - var response = client.get("/urls/" + url.getId()); + Response response = client.get("/urls/" + url.getId()); assertThat(response.code()).isEqualTo(200); }); } @@ -48,19 +79,43 @@ void testUrlSave() throws SQLException { void testCreateUrl() { JavalinTest.test(app, (server, client) -> { String requestedBody = "url=https://ya.ru/"; - var response = client.post("/urls", requestedBody); - var url = UrlRepository.findByName("https://ya.ru"); + Response response = client.post("/urls", requestedBody); + Optional url = UrlRepository.findByName("https://ya.ru"); assertThat(url.get().getName()).isEqualTo("https://ya.ru"); assertThat(response.code()).isEqualTo(200); assertThat(response.body().string()).contains("https://ya.ru"); }); } + @Test + void testBuidPage() { + JavalinTest.test(app, (server, client) -> { + Response response = client.get("/urls/build"); + assertThat(response.code()).isEqualTo(200); + assertThat(response.body().string()).contains("Анализатор страниц"); + }); + + } + @Test void testAllUrls() { JavalinTest.test(app, (server, client) -> { - var response = client.get("/urls"); + Response response = client.get("/urls"); assertThat(response.code()).isEqualTo(200); }); } + + @Test + void testCheck() throws SQLException { + String mockUrl = mockServer.url("/").toString(); + Url url = new Url(mockUrl, LocalDateTime.now()); + UrlRepository.save(url); + + JavalinTest.test(app, (server, client) -> { + Url savedUrl = UrlRepository.findByName(mockServer.url("/").toString()).orElseThrow(); + Response response = client.post(NamedRoutes.urlPathForChecks(savedUrl.getId())); + assertThat(response.code()).isEqualTo(200); + }); + } + } diff --git a/app/src/test/resources/fixtures/test.html b/app/src/test/resources/fixtures/test.html new file mode 100644 index 0000000..ad14de9 --- /dev/null +++ b/app/src/test/resources/fixtures/test.html @@ -0,0 +1,19 @@ + + + + + + + + + Test page + + +
+
+

Just do it!

+
+
+ + +