diff options
| author | Elias Haugsbakk <[email protected]> | 2026-07-22 14:14:24 +0200 |
|---|---|---|
| committer | Elias Haugsbakk <[email protected]> | 2026-07-22 14:14:24 +0200 |
| commit | 5ac83efd59ab85d0bdf82d5847fe9fe0b11232d9 (patch) | |
| tree | d45dde720f638853695940b79a6eb6a2d978ded8 /clams-server/src/main/java/no/eliashaugsbakk/clams/server | |
| parent | 8d86458282efae6c3fc26ab8fb4bd61d1a4e01e3 (diff) | |
update article to post on the backend and blog on the frontend
Diffstat (limited to 'clams-server/src/main/java/no/eliashaugsbakk/clams/server')
19 files changed, 322 insertions, 323 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 c3c987e..30c22e1 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.ArticlesController; +import no.eliashaugsbakk.clams.server.controller.PostsController; import no.eliashaugsbakk.clams.server.controller.MediaController; -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.controller.PostController; +import no.eliashaugsbakk.clams.server.repository.PostsRepo; +import no.eliashaugsbakk.clams.server.repository.PostsRepoSqlite; 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 ArticlesController articlesController; + private final PostsController postsController; private final MediaController mediaController; - private final ArticleController articleController; + private final PostController postController; private final AuthService authService; public AppContext() { @@ -27,19 +27,19 @@ public class AppContext implements AutoCloseable { this.dbManager = new SqliteManager(dbUrl); this.dbManager.init(); - ArticlesRepo articlesRepo = new ArticlesRepoSqlite(dbManager); + PostsRepo postsRepo = new PostsRepoSqlite(dbManager); MediaRepo mediaRepo = new MediaRepoSqlite(dbManager); - SlugService slugService = new SlugService(articlesRepo); + SlugService slugService = new SlugService(postsRepo); - this.articlesController = new ArticlesController(dbManager); + this.postsController = new PostsController(dbManager); this.mediaController = new MediaController(mediaRepo, appConfig); - this.articleController = new ArticleController(articlesRepo, slugService); + this.postController = new PostController(postsRepo, slugService); this.authService = new AuthService(appConfig); } - public ArticlesController getArticlesController() { return articlesController; } + public PostsController getPostsController() { return postsController; } public MediaController getMediaController() { return mediaController; } - public ArticleController getArticleController() { return articleController; } + public PostController getPostController() { return postController; } 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 9deef6b..0157496 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("articles", () -> { - get(appContext.getArticlesController()::handleGetArticles); - get("{slug}", appContext.getArticlesController()::handleGetArticle); + path("posts", () -> { + get(appContext.getPostsController()::handleGetPosts); + get("{slug}", appContext.getPostsController()::handleGetPost); }); path("api", () -> { @@ -50,9 +50,9 @@ public class AppRoutes implements EndpointGroup { } }); - post("articles", appContext.getArticleController()::handlePostArticle); - put("articles/{slug}", appContext.getArticleController()::handlePutArticle); - delete("articles/{slug}", appContext.getArticleController()::handleDeleteArticle); + post("posts", appContext.getPostController()::handlePostPost); + put("posts/{slug}", appContext.getPostController()::handlePutPost); + delete("posts/{slug}", appContext.getPostController()::handleDeletePost); get("media", appContext.getMediaController()::handleGetMediaIndex); get("media/{uuid}", appContext.getMediaController()::handleGetMedia); 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 deleted file mode 100644 index 8759b1a..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticleController.java +++ /dev/null @@ -1,31 +0,0 @@ -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 handlePostArticle(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 deleted file mode 100644 index 80a1cb5..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/ArticlesController.java +++ /dev/null @@ -1,137 +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.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 ArticleItem(String title, String slug, String summary, String formattedDate, int year) {} - - 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", - "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 handleGetArticles(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); - - Map<Integer, List<ArticleItem>> articleItemsByYear = articlesByYear.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().stream() - .map(article -> new ArticleItem( - article.title(), - article.slug(), - article.summary() != null ? article.summary() : "", - article.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER), - article.timePublished().atZone(OSLO_ZONE).getYear() - )) - .toList() - )); - - ctx.render("templates/articles.html", - Map.of( - "page_title", "My articles", - "page_css", "articles", - "articles_by_year", articleItemsByYear, - "search_value", "" - )); - } - - private void handleSearch(Context ctx, String searchTerm) { - String query = searchTerm.trim(); - List<ArticleMetaData> results = query.isEmpty() ? List.of() : articlesSearchService.searchArticles(query); - - List<ArticleItem> resultItems = results.stream() - .filter(ArticleMetaData::isPublished) - .map(article -> new ArticleItem( - article.title(), - article.slug(), - article.summary() != null ? article.summary() : "", - article.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER), - article.timePublished().atZone(OSLO_ZONE).getYear() - )) - .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/PostController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostController.java new file mode 100644 index 0000000..f3aa406 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostController.java @@ -0,0 +1,31 @@ +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.PostsRepo; +import no.eliashaugsbakk.clams.server.service.SlugService; + +public class PostController { + private final PostsRepo postsRepo; + private final SlugService slugService; + + public PostController(PostsRepo postsRepo, SlugService slugService) { + this.postsRepo = postsRepo; + this.slugService = slugService; + } + + public void handlePostPost(Context ctx) { + PostDTO newPost = ctx.bodyAsClass(PostDTO.class); + postsRepo.addPost(new Post(newPost, slugService.toSlug(newPost.title()))); + } + + public void handlePutPost(Context ctx) { + PostDTO updatedPost = ctx.bodyAsClass(PostDTO.class); + postsRepo.updatePost(new Post(updatedPost, ctx.pathParam("slug"))); + } + + public void handleDeletePost(Context ctx) { + postsRepo.deletePost(ctx.pathParam("slug")); + } +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostsController.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostsController.java new file mode 100644 index 0000000..f490b2c --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/controller/PostsController.java @@ -0,0 +1,136 @@ +package no.eliashaugsbakk.clams.server.controller; + +import io.javalin.http.Context; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +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.PostsRepo; +import no.eliashaugsbakk.clams.server.repository.PostsRepoSqlite; +import no.eliashaugsbakk.clams.server.repository.SqliteManager; +import no.eliashaugsbakk.clams.server.service.PostsSearchService; +import no.eliashaugsbakk.clams.server.utils.MarkdownConverter; + +public class PostsController { + private static final ZoneId OSLO_ZONE = ZoneId.of("Europe/Oslo"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy"); + + private final PostsRepo postsRepo; + private final PostsSearchService postsSearchService; + + public record PostItem(String title, String slug, String summary, String formattedDate, int year) {} + + public PostsController(SqliteManager sqliteManager) { + this.postsRepo = new PostsRepoSqlite(sqliteManager); + this.postsSearchService = new PostsSearchService(postsRepo); + } + + public void handleGetPost(Context ctx) { + var postOpt = postsRepo.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", + "published_date", formattedDate, + "author", "by Elias Haugsbakk", + "content", MarkdownConverter.convertToHtml(post.content()), + "back_to_index", "<a href=\"/posts/\">back to all posts</a>" + )); + } + + public void handleGetPosts(Context ctx) { + String searchTerm = ctx.queryParam("search"); + if (searchTerm != null) { + handleSearch(ctx, searchTerm); + } else { + handlePostsIndex(ctx); + } + } + + private void handlePostsIndex(Context ctx) { + List<PostMetaData> allPosts = postsRepo.listPostsMetaData().stream() + .filter(PostMetaData::isPublished) + .toList(); + + Map<Integer, List<PostMetaData>> postsByYear = groupPostsByYear(allPosts); + + Map<Integer, List<PostItem>> postItemsByYear = postsByYear.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().stream() + .map(post -> new PostItem( + post.title(), + post.slug(), + post.summary() != null ? post.summary() : "", + post.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER), + post.timePublished().atZone(OSLO_ZONE).getYear() + )) + .toList() + )); + + ctx.render("templates/posts.html", + Map.of( + "page_title", "My posts", + "page_css", "posts", + "posts_by_year", postItemsByYear, + "search_value", "" + )); + } + + private void handleSearch(Context ctx, String searchTerm) { + String query = searchTerm.trim(); + List<PostMetaData> results = query.isEmpty() ? List.of() : postsSearchService.searchPosts(query); + + List<PostItem> resultItems = results.stream() + .filter(PostMetaData::isPublished) + .map(post -> new PostItem( + post.title(), + post.slug(), + post.summary() != null ? post.summary() : "", + post.timePublished().atZone(OSLO_ZONE).format(DATE_FORMATTER), + post.timePublished().atZone(OSLO_ZONE).getYear() + )) + .toList(); + + ctx.render("templates/posts-search.html", + Map.of( + "page_title", "Search Results -- Elias Haugsbakk", + "page_css", "posts", + "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/model/Article.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Article.java deleted file mode 100644 index b74a79b..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Article.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 27207d7..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 8e71d78..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/ArticleMetaData.java +++ /dev/null @@ -1,6 +0,0 @@ -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 new file mode 100644 index 0000000..dcf0e54 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/Post.java @@ -0,0 +1,12 @@ +package no.eliashaugsbakk.clams.server.model; + +import java.time.Instant; + +public record Post(String title, String slug, String summary, Instant timePublished, Instant lastEdited, String content, + boolean isPublished) { + public Post(PostDTO postDTO, String slug) { + + this(postDTO.title(), slug, postDTO.summary(), Instant.now(), 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 new file mode 100644 index 0000000..12254da --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostDTO.java @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000..175fc55 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/model/PostMetaData.java @@ -0,0 +1,6 @@ +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 deleted file mode 100644 index 221be74..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepo.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.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/PostsRepo.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/PostsRepo.java new file mode 100644 index 0000000..b5553b5 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/PostsRepo.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.Post; +import no.eliashaugsbakk.clams.server.model.PostMetaData; + +public interface PostsRepo { + List<PostMetaData> listPostsMetaData(); + Optional<Post> getPost(String slug); + List<PostMetaData> searchPostsBody(String query); + + void addPost(Post post); + void updatePost(Post post); + void deletePost(String slug); + boolean existsPostBySlug(String slug); +} diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepoSqlite.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/PostsRepoSqlite.java index 60f0f9d..8ed15af 100644 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/ArticlesRepoSqlite.java +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/PostsRepoSqlite.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.Article; -import no.eliashaugsbakk.clams.server.model.ArticleMetaData; +import no.eliashaugsbakk.clams.server.model.Post; +import no.eliashaugsbakk.clams.server.model.PostMetaData; -public class ArticlesRepoSqlite implements ArticlesRepo { +public class PostsRepoSqlite implements PostsRepo { private final SqliteManager manager; - public ArticlesRepoSqlite(SqliteManager manager) { + public PostsRepoSqlite(SqliteManager manager) { this.manager = manager; } @Override - public List<ArticleMetaData> listArticlesMetaData() { + public List<PostMetaData> listPostsMetaData() { String sql = """ SELECT title, slug, summary, published, last_edited, is_published - FROM articles + FROM posts ORDER BY published DESC """; - List<ArticleMetaData> articles = new ArrayList<>(); + List<PostMetaData> posts = new ArrayList<>(); try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql); @@ -41,21 +41,21 @@ public class ArticlesRepoSqlite implements ArticlesRepo { Instant lastEdit = (lastEditRaw != null) ? Instant.parse(lastEditRaw) : null; boolean isPublished = resultSet.getBoolean("is_published"); - articles.add(new ArticleMetaData(title, slug, summary, published, lastEdit, isPublished)); + posts.add(new PostMetaData(title, slug, summary, published, lastEdit, isPublished)); } - return articles; + return posts; } catch (SQLException e) { - throw new RepoException("Error fetching all article metadata sorted by time", e); + throw new RepoException("Error fetching all post metadata sorted by time", e); } } @Override - public Optional<Article> getArticle(String slug) { + public Optional<Post> getPost(String slug) { String sql = """ SELECT title, slug, summary, content, published, last_edited, is_published - FROM articles + FROM posts WHERE slug = ? """; @@ -67,33 +67,33 @@ public class ArticlesRepoSqlite implements ArticlesRepo { try (ResultSet resultSet = stmt.executeQuery()) { if (resultSet.next()) { String title = resultSet.getString("title"); - String articleSlug = resultSet.getString("slug"); + String postSlug = 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 Article(title, articleSlug, summary, published, lastEdited, content, isPublished)); + return Optional.of(new Post(title, postSlug, summary, published, lastEdited, content, isPublished)); } return Optional.empty(); } } catch (SQLException e) { - throw new RepoException("Error fetching full article by slug: " + slug, e); + throw new RepoException("Error fetching full post by slug: " + slug, e); } } @Override - public List<ArticleMetaData> searchArticlesBody(String query) { + public List<PostMetaData> searchPostsBody(String query) { String sql = """ SELECT title, slug, summary, published, last_edited, is_published - FROM articles + FROM posts WHERE content LIKE ? ORDER BY published DESC """; - List<ArticleMetaData> articles = new ArrayList<>(); + List<PostMetaData> posts = new ArrayList<>(); try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -110,46 +110,46 @@ public class ArticlesRepoSqlite implements ArticlesRepo { Instant lastEdit = Instant.parse(resultSet.getString("last_edited")); boolean isPublished = resultSet.getBoolean("is_published"); - articles.add(new ArticleMetaData(title, slug, summary, published, lastEdit, isPublished)); + posts.add(new PostMetaData(title, slug, summary, published, lastEdit, isPublished)); } } - return articles; + return posts; } catch (SQLException e) { - throw new RepoException("Error searching articles for query: " + query, e); + throw new RepoException("Error searching posts for query: " + query, e); } } @Override - public void addArticle(Article article) { + public void addPost(Post post) { String sql = """ - INSERT INTO articles (slug, title, content, summary, published, last_edited, is_published) + INSERT INTO posts (slug, title, content, summary, published, last_edited, is_published) VALUES (?, ?, ?, ?, ?, ?, ?) """; try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { - 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.setString(6, article.timePublished().toString()); - stmt.setBoolean(7, article.isPublished()); + 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.setString(6, post.timePublished().toString()); + stmt.setBoolean(7, post.isPublished()); stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error adding articles article: " + article.slug(), e); + throw new RepoException("Error adding posts post: " + post.slug(), e); } } @Override - public void updateArticle(Article article) { + public void updatePost(Post post) { String sql = """ - UPDATE articles + UPDATE posts SET title = ?, content = ?, summary = ?, published = ?, last_edited = ?, is_published = ? WHERE slug = ? """; @@ -157,25 +157,25 @@ public class ArticlesRepoSqlite implements ArticlesRepo { try (Connection conn = manager.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { - stmt.setString(1, article.title()); - stmt.setString(2, article.content()); - stmt.setString(3, article.summary()); - stmt.setString(4, article.timePublished().toString()); + stmt.setString(1, post.title()); + stmt.setString(2, post.content()); + stmt.setString(3, post.summary()); + stmt.setString(4, post.timePublished().toString()); stmt.setString(5, Instant.now().toString()); - stmt.setBoolean(6, article.isPublished()); - stmt.setString(7, article.slug()); + stmt.setBoolean(6, post.isPublished()); + stmt.setString(7, post.slug()); stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error updating articles article: " + article.slug(), e); + throw new RepoException("Error updating posts post: " + post.slug(), e); } } @Override - public void deleteArticle(String slug) { + public void deletePost(String slug) { String sql = """ - DELETE FROM articles + DELETE FROM posts WHERE slug = ? """; @@ -187,15 +187,15 @@ public class ArticlesRepoSqlite implements ArticlesRepo { stmt.executeUpdate(); } catch (SQLException e) { - throw new RepoException("Error deleting articles article: " + slug, e); + throw new RepoException("Error deleting posts post: " + slug, e); } } @Override - public boolean existsArticleBySlug(String slug) { + public boolean existsPostBySlug(String slug) { String sql = """ SELECT 1 - FROM articles + FROM posts WHERE slug = ? LIMIT 1 """; @@ -210,7 +210,7 @@ public class ArticlesRepoSqlite implements ArticlesRepo { } } catch (SQLException e) { - throw new RepoException("Error checking existence of article by slug: " + slug, e); + throw new RepoException("Error checking existence of post by slug: " + slug, e); } } } 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 4832818..898729a 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 articles = """ - CREATE TABLE IF NOT EXISTS articles ( + String posts = """ + CREATE TABLE IF NOT EXISTS posts ( 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(articles); + stmt.execute(posts); 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 deleted file mode 100644 index db68063..0000000 --- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/ArticlesSearchService.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.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/PostsSearchService.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PostsSearchService.java new file mode 100644 index 0000000..1b29c02 --- /dev/null +++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/PostsSearchService.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.PostMetaData; +import no.eliashaugsbakk.clams.server.repository.PostsRepo; + +public class PostsSearchService { + private final PostsRepo postsRepo; + + public PostsSearchService(PostsRepo postsRepo) { + this.postsRepo = postsRepo; + } + + 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 = postsRepo.listPostsMetaData(); + List<PostMetaData> bodyResults = postsRepo.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 7d356ce..f80d310 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.ArticlesRepo; +import no.eliashaugsbakk.clams.server.repository.PostsRepo; public class SlugService { - private final ArticlesRepo articlesRepo; + private final PostsRepo postsRepo; private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); - public SlugService(ArticlesRepo articlesRepo) { - this.articlesRepo = articlesRepo; + public SlugService(PostsRepo postsRepo) { + this.postsRepo = postsRepo; } public String toSlug(String input) { @@ -31,7 +31,7 @@ public class SlugService { String finalSlug = slug; int counter = 2; while (true) { - if (articlesRepo.existsArticleBySlug(finalSlug)) { + if (postsRepo.existsPostBySlug(finalSlug)) { finalSlug = slug + "-" + counter; counter++; } else { |
