Changeset 18:5d477ac9df07

Added extension test submodule
author unexist
date Mon, 16 Aug 2021 11:27:56 +0200
parents 74cc1a9396df
children 2dcc5fdafdba
files todo-service-extension/pom.xml todo-service-extension/src/main/java/dev/unexist/showcase/todo/adapter/TodoResource.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/adapter/TodoSink.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/application/.gitkeep todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/DueDate.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/Todo.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoBase.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoRepository.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoService.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/infrastructure/persistence/ListTodoRepository.java todo-service-extension/src/main/java/dev/unexist/showcase/todo/infrastructure/serializer/DateSerializer.java todo-service-extension/src/main/resources/META-INF/resources/index.html todo-service-extension/src/main/resources/application.properties todo-service-extension/src/test/java/dev/unexist/showcase/todo/application/TodoResourceTest.java
diffstat 13 files changed, 1122 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/pom.xml	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>todo-service-extension</artifactId>
+    <version>0.1</version>
+
+    <parent>
+        <groupId>dev.unexist.showcase</groupId>
+        <artifactId>showcase-eventbus-quarkus</artifactId>
+        <version>0.1</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <properties>
+        <smallrye-reactive.version>3.8.0</smallrye-reactive.version>
+        <smallrye-reactive-messaging-vertx-eventbus.version>3.1.0</smallrye-reactive-messaging-vertx-eventbus.version>
+    </properties>
+
+    <dependencies>
+        <!-- Quarkus -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-container-image-jib</artifactId>
+        </dependency>
+
+        <!-- Extension -->
+        <dependency>
+            <groupId>dev.unexist.showcase</groupId>
+            <artifactId>eventbus-extension</artifactId>
+            <version>0.1</version>
+        </dependency>
+
+        <!-- Testing -->
+        <dependency>
+            <groupId>io.smallrye.reactive</groupId>
+            <artifactId>smallrye-reactive-messaging-in-memory</artifactId>
+            <version>${smallrye-reactive.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/adapter/TodoResource.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,156 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo resource
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.adapter;
+
+import dev.unexist.showcase.todo.domain.todo.Todo;
+import dev.unexist.showcase.todo.domain.todo.TodoBase;
+import dev.unexist.showcase.todo.domain.todo.TodoService;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+
+@Path("/todo")
+public class TodoResource {
+
+    @Inject
+    TodoService todoService;
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Create new todo")
+    @APIResponses({
+            @APIResponse(responseCode = "201", description = "Todo created"),
+            @APIResponse(responseCode = "406", description = "Bad data"),
+            @APIResponse(responseCode = "500", description = "Server error")
+    })
+    public Response create(TodoBase base, @Context UriInfo uriInfo) {
+        Response.ResponseBuilder response;
+
+        if (this.todoService.create(base)) {
+            URI uri = uriInfo.getAbsolutePathBuilder().build();
+
+            response = Response.created(uri);
+        } else {
+            response = Response.status(Response.Status.NOT_ACCEPTABLE);
+        }
+
+        return response.build();
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Get all todos")
+    @APIResponses({
+            @APIResponse(responseCode = "200", description = "List of todo", content =
+                @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = Todo.class))),
+            @APIResponse(responseCode = "204", description = "Nothing found"),
+            @APIResponse(responseCode = "500", description = "Server error")
+    })
+    public Response getAll() {
+        List<Todo> todoList = this.todoService.getAll();
+
+        Response.ResponseBuilder response;
+
+        if (todoList.isEmpty()) {
+            response = Response.noContent();
+        } else {
+            response = Response.ok(Entity.json(todoList));
+        }
+
+        return response.build();
+    }
+
+    @GET
+    @Path("{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Get todo by id")
+    @APIResponses({
+            @APIResponse(responseCode = "200", description = "Todo found", content =
+                @Content(schema = @Schema(implementation = Todo.class))),
+            @APIResponse(responseCode = "404", description = "Todo not found"),
+            @APIResponse(responseCode = "500", description = "Server error")
+    })
+    public Response getAll(@PathParam("id") int id) {
+        Optional<Todo> result = this.todoService.findById(id);
+
+        Response.ResponseBuilder response;
+
+        if (result.isPresent()) {
+            response = Response.ok(Entity.json(result.get()));
+        } else {
+            response = Response.status(Response.Status.NOT_FOUND);
+        }
+
+        return response.build();
+    }
+
+    @PUT
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Update todo by id")
+    @APIResponses({
+            @APIResponse(responseCode = "204", description = "Todo updated"),
+            @APIResponse(responseCode = "404", description = "Todo not found"),
+            @APIResponse(responseCode = "500", description = "Server error")
+    })
+    public Response update(@PathParam("id") int id, TodoBase base) {
+        Response.ResponseBuilder response;
+
+        if (this.todoService.update(id, base)) {
+            response = Response.noContent();
+        } else {
+            response = Response.status(Response.Status.NOT_FOUND);
+        }
+
+        return response.build();
+    }
+
+    @DELETE
+    @Path("{id}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(summary = "Delete todo by id")
+    public Response delete(@PathParam("id") int id) {
+        Response.ResponseBuilder response;
+
+        if (this.todoService.delete(id)) {
+            response = Response.noContent();
+        } else {
+            response = Response.status(Response.Status.NOT_FOUND);
+        }
+
+        return response.build();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/adapter/TodoSink.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,78 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo sink
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.adapter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkus.vertx.ConsumeEvent;
+import io.vertx.mutiny.core.eventbus.EventBus;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.microprofile.reactive.messaging.Channel;
+import org.eclipse.microprofile.reactive.messaging.Emitter;
+import org.eclipse.microprofile.reactive.messaging.Incoming;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+@ApplicationScoped
+public class TodoSink {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TodoSink.class);
+
+    static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Inject
+    EventBus bus;
+
+    @Channel("todo_out")
+    Emitter<String> emitter;
+
+    @Incoming("todo_in")
+    public void consumeAll(String message) {
+        String typeName = StringUtils.EMPTY;
+
+        LOGGER.info("consumeAll: {}", message);
+
+        try {
+            JsonNode json = MAPPER.readTree(message);
+
+            typeName = json.get("type").asText();
+        } catch (JsonProcessingException e) {
+            LOGGER.error("Error reading JSON", e);
+        }
+
+        this.bus.send(typeName, message);
+    }
+
+    @ConsumeEvent("todo_in1")
+    public void consumeTodoIn1(String message) {
+        LOGGER.info("consumeTest1: {}", message);
+
+        this.emitter.send(message);
+    }
+
+    @ConsumeEvent("todo_in2")
+    public void consumeTodoIn2(String message) {
+        LOGGER.info("consumeTest2: {}", message);
+
+        this.emitter.send(message);
+    }
+
+    @Incoming("todo_in3")
+    public void consumeTodoIn3(String message) {
+        LOGGER.info("consumeTest3: {}", message);
+
+        this.emitter.send(message);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/DueDate.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,65 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file DueDate class
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.domain.todo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import dev.unexist.showcase.todo.infrastructure.serializer.DateSerializer;
+
+import java.time.LocalDate;
+
+public class DueDate {
+    @JsonSerialize(using = DateSerializer.class)
+    private LocalDate start;
+
+    @JsonSerialize(using = DateSerializer.class)
+    private LocalDate due;
+
+    /**
+     * Get start date
+     *
+     * @return Start date
+     **/
+
+    public LocalDate getStart() {
+        return start;
+    }
+
+    /**
+     * Set start date
+     *
+     * @param  start  Date to set
+     **/
+
+    public void setStart(LocalDate start) {
+        this.start = start;
+    }
+
+    /**
+     * Get due date
+     *
+     * @return Due date
+     **/
+
+    public LocalDate getDue() {
+        return due;
+    }
+
+    /**
+     * Set due date
+     *
+     * @param  due  Date to set
+     **/
+
+    public void setDue(LocalDate due) {
+        this.due = due;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/Todo.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,66 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo class and aggregate root
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.domain.todo;
+
+public class Todo extends TodoBase {
+    private int id;
+
+    /**
+     * Constructor
+     **/
+
+    public Todo() {
+    }
+
+    /**
+     * Constructor
+     *
+     * @param  base  Base entry
+     **/
+
+    public Todo(final TodoBase base) {
+        this.update(base);
+    }
+
+    /**
+     * Update values from base
+     *
+     * @param  base  Todo base class
+     **/
+
+    public void update(final TodoBase base) {
+        this.setDueDate(base.getDueDate());
+        this.setTitle(base.getTitle());
+        this.setDescription(base.getDescription());
+        this.setDone(base.getDone());
+    }
+
+    /**
+     * Get id of entry
+     *
+     * @return Id of the entry
+     **/
+
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Set id of entry
+     *
+     * @param  id  Id of the entry
+     **/
+
+    public void setId(int id) {
+        this.id = id;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoBase.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,117 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo base class
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.domain.todo;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Objects;
+
+public class TodoBase {
+
+    @NotBlank
+    private String title;
+
+    @NotBlank
+    private String description;
+
+    private Boolean done;
+
+    @NotNull
+    private DueDate dueDate;
+
+    /**
+     * Get title of the entry
+     *
+     * @return Title of the entry
+     **/
+
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * Set title of the entry
+     *
+     * @param  title  Title of the entry
+     **/
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * Get description of entry
+     *
+     * @return Description of the entry
+     **/
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Set description of the entry
+     *
+     * @param description
+     *          Description of the entry
+     **/
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * Get done state of entry
+     *
+     * @return Done state of the entry
+     **/
+
+    public Boolean getDone() {
+        return done;
+    }
+
+    /**
+     * Set done state of entry
+     *
+     * @param  done  Done state of the entry
+     **/
+
+    public void setDone(Boolean done) {
+        this.done = done;
+    }
+
+    /**
+     * Get due state of the entry
+     *
+     * @return Due state of the entry
+     **/
+
+    public DueDate getDueDate() {
+        return dueDate;
+    }
+
+    /**
+     * Set due date of the entry
+     *
+     * @param  dueDate  Due date of the entry
+     **/
+
+    public void setDueDate(DueDate dueDate) {
+        Objects.requireNonNull(dueDate, "DueDate cannot be null");
+
+        this.dueDate = dueDate;
+
+        if (null != dueDate.getStart() && null != dueDate.getDue()) {
+            this.done = dueDate.getStart().isBefore(dueDate.getDue());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoRepository.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,66 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo repository interface
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.domain.todo;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface TodoRepository {
+
+    /**
+     * Add {@link Todo} entry to list
+     *
+     * @param  todo  {@link Todo} entry to add
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    boolean add(Todo todo);
+
+    /**
+     * Update {@link Todo} with given id
+     *
+     * @param  todo  A {@link Todo} to update
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    boolean update(Todo todo);
+
+    /**
+     * Delete {@link Todo} with given id
+     *
+     * @param  id  Id to delete
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    boolean deleteById(int id);
+
+    /**
+     * Get all {@link Todo} entries
+     *
+     * @return List of all stored {@link Todo}
+     **/
+
+    List<Todo> getAll();
+
+    /**
+     * Find {@link Todo} by given id
+     *
+     * @param  id  Id to find
+     *
+     * @return A {@link Optional} with the result of the lookup
+     **/
+
+    Optional<Todo> findById(int id);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/domain/todo/TodoService.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,94 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo service and domain service
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.domain.todo;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Optional;
+
+@ApplicationScoped
+public class TodoService {
+
+    @Inject
+    TodoRepository todoRepository;
+
+    /**
+     * Create new {@link Todo} entry and store it in repository
+     *
+     * @param  base  A {@link TodoBase} entry
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    public boolean create(TodoBase base) {
+        Todo todo = new Todo(base);
+
+        return this.todoRepository.add(todo);
+    }
+
+    /**
+     * Update {@link Todo} at with given id
+     *
+     * @param  id    Id to update
+     * @param  base  Values for the entry
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    public boolean update(int id, TodoBase base) {
+        Optional<Todo> todo = this.findById(id);
+        boolean ret = false;
+
+        if (todo.isPresent()) {
+            todo.get().update(base);
+
+            ret = this.todoRepository.update(todo.get());
+        }
+
+        return ret;
+    }
+
+    /**
+     * Delete {@link Todo} with given id
+     *
+     * @param  id  Id to delete
+     *
+     * @return Either {@code true} on success; otherwise {@code false}
+     **/
+
+    public boolean delete(int id) {
+        return this.todoRepository.deleteById(id);
+    }
+
+    /**
+     * Get all {@link Todo} entries
+     *
+     * @return List of all {@link Todo}; might be empty
+     **/
+
+    public List<Todo> getAll() {
+        return this.todoRepository.getAll();
+    }
+
+    /**
+     * Find {@link Todo} by given id
+     *
+     * @param  id  Id to look for
+     *
+     * @return A {@link Optional} of the entry
+     **/
+
+    public Optional<Todo> findById(int id) {
+        return this.todoRepository.findById(id);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/infrastructure/persistence/ListTodoRepository.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,87 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo repository
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.infrastructure.persistence;
+
+import dev.unexist.showcase.todo.domain.todo.Todo;
+import dev.unexist.showcase.todo.domain.todo.TodoRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@ApplicationScoped
+public class ListTodoRepository implements TodoRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ListTodoRepository.class);
+
+    private final List<Todo> list;
+
+    /**
+     * Constructor
+     **/
+
+    ListTodoRepository() {
+        this.list = new ArrayList<>();
+    }
+
+    @Override
+    public boolean add(final Todo todo) {
+        todo.setId(this.list.size() + 1);
+
+        return this.list.add(todo);
+    }
+
+    @Override
+    public boolean update(final Todo todo) {
+        boolean ret = false;
+
+        try {
+            this.list.set(todo.getId(), todo);
+
+            ret = true;
+        } catch (IndexOutOfBoundsException e) {
+            LOGGER.warn("update: id={} not found", todo.getId());
+        }
+
+        return ret;
+    }
+
+    @Override
+    public boolean deleteById(int id) {
+        boolean ret = false;
+
+        try {
+            this.list.remove(id);
+
+            ret = true;
+        } catch (IndexOutOfBoundsException e) {
+            LOGGER.warn("deleteById: id={} not found", id);
+        }
+
+        return ret;
+    }
+
+    @Override
+    public List<Todo> getAll() {
+        return Collections.unmodifiableList(this.list);
+    }
+
+    @Override
+    public Optional<Todo> findById(int id) {
+        return this.list.stream()
+                .filter(t -> t.getId() == id)
+                .findFirst();
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/java/dev/unexist/showcase/todo/infrastructure/serializer/DateSerializer.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,39 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Todo serializer
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.infrastructure.serializer;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class DateSerializer extends JsonSerializer<LocalDate> {
+    public static final String PATTERN = "yyyy-MM-dd";
+
+    /**
+     * Serialize {@link LocalDate} to format
+     *
+     * @param  value        Value to convert
+     * @param  gen          A {@link JsonGenerator}
+     * @param  serializers  A {@link SerializerProvider}
+     * @throws IOException
+     **/
+
+    @Override
+    public void serialize(LocalDate value, JsonGenerator gen,
+                          SerializerProvider serializers) throws IOException {
+        gen.writeString(value.format(DateTimeFormatter.ofPattern(PATTERN)));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/resources/META-INF/resources/index.html	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,233 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>unexist.dev</title>
+    <style type="text/css">
+        :root {
+            /* Colors */
+            --font-color: #fff;
+            --font-color2: #aaa;
+            --link-color: #8cf;
+            --highlight-color: #ff8000;
+            --border-color: #446979;
+            --top-color: #313536;
+            --bottom-color: #1D2021;
+            --h1-color: #888;
+            --h2-color: #777;
+            --h3-color: #666;
+            --h4-color: #d7ba7d;
+            --code-color: #d7ba7d;
+            --cite-color: #777;
+
+            /* Misc */
+            --margin: 10px;
+            --padding: 10px;
+            --radius: 8px;
+            --border: 1px solid var(--top-color);
+        }
+
+        * {
+            margin: 0px;
+            padding: 0px;
+        }
+
+        /* Layout */
+        body {
+            background-color: var(--bottom-color);
+            color: var(--font-color);
+            font-family: HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;
+        }
+
+        nav {
+            background-color: var(--bottom-color);
+            border-radius: var(--radius) var(--radius) 0px 0px;
+            font-size: 90%;
+        }
+
+        footer {
+            color: var(--font-color2);
+            background-color: var(--top-color);
+            border-left: var(--border);
+            border-right: var(--border);
+            border-bottom: var(--border);
+            border-radius: 0px 0px var(--radius) var(--radius);
+            font-size: 70%;
+            text-align: center;
+        }
+
+        footer a:link, footer a:visited {
+            color: var(--font-color2);
+            text-decoration: underline;
+        }
+
+        footer a:hover {
+            color: var(--link-color);
+            text-decoration: none;
+        }
+
+        #container {
+            width: 100%;
+        }
+
+        #split {
+            display: table;
+            padding-top: var(--padding);
+            border-left: var(--border);
+            border-right: var(--border);
+        }
+
+        #content {
+            display: table-cell;
+            padding-right: var(--padding);
+        }
+
+        #content h1 {
+            text-decoration: none;
+            border-bottom: 1px solid var(--h1-color);
+            margin-bottom: 10px;
+        }
+
+        #content h2 {
+            text-decoration: none;
+            border-bottom: 1px solid var(--h2-color);
+            margin-bottom: 10px;
+        }
+
+        #sidebar a {
+            display: block;
+        }
+
+        #header {
+            background-color: var(--top-color);
+            width: 100%;
+        }
+
+        #headline, #split, nav, footer {
+            width: 70%;
+            margin: 0px auto;
+            padding: var(--padding);
+
+            /* Stupid hack */
+            min-width: 844px;
+        }
+
+        @media screen and (max-width: 899px) {
+            #headline, #split, nav, footer {
+                width: 100%;
+            }
+        }
+
+        #headline h1 {
+            display: inline-block;
+            text-decoration: none;
+            border-bottom: none;
+        }
+
+        #headline h2 {
+            display: inline-block;
+            padding-left: var(--padding);
+            border-bottom: none;
+        }
+
+        /* Text */
+        a:link, a:visited {
+            text-decoration: none;
+            color: var(--link-color);
+        }
+
+        a:hover {
+            color: var(--highlight-color);
+        }
+
+        h1 {
+            color: var(--h1-color);
+            text-decoration: none;
+            border-bottom: 1px solid var(--h1-color);
+        }
+
+        h2 {
+            color: var(--h2-color);
+            text-decoration: none;
+            border-bottom: 1px solid var(--h2-color);
+        }
+
+        h3 {
+            color: var(--h3-color);
+            text-decoration: underline;
+            display: inline;
+        }
+
+        h3, h4 {
+            padding-top: 10px;
+        }
+
+        h4 {
+            color: var(--h4-color);
+        }
+
+        .post-content li {
+            margin-left: 40px;
+            padding: 0px 0px var(--padding) 0px;
+            color: var(--font-color2);
+        }
+    </style>
+</head>
+<body>
+<div id="container">
+    <div id="header">
+        <div id="headline">
+            <h1>unexist.dev</h1>
+            <h2></h2>
+            <h2></h2>
+            <p>Showcase for @project.artifactId@</p>
+        </div>
+        <nav>
+            <a href="/">Home</a>
+            <a href="https://unexist.dev">Projects</a>
+            <a href="https://hg.unexist.dev">Repositories</a>
+        </nav>
+    </div>
+    <div id="split">
+        <div id="content">
+            <div id="posts">
+                <h2>Information</h2>
+
+                <div class="post-content">
+                    <ul>
+                        <li>
+                            <h3>Group</h3>: @project.groupId@
+                        </li>
+                        <li>
+                            <h3>Artifact</h3>: @project.artifactId@
+                        </li>
+                        <li>
+                            <h3>Version</h3>: @project.version@
+                        </li>
+                        <li>
+                            <h3>Build</h3> on @timestamp@ by @hg.author@
+                        </li>
+                    </ul>
+                </div>
+
+                <h2>Links</h2>
+
+                <div class="post-content">
+                    <ul>
+                        <li><a href="/q/dev">Developer console</a></li>
+                        <li><a href="/q/swagger-ui">Swagger UI</a></li>
+                    </ul>
+                </div>
+            </div>
+        </div>
+    </div>
+    <footer>
+        <a href="/feed.xml">RSS</a>
+        &middot;
+        <a href="https://github.com/unexist">unexist @ GitHub</a>
+        &middot;
+        <a href="https://twitter.com/chkappel">unexist @ Twitter</a>
+    </footer>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/main/resources/application.properties	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,49 @@
+# Configuration file
+# key = value
+#quarkus.swagger-ui.always-include=true
+#quarkus.servlet.context-path=/todo
+
+# Disable pass-though of infrastructure health
+#quarkus.datasource.health=false
+#quarkus.reactive-messaging.enabled=false
+#kafka.health.enabled=false
+
+# Opencontainers labels (https://github.com/opencontainers/image-spec/blob/master/annotations.md)
+quarkus.jib.labels."org.opencontainers.image.created"=@timestamp@
+quarkus.jib.labels."org.opencontainers.image.authors"=@hg.author@
+quarkus.jib.labels."org.opencontainers.image.url"=https://unexist.dev
+#quarkus.jib.labels."org.opencontainers.image.documentation"=DOCS
+#quarkus.jib.labels."org.opencontainers.image.source"=SRC
+quarkus.jib.labels."org.opencontainers.image.version"=@project.version@
+quarkus.jib.labels."org.opencontainers.image.revision"=@hg.rev@
+quarkus.jib.labels."org.opencontainers.image.licenses"=GPLv3
+quarkus.jib.labels."org.opencontainers.image.title"=@project.artifactId@
+quarkus.jib.labels."org.opencontainers.image.description"=@project.name@
+
+# OpenAPI3 specifications (https://quarkus.io/blog/openapi-for-everyone)
+mp.openapi.extensions.smallrye.info.title=OpenAPI for @project.artifactId@
+%dev.mp.openapi.extensions.smallrye.info.title=OpenAPI for @project.artifactId@ [development]
+%test.mp.openapi.extensions.smallrye.info.title=OpenAPI for @project.artifactId@ [test]
+mp.openapi.extensions.smallrye.info.version=@project.version@
+mp.openapi.extensions.smallrye.info.description=Last build on @timestamp@
+mp.openapi.extensions.smallrye.info.contact.email=christoph@unexist.dev
+mp.openapi.extensions.smallrye.info.contact.name=@hg.author@
+mp.openapi.extensions.smallrye.info.contact.url=https://unexist.dev
+mp.openapi.extensions.smallrye.info.license.name=Apache License v2.0
+mp.openapi.extensions.smallrye.info.license.url=https://www.apache.org/licenses/LICENSE-2.0
+
+# Messaging
+mp.messaging.incoming.todo_in.connector=smallrye-kafka
+%test.mp.messaging.incoming.todo_in.connector=smallrye-in-memory
+mp.messaging.incoming.todo_in.topic=todo_in
+mp.messaging.incoming.todo_in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
+#mp.messaging.incoming.todo_in.bootstrap.servers=localhost:9092
+
+mp.messaging.outgoing.todo_out.connector=smallrye-kafka
+%test.mp.messaging.outgoing.todo_out.connector=smallrye-in-memory
+mp.messaging.outgoing.todo_out.topic=todo_out
+mp.messaging.outgoing.todo_out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
+#mp.messaging.outgoing.todo_out.bootstrap.servers=localhost:9092
+
+mp.messaging.incoming.todo_in3.connector=smallrye-vertx-eventbus
+mp.messaging.incoming.todo_in3.address=todo_in3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/todo-service-extension/src/test/java/dev/unexist/showcase/todo/application/TodoResourceTest.java	Mon Aug 16 11:27:56 2021 +0200
@@ -0,0 +1,29 @@
+/**
+ * @package Showcase-Eventbus-Quarkus
+ *
+ * @file Stupid integration test
+ * @copyright 2020-2021 Christoph Kappel <christoph@unexist.dev>
+ * @version $Id$
+ *
+ * This program can be distributed under the terms of the Apache License v2.0.
+ * See the file LICENSE for details.
+ **/
+
+package dev.unexist.showcase.todo.application;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+
+@Disabled
+public class TodoResourceTest {
+
+    @Test
+    public void testTodoEndpoint() {
+        given()
+          .when().get("/todo")
+          .then()
+             .statusCode(204);
+    }
+}
\ No newline at end of file