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", "back to all posts" )); } 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 allPosts = postsRepo.listPostsMetaData().stream() .filter(PostMetaData::isPublished) .toList(); Map> postsByYear = groupPostsByYear(allPosts); Map> 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", "Blog", "page_css", "posts", "posts_by_year", postItemsByYear, "search_value", "" )); } private void handleSearch(Context ctx, String searchTerm) { String query = searchTerm.trim(); List results = query.isEmpty() ? List.of() : postsSearchService.searchPosts(query); List 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> groupPostsByYear(List posts) { Map> unsortedGroups = posts.stream() .collect(Collectors.groupingBy( post -> post.timePublished().atZone(OSLO_ZONE).getYear(), Collectors.toList() )); Map> sortedGroups = new TreeMap<>(Comparator.reverseOrder()); sortedGroups.putAll(unsortedGroups); return sortedGroups; } }