From c4f4d792d82d3e63de2ace745cacdc3a07458514 Mon Sep 17 00:00:00 2001 From: VictorGotsenko Date: Tue, 1 Jul 2025 18:57:25 +0500 Subject: [PATCH] Step05 - done --- Makefile | 9 ++ app/Makefile | 12 +++ app/build.gradle.kts | 18 ++-- app/src/main/java/hexlet/code/App.java | 28 +++--- .../hexlet/code/controller/UrlController.java | 93 +++++++++++++++++++ .../main/java/hexlet/code/dto/BasePage.java | 14 +++ .../java/hexlet/code/dto/urls/UrlPage.java | 13 +++ .../java/hexlet/code/dto/urls/UrlsPage.java | 15 +++ .../hexlet/code/repository/UrlRepository.java | 91 ++++++++++++++++++ .../java/hexlet/code/util/NamedRoutes.java | 24 +++++ app/src/main/resources/templates/index.jte | 30 ++++++ app/src/main/resources/templates/index_s.jte | 4 - .../main/resources/templates/layout/page.jte | 58 +++++++++++- .../main/resources/templates/urls/build.jte | 30 ++++++ .../resources/templates/urls/indexList.jte | 27 ++++++ .../main/resources/templates/urls/show.jte | 25 +++++ 16 files changed, 461 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/hexlet/code/controller/UrlController.java create mode 100644 app/src/main/java/hexlet/code/dto/BasePage.java create mode 100644 app/src/main/java/hexlet/code/dto/urls/UrlPage.java create mode 100644 app/src/main/java/hexlet/code/dto/urls/UrlsPage.java create mode 100644 app/src/main/java/hexlet/code/repository/UrlRepository.java create mode 100644 app/src/main/java/hexlet/code/util/NamedRoutes.java create mode 100644 app/src/main/resources/templates/index.jte delete mode 100644 app/src/main/resources/templates/index_s.jte create mode 100644 app/src/main/resources/templates/urls/build.jte create mode 100644 app/src/main/resources/templates/urls/indexList.jte create mode 100644 app/src/main/resources/templates/urls/show.jte diff --git a/Makefile b/Makefile index 3de5eda..3c78af4 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,19 @@ lint: make -C app lint build: make -C app build +install: + make -C app install +run: + make -C app run +run-dist: + make -C app run-dist test: make -C app test report: make -C app report +check-deps: + make -C app check-deps update-deps: make -C app update-deps + build-run: build run diff --git a/app/Makefile b/app/Makefile index 9ebac7d..d7174c0 100644 --- a/app/Makefile +++ b/app/Makefile @@ -1,16 +1,28 @@ .PHONY: build .DEFAULT_GOAL := build-run + +setup: + ./gradlew wrapper --gradle-version 8.7 clean: ./gradlew clean lint: ./gradlew checkstyleMain build: ./gradlew clean build +install: + ./gradlew clean install +run-dist: + ./build/install/app/bin/app +run: + ./gradlew run test: ./gradlew test report: ./gradlew jacocoTestReport +check-deps: + ./gradlew dependencyUpdates -Drevision=release update-deps: ./gradlew useLatestVersions + build-run: build run diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 97772be..dd9af86 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,7 +19,7 @@ group = "hexlet.code" version = "1.0-SNAPSHOT" application { - mainClass = "hexlet.code.App" + mainClass = "hexlet.code.App" } repositories { @@ -35,13 +35,12 @@ dependencies { implementation("org.slf4j:slf4j-simple:2.1.0-alpha1") - // LOMBOK compileOnly("org.projectlombok:lombok:1.18.38") - annotationProcessor("org.projectlombok:lombok:1.18.38") + annotationProcessor("org.projectlombok:lombok:1.18.38") - testCompileOnly("org.projectlombok:lombok:1.18.38") - testAnnotationProcessor("org.projectlombok:lombok:1.18.38") + testCompileOnly("org.projectlombok:lombok:1.18.38") + testAnnotationProcessor("org.projectlombok:lombok:1.18.38") // CheckStyle implementation("com.puppycrawl.tools:checkstyle:10.26.0") @@ -70,6 +69,13 @@ jacoco { toolVersion = "0.8.12" } + +// Solution for warning "Recompile with -Xlint:unchecked for details" +tasks.withType { + options.compilerArgs.addAll(listOf("-Xlint:unchecked", "-Xlint:deprecation")) // Add other desired flags +} + + tasks.jacocoTestReport { reports { xml.required = true @@ -85,5 +91,5 @@ sonar { property("sonar.projectKey", "VictorGotsenko_java-project-72") property("sonar.organization", "victorgotsenko") property("sonar.host.url", "https://sonarcloud.io") - } + } } diff --git a/app/src/main/java/hexlet/code/App.java b/app/src/main/java/hexlet/code/App.java index a04dff7..e685908 100644 --- a/app/src/main/java/hexlet/code/App.java +++ b/app/src/main/java/hexlet/code/App.java @@ -1,7 +1,12 @@ package hexlet.code; +import hexlet.code.repository.BaseRepository; +import hexlet.code.controller.UrlController; +import hexlet.code.util.NamedRoutes; + import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.sql.Connection; @@ -21,17 +26,12 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import hexlet.code.repository.BaseRepository; - - @Slf4j public class App { - private static TemplateEngine createTemplateEngine() { ClassLoader classLoader = App.class.getClassLoader(); ResourceCodeResolver codeResolver = new ResourceCodeResolver("templates", classLoader); - TemplateEngine templateEngine = TemplateEngine.create(codeResolver, ContentType.Html); - return templateEngine; + return TemplateEngine.create(codeResolver, ContentType.Html); } private static String getDatabaseUrl() { @@ -40,9 +40,8 @@ private static String getDatabaseUrl() { return System.getenv().getOrDefault("JDBC_DATABASE_URL", "jdbc:h2:mem:project;DB_CLOSE_DELAY=-1;"); } - private static String readResourceFile(String fileName) throws IOException { - var inputStream = App.class.getClassLoader().getResourceAsStream(fileName); + InputStream inputStream = App.class.getClassLoader().getResourceAsStream(fileName); try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.joining("\n")); } @@ -62,23 +61,22 @@ public static Javalin getApp() throws IOException, SQLException { // } BaseRepository.dataSource = dataSource; - Javalin app = Javalin.create(config -> { config.bundledPlugins.enableDevLogging(); config.fileRenderer(new JavalinJte(createTemplateEngine())); }); // root path - app.get("/", ctx -> { -// ctx.result("Hello World"); - ctx.render("index_s.jte"); - - }); + app.get(NamedRoutes.mainPage(), ctx -> ctx.render("index.jte")); + // other + app.post(NamedRoutes.urlsPath(), UrlController::create); + app.get(NamedRoutes.buildPath(), UrlController::build); + app.get(NamedRoutes.urlsPath(), UrlController::index); + app.get(NamedRoutes.urlPath("{id}"), UrlController::show); return app; } - public static void main(String[] args) throws IOException, SQLException { //throws SQLException Javalin app = getApp(); app.start(7070); diff --git a/app/src/main/java/hexlet/code/controller/UrlController.java b/app/src/main/java/hexlet/code/controller/UrlController.java new file mode 100644 index 0000000..f89f9e9 --- /dev/null +++ b/app/src/main/java/hexlet/code/controller/UrlController.java @@ -0,0 +1,93 @@ +package hexlet.code.controller; + +import hexlet.code.dto.BasePage; +import hexlet.code.dto.urls.UrlPage; +import hexlet.code.dto.urls.UrlsPage; +import hexlet.code.model.Url; +import hexlet.code.repository.UrlRepository; +import hexlet.code.util.NamedRoutes; + +import io.javalin.http.Context; +import io.javalin.http.NotFoundResponse; + +import static io.javalin.rendering.template.TemplateUtil.model; + +import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.MalformedURLException; + +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.List; + +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.utils.URIBuilder; + +@Slf4j +public class UrlController { + public static void build(Context ctx) { + BasePage page = new BasePage(); + 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; + try { + unifmResourceId = new URI(name).toURL(); + } catch (Exception e) { + ctx.sessionAttribute("flash", "Некорректный URL"); + ctx.sessionAttribute("flash-type", "danger"); + ctx.redirect(NamedRoutes.buildPath()); + return; + } + + if (name == null || name.isEmpty()) { + ctx.sessionAttribute("flash", "Поле URL не должно быть пустым"); + ctx.sessionAttribute("flash-type", "warning"); + ctx.redirect(NamedRoutes.buildPath()); + return; + } + + String protocol = unifmResourceId.getProtocol(); + int port = unifmResourceId.getPort(); + String host = unifmResourceId.getHost(); + + URL url = new URIBuilder().setScheme(protocol).setHost(host).setPort(port).build().toURL(); + + if (UrlRepository.findByName(String.valueOf(url)).isEmpty()) { + Url newUrl = new Url(String.valueOf(url), LocalDateTime.now()); + UrlRepository.save(newUrl); + } else { + ctx.sessionAttribute("flash", "Страница уже существует"); + ctx.sessionAttribute("flash-type", "info"); + ctx.redirect(NamedRoutes.urlsPath()); + return; + } + + ctx.sessionAttribute("flash", "Страница успешно добавлена"); + ctx.sessionAttribute("flash-type", "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")); + ctx.render("urls/indexList.jte", model("page", page)); + } + + 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")); + UrlPage page = new UrlPage(url); + 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/BasePage.java b/app/src/main/java/hexlet/code/dto/BasePage.java new file mode 100644 index 0000000..27290b1 --- /dev/null +++ b/app/src/main/java/hexlet/code/dto/BasePage.java @@ -0,0 +1,14 @@ +package hexlet.code.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.time.format.DateTimeFormatter; + +@Getter +@Setter +public class BasePage { + private String flash; + private String flashType; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); +} diff --git a/app/src/main/java/hexlet/code/dto/urls/UrlPage.java b/app/src/main/java/hexlet/code/dto/urls/UrlPage.java new file mode 100644 index 0000000..1a587b6 --- /dev/null +++ b/app/src/main/java/hexlet/code/dto/urls/UrlPage.java @@ -0,0 +1,13 @@ +package hexlet.code.dto.urls; + +import hexlet.code.dto.BasePage; +import hexlet.code.model.Url; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class UrlPage extends BasePage { + private Url url; +} diff --git a/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java b/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java new file mode 100644 index 0000000..4dd77f2 --- /dev/null +++ b/app/src/main/java/hexlet/code/dto/urls/UrlsPage.java @@ -0,0 +1,15 @@ +package hexlet.code.dto.urls; + +import hexlet.code.dto.BasePage; +import hexlet.code.model.Url; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class UrlsPage extends BasePage { + private List urls; +} diff --git a/app/src/main/java/hexlet/code/repository/UrlRepository.java b/app/src/main/java/hexlet/code/repository/UrlRepository.java new file mode 100644 index 0000000..9892c62 --- /dev/null +++ b/app/src/main/java/hexlet/code/repository/UrlRepository.java @@ -0,0 +1,91 @@ +package hexlet.code.repository; + +import hexlet.code.model.Url; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class UrlRepository extends BaseRepository { + public static void save(Url url) throws SQLException { + String sql = "INSERT INTO urls (name, created_at) VALUES (?, ?)"; + try (Connection conn = dataSource.getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + preparedStatement.setString(1, url.getName()); + LocalDateTime createdAt = LocalDateTime.now(); + preparedStatement.setTimestamp(2, Timestamp.valueOf(createdAt)); + preparedStatement.executeUpdate(); + var generatedKeys = preparedStatement.getGeneratedKeys(); + if (generatedKeys.next()) { + url.setId(generatedKeys.getLong(1)); + url.setCreatedAt(createdAt); + } else { + throw new SQLException("DB have not returned an id after saving an entity"); + } + } + } + + public static Optional find(Long id) throws SQLException { + String sql = "SELECT * FROM urls WHERE id = ?"; + try (Connection conn = dataSource.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(); + Url url = new Url(name, createAt); + url.setCreatedAt(createAt); + url.setId(id); + return Optional.of(url); + } + return Optional.empty(); + } + } + + public static Optional findByName(String name) throws SQLException { + String sql = "SELECT * FROM urls WHERE name = ?"; + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(sql)) { + preparedStatement.setString(1, name); + ResultSet resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + Long id = resultSet.getLong("id"); + Url url = new Url(name, createdAt); + url.setId(id); + return Optional.of(url); + } + return Optional.empty(); + } + } + + public static List getEntities() throws SQLException { + String sql = "SELECT * FROM urls"; + try (Connection conn = dataSource.getConnection(); + PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + ResultSet resultSet = preparedStatement.executeQuery(); + List result = new ArrayList(); + while (resultSet.next()) { + Long id = resultSet.getLong("id"); + String name = resultSet.getString("name"); + LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + + Url url = new Url(name, createdAt); + url.setId(id); + result.add(url); + } + return result; + } + } +} diff --git a/app/src/main/java/hexlet/code/util/NamedRoutes.java b/app/src/main/java/hexlet/code/util/NamedRoutes.java new file mode 100644 index 0000000..c6b06ba --- /dev/null +++ b/app/src/main/java/hexlet/code/util/NamedRoutes.java @@ -0,0 +1,24 @@ +package hexlet.code.util; + +public class NamedRoutes { + public static String mainPage() { + return "/"; + } + + public static String buildPath() { + return "/urls/build"; + } + + public static String urlsPath() { + return "/urls"; + } + + public static String urlPath(String id) { + return "/urls/" + id; + } + + public static String urlPath(Long id) { + return urlPath(String.valueOf(id)); + } + +} diff --git a/app/src/main/resources/templates/index.jte b/app/src/main/resources/templates/index.jte new file mode 100644 index 0000000..34a3151 --- /dev/null +++ b/app/src/main/resources/templates/index.jte @@ -0,0 +1,30 @@ +@import hexlet.code.util.NamedRoutes +@param hexlet.code.dto.BasePage page + +@template.layout.page( +page = page, +content = @` +
+
+
+

Анализатор страниц

+

Бесплатно проверяйте сайты на SEO пригодность

+
+
+
+
+ + +
+
+
+ +
+
+
+

Пример: https://www.example.com

+
+
+
+`) \ No newline at end of file diff --git a/app/src/main/resources/templates/index_s.jte b/app/src/main/resources/templates/index_s.jte deleted file mode 100644 index 90a0b6c..0000000 --- a/app/src/main/resources/templates/index_s.jte +++ /dev/null @@ -1,4 +0,0 @@ -@template.layout.page( -content = @` -

Hello World!

-`) \ 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 ba35299..a119e29 100644 --- a/app/src/main/resources/templates/layout/page.jte +++ b/app/src/main/resources/templates/layout/page.jte @@ -1,4 +1,6 @@ +@import hexlet.code.util.NamedRoutes @param gg.jte.Content content +@param hexlet.code.dto.BasePage page = null @@ -6,12 +8,58 @@ - flash example - + Анализатор страниц + - -
+ + + + + + +
+ @if(page != null && page.getFlash() != null && page.getFlashType() != null) + @if(page.getFlashType().equals("info")) + + + @elseif(page.getFlashType().equals("danger")) + + + @elseif(page.getFlashType().equals("success")) + + @endif + @endif + ${content}
diff --git a/app/src/main/resources/templates/urls/build.jte b/app/src/main/resources/templates/urls/build.jte new file mode 100644 index 0000000..34a3151 --- /dev/null +++ b/app/src/main/resources/templates/urls/build.jte @@ -0,0 +1,30 @@ +@import hexlet.code.util.NamedRoutes +@param hexlet.code.dto.BasePage page + +@template.layout.page( +page = page, +content = @` +
+
+
+

Анализатор страниц

+

Бесплатно проверяйте сайты на SEO пригодность

+
+
+
+
+ + +
+
+
+ +
+
+
+

Пример: https://www.example.com

+
+
+
+`) \ 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 new file mode 100644 index 0000000..f0e0e2b --- /dev/null +++ b/app/src/main/resources/templates/urls/indexList.jte @@ -0,0 +1,27 @@ +@param hexlet.code.dto.urls.UrlsPage page + +@template.layout.page( +page = page, +content = @` +
+

Сайты

+ + + + + + + + + @if(!page.getUrls().isEmpty()) + @for(var url : page.getUrls()) + + + + + @endfor + @endif + +
IDИмя
${url.getId()}${url.getName()}
+
+`) \ No newline at end of file diff --git a/app/src/main/resources/templates/urls/show.jte b/app/src/main/resources/templates/urls/show.jte new file mode 100644 index 0000000..2eabc96 --- /dev/null +++ b/app/src/main/resources/templates/urls/show.jte @@ -0,0 +1,25 @@ +@param hexlet.code.dto.urls.UrlPage page + +@template.layout.page( +page = page, +content = @` +
+

Сайт: ${page.getUrl().getName()}

+ + + + + + + + + + + + + + + +
ID${page.getUrl().getId()}
Имя${page.getUrl().getName()}
Дата создания${page.getUrl().getCreatedAt().format(page.getFormatter())}
+
+`)