commit d577c5400f097bc6aa7f33930bae2fea4bc67d45 parent 3f3cd143229173efe545c850a4dd498ceb53ac27 Author: Wim Dupont <wim@wimdupont.com> Date: Sat, 15 Jul 2023 11:57:30 +0200 api init (WIP) Former-commit-id: ba434d4eb7a5567b91455e8a778c387bb9d26a06 Diffstat:
30 files changed, 579 insertions(+), 239 deletions(-)
diff --git a/pom.xml b/pom.xml @@ -15,6 +15,7 @@ <properties> <java.version>17</java.version> + <springdoc-openapi.version>2.1.0</springdoc-openapi.version> <asciidoctorj.version>2.5.10</asciidoctorj.version> <flyway.configFiles>src/main/resources/application.properties</flyway.configFiles> </properties> @@ -46,6 +47,15 @@ </dependency> <dependency> <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-starter-webflux-ui</artifactId> + <version>${springdoc-openapi.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> diff --git a/src/main/java/com/wimdupont/personalweb/config/SwaggerConfig.java b/src/main/java/com/wimdupont/personalweb/config/SwaggerConfig.java @@ -0,0 +1,62 @@ +package com.wimdupont.personalweb.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@Configuration +public class SwaggerConfig { + + private static final String DOCUMENTATION_FILE = "api-documentation.md"; + private final String serverUrl; + + public SwaggerConfig(@Value("${server.url}") String serverUrl) { + this.serverUrl = serverUrl; + } + + @Bean + public OpenAPI getOpenAPI() { + return new OpenAPI() + .servers(StringUtils.isNotBlank(serverUrl) + ? List.of(new Server().url(serverUrl)) + : null) + .info(getInfo()); + } + + + private Info getInfo() { + Info info = new Info().title("Wim Dupont's API") + .version("v1"); + + try (InputStream in = getClass().getClassLoader().getResourceAsStream(DOCUMENTATION_FILE)) { + if (in != null) { + info.description(new String(in.readAllBytes())); + } + } catch (IOException e) { + e.printStackTrace(); + } + return info; + } + + @Bean + public GroupedOpenApi overviewGroupedOpenApi() { + return GroupedOpenApi.builder() + .group("overview") + .pathsToMatch("/api/**") + .build(); + } + + @Bean + public String serverUrl() { + return serverUrl; + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/BlogController.java b/src/main/java/com/wimdupont/personalweb/controller/BlogController.java @@ -1,40 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import com.wimdupont.personalweb.service.ArticleService; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.io.IOException; - -@Controller -@RequestMapping("/blog") -public class BlogController { - - private final ArticleService articleService; - - public BlogController(ArticleService articleService) { - this.articleService = articleService; - } - - @GetMapping - public String getBlog(Model model) { - model.addAttribute("articles", articleService.getFileDtoList()); - return "blog"; - } - - @GetMapping(value = "/article/{name}") - public String getBlogArticle(@PathVariable(value = "name") String name, Model model) { - String article; - try { - article = articleService.getHtmlFileString(name, true); - } catch (IOException e) { - article = String.format("Article with name \"%s\" not found.", name); - } - model.addAttribute("article", article); - model.addAttribute("title", name); - return "article"; - } -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/BookController.java b/src/main/java/com/wimdupont/personalweb/controller/BookController.java @@ -1,25 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import com.wimdupont.personalweb.service.BookService; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -@RequestMapping("/books") -public class BookController { - - private final BookService bookService; - - public BookController(BookService bookService) { - this.bookService = bookService; - } - - @GetMapping - public String getBooks(Model model) { - model.addAttribute("books", bookService.findAllSortedByCategory()); - return "books"; - } - -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/ContactController.java b/src/main/java/com/wimdupont/personalweb/controller/ContactController.java @@ -1,45 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.InputStreamResource; -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -import static com.wimdupont.personalweb.util.Constants.GPG_PUBLIC_KEY; - -@Controller -@RequestMapping("/contact") -public class ContactController { - - private static final Logger LOG = LoggerFactory.getLogger(ContactController.class); - - @GetMapping - public String getContact(Model model) { - return "contact"; - } - - @GetMapping(path = "/pubkey.gpg") - public ResponseEntity<Resource> getPublicKey(Model model) { - try { - File file = new File(GPG_PUBLIC_KEY); - InputStreamResource resource = new InputStreamResource(new FileInputStream(file)); - return ResponseEntity.ok() - .contentLength(file.length()) - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - } - return null; - } -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/DonateController.java b/src/main/java/com/wimdupont/personalweb/controller/DonateController.java @@ -1,16 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -@RequestMapping("/donate") -public class DonateController { - - @GetMapping - public String getDonate(Model model) { - return "donate"; - } -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/FeedController.java b/src/main/java/com/wimdupont/personalweb/controller/FeedController.java @@ -1,30 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.wimdupont.personalweb.service.RssFeedGenerator; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import static org.springframework.http.MediaType.APPLICATION_XML_VALUE; - -@Controller -public class FeedController { - - private final RssFeedGenerator rssFeedGenerator; - - public FeedController(RssFeedGenerator rssFeedGenerator) { - this.rssFeedGenerator = rssFeedGenerator; - } - - @GetMapping(path = "/rss.xml") - @ResponseBody - public ResponseEntity<String> getRss() throws JsonProcessingException { - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(APPLICATION_XML_VALUE)) - .body(new XmlMapper().writeValueAsString(rssFeedGenerator.getRssFeed())); - } -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/GuideController.java b/src/main/java/com/wimdupont/personalweb/controller/GuideController.java @@ -1,43 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import com.wimdupont.personalweb.converter.GuideMetaToDtoConverter; -import com.wimdupont.personalweb.service.GuideService; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.util.Optional; - -@Controller -@RequestMapping("/guides") -public class GuideController { - - private final GuideService guideService; - private final GuideMetaToDtoConverter guideMetaToDtoConverter; - - public GuideController(GuideService guideService, - GuideMetaToDtoConverter guideMetaToDtoConverter) { - this.guideService = guideService; - this.guideMetaToDtoConverter = guideMetaToDtoConverter; - } - - @GetMapping - public String getGuides(Model model) { - model.addAttribute("guides", guideService.findAllMetaData().stream() - .map(guideMetaToDtoConverter::convertForMetaData) - .toList()); - return "guides"; - } - - @GetMapping(value = "/{name}") - public String getGuide(@PathVariable(value = "name") String name, Model model) { - var text = Optional.ofNullable(guideService.findGuideByTitle(name) - .orElseThrow().getHtmlText()) - .orElse("Oops, nothing here."); - model.addAttribute("guide", text); - model.addAttribute("title", name); - return "guide"; - } -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/IndexController.java b/src/main/java/com/wimdupont/personalweb/controller/IndexController.java @@ -1,23 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import com.wimdupont.personalweb.api.AffirmationApi; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class IndexController { - - private final AffirmationApi affirmationApi; - - public IndexController(AffirmationApi affirmationApi) { - this.affirmationApi = affirmationApi; - } - - @GetMapping("/") - public String home(Model model) { - model.addAttribute("affirmation", affirmationApi.getAffirmation()); - return "home"; - } - -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/LinkController.java b/src/main/java/com/wimdupont/personalweb/controller/LinkController.java @@ -1,17 +0,0 @@ -package com.wimdupont.personalweb.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -@RequestMapping("/links") -public class LinkController { - - @GetMapping - public String getRss(Model model) { - return "links"; - } - -} diff --git a/src/main/java/com/wimdupont/personalweb/controller/api/InszController.java b/src/main/java/com/wimdupont/personalweb/controller/api/InszController.java @@ -0,0 +1,47 @@ +package com.wimdupont.personalweb.controller.api; + +import com.wimdupont.personalweb.service.InszService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/insz") +@Tag(name = "INSZ", description = "Belgian national identification number") +public class InszController { + + private final InszService inszService; + + public InszController(InszService inszService) { + this.inszService = inszService; + } + + //TODO +// @GetMapping(value = "/generate") +// @Operation(summary = "Generate INSZ", +// description = "Generate random INSZ taking filled in parameters in account") +// public String generate( +// @RequestParam(required = false) Integer year, +// @RequestParam(required = false) Integer month, +// @RequestParam(required = false) Integer day, +// @RequestParam(required = false) Integer birthCounter, +// @RequestParam(required = false) Gender gender) { +// return inszService.generate(InszGenerator.Builder.newBuilder() +// .year(year) +// .month(month) +// .day(day) +// .birthCounter(birthCounter) +// .gender(gender) +// .build()); +// } + + @GetMapping(value = "/valid") + @Operation(summary = "Validate INSZ", + description = "Removes '.' and '-' characters before validation") + public boolean validate(@RequestParam(required = false) String insz) { + return inszService.validate(insz); + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/api/RandomController.java b/src/main/java/com/wimdupont/personalweb/controller/api/RandomController.java @@ -0,0 +1,49 @@ +package com.wimdupont.personalweb.controller.api; + +import com.wimdupont.personalweb.model.Coin; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Random; +import java.util.UUID; + +@RestController +@RequestMapping("/api/random") +@Tag(name = "Random", description = "Random generators") +public class RandomController { + + @GetMapping(value = "/uuid") + @Operation(summary = "Generate random UUID") + public String randomUuid() { + return UUID.randomUUID().toString(); + } + + @GetMapping(value = "/coin-flip") + @Operation(summary = "Flip a coin") + public String coinFlip() { + return Math.random() < 0.5 + ? Coin.HEADS.name() : Coin.TAILS.name(); + } + + @GetMapping(value = "/number") + @Operation(summary = "Get random number") + public int randomNumber( + @RequestParam(defaultValue = "1") Integer min, + @RequestParam(defaultValue = "6") Integer max) { + return new Random().nextInt(max - min + 1) + min; + } + +// TODO FIX CSRF +// @PostMapping(value = "/name") +// @Operation(summary = "Get random value of list") +// public String name(@RequestBody List<String> names) { +// if (names != null && !names.isEmpty()) { +// return names.get(new Random().nextInt(names.size())); +// } +// throw new AtleastOneNameRequiredException(); +// } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/BlogController.java b/src/main/java/com/wimdupont/personalweb/controller/web/BlogController.java @@ -0,0 +1,40 @@ +package com.wimdupont.personalweb.controller.web; + +import com.wimdupont.personalweb.service.ArticleService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.io.IOException; + +@Controller +@RequestMapping("/blog") +public class BlogController { + + private final ArticleService articleService; + + public BlogController(ArticleService articleService) { + this.articleService = articleService; + } + + @GetMapping + public String getBlog(Model model) { + model.addAttribute("articles", articleService.getFileDtoList()); + return "blog"; + } + + @GetMapping(value = "/article/{name}") + public String getBlogArticle(@PathVariable(value = "name") String name, Model model) { + String article; + try { + article = articleService.getHtmlFileString(name, true); + } catch (IOException e) { + article = String.format("Article with name \"%s\" not found.", name); + } + model.addAttribute("article", article); + model.addAttribute("title", name); + return "article"; + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/BookController.java b/src/main/java/com/wimdupont/personalweb/controller/web/BookController.java @@ -0,0 +1,25 @@ +package com.wimdupont.personalweb.controller.web; + +import com.wimdupont.personalweb.service.BookService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/books") +public class BookController { + + private final BookService bookService; + + public BookController(BookService bookService) { + this.bookService = bookService; + } + + @GetMapping + public String getBooks(Model model) { + model.addAttribute("books", bookService.findAllSortedByCategory()); + return "books"; + } + +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/ContactController.java b/src/main/java/com/wimdupont/personalweb/controller/web/ContactController.java @@ -0,0 +1,45 @@ +package com.wimdupont.personalweb.controller.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import static com.wimdupont.personalweb.util.Constants.GPG_PUBLIC_KEY; + +@Controller +@RequestMapping("/contact") +public class ContactController { + + private static final Logger LOG = LoggerFactory.getLogger(ContactController.class); + + @GetMapping + public String getContact(Model model) { + return "contact"; + } + + @GetMapping(path = "/pubkey.gpg") + public ResponseEntity<Resource> getPublicKey(Model model) { + try { + File file = new File(GPG_PUBLIC_KEY); + InputStreamResource resource = new InputStreamResource(new FileInputStream(file)); + return ResponseEntity.ok() + .contentLength(file.length()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + return null; + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/DonateController.java b/src/main/java/com/wimdupont/personalweb/controller/web/DonateController.java @@ -0,0 +1,16 @@ +package com.wimdupont.personalweb.controller.web; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/donate") +public class DonateController { + + @GetMapping + public String getDonate(Model model) { + return "donate"; + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/FeedController.java b/src/main/java/com/wimdupont/personalweb/controller/web/FeedController.java @@ -0,0 +1,30 @@ +package com.wimdupont.personalweb.controller.web; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.wimdupont.personalweb.service.RssFeedGenerator; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import static org.springframework.http.MediaType.APPLICATION_XML_VALUE; + +@Controller +public class FeedController { + + private final RssFeedGenerator rssFeedGenerator; + + public FeedController(RssFeedGenerator rssFeedGenerator) { + this.rssFeedGenerator = rssFeedGenerator; + } + + @GetMapping(path = "/rss.xml") + @ResponseBody + public ResponseEntity<String> getRss() throws JsonProcessingException { + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(APPLICATION_XML_VALUE)) + .body(new XmlMapper().writeValueAsString(rssFeedGenerator.getRssFeed())); + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/GuideController.java b/src/main/java/com/wimdupont/personalweb/controller/web/GuideController.java @@ -0,0 +1,43 @@ +package com.wimdupont.personalweb.controller.web; + +import com.wimdupont.personalweb.converter.GuideMetaToDtoConverter; +import com.wimdupont.personalweb.service.GuideService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.Optional; + +@Controller +@RequestMapping("/guides") +public class GuideController { + + private final GuideService guideService; + private final GuideMetaToDtoConverter guideMetaToDtoConverter; + + public GuideController(GuideService guideService, + GuideMetaToDtoConverter guideMetaToDtoConverter) { + this.guideService = guideService; + this.guideMetaToDtoConverter = guideMetaToDtoConverter; + } + + @GetMapping + public String getGuides(Model model) { + model.addAttribute("guides", guideService.findAllMetaData().stream() + .map(guideMetaToDtoConverter::convertForMetaData) + .toList()); + return "guides"; + } + + @GetMapping(value = "/{name}") + public String getGuide(@PathVariable(value = "name") String name, Model model) { + var text = Optional.ofNullable(guideService.findGuideByTitle(name) + .orElseThrow().getHtmlText()) + .orElse("Oops, nothing here."); + model.addAttribute("guide", text); + model.addAttribute("title", name); + return "guide"; + } +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/IndexController.java b/src/main/java/com/wimdupont/personalweb/controller/web/IndexController.java @@ -0,0 +1,23 @@ +package com.wimdupont.personalweb.controller.web; + +import com.wimdupont.personalweb.api.AffirmationApi; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class IndexController { + + private final AffirmationApi affirmationApi; + + public IndexController(AffirmationApi affirmationApi) { + this.affirmationApi = affirmationApi; + } + + @GetMapping("/") + public String home(Model model) { + model.addAttribute("affirmation", affirmationApi.getAffirmation()); + return "home"; + } + +} diff --git a/src/main/java/com/wimdupont/personalweb/controller/web/LinkController.java b/src/main/java/com/wimdupont/personalweb/controller/web/LinkController.java @@ -0,0 +1,17 @@ +package com.wimdupont.personalweb.controller.web; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/links") +public class LinkController { + + @GetMapping + public String getRss(Model model) { + return "links"; + } + +} diff --git a/src/main/java/com/wimdupont/personalweb/exceptions/AtleastOneNameRequiredException.java b/src/main/java/com/wimdupont/personalweb/exceptions/AtleastOneNameRequiredException.java @@ -0,0 +1,10 @@ +package com.wimdupont.personalweb.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class AtleastOneNameRequiredException extends RuntimeException { + + +} diff --git a/src/main/java/com/wimdupont/personalweb/model/Coin.java b/src/main/java/com/wimdupont/personalweb/model/Coin.java @@ -0,0 +1,5 @@ +package com.wimdupont.personalweb.model; + +public enum Coin { + HEADS, TAILS +} diff --git a/src/main/java/com/wimdupont/personalweb/model/Gender.java b/src/main/java/com/wimdupont/personalweb/model/Gender.java @@ -0,0 +1,7 @@ +package com.wimdupont.personalweb.model; + +public enum Gender { + MALE, + FEMALE + +} diff --git a/src/main/java/com/wimdupont/personalweb/model/InszGenerator.java b/src/main/java/com/wimdupont/personalweb/model/InszGenerator.java @@ -0,0 +1,72 @@ +package com.wimdupont.personalweb.model; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; + +public record InszGenerator( + @Min(value = 0) + @Max(value = 2999) + Integer year, + @Min(value = 1) + @Max(value = 12) + Integer month, + @Min(value = 1) + @Max(value = 31) + Integer day, + @Min(value = 1) + @Max(value = 999) + Integer birthCounter, + Gender gender) { + + private InszGenerator(Builder builder) { + this(builder.year, + builder.month, + builder.day, + builder.birthCounter, + builder.gender); + } + + public static final class Builder { + private Integer year; + private Integer month; + private Integer day; + private Integer birthCounter; + private Gender gender; + + private Builder() { + } + + public static Builder newBuilder() { + return new Builder(); + } + + public Builder year(Integer val) { + year = val; + return this; + } + + public Builder month(Integer val) { + month = val; + return this; + } + + public Builder day(Integer val) { + day = val; + return this; + } + + public Builder birthCounter(Integer val) { + birthCounter = val; + return this; + } + + public Builder gender(Gender val) { + gender = val; + return this; + } + + public InszGenerator build() { + return new InszGenerator(this); + } + } +} diff --git a/src/main/java/com/wimdupont/personalweb/service/InszService.java b/src/main/java/com/wimdupont/personalweb/service/InszService.java @@ -0,0 +1,42 @@ +package com.wimdupont.personalweb.service; + +import com.wimdupont.personalweb.model.InszGenerator; +import jakarta.validation.Valid; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +@Service +@Validated +public class InszService { + + public String generate(@Valid InszGenerator insz) { + if (insz.gender() == null) { + return "Random!"; + } + return insz.gender().name(); + } + + public boolean validate(String insz) { + if (insz == null) { + return false; + } + insz = insz.replaceAll("[.-]", ""); + + if (insz.length() != 11) { + return false; + } + + var baseNumber = insz.substring(0, 9); + var checksum = insz.substring(9, 11); + + return hasCorrectChecksum(baseNumber, checksum) || hasCorrectChecksum(2 + baseNumber, checksum); + } + + private boolean hasCorrectChecksum(String baseNumber, String checkSum) throws NumberFormatException { + try { + return 97 - Integer.parseInt(baseNumber) % 97 == Integer.parseInt(checkSum); + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/src/main/resources/api-documentation.md b/src/main/resources/api-documentation.md @@ -0,0 +1,3 @@ +Free of charge to use any amount of requests. + +*[Donations are accepted](https://wimdupont.com/donate)* diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties @@ -0,0 +1 @@ +server.url= diff --git a/src/main/resources/static/images/swagger.svg b/src/main/resources/static/images/swagger.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" + preserveAspectRatio="xMidYMid"> + <g> + <path d="M127.059657,255.996921 C58.8506544,255.526472 -0.457073619,198.918442 0.00265506057,126.998303 C0.444649399,57.7958628 57.9516598,-0.468967577 129.11002,0.00284555012 C198.267128,0.462386081 256.613109,57.8667711 255.995136,128.194199 C256.568091,197.883453 197.934268,256.489189 127.059657,255.996921 Z M127.059657,255.996921 C58.8506544,255.526472 -0.457073619,198.918442 0.00265506057,126.998303 C0.444649399,57.7958628 57.9516598,-0.468967577 129.11002,0.00284555012 C198.267128,0.462386081 256.613109,57.8667711 255.995136,128.194199 C256.568091,197.883453 197.934268,256.489189 127.059657,255.996921 Z" + fill="#FFFFFF"></path> + <path d="M127.184644,238.997327 C68.0323765,238.589271 16.6036091,189.498744 17.0023028,127.131428 C17.3860285,67.1185953 67.2554,16.5917106 128.963117,17.0024872 C188.934544,17.4010221 239.531905,67.1825241 238.995778,128.169251 C239.492444,188.602381 188.64743,239.424426 127.184644,238.997327 Z M127.184644,238.997327 C68.0323765,238.589271 16.6036091,189.498744 17.0023028,127.131428 C17.3860285,67.1185953 67.2554,16.5917106 128.963117,17.0024872 C188.934544,17.4010221 239.531905,67.1825241 238.995778,128.169251 C239.492444,188.602381 188.64743,239.424426 127.184644,238.997327 Z" + fill="#49A32B"></path> + <path d="M169.327319,127.956161 C169.042723,133.246373 164.421106,137.639224 159.866213,136.872586 C159.844426,136.872586 159.821277,136.872586 159.798128,136.872586 C154.753021,136.879395 150.658383,132.794288 150.652936,127.749182 C150.824511,122.690458 155.019915,118.703395 160.08,118.789182 C165.125106,118.813692 169.59966,123.077182 169.327319,127.956161 Z M88.2011915,179.220161 C90.1034894,179.27599 92.0071489,179.235139 94.2008511,179.235139 L94.2008511,193.021012 C80.5661277,195.326373 69.3348085,191.455054 66.5787234,179.929607 C65.6350638,175.69199 65.0549787,171.380841 64.8425532,167.04382 C64.5497872,162.452161 65.0563404,157.808756 64.706383,153.225267 C63.7368511,140.613182 62.1028085,136.30748 50,135.711054 L50,120.014714 C50.8674043,119.81182 51.7470638,119.662033 52.6321702,119.562629 C59.2677447,119.23582 62.0646809,117.201437 63.5489362,110.665267 C64.2243404,106.992756 64.6246809,103.275309 64.7431489,99.5428839 C65.268766,92.3258627 65.0822128,84.991735 66.2845957,77.8918201 C68.0221277,67.6245861 74.3962553,62.6366712 84.9249362,62.0783733 C87.9206809,61.9176925 90.9259574,62.0538627 94.3206809,62.0538627 L94.3206809,76.1447563 C92.9235745,76.2441605 91.6435745,76.4470542 90.3717447,76.4089265 C81.7916596,76.146118 81.3477447,79.0683308 80.7213617,86.1709691 C80.3305532,90.6250967 80.8697872,95.1554797 80.5661277,99.6245861 C80.2488511,104.071905 79.6537872,108.496075 78.7850213,112.869863 C77.547234,119.208586 73.6500426,123.922799 68.2495319,127.92348 C78.7332766,134.745607 79.9261277,145.346458 80.6069787,156.110714 C80.9732766,161.895224 80.8057872,167.720586 81.3926809,173.476501 C81.8502128,177.944246 83.5877447,179.08399 88.2011915,179.220161 Z M97.0372766,118.789182 C97.0917447,118.789182 97.1448511,118.789182 97.1993191,118.789182 C102.211745,118.872246 106.209702,123.002288 106.126638,128.016075 C106.126638,128.180841 106.121191,128.344246 106.11166,128.50765 C105.829787,133.407054 101.630298,137.149012 96.7308936,136.867139 C96.5334468,136.871224 96.3373617,136.867139 96.1399149,136.857607 C91.1506383,136.609778 87.3065532,132.36399 87.554383,127.374714 C87.8022128,122.385437 92.048,118.541352 97.0372766,118.789182 Z M128.273362,118.789182 C133.755574,118.746969 137.396766,122.29965 137.425362,127.719224 C137.455319,133.284501 134.003404,136.845352 128.556596,136.868501 C123.017191,136.893012 119.370553,133.389352 119.340596,128.002458 C119.324255,127.727395 119.32017,127.452331 119.32834,127.177267 C119.482213,122.390884 123.486979,118.635309 128.273362,118.789182 Z M193.673191,111.92348 C195.131574,117.370288 197.970723,119.284841 203.704851,119.546288 C204.644426,119.589863 205.579915,119.749182 206.868085,119.892161 L206.868085,135.584416 C206.170894,135.813182 205.456,135.984756 204.730213,136.096416 C197.046128,136.574373 193.54383,139.726714 192.76766,147.431224 C192.272,152.349692 192.312851,157.322629 191.972426,162.258799 C191.829447,167.678373 191.336511,173.082969 190.49634,178.438544 C188.535489,188.142033 182.477277,192.982884 172.467404,193.573863 C169.245617,193.764501 166.000681,193.60382 162.526979,193.60382 L162.526979,179.578288 C164.396596,179.462544 166.046979,179.303224 167.701447,179.263735 C173.682043,179.120756 175.796766,177.192586 176.089532,171.252841 C176.413617,164.727565 176.555234,158.194118 176.846638,151.66748 C177.270128,142.233607 179.853277,133.806033 188.641702,127.922118 C183.612936,124.336756 179.575489,119.994288 178.529702,114.138969 C177.264681,107.041778 176.85617,99.7879903 176.175319,92.5913946 C175.838979,88.9937776 175.855319,85.3648414 175.504,81.7699478 C175.125447,77.8890967 172.459234,76.5464584 168.926979,76.4593095 C166.903489,76.4102882 164.87183,76.4497776 162.284596,76.4497776 L162.284596,62.7537776 C178.793872,60.0126712 190.198128,65.5057776 191.257532,81.3015222 C191.701447,87.9343733 191.636085,94.5985435 192.060936,101.231395 C192.247489,104.839905 192.786723,108.421182 193.673191,111.92348 Z" + fill="#FFFFFF"></path> + </g> +</svg> diff --git a/src/main/resources/templates/links.html b/src/main/resources/templates/links.html @@ -11,6 +11,23 @@ <div th:insert="~{navigation}"/> </header> <p class="subheader">Links to websites on various topics</p> + <ul> + <li><a target="_blank" th:href="@{{baseUrl}/swagger-ui.html(baseUrl=${@serverUrl})}"> + <img alt="OpenAPI" title="API" src="/images/swagger.svg" width="12" height="12"> + API</a> + - OpenAPI Swagger page with various operations for random number generation etc. + </li> + <li><a target="_blank" href="https://gitlab.com/users/WimDupont/projects"> + <img alt="Git" title="Git" src="/images/git.png" width="12" height="12"> + Git</a> + - Git page with source code of this website and other projects + </li> + <li><a href="/rss.xml"> + <img alt="RSS Feed" title="RSS Feed" src="/images/rssfeed.png" width="12" height="12"> + RSS</a> + - Feed of the blog page + </li> + </ul> <h3>Privacy</h3> <ul> <li> diff --git a/src/main/resources/templates/navigation.html b/src/main/resources/templates/navigation.html @@ -7,6 +7,9 @@ <a href="/contact">📧 Contact</a> <a href="/donate">💰 Donate</a> <div class="right"> + <a class="navimg" target="_blank" th:href="@{{baseUrl}/swagger-ui.html(baseUrl=${@serverUrl})}"> + <img alt="OpenAPI" title="API" src="/images/swagger.svg" width="20" height="20"> + </a> <a class="navimg" target="_blank" href="https://gitlab.com/users/WimDupont/projects"> <img alt="Git" title="Git" src="/images/git.png" width="20" height="20"> </a>