commit f460c46f4bc8a8542da80c3bea7d93dad69eb6f1
Author: Johannes Loher <johannes.loher@fg4f.de>
Date:   Tue Dec 18 22:18:06 2018 +0100

    initial commit

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c1d04dc
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,33 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.d]
+indent_style = space
+indent_size = 4
+tab_width = 4
+max_line_length = 120
+dfmt_brace_style = allman
+dfmt_soft_max_line_length = 80
+dfmt_align_switch_statements = true
+dfmt_outdent_attributes = true
+dfmt_split_operator_at_line_end = false
+dfmt_space_after_cast = true
+dfmt_space_after_keywords = true
+dfmt_space_before_function_parameters = false
+dfmt_selective_import_space = true
+dfmt_compact_labeled_statements = true
+dfmt_template_constraint_style = conditional_newline_indent
+dfmt_single_template_constraint_indent = false
+
+[*.sdl]
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+[*.json]
+indent_style = tab
+indent_size = 4
+tab_width = 4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4820f10
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+.dub
+docs.json
+__dummy.html
+docs/
+d-webservice-example
+d-webservice-example.so
+d-webservice-example.dylib
+d-webservice-example.dll
+d-webservice-example.a
+d-webservice-example.lib
+d-webservice-example-test-*
+*.exe
+*.o
+*.obj
+*.lst
diff --git a/dub.sdl b/dub.sdl
new file mode 100644
index 0000000..f04e92d
--- /dev/null
+++ b/dub.sdl
@@ -0,0 +1,13 @@
+name "d-webservice-example"
+description "An example webservice application in D."
+authors "Johannes Loher"
+copyright "Copyright © 2018, Johannes Loher"
+license "MIT"
+dependency "aedi" version="~>1.0.0"
+dependency "fluent-asserts" version="~>0.12.3"
+dependency "vibe-d" version="~>0.8.4"
+
+configuration "executable" {
+    targetType "executable"
+    mainSourceFile "source/d_webservice_example/application.d"
+}
diff --git a/dub.selections.json b/dub.selections.json
new file mode 100644
index 0000000..714698e
--- /dev/null
+++ b/dub.selections.json
@@ -0,0 +1,22 @@
+{
+	"fileVersion": 1,
+	"versions": {
+		"aedi": "1.0.0",
+		"botan": "1.12.10",
+		"botan-math": "1.0.3",
+		"ddmp": "0.0.1-0.dev.3",
+		"diet-ng": "1.5.0",
+		"eventcore": "0.8.39",
+		"fluent-asserts": "0.12.3",
+		"libasync": "0.8.3",
+		"libdparse": "0.8.8",
+		"libevent": "2.0.2+2.0.16",
+		"memutils": "0.4.13",
+		"mir-linux-kernel": "1.0.1",
+		"openssl": "1.1.6+1.0.1g",
+		"stdx-allocator": "2.77.4",
+		"taggedalgebraic": "0.10.12",
+		"vibe-core": "1.4.5",
+		"vibe-d": "0.8.4"
+	}
+}
\ No newline at end of file
diff --git a/source/d_webservice_example/application.d b/source/d_webservice_example/application.d
new file mode 100644
index 0000000..9fc2732
--- /dev/null
+++ b/source/d_webservice_example/application.d
@@ -0,0 +1,26 @@
+module d_webservice_example.application;
+
+import aermicioi.aedi : locate, singleton;
+import vibe.vibe;
+
+void main() @safe
+{
+    import d_webservice_example.component_registration : registerComponents;
+    import d_webservice_example.controller.todo_controller : TodoController;
+
+    auto container = singleton();
+    scope (exit)
+        container.terminate;
+
+    container.registerComponents();
+    container.instantiate;
+
+    auto router = new URLRouter;
+    router.registerRestInterface(container.locate!TodoController);
+    auto settings = new HTTPServerSettings;
+    settings.port = 8080;
+    settings.bindAddresses = ["::", "0.0.0.0"];
+    listenHTTP(settings, router);
+
+    runApplication();
+}
diff --git a/source/d_webservice_example/business/todo_service.d b/source/d_webservice_example/business/todo_service.d
new file mode 100644
index 0000000..b8f04d9
--- /dev/null
+++ b/source/d_webservice_example/business/todo_service.d
@@ -0,0 +1,68 @@
+module d_webservice_example.business.todo_service;
+
+import aermicioi.aedi : autowired, component;
+import d_webservice_example.model.todo : Todo;
+import std.uuid : UUID, randomUUID;
+
+@component class TodoService
+{
+    import d_webservice_example.data.todo_update_do : TodoUpdateDO;
+
+private:
+    TodoRepository todoRepository;
+
+public:
+
+    @autowired this(TodoRepository todoRepository) @safe
+    {
+        this.todoRepository = todoRepository;
+    }
+
+    Todo createTodo(Todo newTodo) @safe
+    {
+        immutable todo = Todo(newTodo.title, newTodo.content, randomUUID());
+        return todoRepository.save(todo);
+    }
+
+    Todo[] getAllTodos() @safe
+    {
+        return todoRepository.findAll();
+    }
+
+    Todo getTodoByUuid(const UUID uuid) @safe
+    {
+        import std.exception : enforce;
+        import vibe.http.common : HTTPStatus, HTTPStatusException;
+
+        immutable maybeTodo = todoRepository.findByUuid(uuid);
+        enforce(!maybeTodo.isNull, new HTTPStatusException(HTTPStatus.NotFound));
+        return maybeTodo.get;
+    }
+
+    Todo updateTodo(UUID uuid, const TodoUpdateDO todoUpdate) @safe
+    {
+        auto todo = getTodoByUuid(uuid);
+        if (todoUpdate.title != null)
+            todo.title = todoUpdate.title;
+        if (todoUpdate.content != null)
+            todo.content = todoUpdate.content;
+
+        return todoRepository.save(todo);
+    }
+
+    void deleteTodo(UUID uuid) @safe
+    {
+        immutable todo = getTodoByUuid(uuid);
+        todoRepository.remove(todo);
+    }
+}
+
+interface TodoRepository
+{
+    import std.typecons : Nullable;
+
+    Todo save(Todo todo) @safe;
+    bool remove(Todo todo) @safe;
+    Nullable!Todo findByUuid(UUID uuid) @safe;
+    Todo[] findAll() @safe;
+}
diff --git a/source/d_webservice_example/component_registration.d b/source/d_webservice_example/component_registration.d
new file mode 100644
index 0000000..cdab3e9
--- /dev/null
+++ b/source/d_webservice_example/component_registration.d
@@ -0,0 +1,10 @@
+module d_webservice_example.component_registration;
+
+import aermicioi.aedi : ConfigurableContainer, scan;
+
+void registerComponents(ConfigurableContainer container) @safe
+{
+    container.scan!(d_webservice_example.business.todo_service);
+    container.scan!(d_webservice_example.controller.todo_controller);
+    container.scan!(d_webservice_example.dataaccess.in_memory_todo_repository);
+}
diff --git a/source/d_webservice_example/controller/todo_controller.d b/source/d_webservice_example/controller/todo_controller.d
new file mode 100644
index 0000000..0a797fc
--- /dev/null
+++ b/source/d_webservice_example/controller/todo_controller.d
@@ -0,0 +1,81 @@
+module d_webservice_example.controller.todo_controller;
+
+import aermicioi.aedi : autowired, component;
+import d_webservice_example.transport.todo_to : TodoTO;
+import std.typecons : Nullable;
+import std.uuid : UUID;
+import vibe.web.rest : path;
+
+@path("/api/v1/todos")
+interface TodoApi
+{
+    @path("/")
+    TodoTO addTodo(string title, string content) @safe;
+
+    @path("/")
+    TodoTO[] getTodos() @safe;
+
+    @path("/:uuid")
+    TodoTO getTodo(UUID _uuid) @safe;
+
+    @path("/:uuid")
+    TodoTO updateTodo(UUID _uuid, Nullable!string title, Nullable!string content) @safe;
+
+    @path("/:uuid")
+    void deleteTodo(UUID _uuid) @safe;
+}
+
+@component class TodoController : TodoApi
+{
+    import d_webservice_example.business.todo_service : TodoService;
+    import d_webservice_example.mapper.todo_mapper : asTodoTO;
+
+private:
+    TodoService todoService;
+
+public:
+
+    @autowired this(TodoService todoService) @safe
+    {
+        this.todoService = todoService;
+    }
+
+    // TODO: validation
+    override TodoTO addTodo(string title, string content) @safe
+    {
+        import d_webservice_example.model.todo : Todo;
+
+        return todoService.createTodo(Todo(title, content)).asTodoTO;
+    }
+
+    override TodoTO[] getTodos() @safe
+    {
+        import std.algorithm.iteration : map;
+        import std.array : array;
+
+        return todoService.getAllTodos().map!asTodoTO.array;
+    }
+
+    override TodoTO getTodo(UUID _uuid) @safe
+    {
+        return todoService.getTodoByUuid(_uuid).asTodoTO;
+    }
+
+    override TodoTO updateTodo(UUID _uuid, Nullable!string title, Nullable!string content) @safe
+    {
+        import d_webservice_example.data.todo_update_do : TodoUpdateDO;
+
+        TodoUpdateDO update;
+        if (!title.isNull)
+            update.title = title.get;
+        if (!content.isNull)
+            update.content = content.get;
+
+        return todoService.updateTodo(_uuid, update).asTodoTO;
+    }
+
+    override void deleteTodo(UUID _uuid) @safe
+    {
+        todoService.deleteTodo(_uuid);
+    }
+}
diff --git a/source/d_webservice_example/data/todo_update_do.d b/source/d_webservice_example/data/todo_update_do.d
new file mode 100644
index 0000000..336ea49
--- /dev/null
+++ b/source/d_webservice_example/data/todo_update_do.d
@@ -0,0 +1,7 @@
+module d_webservice_example.data.todo_update_do;
+
+struct TodoUpdateDO
+{
+    string title;
+    string content;
+}
\ No newline at end of file
diff --git a/source/d_webservice_example/dataaccess/in_memory_todo_repository.d b/source/d_webservice_example/dataaccess/in_memory_todo_repository.d
new file mode 100644
index 0000000..fa13623
--- /dev/null
+++ b/source/d_webservice_example/dataaccess/in_memory_todo_repository.d
@@ -0,0 +1,45 @@
+module d_webservice_example.dataaccess.in_memory_todo_repository;
+
+import aermicioi.aedi : component, qualifier;
+import d_webservice_example.business.todo_service : TodoRepository;
+
+@component @qualifier!TodoRepository() class InMemoryTodoRepository : TodoRepository
+{
+    import d_webservice_example.model.todo : Todo;
+    import std.typecons : Nullable, nullable;
+    import std.uuid : randomUUID, UUID;
+
+private:
+    Todo[string] todos;
+
+public:
+    override Todo save(Todo todo)
+    {
+        todo.id = randomUUID.toString;
+        todos[todo.id] = todo;
+        return todo;
+    }
+
+    override Todo[] findAll()
+    {
+        import std.array : array;
+
+        return todos.byValue.array;
+    }
+
+    override Nullable!Todo findByUuid(UUID uuid)
+    {
+        import std.algorithm.searching : find;
+
+        auto foundTodos = todos.byValue.find!(todo => todo.uuid == uuid);
+        if (foundTodos.empty)
+            return Nullable!Todo.init;
+        else
+            return nullable(foundTodos.front);
+    }
+
+    override bool remove(Todo todo)
+    {
+        return todos.remove(todo.id);
+    }
+}
diff --git a/source/d_webservice_example/mapper/todo_mapper.d b/source/d_webservice_example/mapper/todo_mapper.d
new file mode 100644
index 0000000..51d7eff
--- /dev/null
+++ b/source/d_webservice_example/mapper/todo_mapper.d
@@ -0,0 +1,10 @@
+module d_webservice_example.mapper.todo_mapper;
+
+import d_webservice_example.model.todo : Todo;
+import d_webservice_example.transport.todo_to : TodoTO;
+
+TodoTO asTodoTO(Todo todo) nothrow pure @safe @nogc
+{
+
+    return TodoTO(todo.uuid, todo.title, todo.content);
+}
diff --git a/source/d_webservice_example/model/todo.d b/source/d_webservice_example/model/todo.d
new file mode 100644
index 0000000..4536e2a
--- /dev/null
+++ b/source/d_webservice_example/model/todo.d
@@ -0,0 +1,28 @@
+module d_webservice_example.model.todo;
+
+struct Todo
+{
+    import std.uuid : UUID;
+    import vibe.data.serialization : name;
+
+    this(string title, string content) nothrow pure @safe @nogc
+    {
+        this.title = title;
+        this.content = content;
+    }
+
+    this(string title, string content, UUID uuid) nothrow pure @safe @nogc
+    {
+        this.title = title;
+        this.content = content;
+        this.uuid = uuid;
+    }
+
+    string title;
+    string content;
+
+    UUID uuid;
+
+    @name("_id")
+    string id;
+}
diff --git a/source/d_webservice_example/transport/todo_to.d b/source/d_webservice_example/transport/todo_to.d
new file mode 100644
index 0000000..fce15ad
--- /dev/null
+++ b/source/d_webservice_example/transport/todo_to.d
@@ -0,0 +1,11 @@
+module d_webservice_example.transport.todo_to;
+
+struct TodoTO
+{
+    import std.typecons : Nullable;
+    import std.uuid : UUID;
+
+    UUID uuid;
+    string title;
+    string content;
+}