commit 214b50e2b5d5a715d6b0a3753f2e6ff4b0a82bd1 parent 2564704ea8786dd56625c0759787107223205aee Author: Wim Dupont <wim@wimdupont.com> Date: Sun, 17 Dec 2023 08:55:37 +0100 improvements and added readme Former-commit-id: ede021a4aaf4b053d2fd6cc407519ea4a25b251b Diffstat:
23 files changed, 378 insertions(+), 373 deletions(-)
diff --git a/README.adoc b/README.adoc @@ -0,0 +1,25 @@ += PersonalWeb + +Source code of https://wimdupont.com[personal website]. + +== Purpose + +* To have an online presence without relying on external platforms. +* To provide simplicity for maintaining and updating site content + +== APIs + +=== Affirmations + +Affirmation quotes on homepage are retrieved from external source: https://www.affirmations.dev/ + +Link to project: https://github.com/annthurium/affirmations + +== GIT + +Web content for blog and guides is retrieved from self-maintained git project using the https://docs.gitlab.com/ee/api/api_resources.html[Gitlab REST API] + +The data is retrieved in https://asciidoc.org/[asciidoc] format (base64 encoded) and is persisted after converting to html when a new file or when changes are detected. This process is a scheduled task. + +Link to project: https://gitlab.com/WimDupont/webcontent + diff --git a/src/main/java/com/wimdupont/personalweb/PersonalWebApplication.java b/src/main/java/com/wimdupont/personalweb/PersonalWebApplication.java @@ -1,6 +1,6 @@ package com.wimdupont.personalweb; -import com.wimdupont.personalweb.service.ScheduledFileGenerator; +import com.wimdupont.personalweb.service.ScheduledContentGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @@ -14,6 +14,6 @@ public class PersonalWebApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(PersonalWebApplication.class, args); - context.getBean(ScheduledFileGenerator.class).generate(); + context.getBean(ScheduledContentGenerator.class).generate(); } } diff --git a/src/main/java/com/wimdupont/personalweb/api/AffirmationApi.java b/src/main/java/com/wimdupont/personalweb/api/AffirmationApi.java @@ -1,6 +1,7 @@ package com.wimdupont.personalweb.api; import com.wimdupont.personalweb.api.dto.Affirmation; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @@ -10,17 +11,18 @@ import java.time.Duration; @Component public class AffirmationApi { + private final String affirmationUrl; private final WebClient webClient; - public AffirmationApi(WebClient webClient) { + public AffirmationApi(@Value("${affirmation.url}") String affirmationUrl, + WebClient webClient) { + this.affirmationUrl = affirmationUrl; this.webClient = webClient; } - private static final String AFFIRMATION_URL = "https://www.affirmations.dev/"; - public Mono<Affirmation> getAffirmation() { return webClient.get() - .uri(AFFIRMATION_URL) + .uri(affirmationUrl) .retrieve() .bodyToMono(Affirmation.class) .timeout(Duration.ofSeconds(3)) diff --git a/src/main/java/com/wimdupont/personalweb/api/GitApi.java b/src/main/java/com/wimdupont/personalweb/api/GitApi.java @@ -0,0 +1,62 @@ +package com.wimdupont.personalweb.api; + +import com.wimdupont.personalweb.api.dto.Commit; +import com.wimdupont.personalweb.api.dto.RepositoryFile; +import com.wimdupont.personalweb.api.dto.RepositoryTreeItem; +import com.wimdupont.personalweb.model.AdocContentType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriUtils; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +@Component +public class GitApi { + + private final WebClient webClient; + private final String repositoryUrl; + private final String gitBranch; + + public GitApi(@Value("${git.repository.url}") String repositoryUrl, + @Value("${git.repository.branch}") String gitBranch, + WebClient webClient) { + this.repositoryUrl = repositoryUrl; + this.gitBranch = gitBranch; + this.webClient = webClient; + } + + public Optional<List<RepositoryTreeItem>> getRepositoryTree(AdocContentType adocContentType) { + return webClient.get().uri(String.format("%s/tree?ref=main&path=%s", + repositoryUrl, + adocContentType.getPathPrefix())) + .retrieve() + .bodyToFlux(RepositoryTreeItem.class) + .timeout(Duration.ofSeconds(5)) + .collectList().blockOptional(); + } + + public Optional<RepositoryFile> getRepositoryFile(String filePath) { + return webClient.get().uri(URI.create(String.format("%s/files/%s?ref=%s", + repositoryUrl, + UriUtils.encode(filePath, StandardCharsets.UTF_8), + gitBranch))) + .retrieve().bodyToMono(RepositoryFile.class) + .timeout(Duration.ofSeconds(5)) + .blockOptional(); + } + + public Optional<Commit> getCommit(String commitId) { + return webClient.get().uri(String.format("%s/commits/%s", + repositoryUrl, + commitId)) + .retrieve().bodyToMono(Commit.class) + .timeout(Duration.ofSeconds(5)) + .blockOptional(); + } + +} diff --git a/src/main/java/com/wimdupont/personalweb/api/GitlabApi.java b/src/main/java/com/wimdupont/personalweb/api/GitlabApi.java @@ -1,58 +0,0 @@ -package com.wimdupont.personalweb.api; - -import com.wimdupont.personalweb.api.dto.Commit; -import com.wimdupont.personalweb.api.dto.RepositoryFile; -import com.wimdupont.personalweb.api.dto.RepositoryTreeItem; -import com.wimdupont.personalweb.model.AdocContentType; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.util.UriUtils; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Optional; - -@Component -public class GitlabApi { - - private final WebClient webClient; - private final String repositoryUrl; - - public GitlabApi(@Value("${gitlab.repository.url}") String repositoryUrl, - WebClient webClient) { - this.repositoryUrl = repositoryUrl; - this.webClient = webClient; - } - - public Optional<List<RepositoryTreeItem>> getRepositoryTree(AdocContentType adocContentType) { - return webClient.get().uri(String.format("%s/tree?ref=main&path=%s", - repositoryUrl, - adocContentType.getPathPrefix())) - .retrieve() - .bodyToFlux(RepositoryTreeItem.class) - .timeout(Duration.ofSeconds(5)) - .collectList().blockOptional(); - } - - public Optional<RepositoryFile> getRepositoryFile(String filePath) { - return webClient.get().uri(URI.create(String.format("%s/files/%s?ref=main", - repositoryUrl, - UriUtils.encode(filePath, StandardCharsets.UTF_8)))) - .retrieve().bodyToMono(RepositoryFile.class) - .timeout(Duration.ofSeconds(5)) - .blockOptional(); - } - - public Optional<Commit> getCommit(String commitId) { - return webClient.get().uri(String.format("%s/commits/%s", - repositoryUrl, - commitId)) - .retrieve().bodyToMono(Commit.class) - .timeout(Duration.ofSeconds(5)) - .blockOptional(); - } - -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/api/EncodingController.java b/src/main/java/com/wimdupont/personalweb/controller/api/EncodingController.java @@ -16,7 +16,6 @@ import java.util.Base64; @Tag(name = "Encoding") public class EncodingController { - @PostMapping(value = "/base64/encode") @Operation(summary = "Encode value to base64") public String encodeBase64(@RequestBody String value) { diff --git a/src/main/java/com/wimdupont/personalweb/converter/AdocToHtmlStringConverter.java b/src/main/java/com/wimdupont/personalweb/converter/AdocToHtmlStringConverter.java @@ -0,0 +1,24 @@ +package com.wimdupont.personalweb.converter; + +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.Options; +import org.asciidoctor.SafeMode; +import org.springframework.stereotype.Component; + +@Component +public class AdocToHtmlStringConverter { + + private final Asciidoctor ASCIIDOCTOR; + private final Options OPTIONS; + + public AdocToHtmlStringConverter() { + this.ASCIIDOCTOR = Asciidoctor.Factory.create(); + this.OPTIONS = Options.builder() + .safe(SafeMode.UNSAFE) + .build(); + } + + public String convert(String adocString) { + return ASCIIDOCTOR.convert(adocString, OPTIONS); + } +} diff --git a/src/main/java/com/wimdupont/personalweb/model/dao/AdocContent.java b/src/main/java/com/wimdupont/personalweb/model/dao/AdocContent.java @@ -1,6 +1,7 @@ package com.wimdupont.personalweb.model.dao; import com.wimdupont.personalweb.model.AdocContentType; +import com.wimdupont.personalweb.model.dao.projection.AdocContentMeta; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -9,7 +10,7 @@ import jakarta.persistence.Id; import java.time.LocalDateTime; @Entity -public class AdocContent extends Auditable { +public class AdocContent extends Auditable implements AdocContentMeta { @Id private String path; diff --git a/src/main/java/com/wimdupont/personalweb/service/AdocContentService.java b/src/main/java/com/wimdupont/personalweb/service/AdocContentService.java @@ -48,7 +48,7 @@ public class AdocContentService { public void save(AdocContent adocContent) { var saved = adocContentRepository.save(adocContent); - LOGGER.info("{} saved: {}", saved.getContentType().name(), saved); + LOGGER.info("{} saved: {}", saved.getContentType().name(), saved.getPath()); } public Optional<AdocContent> findByContentSha256(String contentSha256) { diff --git a/src/main/java/com/wimdupont/personalweb/service/AdocConverter.java b/src/main/java/com/wimdupont/personalweb/service/AdocConverter.java @@ -1,21 +0,0 @@ -package com.wimdupont.personalweb.service; - -import jakarta.transaction.Transactional; -import org.asciidoctor.Asciidoctor; -import org.asciidoctor.Options; -import org.asciidoctor.SafeMode; -import org.springframework.stereotype.Service; - -@Service -@Transactional -public class AdocConverter { - - private static final Asciidoctor ASCIIDOCTOR = Asciidoctor.Factory.create(); - private static final Options OPTIONS = Options.builder() - .safe(SafeMode.UNSAFE) - .build(); - - public String convert(String adocString){ - return ASCIIDOCTOR.convert(adocString, OPTIONS); - } -} diff --git a/src/main/java/com/wimdupont/personalweb/service/RepositoryFileService.java b/src/main/java/com/wimdupont/personalweb/service/RepositoryFileService.java @@ -1,6 +1,6 @@ package com.wimdupont.personalweb.service; -import com.wimdupont.personalweb.api.GitlabApi; +import com.wimdupont.personalweb.api.GitApi; import com.wimdupont.personalweb.api.dto.Commit; import com.wimdupont.personalweb.api.dto.RepositoryFile; import com.wimdupont.personalweb.model.AdocContentType; @@ -13,22 +13,22 @@ import java.util.Optional; @Service public class RepositoryFileService { - private final GitlabApi gitlabApi; + private final GitApi gitApi; - public RepositoryFileService(GitlabApi gitlabApi) { - this.gitlabApi = gitlabApi; + public RepositoryFileService(GitApi gitApi) { + this.gitApi = gitApi; } public List<RepositoryFile> findRepositoryFiles(AdocContentType adocContentType) { var repositoryFiles = new ArrayList<RepositoryFile>(); - gitlabApi.getRepositoryTree(adocContentType) + gitApi.getRepositoryTree(adocContentType) .orElseThrow().stream() - .map(repositoryTreeItem -> gitlabApi.getRepositoryFile(repositoryTreeItem.path())) + .map(repositoryTreeItem -> gitApi.getRepositoryFile(repositoryTreeItem.path())) .forEach(repositoryFile -> repositoryFile.ifPresent(repositoryFiles::add)); return repositoryFiles; } public Optional<Commit> getCommit(String commitId){ - return gitlabApi.getCommit(commitId); + return gitApi.getCommit(commitId); } } diff --git a/src/main/java/com/wimdupont/personalweb/service/ScheduledContentGenerator.java b/src/main/java/com/wimdupont/personalweb/service/ScheduledContentGenerator.java @@ -0,0 +1,75 @@ +package com.wimdupont.personalweb.service; + +import com.wimdupont.personalweb.converter.AdocToHtmlStringConverter; +import com.wimdupont.personalweb.model.AdocContentType; +import com.wimdupont.personalweb.model.dao.AdocContent; +import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Base64; + +@Service +@Transactional +public class ScheduledContentGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledContentGenerator.class); + private final RssFeedGenerator rssFeedGenerator; + private final AdocContentService adocContentService; + private final RepositoryFileService repositoryFileService; + private final AdocToHtmlStringConverter adocToHtmlStringConverter; + + public ScheduledContentGenerator(RssFeedGenerator rssFeedGenerator, + AdocContentService adocContentService, + RepositoryFileService repositoryFileService, + AdocToHtmlStringConverter adocToHtmlStringConverter) { + this.rssFeedGenerator = rssFeedGenerator; + this.adocContentService = adocContentService; + this.repositoryFileService = repositoryFileService; + this.adocToHtmlStringConverter = adocToHtmlStringConverter; + } + + @Scheduled(cron = "${scheduled.content-generator.cron}") + public void generate() { + LOGGER.info("Content generator started."); + updateAdocContent(); + rssFeedGenerator.generate(); + LOGGER.info("Content generator completed."); + } + + private void updateAdocContent() { + for (AdocContentType contentType : AdocContentType.values()) { + var remoteRepositoryFiles = repositoryFileService.findRepositoryFiles(contentType); + adocContentService.removeWhenRemoteNotFound(contentType, remoteRepositoryFiles); + remoteRepositoryFiles.stream() + .filter(repositoryFile -> adocContentService + .findByContentSha256(repositoryFile.contentSha256()) + .isEmpty()) + .map(repositoryFile -> AdocContent.Builder.newBuilder() + .path(repositoryFile.path()) + .contentSha256(repositoryFile.contentSha256()) + .htmlText(toHtml(repositoryFile.contentBase64())) + .contentType(contentType) + .committedDate(fetchCommittedDate(repositoryFile.lastCommitId())) + .build()) + .forEach(adocContentService::save); + } + } + + private String toHtml(String contentBase64) { + return adocToHtmlStringConverter.convert(new String(Base64.getDecoder() + .decode(contentBase64.getBytes(StandardCharsets.UTF_8)))); + } + + private LocalDateTime fetchCommittedDate(String commitId) { + return LocalDateTime.ofInstant(repositoryFileService + .getCommit(commitId) + .orElseThrow() + .committedDate(), ZoneId.systemDefault()); + } +} diff --git a/src/main/java/com/wimdupont/personalweb/service/ScheduledFileGenerator.java b/src/main/java/com/wimdupont/personalweb/service/ScheduledFileGenerator.java @@ -1,74 +0,0 @@ -package com.wimdupont.personalweb.service; - -import com.wimdupont.personalweb.model.AdocContentType; -import com.wimdupont.personalweb.model.dao.AdocContent; -import jakarta.transaction.Transactional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Base64; - -@Service -@Transactional -public class ScheduledFileGenerator { - - private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledFileGenerator.class); - private final RssFeedGenerator rssFeedGenerator; - private final AdocContentService adocContentService; - private final RepositoryFileService repositoryFileService; - private final AdocConverter adocConverter; - - public ScheduledFileGenerator(RssFeedGenerator rssFeedGenerator, - AdocContentService adocContentService, - RepositoryFileService repositoryFileService, - AdocConverter adocConverter) { - this.rssFeedGenerator = rssFeedGenerator; - this.adocContentService = adocContentService; - this.repositoryFileService = repositoryFileService; - this.adocConverter = adocConverter; - } - - @Scheduled(cron = "0 0 0 * * *") - public void generate() { - LOGGER.info("Content generator started."); - updateAdocContent(); - rssFeedGenerator.generate(); - LOGGER.info("Content generator completed."); - } - - private void updateAdocContent() { - for (AdocContentType contentType : AdocContentType.values()) { - var remoteRepositoryFiles = repositoryFileService.findRepositoryFiles(contentType); - adocContentService.removeWhenRemoteNotFound(contentType, remoteRepositoryFiles); - remoteRepositoryFiles.stream() - .filter(repositoryFile -> adocContentService - .findByContentSha256(repositoryFile.contentSha256()) - .isEmpty()) - .map(repositoryFile -> AdocContent.Builder.newBuilder() - .path(repositoryFile.path()) - .contentSha256(repositoryFile.contentSha256()) - .htmlText(toHtml(repositoryFile.contentBase64())) - .contentType(contentType) - .committedDate(fetchCommittedDate(repositoryFile.lastCommitId())) - .build()) - .forEach(adocContentService::save); - } - } - - private String toHtml(String contentBase64) { - return adocConverter.convert(new String(Base64.getDecoder() - .decode(contentBase64.getBytes(StandardCharsets.UTF_8)))); - } - - private LocalDateTime fetchCommittedDate(String commitId) { - return LocalDateTime.ofInstant(repositoryFileService - .getCommit(commitId) - .orElseThrow() - .committedDate(), ZoneId.systemDefault()); - } -} diff --git a/src/main/java/com/wimdupont/personalweb/util/AdocContentUtil.java b/src/main/java/com/wimdupont/personalweb/util/AdocContentUtil.java @@ -1,7 +1,6 @@ package com.wimdupont.personalweb.util; import com.wimdupont.personalweb.model.AdocContentType; -import com.wimdupont.personalweb.model.dao.AdocContent; import com.wimdupont.personalweb.model.dao.projection.AdocContentMeta; public class AdocContentUtil { @@ -13,17 +12,10 @@ public class AdocContentUtil { return adocContentType.getPathPrefix() + "/" + title + Constants.ADOC_SUFFIX; } - public static String toTitle(AdocContent adocContent) { - return toTitle(adocContent.getPath(), adocContent.getContentType()); - } - public static String toTitle(AdocContentMeta adocContentMeta) { - return toTitle(adocContentMeta.getPath(), adocContentMeta.getContentType()); - } - - private static String toTitle(String path, AdocContentType adocContentType) { - return path + return adocContentMeta.getPath() .replace(Constants.ADOC_SUFFIX, "") - .replaceFirst(adocContentType.getPathPrefix() + "/", ""); + .replaceFirst(adocContentMeta.getContentType().getPathPrefix() + "/", ""); } + } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties @@ -1 +1,2 @@ server.url= +scheduled.content-generator.cron=0 0 * * * * diff --git a/src/test/java/com/wimdupont/personalweb/converter/AdocContentMetaToDtoConverterTest.java b/src/test/java/com/wimdupont/personalweb/converter/AdocContentMetaToDtoConverterTest.java @@ -1,14 +1,13 @@ package com.wimdupont.personalweb.converter; import com.wimdupont.personalweb.model.AdocContentType; -import com.wimdupont.personalweb.model.dao.projection.AdocContentMeta; +import com.wimdupont.personalweb.model.dao.projection.AdocContentMetaMother; +import com.wimdupont.personalweb.model.dao.projection.AdocContentMetaMother.Defaults; import com.wimdupont.personalweb.model.dto.AdocContentMetaDto; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; - class AdocContentMetaToDtoConverterTest { private AdocContentMetaToDtoConverter converter; @@ -20,94 +19,38 @@ class AdocContentMetaToDtoConverterTest { @Test void convertWithAdocExtensionShouldRemoveExtension() { - var dateTime = LocalDateTime.now(); - AdocContentMetaDto result = converter.convertForMetaData(new AdocContentMeta() { - @Override - public String getPath() { - return "guide.adoc"; - } - - @Override - public AdocContentType getContentType() { - return AdocContentType.GUIDE; - } + AdocContentMetaDto result = converter.convertForMetaData( + AdocContentMetaMother.withDefaults("guide.adoc")); - @Override - public LocalDateTime getCommittedDate() { - return dateTime; - } - }); Assertions.assertEquals("guide", result.title()); - Assertions.assertEquals(dateTime.toLocalDate(), result.date()); + Assertions.assertEquals(Defaults.COMMITTED_DATE.toLocalDate(), result.date()); } @Test void convertWithOtherExtensionShouldNotRemoveExtension() { - var dateTime = LocalDateTime.now(); - AdocContentMetaDto result = converter.convertForMetaData(new AdocContentMeta() { - @Override - public String getPath() { - return "guide.md"; - } - - @Override - public AdocContentType getContentType() { - return AdocContentType.GUIDE; - } + AdocContentMetaDto result = converter.convertForMetaData( + AdocContentMetaMother.withDefaults("guide.md")); - @Override - public LocalDateTime getCommittedDate() { - return dateTime; - } - }); Assertions.assertEquals("guide.md", result.title()); - Assertions.assertEquals(dateTime.toLocalDate(), result.date()); + Assertions.assertEquals(Defaults.COMMITTED_DATE.toLocalDate(), result.date()); } @Test void convertWithoutExtensionRemainTheSame() { - var dateTime = LocalDateTime.now(); - AdocContentMetaDto result = converter.convertForMetaData(new AdocContentMeta() { - @Override - public String getPath() { - return "guide"; - } + AdocContentMetaDto result = converter.convertForMetaData( + AdocContentMetaMother.withDefaults("guide")); - @Override - public AdocContentType getContentType() { - return AdocContentType.GUIDE; - } - - @Override - public LocalDateTime getCommittedDate() { - return dateTime; - } - }); Assertions.assertEquals("guide", result.title()); - Assertions.assertEquals(dateTime.toLocalDate(), result.date()); + Assertions.assertEquals(Defaults.COMMITTED_DATE.toLocalDate(), result.date()); } @Test void convertWithTypePrefixShouldRemove() { - var dateTime = LocalDateTime.now(); - AdocContentMetaDto result = converter.convertForMetaData(new AdocContentMeta() { - @Override - public String getPath() { - return AdocContentType.GUIDE.getPathPrefix() + "/guide"; - } - - @Override - public AdocContentType getContentType() { - return AdocContentType.GUIDE; - } + AdocContentMetaDto result = converter.convertForMetaData( + AdocContentMetaMother.withDefaults(AdocContentType.GUIDE.getPathPrefix() + "/guide")); - @Override - public LocalDateTime getCommittedDate() { - return dateTime; - } - }); Assertions.assertEquals("guide", result.title()); - Assertions.assertEquals(dateTime.toLocalDate(), result.date()); + Assertions.assertEquals(Defaults.COMMITTED_DATE.toLocalDate(), result.date()); } } diff --git a/src/test/java/com/wimdupont/personalweb/converter/BookToDtoConverterTest.java b/src/test/java/com/wimdupont/personalweb/converter/BookToDtoConverterTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; class BookToDtoConverterTest { - private BookToDtoConverter converter; private Book book; diff --git a/src/test/java/com/wimdupont/personalweb/model/dao/AdocContentMother.java b/src/test/java/com/wimdupont/personalweb/model/dao/AdocContentMother.java @@ -7,7 +7,6 @@ import java.time.LocalDateTime; public class AdocContentMother { public static class Defaults { - public static final String PATH = "path"; public static final String HTML_TEXT = "htmlText"; public static final String CONTENT_SHA_256 = "contentSha256"; diff --git a/src/test/java/com/wimdupont/personalweb/model/dao/BookMother.java b/src/test/java/com/wimdupont/personalweb/model/dao/BookMother.java @@ -2,23 +2,25 @@ package com.wimdupont.personalweb.model.dao; public class BookMother { - private static final String ID = "id"; - private static final String TITLE = "title"; - private static final String AUTHOR = "author"; - private static final String ISBN = "isbn"; - private static final String CATEGORY = "category"; - private static final String SERIES = "series"; - private static final Double SERIES_NUMBER = 2.5; + public static class Defaults { + public static final String ID = "id"; + public static final String TITLE = "title"; + public static final String AUTHOR = "author"; + public static final String ISBN = "isbn"; + public static final String CATEGORY = "category"; + public static final String SERIES = "series"; + public static final Double SERIES_NUMBER = 2.5; + } public static Book withDefaults() { var book = new Book(); - book.setId(ID); - book.setTitle(TITLE); - book.setAuthor(AUTHOR); - book.setIsbn(ISBN); - book.setCategory(CATEGORY); - book.setSeries(SERIES); - book.setSeriesNumber(SERIES_NUMBER); + book.setId(Defaults.ID); + book.setTitle(Defaults.TITLE); + book.setAuthor(Defaults.AUTHOR); + book.setIsbn(Defaults.ISBN); + book.setCategory(Defaults.CATEGORY); + book.setSeries(Defaults.SERIES); + book.setSeriesNumber(Defaults.SERIES_NUMBER); return book; } } diff --git a/src/test/java/com/wimdupont/personalweb/model/dao/projection/AdocContentMetaMother.java b/src/test/java/com/wimdupont/personalweb/model/dao/projection/AdocContentMetaMother.java @@ -0,0 +1,33 @@ +package com.wimdupont.personalweb.model.dao.projection; + +import com.wimdupont.personalweb.model.AdocContentType; + +import java.time.LocalDateTime; + +public class AdocContentMetaMother { + + public static class Defaults { + public static final AdocContentType ADOC_CONTENT_TYPE = AdocContentType.GUIDE; + public static final LocalDateTime COMMITTED_DATE = LocalDateTime.of(2000, 1, 1, 0, 0); + } + + public static AdocContentMeta withDefaults(String path) { + return new AdocContentMeta() { + @Override + public String getPath() { + return path; + } + + @Override + public AdocContentType getContentType() { + return Defaults.ADOC_CONTENT_TYPE; + } + + @Override + public LocalDateTime getCommittedDate() { + return Defaults.COMMITTED_DATE; + } + }; + } + +} diff --git a/src/test/java/com/wimdupont/personalweb/service/RepositoryFileServiceTest.java b/src/test/java/com/wimdupont/personalweb/service/RepositoryFileServiceTest.java @@ -1,6 +1,6 @@ package com.wimdupont.personalweb.service; -import com.wimdupont.personalweb.api.GitlabApi; +import com.wimdupont.personalweb.api.GitApi; import com.wimdupont.personalweb.api.dto.RepositoryFile; import com.wimdupont.personalweb.api.dto.RepositoryFileMother; import com.wimdupont.personalweb.api.dto.RepositoryTreeItem; @@ -23,7 +23,7 @@ class RepositoryFileServiceTest { private RepositoryFile repositoryFile; private List<RepositoryTreeItem> repositoryTreeItems; @Mock - private GitlabApi gitlabApi; + private GitApi gitApi; @InjectMocks private RepositoryFileService repositoryFileService; @@ -35,9 +35,9 @@ class RepositoryFileServiceTest { @Test public void findRepositoryFiles() { - Mockito.when(gitlabApi.getRepositoryTree(AdocContentType.GUIDE)) + Mockito.when(gitApi.getRepositoryTree(AdocContentType.GUIDE)) .thenReturn(Optional.of(repositoryTreeItems)); - Mockito.when(gitlabApi.getRepositoryFile("path")) + Mockito.when(gitApi.getRepositoryFile("path")) .thenReturn(Optional.of(repositoryFile)); var result = repositoryFileService.findRepositoryFiles(AdocContentType.GUIDE); diff --git a/src/test/java/com/wimdupont/personalweb/service/ScheduledContentGeneratorTest.java b/src/test/java/com/wimdupont/personalweb/service/ScheduledContentGeneratorTest.java @@ -0,0 +1,103 @@ +package com.wimdupont.personalweb.service; + +import com.wimdupont.personalweb.api.dto.Commit; +import com.wimdupont.personalweb.api.dto.CommitMother; +import com.wimdupont.personalweb.api.dto.RepositoryFile; +import com.wimdupont.personalweb.api.dto.RepositoryFileMother; +import com.wimdupont.personalweb.converter.AdocToHtmlStringConverter; +import com.wimdupont.personalweb.model.AdocContentType; +import com.wimdupont.personalweb.model.dao.AdocContent; +import com.wimdupont.personalweb.model.dao.AdocContentMother; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@ExtendWith(MockitoExtension.class) +class ScheduledContentGeneratorTest { + + private RepositoryFile repositoryFile; + private Commit commit; + @Mock + private RssFeedGenerator rssFeedGenerator; + @Mock + private AdocContentService adocContentService; + @Mock + private RepositoryFileService repositoryFileService; + @Mock + private AdocToHtmlStringConverter adocToHtmlStringConverter; + @InjectMocks + private ScheduledContentGenerator scheduledContentGenerator; + @Captor + private ArgumentCaptor<AdocContent> adocContentArgumentCaptor; + + @BeforeEach + public void setup() { + repositoryFile = RepositoryFileMother.withDefaults(); + commit = CommitMother.withDefaults(); + } + + @Test + public void generateWhenHashFoundShouldNotSaveEntity() { + var guideToUpsert = AdocContentMother.withDefaults().contentSha256("contentSha256").build(); + var foundFiles = List.of(repositoryFile); + Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.GUIDE)) + .thenReturn(new ArrayList<>()); + Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.BLOG_ARTICLE)) + .thenReturn(List.of(repositoryFile)); + Mockito.when(adocContentService.findByContentSha256(repositoryFile.contentSha256())) + .thenReturn(Optional.of(guideToUpsert)); + + scheduledContentGenerator.generate(); + + Mockito.verify(adocContentService, Mockito.times(1)) + .removeWhenRemoteNotFound(AdocContentType.BLOG_ARTICLE, foundFiles); + Mockito.verify(rssFeedGenerator, Mockito.times(1)) + .generate(); + Mockito.verify(adocContentService, Mockito.times(0)) + .save(adocContentArgumentCaptor.capture()); + } + + @Test + public void generateWhenHashNotFoundShouldSaveEntity() { + var foundFiles = List.of(repositoryFile); + Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.GUIDE)) + .thenReturn(new ArrayList<>()); + Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.BLOG_ARTICLE)) + .thenReturn(List.of(repositoryFile)); + Mockito.when(adocContentService.findByContentSha256(repositoryFile.contentSha256())) + .thenReturn(Optional.empty()); + Mockito.when(adocToHtmlStringConverter.convert(Mockito.anyString())) + .thenReturn("content"); + Mockito.when(repositoryFileService.getCommit(repositoryFile.lastCommitId())) + .thenReturn(Optional.of(commit)); + + scheduledContentGenerator.generate(); + + Mockito.verify(adocContentService, Mockito.times(1)) + .removeWhenRemoteNotFound(AdocContentType.BLOG_ARTICLE, foundFiles); + Mockito.verify(rssFeedGenerator, Mockito.times(1)) + .generate(); + Mockito.verify(adocContentService, Mockito.times(1)) + .save(adocContentArgumentCaptor.capture()); + + Assertions.assertEquals(repositoryFile.path(), adocContentArgumentCaptor.getValue().getPath()); + Assertions.assertEquals(repositoryFile.contentSha256(), adocContentArgumentCaptor.getValue().getContentSha256()); + Assertions.assertEquals("content", adocContentArgumentCaptor.getValue().getHtmlText()); + Assertions.assertEquals(AdocContentType.BLOG_ARTICLE, adocContentArgumentCaptor.getValue().getContentType()); + Assertions.assertEquals(LocalDateTime.ofInstant(commit.committedDate(), ZoneId.systemDefault()), + adocContentArgumentCaptor.getValue().getCommittedDate()); + } +} diff --git a/src/test/java/com/wimdupont/personalweb/service/ScheduledFileGeneratorTest.java b/src/test/java/com/wimdupont/personalweb/service/ScheduledFileGeneratorTest.java @@ -1,102 +0,0 @@ -package com.wimdupont.personalweb.service; - -import com.wimdupont.personalweb.api.dto.Commit; -import com.wimdupont.personalweb.api.dto.CommitMother; -import com.wimdupont.personalweb.api.dto.RepositoryFile; -import com.wimdupont.personalweb.api.dto.RepositoryFileMother; -import com.wimdupont.personalweb.model.AdocContentType; -import com.wimdupont.personalweb.model.dao.AdocContent; -import com.wimdupont.personalweb.model.dao.AdocContentMother; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@ExtendWith(MockitoExtension.class) -class ScheduledFileGeneratorTest { - - private RepositoryFile repositoryFile; - private Commit commit; - @Mock - private RssFeedGenerator rssFeedGenerator; - @Mock - private AdocContentService adocContentService; - @Mock - private RepositoryFileService repositoryFileService; - @Mock - private AdocConverter adocConverter; - @InjectMocks - private ScheduledFileGenerator scheduledFileGenerator; - @Captor - private ArgumentCaptor<AdocContent> adocContentArgumentCaptor; - - @BeforeEach - public void setup() { - repositoryFile = RepositoryFileMother.withDefaults(); - commit = CommitMother.withDefaults(); - } - - @Test - public void generateWhenHashFoundShouldNotSaveEntity() { - var guideToUpsert = AdocContentMother.withDefaults().contentSha256("contentSha256").build(); - var foundFiles = List.of(repositoryFile); - Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.GUIDE)) - .thenReturn(new ArrayList<>()); - Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.BLOG_ARTICLE)) - .thenReturn(List.of(repositoryFile)); - Mockito.when(adocContentService.findByContentSha256(repositoryFile.contentSha256())) - .thenReturn(Optional.of(guideToUpsert)); - - scheduledFileGenerator.generate(); - - Mockito.verify(adocContentService, Mockito.times(1)) - .removeWhenRemoteNotFound(AdocContentType.BLOG_ARTICLE, foundFiles); - Mockito.verify(rssFeedGenerator, Mockito.times(1)) - .generate(); - Mockito.verify(adocContentService, Mockito.times(0)) - .save(adocContentArgumentCaptor.capture()); - } - - @Test - public void generateWhenHashNotFoundShouldSaveEntity() { - var foundFiles = List.of(repositoryFile); - Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.GUIDE)) - .thenReturn(new ArrayList<>()); - Mockito.when(repositoryFileService.findRepositoryFiles(AdocContentType.BLOG_ARTICLE)) - .thenReturn(List.of(repositoryFile)); - Mockito.when(adocContentService.findByContentSha256(repositoryFile.contentSha256())) - .thenReturn(Optional.empty()); - Mockito.when(adocConverter.convert(Mockito.anyString())) - .thenReturn("content"); - Mockito.when(repositoryFileService.getCommit(repositoryFile.lastCommitId())) - .thenReturn(Optional.of(commit)); - - scheduledFileGenerator.generate(); - - Mockito.verify(adocContentService, Mockito.times(1)) - .removeWhenRemoteNotFound(AdocContentType.BLOG_ARTICLE, foundFiles); - Mockito.verify(rssFeedGenerator, Mockito.times(1)) - .generate(); - Mockito.verify(adocContentService, Mockito.times(1)) - .save(adocContentArgumentCaptor.capture()); - - Assertions.assertEquals(repositoryFile.path(), adocContentArgumentCaptor.getValue().getPath()); - Assertions.assertEquals(repositoryFile.contentSha256(), adocContentArgumentCaptor.getValue().getContentSha256()); - Assertions.assertEquals("content", adocContentArgumentCaptor.getValue().getHtmlText()); - Assertions.assertEquals(AdocContentType.BLOG_ARTICLE, adocContentArgumentCaptor.getValue().getContentType()); - Assertions.assertEquals(LocalDateTime.ofInstant(commit.committedDate(), ZoneId.systemDefault()), - adocContentArgumentCaptor.getValue().getCommittedDate()); - } -}