diff options
Diffstat (limited to 'clams-server/src/main/java/no')
20 files changed, 321 insertions, 320 deletions
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java index 9f88053..c3c987e 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java @@ -1,11 +1,11 @@ package no.eliashaugsbakk.clams.server.config; import java.nio.file.Path; -import no.eliashaugsbakk.clams.server.controller.BlogController; +import no.eliashaugsbakk.clams.server.controller.ArticlesController; import no.eliashaugsbakk.clams.server.controller.MediaController; -import no.eliashaugsbakk.clams.server.controller.PostController; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepo; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepoSqlite; +import no.eliashaugsbakk.clams.server.controller.ArticleController; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepo; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepoSqlite; import no.eliashaugsbakk.clams.server.repository.MediaRepo; import no.eliashaugsbakk.clams.server.repository.MediaRepoSqlite; import no.eliashaugsbakk.clams.server.repository.SqliteManager; @@ -14,9 +14,9 @@ import no.eliashaugsbakk.clams.server.service.SlugService; public class AppContext implements AutoCloseable { private final SqliteManager dbManager; - private final BlogController blogController; + private final ArticlesController articlesController; private final MediaController mediaController; - private final PostController postController; + private final ArticleController articleController; private final AuthService authService; public AppContext() { @@ -27,19 +27,19 @@ public class AppContext implements AutoCloseable { this.dbManager = new SqliteManager(dbUrl); this.dbManager.init(); - BlogPostRepo blogPostRepo = new BlogPostRepoSqlite(dbManager); + ArticlesRepo articlesRepo = new ArticlesRepoSqlite(dbManager); MediaRepo mediaRepo = new MediaRepoSqlite(dbManager); - SlugService slugService = new SlugService(blogPostRepo); + SlugService slugService = new SlugService(articlesRepo); - this.blogController = new BlogController(dbManager); + this.articlesController = new ArticlesController(dbManager); this.mediaController = new MediaController(mediaRepo, appConfig); - this.postController = new PostController(blogPostRepo, slugService); + this.articleController = new ArticleController(articlesRepo, slugService); this.authService = new AuthService(appConfig); } - public BlogController getBlogController() { return blogController; } + public ArticlesController getArticlesController() { return articlesController; } public MediaController getMediaController() { return mediaController; } - public PostController getPostController() { return postController; } + public ArticleController getArticleController() { return articleController; } public AuthService getAuthService() { return authService; } @Override diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java index fb5c936..3df4d6d 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java @@ -25,9 +25,9 @@ public class AppRoutes implements EndpointGroup { get("/projects", ctx -> ctx.render("templates/projects.html", Map.of("page_title", "Projects - Elias Haugsbakk", "page_css", "projects"))); - path("blog", () -> { - get(appContext.getBlogController()::handleBlogRequest); - get("{slug}", appContext.getBlogController()::handleGetPost); + path("articles", () -> { + get(appContext.getArticlesController()::handleArticlesRequest); + get("{slug}", appContext.getArticlesController()::handleGetArticle); }); path("api", () -> { @@ -50,13 +50,13 @@ public class AppRoutes implements EndpointGroup { } }); - post("posts", appContext.getPostController()::handlePostPost); - put("posts/{slug}", appContext.getPostController()::handlePutPost); - delete("posts/{slug}", appContext.getPostController()::handleDeletePost); + post("articles", appContext.getArticleController()::handleArticleArticle); + put("articles/{slug}", appContext.getArticleController()::handlePutArticle); + delete("articles/{slug}", appContext.getArticleController()::handleDeleteArticle); get("media", appContext.getMediaController()::handleGetMediaIndex); get("media/{uuid}", appContext.getMediaController()::handleGetMedia); - post("media", appContext.getMediaController()::handlePostMedia); + post("media", appContext.getMediaController()::handleArticleMedia); delete("media/{uuid}", appContext.getMediaController()::handleDeleteMedia); }); } diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticleController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticleController.java new file mode 100644 index 0000000..a57414f --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticleController.java @@ -0,0 +1,31 @@ +package no.eliashaugsbakk.clams.server.controller; + +import io.javalin.http.Context; +import no.eliashaugsbakk.clams.server.model.Article; +import no.eliashaugsbakk.clams.server.model.ArticleDTO; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepo; +import no.eliashaugsbakk.clams.server.service.SlugService; + +public class ArticleController { + private final ArticlesRepo articlesRepo; + private final SlugService slugService; + + public ArticleController(ArticlesRepo articlesRepo, SlugService slugService) { + this.articlesRepo = articlesRepo; + this.slugService = slugService; + } + + public void handleArticleArticle(Context ctx) { + ArticleDTO newArticle = ctx.bodyAsClass(ArticleDTO.class); + articlesRepo.addArticle(new Article(newArticle, slugService.toSlug(newArticle.title()))); + } + + public void handlePutArticle(Context ctx) { + ArticleDTO updatedArticle = ctx.bodyAsClass(ArticleDTO.class); + articlesRepo.updateArticle(new Article(updatedArticle, ctx.pathParam("slug"))); + } + + public void handleDeleteArticle(Context ctx) { + articlesRepo.deleteArticle(ctx.pathParam("slug")); + } +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticlesController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticlesController.java new file mode 100644 index 0000000..fb53d86 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticlesController.java @@ -0,0 +1,132 @@ +package no.eliashaugsbakk.clams.server.controller; + +import io.javalin.http.Context; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import no.eliashaugsbakk.clams.server.model.Article; +import no.eliashaugsbakk.clams.server.model.ArticleMetaData; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepo; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepoSqlite; +import no.eliashaugsbakk.clams.server.repository.SqliteManager; +import no.eliashaugsbakk.clams.server.service.ArticlesSearchService; +import no.eliashaugsbakk.clams.server.utils.MarkdownConverter; + +public class ArticlesController { + private static final ZoneId OSLO_ZONE = ZoneId.of("Europe/Oslo"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy"); + + private final ArticlesRepo articlesRepo; + private final ArticlesSearchService articlesSearchService; + + public record SearchResultItem(String title, String slug, String summary, String formattedDate) {} + + public ArticlesController(SqliteManager sqliteManager) { + this.articlesRepo = new ArticlesRepoSqlite(sqliteManager); + this.articlesSearchService = new ArticlesSearchService(articlesRepo); + } + + public void handleGetArticle(Context ctx) { + var articleOpt = articlesRepo.getArticle(ctx.pathParam("slug")); + + if (articleOpt.isEmpty()) { + ctx.status(404); + return; + } + + Article article = articleOpt.get(); + + if (!article.isPublished()) { + ctx.status(404); + return; + } + + String formattedDate = article.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER); + + ctx.render("templates/article.html", Map.of( + "page_title", article.title() + " -- Elias Haugsbakk", + "page_css", "article", + "title", article.title(), + "published_date", formattedDate, + "author", "by Elias Haugsbakk", + "content", MarkdownConverter.convertToHtml(article.content()), + "back_to_index", "<a href=\"/articles/\">back to all articles</a>" + )); + } + + public void handleArticlesRequest(Context ctx) { + String searchTerm = ctx.queryParam("search"); + if (searchTerm != null) { + handleSearch(ctx, searchTerm); + } else { + handleArticlesIndex(ctx); + } + } + + private void handleArticlesIndex(Context ctx) { + List<ArticleMetaData> allArticles = articlesRepo.listArticlesMetaData().stream() + .filter(ArticleMetaData::isPublished) + .toList(); + Map<Integer, List<ArticleMetaData>> articlesByYear = groupArticlesByYear(allArticles); + + var featuredSlugs = List.of( + "" + ); + + List<Article> featuredArticles = new ArrayList<>(); + for (String slug : featuredSlugs) { + articlesRepo.getArticle(slug).ifPresent(featuredArticles::add); + } + + ctx.render("templates/articles.html", + Map.of( + "page_title", "My articles", + "page_css", "articles", + "articles_by_year", articlesByYear, + "featured_articles", featuredArticles, + "search_value", "" + )); + } + + private void handleSearch(Context ctx, String searchTerm) { + String query = searchTerm.trim(); + List<ArticleMetaData> results = query.isEmpty() ? List.of() : articlesSearchService.searchArticles(query); + + List<SearchResultItem> resultItems = results.stream() + .filter(ArticleMetaData::isPublished) + .map(article -> new SearchResultItem( + article.title(), + article.slug(), + article.summary() != null ? article.summary() : "", + article.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER) + )) + .toList(); + + ctx.render("templates/articles-search.html", + Map.of( + "page_title", "Search Results -- Elias Haugsbakk", + "page_css", "articles", + "search_term", query, + "results", resultItems, + "results_count", resultItems.size() + )); + } + + private Map<Integer, List<ArticleMetaData>> groupArticlesByYear(List<ArticleMetaData> articles) { + Map<Integer, List<ArticleMetaData>> unsortedGroups = articles.stream() + .collect(Collectors.groupingBy( + article -> article.timePublished().atZone(OSLO_ZONE).getYear(), + Collectors.toList() + )); + + Map<Integer, List<ArticleMetaData>> sortedGroups = new TreeMap<>(Comparator.reverseOrder()); + sortedGroups.putAll(unsortedGroups); + + return sortedGroups; + } +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/BlogController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/BlogController.java deleted file mode 100644 index 33a6ac6..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/BlogController.java +++ /dev/null @@ -1,132 +0,0 @@ -package no.eliashaugsbakk.clams.server.controller; - -import io.javalin.http.Context; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; -import no.eliashaugsbakk.clams.server.model.Post; -import no.eliashaugsbakk.clams.server.model.PostMetaData; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepo; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepoSqlite; -import no.eliashaugsbakk.clams.server.repository.SqliteManager; -import no.eliashaugsbakk.clams.server.service.BlogSearchService; -import no.eliashaugsbakk.clams.server.utils.MarkdownConverter; - -public class BlogController { - private static final ZoneId OSLO_ZONE = ZoneId.of("Europe/Oslo"); - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy"); - - private final BlogPostRepo blogPostRepo; - private final BlogSearchService blogSearchService; - - public record SearchResultItem(String title, String slug, String summary, String formattedDate) {} - - public BlogController(SqliteManager sqliteManager) { - this.blogPostRepo = new BlogPostRepoSqlite(sqliteManager); - this.blogSearchService = new BlogSearchService(blogPostRepo); - } - - public void handleGetPost(Context ctx) { - var postOpt = blogPostRepo.getPost(ctx.pathParam("slug")); - - if (postOpt.isEmpty()) { - ctx.status(404); - return; - } - - Post post = postOpt.get(); - - if (!post.isPublished()) { - ctx.status(404); - return; - } - - String formattedDate = post.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER); - - ctx.render("templates/post.html", Map.of( - "page_title", post.title() + " -- Elias Haugsbakk", - "page_css", "post", - "title", post.title(), - "published_date", formattedDate, - "author", "by Elias Haugsbakk", - "content", MarkdownConverter.convertToHtml(post.content()), - "back_to_index", "<a href=\"/blog/\">back to all posts</a>" - )); - } - - public void handleBlogRequest(Context ctx) { - String searchTerm = ctx.queryParam("search"); - if (searchTerm != null) { - handleSearch(ctx, searchTerm); - } else { - handleBlogIndex(ctx); - } - } - - private void handleBlogIndex(Context ctx) { - List<PostMetaData> allPosts = blogPostRepo.listPostsMetaData().stream() - .filter(PostMetaData::isPublished) - .toList(); - Map<Integer, List<PostMetaData>> postsByYear = groupPostsByYear(allPosts); - - var featuredSlugs = List.of( - "" - ); - - List<Post> featuredPosts = new ArrayList<>(); - for (String slug : featuredSlugs) { - blogPostRepo.getPost(slug).ifPresent(featuredPosts::add); - } - - ctx.render("templates/blog.html", - Map.of( - "page_title", "Elias Haugsbakk's Blog", - "page_css", "blog", - "posts_by_year", postsByYear, - "featured_posts", featuredPosts, - "search_value", "" - )); - } - - private void handleSearch(Context ctx, String searchTerm) { - String query = searchTerm.trim(); - List<PostMetaData> results = query.isEmpty() ? List.of() : blogSearchService.searchPosts(query); - - List<SearchResultItem> resultItems = results.stream() - .filter(PostMetaData::isPublished) - .map(post -> new SearchResultItem( - post.title(), - post.slug(), - post.summary() != null ? post.summary() : "", - post.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER) - )) - .toList(); - - ctx.render("templates/blog-search.html", - Map.of( - "page_title", "Search Results -- Elias Haugsbakk", - "page_css", "blog", - "search_term", query, - "results", resultItems, - "results_count", resultItems.size() - )); - } - - private Map<Integer, List<PostMetaData>> groupPostsByYear(List<PostMetaData> posts) { - Map<Integer, List<PostMetaData>> unsortedGroups = posts.stream() - .collect(Collectors.groupingBy( - post -> post.timePublished().atZone(OSLO_ZONE).getYear(), - Collectors.toList() - )); - - Map<Integer, List<PostMetaData>> sortedGroups = new TreeMap<>(Comparator.reverseOrder()); - sortedGroups.putAll(unsortedGroups); - - return sortedGroups; - } -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/MediaController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/MediaController.java index 8855b7b..d4e40b9 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/MediaController.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/MediaController.java @@ -28,7 +28,7 @@ public class MediaController { this.appConfig = appConfig; } - public void handlePostMedia(Context ctx) { + public void handleArticleMedia(Context ctx) { UploadedFile file = ctx.uploadedFile("image"); if (file == null) { diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostController.java deleted file mode 100644 index 93976fd..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostController.java +++ /dev/null @@ -1,31 +0,0 @@ -package no.eliashaugsbakk.clams.server.controller; - -import io.javalin.http.Context; -import no.eliashaugsbakk.clams.server.model.Post; -import no.eliashaugsbakk.clams.server.model.PostDTO; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepo; -import no.eliashaugsbakk.clams.server.service.SlugService; - -public class PostController { - private final BlogPostRepo blogPostRepo; - private final SlugService slugService; - - public PostController(BlogPostRepo blogPostRepo, SlugService slugService) { - this.blogPostRepo = blogPostRepo; - this.slugService = slugService; - } - - public void handlePostPost(Context ctx) { - PostDTO newPost = ctx.bodyAsClass(PostDTO.class); - blogPostRepo.addBlogPost(new Post(newPost, slugService.toSlug(newPost.title()))); - } - - public void handlePutPost(Context ctx) { - PostDTO updatedPost = ctx.bodyAsClass(PostDTO.class); - blogPostRepo.updateBlogPost(new Post(updatedPost, ctx.pathParam("slug"))); - } - - public void handleDeletePost(Context ctx) { - blogPostRepo.deleteBlogPost(ctx.pathParam("slug")); - } -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Article.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Article.java new file mode 100644 index 0000000..b74a79b --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Article.java @@ -0,0 +1,12 @@ +package no.eliashaugsbakk.clams.server.model; + +import java.time.Instant; + +public record Article(String title, String slug, String summary, Instant timePublished, Instant lastEdited, String content, + boolean isPublished) { + public Article(ArticleDTO articleDTO, String slug) { + + this(articleDTO.title(), slug, articleDTO.summary(), Instant.now(), Instant.now(), articleDTO.content(), + articleDTO.isPublished()); + } +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleDTO.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleDTO.java new file mode 100644 index 0000000..27207d7 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleDTO.java @@ -0,0 +1,4 @@ +package no.eliashaugsbakk.clams.server.model; + +public record ArticleDTO(String title, String summary, String content, boolean isPublished) { +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleMetaData.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleMetaData.java new file mode 100644 index 0000000..8e71d78 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleMetaData.java @@ -0,0 +1,6 @@ +package no.eliashaugsbakk.clams.server.model; + +import java.time.Instant; + +public record ArticleMetaData(String title, String slug, String summary, Instant timePublished, Instant lastEdit, Boolean isPublished) { +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Post.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Post.java deleted file mode 100644 index 4a7fcf6..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Post.java +++ /dev/null @@ -1,12 +0,0 @@ -package no.eliashaugsbakk.clams.server.model; - -import java.time.Instant; - -public record Post(String title, String slug, String summary, Instant timePublished, String content, - boolean isPublished) { - public Post(PostDTO postDTO, String slug) { - - this(postDTO.title(), slug, postDTO.summary(), Instant.now(), postDTO.content(), - postDTO.isPublished()); - } -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostDTO.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostDTO.java deleted file mode 100644 index 12254da..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package no.eliashaugsbakk.clams.server.model; - -public record PostDTO(String title, String summary, String content, boolean isPublished) { -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostMetaData.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostMetaData.java deleted file mode 100644 index 175fc55..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostMetaData.java +++ /dev/null @@ -1,6 +0,0 @@ -package no.eliashaugsbakk.clams.server.model; - -import java.time.Instant; - -public record PostMetaData(String title, String slug, String summary, Instant timePublished, Instant lastEdit, Boolean isPublished) { -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepo.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepo.java new file mode 100644 index 0000000..221be74 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepo.java @@ -0,0 +1,17 @@ +package no.eliashaugsbakk.clams.server.repository; + +import java.util.List; +import java.util.Optional; +import no.eliashaugsbakk.clams.server.model.Article; +import no.eliashaugsbakk.clams.server.model.ArticleMetaData; + +public interface ArticlesRepo { + List<ArticleMetaData> listArticlesMetaData(); + Optional<Article> getArticle(String slug); + List<ArticleMetaData> searchArticlesBody(String query); + + void addArticle(Article article); + void updateArticle(Article article); + void deleteArticle(String slug); + boolean existsArticleBySlug(String slug); +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/BlogPostRepoSqlite.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepoSqlite.java index 754c915..86a615b 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/BlogPostRepoSqlite.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepoSqlite.java @@ -8,25 +8,25 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import no.eliashaugsbakk.clams.server.model.Post; -import no.eliashaugsbakk.clams.server.model.PostMetaData; +import no.eliashaugsbakk.clams.server.model.Article; +import no.eliashaugsbakk.clams.server.model.ArticleMetaData; -public class BlogPostRepoSqlite implements BlogPostRepo { +public class ArticlesRepoSqlite implements ArticlesRepo { private final SqliteManager manager; - public BlogPostRepoSqlite(SqliteManager manager) { + public ArticlesRepoSqlite(SqliteManager manager) { this.manager = manager; } @Override - public List<PostMetaData> listPostsMetaData() { + public List<ArticleMetaData> listArticlesMetaData() { String sql = """ SELECT title, slug, summary, published, last_edited, is_published - FROM posts + FROM articles ORDER BY published DESC """; - List<PostMetaData> posts = new ArrayList<>(); + List<ArticleMetaData> articles = new ArrayList<>(); try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql); @@ -41,21 +41,21 @@ public class BlogPostRepoSqlite implements BlogPostRepo { Instant lastEdit = (lastEditRaw != null) ? Instant.parse(lastEditRaw) : null; boolean isPublished = resultSet.getBoolean("is_published"); - posts.add(new PostMetaData(title, slug, summary, published, lastEdit, isPublished)); + articles.add(new ArticleMetaData(title, slug, summary, published, lastEdit, isPublished)); } - return posts; + return articles; } catch (SQLException e) { - throw new RepoException("Error fetching all post metadata sorted by time", e); + throw new RepoException("Error fetching all article metadata sorted by time", e); } } @Override - public Optional<Post> getPost(String slug) { + public Optional<Article> getArticle(String slug) { String sql = """ - SELECT title, slug, summary, content, published, is_published - FROM posts + SELECT title, slug, summary, content, published, last_edited, is_published + FROM articles WHERE slug = ? """; @@ -67,32 +67,33 @@ public class BlogPostRepoSqlite implements BlogPostRepo { try (ResultSet resultSet = stmt.executeQuery()) { if (resultSet.next()) { String title = resultSet.getString("title"); - String postSlug = resultSet.getString("slug"); + String articleSlug = resultSet.getString("slug"); String summary = resultSet.getString("summary"); String content = resultSet.getString("content"); Instant published = Instant.parse(resultSet.getString("published")); + Instant lastEdited = Instant.parse(resultSet.getString("last_edited")); boolean isPublished = resultSet.getBoolean("is_published"); - return Optional.of(new Post(title, postSlug, summary, published, content, isPublished)); + return Optional.of(new Article(title, articleSlug, summary, published, lastEdited, content, isPublished)); } return Optional.empty(); } } catch (SQLException e) { - throw new RepoException("Error fetching full post by slug: " + slug, e); + throw new RepoException("Error fetching full article by slug: " + slug, e); } } @Override - public List<PostMetaData> searchPostsBody(String query) { + public List<ArticleMetaData> searchArticlesBody(String query) { String sql = """ SELECT title, slug, summary, published, last_edited, is_published - FROM posts + FROM articles WHERE content LIKE ? ORDER BY published DESC """; - List<PostMetaData> posts = new ArrayList<>(); + List<ArticleMetaData> articles = new ArrayList<>(); try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -109,45 +110,45 @@ public class BlogPostRepoSqlite implements BlogPostRepo { Instant lastEdit = Instant.parse(resultSet.getString("last_edited")); boolean isPublished = resultSet.getBoolean("is_published"); - posts.add(new PostMetaData(title, slug, summary, published, lastEdit, isPublished)); + articles.add(new ArticleMetaData(title, slug, summary, published, lastEdit, isPublished)); } } - return posts; + return articles; } catch (SQLException e) { - throw new RepoException("Error searching posts for query: " + query, e); + throw new RepoException("Error searching articles for query: " + query, e); } } @Override - public void addBlogPost(Post post) { + public void addArticle(Article article) { String sql = """ - INSERT INTO posts (slug, title, content, summary, published, is_published) + INSERT INTO articles (slug, title, content, summary, published, is_published) VALUES (?, ?, ?, ?, ?, ?) """; try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { - stmt.setString(1, post.slug()); - stmt.setString(2, post.title()); - stmt.setString(3, post.content()); - stmt.setString(4, post.summary()); - stmt.setString(5, post.timePublished().toString()); - stmt.setBoolean(6, post.isPublished()); + stmt.setString(1, article.slug()); + stmt.setString(2, article.title()); + stmt.setString(3, article.content()); + stmt.setString(4, article.summary()); + stmt.setString(5, article.timePublished().toString()); + stmt.setBoolean(6, article.isPublished()); stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error adding blog post: " + post.slug(), e); + throw new RepoException("Error adding articles article: " + article.slug(), e); } } @Override - public void updateBlogPost(Post post) { + public void updateArticle(Article article) { String sql = """ - UPDATE posts + UPDATE articles SET title = ?, content = ?, summary = ?, published = ?, last_edited = ?, is_published = ? WHERE slug = ? """; @@ -155,25 +156,25 @@ public class BlogPostRepoSqlite implements BlogPostRepo { try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { - stmt.setString(1, post.title()); - stmt.setString(2, post.content()); - stmt.setString(3, post.summary()); - stmt.setString(4, post.timePublished().toString()); + stmt.setString(1, article.title()); + stmt.setString(2, article.content()); + stmt.setString(3, article.summary()); + stmt.setString(4, article.timePublished().toString()); stmt.setString(5, Instant.now().toString()); - stmt.setBoolean(6, post.isPublished()); - stmt.setString(7, post.slug()); + stmt.setBoolean(6, article.isPublished()); + stmt.setString(7, article.slug()); stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error updating blog post: " + post.slug(), e); + throw new RepoException("Error updating articles article: " + article.slug(), e); } } @Override - public void deleteBlogPost(String slug) { + public void deleteArticle(String slug) { String sql = """ - DELETE FROM posts + DELETE FROM articles WHERE slug = ? """; @@ -185,15 +186,15 @@ public class BlogPostRepoSqlite implements BlogPostRepo { stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error deleting blog post: " + slug, e); + throw new RepoException("Error deleting articles article: " + slug, e); } } @Override - public boolean existsPostBySlug(String slug) { + public boolean existsArticleBySlug(String slug) { String sql = """ SELECT 1 - FROM posts + FROM articles WHERE slug = ? LIMIT 1 """; @@ -208,7 +209,7 @@ public class BlogPostRepoSqlite implements BlogPostRepo { } } catch (SQLException e) { - throw new RepoException("Error checking existence of post by slug: " + slug, e); + throw new RepoException("Error checking existence of article by slug: " + slug, e); } } } diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/BlogPostRepo.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/BlogPostRepo.java deleted file mode 100644 index 6455f7d..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/BlogPostRepo.java +++ /dev/null @@ -1,17 +0,0 @@ -package no.eliashaugsbakk.clams.server.repository; - -import java.util.List; -import java.util.Optional; -import no.eliashaugsbakk.clams.server.model.Post; -import no.eliashaugsbakk.clams.server.model.PostMetaData; - -public interface BlogPostRepo { - List<PostMetaData> listPostsMetaData(); - Optional<Post> getPost(String slug); - List<PostMetaData> searchPostsBody(String query); - - void addBlogPost(Post post); - void updateBlogPost(Post post); - void deleteBlogPost(String slug); - boolean existsPostBySlug(String slug); -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java index 06a4ee2..4054ae4 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java @@ -25,8 +25,8 @@ public class SqliteManager implements AutoCloseable { } public void init() { - String posts = """ - CREATE TABLE IF NOT EXISTS posts ( + String articles = """ + CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, @@ -49,7 +49,7 @@ public class SqliteManager implements AutoCloseable { try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { - stmt.execute(posts); + stmt.execute(articles); stmt.execute(images); } catch (SQLException e) { diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/ArticlesSearchService.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/ArticlesSearchService.java new file mode 100644 index 0000000..db68063 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/ArticlesSearchService.java @@ -0,0 +1,44 @@ +package no.eliashaugsbakk.clams.server.service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import no.eliashaugsbakk.clams.server.model.ArticleMetaData; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepo; + +public class ArticlesSearchService { + private final ArticlesRepo articlesRepo; + + public ArticlesSearchService(ArticlesRepo articlesRepo) { + this.articlesRepo = articlesRepo; + } + + public List<ArticleMetaData> searchArticles(String query) { + if (query == null || query.isBlank()) return List.of(); + + int titleValue = 10; + int summaryValue = 5; + int bodyValue = 1; + + Map<ArticleMetaData, Integer> searchRanking = new HashMap<>(); + String lowerQuery = query.toLowerCase(); + + List<ArticleMetaData> articlesMetaData = articlesRepo.listArticlesMetaData(); + List<ArticleMetaData> bodyResults = articlesRepo.searchArticlesBody(query); + + articlesMetaData.stream() + .filter(article -> article.title().toLowerCase().contains(lowerQuery)) + .forEach(article -> searchRanking.merge(article, titleValue, Integer::sum)); + + articlesMetaData.stream() + .filter(article -> article.summary() != null && article.summary().toLowerCase().contains(lowerQuery)) + .forEach(article -> searchRanking.merge(article, summaryValue, Integer::sum)); + + bodyResults.forEach(article -> searchRanking.merge(article, bodyValue, Integer::sum)); + + return searchRanking.entrySet().stream() + .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())) + .map(Map.Entry::getKey) + .toList(); + } +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/BlogSearchService.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/BlogSearchService.java deleted file mode 100644 index afc47f5..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/BlogSearchService.java +++ /dev/null @@ -1,44 +0,0 @@ -package no.eliashaugsbakk.clams.server.service; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import no.eliashaugsbakk.clams.server.model.PostMetaData; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepo; - -public class BlogSearchService { - private final BlogPostRepo blogPostRepo; - - public BlogSearchService(BlogPostRepo blogPostRepo) { - this.blogPostRepo = blogPostRepo; - } - - public List<PostMetaData> searchPosts(String query) { - if (query == null || query.isBlank()) return List.of(); - - int titleValue = 10; - int summaryValue = 5; - int bodyValue = 1; - - Map<PostMetaData, Integer> searchRanking = new HashMap<>(); - String lowerQuery = query.toLowerCase(); - - List<PostMetaData> postsMetaData = blogPostRepo.listPostsMetaData(); - List<PostMetaData> bodyResults = blogPostRepo.searchPostsBody(query); - - postsMetaData.stream() - .filter(post -> post.title().toLowerCase().contains(lowerQuery)) - .forEach(post -> searchRanking.merge(post, titleValue, Integer::sum)); - - postsMetaData.stream() - .filter(post -> post.summary() != null && post.summary().toLowerCase().contains(lowerQuery)) - .forEach(post -> searchRanking.merge(post, summaryValue, Integer::sum)); - - bodyResults.forEach(post -> searchRanking.merge(post, bodyValue, Integer::sum)); - - return searchRanking.entrySet().stream() - .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())) - .map(Map.Entry::getKey) - .toList(); - } -} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/SlugService.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/SlugService.java index 19af1da..7d356ce 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/SlugService.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/SlugService.java @@ -3,15 +3,15 @@ package no.eliashaugsbakk.clams.server.service; import java.text.Normalizer; import java.util.Locale; import java.util.regex.Pattern; -import no.eliashaugsbakk.clams.server.repository.BlogPostRepo; +import no.eliashaugsbakk.clams.server.repository.ArticlesRepo; public class SlugService { - private final BlogPostRepo blogPostRepo; + private final ArticlesRepo articlesRepo; private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); - public SlugService(BlogPostRepo blogPostRepo) { - this.blogPostRepo = blogPostRepo; + public SlugService(ArticlesRepo articlesRepo) { + this.articlesRepo = articlesRepo; } public String toSlug(String input) { @@ -31,7 +31,7 @@ public class SlugService { String finalSlug = slug; int counter = 2; while (true) { - if (blogPostRepo.existsPostBySlug(finalSlug)) { + if (articlesRepo.existsArticleBySlug(finalSlug)) { finalSlug = slug + "-" + counter; counter++; } else { |
