commit f460c46f4bc8a8542da80c3bea7d93dad69eb6f1 Author: Johannes Loher 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; +}