feat: Add notifications

This commit is contained in:
Johannes Loher 2019-01-03 03:33:42 +01:00
parent 2d2853d363
commit f9f4bbf19f
15 changed files with 274 additions and 32 deletions

View file

@ -6,7 +6,7 @@
"botan-math": "1.0.3", "botan-math": "1.0.3",
"ddmp": "0.0.1-0.dev.3", "ddmp": "0.0.1-0.dev.3",
"diet-ng": "1.5.0", "diet-ng": "1.5.0",
"eventcore": "0.8.39", "eventcore": "0.8.40",
"fluent-asserts": "0.12.3", "fluent-asserts": "0.12.3",
"libasync": "0.8.3", "libasync": "0.8.3",
"libdparse": "0.8.8", "libdparse": "0.8.8",
@ -14,9 +14,9 @@
"memutils": "0.4.13", "memutils": "0.4.13",
"mir-linux-kernel": "1.0.1", "mir-linux-kernel": "1.0.1",
"openssl": "1.1.6+1.0.1g", "openssl": "1.1.6+1.0.1g",
"stdx-allocator": "2.77.4", "stdx-allocator": "2.77.5",
"taggedalgebraic": "0.10.12", "taggedalgebraic": "0.10.12",
"vibe-core": "1.4.5", "vibe-core": "1.4.6",
"vibe-d": "0.8.4" "vibe-d": "0.8.4"
} }
} }

View file

@ -1,12 +1,15 @@
module d_webservice_example.application; module d_webservice_example.application;
import aermicioi.aedi : locate, singleton; import aermicioi.aedi : Container, locate, singleton;
import vibe.vibe; import vibe.http.router : URLRouter;
void main() @safe void main() @safe
{ {
import d_webservice_example.component_registration : registerComponents; import d_webservice_example.component_registration : registerComponents;
import d_webservice_example.controller.todo_controller : TodoController; import vibe.core.core : runApplication;
import vibe.http.server : HTTPServerSettings, listenHTTP;
setupLogging;
auto container = singleton; auto container = singleton;
scope (exit) scope (exit)
@ -15,12 +18,38 @@ void main() @safe
container.registerComponents; container.registerComponents;
container.instantiate; container.instantiate;
auto router = new URLRouter;
router.registerRestInterface(container.locate!TodoController);
auto settings = new HTTPServerSettings; auto settings = new HTTPServerSettings;
settings.port = 8080; settings.port = 8080;
settings.bindAddresses = ["::", "0.0.0.0"]; settings.bindAddresses = ["::", "0.0.0.0"];
auto router = setupRouter(container);
listenHTTP(settings, router); listenHTTP(settings, router);
runApplication; runApplication;
} }
void setupLogging() nothrow @safe
{
import vibe.core.log : setLogFormat, FileLogger;
debug
{
import vibe.core.log : LogLevel, setLogLevel;
setLogLevel(LogLevel.diagnostic);
}
setLogFormat(FileLogger.Format.threadTime, FileLogger.Format.threadTime);
}
URLRouter setupRouter(Container container) @safe
{
import d_webservice_example.controller.todo_controller : TodoController;
import d_webservice_example.controller.todo_notification_controller : TodoNotificationController;
import vibe.web.rest : registerRestInterface;
import vibe.web.web : registerWebInterface;
auto router = new URLRouter;
router.registerRestInterface(container.locate!TodoController);
router.registerWebInterface(container.locate!TodoNotificationController);
return router;
}

View file

@ -0,0 +1,43 @@
module d_webservice_example.business.todo_notification_service;
class TodoNotificationService
{
import d_webservice_example.data.todo_notification : TodoNotification;
import d_webservice_example.enums.todo_notification_type : TodoNotificationType;
import d_webservice_example.mapper.todo_mapper : asTodoTO;
import d_webservice_example.model.todo : Todo;
import std.traits : EnumMembers;
private:
bool[void delegate(scope TodoNotification) @safe] listeners;
public:
this() const nothrow pure @nogc @safe
{
}
void registerListener(scope void delegate(scope const TodoNotification) @safe listener) @safe
{
listeners[listener] = true;
}
void unRegisterListener(scope void delegate(scope const TodoNotification) @safe listener) @safe
{
listeners.remove(listener);
}
static foreach (method; EnumMembers!TodoNotificationType)
{
import std.conv: to;
import std.format : format;
mixin(format!q{
void onTodo%1$sd(const Todo todo) @safe
{
import std.algorithm.iteration : each;
immutable todoChange = TodoNotification(TodoNotificationType.%1$s, todo);
listeners.byKey.each!(listener => listener(todoChange));
}
}(method.to!string));
}
}

View file

@ -5,7 +5,9 @@ import std.uuid : UUID, randomUUID;
class TodoService class TodoService
{ {
import d_webservice_example.business.todo_notification_service : TodoNotificationService;
import d_webservice_example.data.todo_update_do : TodoUpdateDO; import d_webservice_example.data.todo_update_do : TodoUpdateDO;
import vibe.core.log : logInfo;
private: private:
TodoRepository todoRepository; TodoRepository todoRepository;
@ -17,10 +19,12 @@ public:
this.todoRepository = todoRepository; this.todoRepository = todoRepository;
} }
Todo createTodo(Todo newTodo) @safe Todo createTodo(const Todo newTodo) @safe
{ {
immutable todo = Todo(newTodo.title, newTodo.content, randomUUID); immutable createdTodo = todoRepository.save(Todo(newTodo.title,
return todoRepository.save(todo); newTodo.content, randomUUID));
logInfo("Created todo %s", createdTodo);
return createdTodo;
} }
Todo[] getAllTodos() @safe Todo[] getAllTodos() @safe
@ -46,13 +50,17 @@ public:
if (todoUpdate.content != null) if (todoUpdate.content != null)
todo.content = todoUpdate.content; todo.content = todoUpdate.content;
return todoRepository.save(todo); immutable updatedTodo = todoRepository.save(todo);
logInfo("Updated todo %s", updatedTodo);
return updatedTodo;
} }
void deleteTodo(UUID uuid) @safe Todo deleteTodo(UUID uuid) @safe
{ {
immutable todo = getTodoByUuid(uuid); immutable todo = getTodoByUuid(uuid);
todoRepository.remove(todo); todoRepository.remove(todo);
logInfo("Deleted todo %s", todo);
return todo;
} }
} }

View file

@ -4,11 +4,20 @@ import aermicioi.aedi;
void registerComponents(ConfigurableContainer container) @safe void registerComponents(ConfigurableContainer container) @safe
{ {
import d_webservice_example.business.todo_notification_service : TodoNotificationService;
import d_webservice_example.business.todo_service : TodoRepository, TodoService; import d_webservice_example.business.todo_service : TodoRepository, TodoService;
import d_webservice_example.controller.todo_controller : TodoController; import d_webservice_example.controller.todo_controller : TodoController;
import d_webservice_example.controller.todo_notification_controller : TodoNotificationController;
import d_webservice_example.dataaccess.in_memory_todo_repository : InMemoryTodoRepository; import d_webservice_example.dataaccess.in_memory_todo_repository : InMemoryTodoRepository;
import d_webservice_example.facade.todo_facade : TodoFacade;
container.configure.register!(TodoRepository, InMemoryTodoRepository); with (container.configure)
container.configure.register!TodoService.autowire; {
container.configure.register!TodoController.autowire; register!TodoService.autowire;
register!TodoNotificationService.autowire;
register!TodoController.autowire;
register!TodoNotificationController.autowire;
register!(TodoRepository, InMemoryTodoRepository).autowire;
register!TodoFacade.autowire;
}
} }

View file

@ -26,25 +26,26 @@ interface TodoApi
class TodoController : TodoApi class TodoController : TodoApi
{ {
import d_webservice_example.business.todo_service : TodoService; import d_webservice_example.facade.todo_facade : TodoFacade;
import d_webservice_example.mapper.todo_mapper : asTodoTO; import d_webservice_example.mapper.todo_mapper : asTodoTO;
import vibe.core.log : logDiagnostic;
private: private:
TodoService todoService; TodoFacade todoFacade;
public: public:
this(TodoService todoService) nothrow pure @nogc @safe this(TodoFacade todoFacade) nothrow pure @nogc @safe
{ {
this.todoService = todoService; this.todoFacade = todoFacade;
} }
// TODO: validation
override TodoTO addTodo(string title, string content) @safe override TodoTO addTodo(string title, string content) @safe
{ {
import d_webservice_example.model.todo : Todo; import d_webservice_example.model.todo : Todo;
return todoService.createTodo(Todo(title, content)).asTodoTO; logDiagnostic("Received request to add a todo with title '%s' and content '%s'", title, content);
return todoFacade.createTodo(Todo(title, content)).asTodoTO;
} }
override TodoTO[] getTodos() @safe override TodoTO[] getTodos() @safe
@ -52,29 +53,35 @@ public:
import std.algorithm.iteration : map; import std.algorithm.iteration : map;
import std.array : array; import std.array : array;
return todoService.getAllTodos.map!asTodoTO.array; logDiagnostic("Received request to get all todos");
return todoFacade.getAllTodos.map!asTodoTO.array;
} }
override TodoTO getTodo(UUID _uuid) @safe override TodoTO getTodo(UUID _uuid) @safe
{ {
return todoService.getTodoByUuid(_uuid).asTodoTO;
logDiagnostic("Received request to get todo '%s'", _uuid);
return todoFacade.getTodoByUuid(_uuid).asTodoTO;
} }
override TodoTO updateTodo(UUID _uuid, Nullable!string title, Nullable!string content) @safe override TodoTO updateTodo(UUID _uuid, Nullable!string title, Nullable!string content) @safe
{ {
import d_webservice_example.data.todo_update_do : TodoUpdateDO; import d_webservice_example.data.todo_update_do : TodoUpdateDO;
logDiagnostic("Received request to update todo '%s' with title '%s' and content '%s'",
_uuid, title, content);
TodoUpdateDO update; TodoUpdateDO update;
if (!title.isNull) if (!title.isNull)
update.title = title.get; update.title = title.get;
if (!content.isNull) if (!content.isNull)
update.content = content.get; update.content = content.get;
return todoService.updateTodo(_uuid, update).asTodoTO; return todoFacade.updateTodo(_uuid, update).asTodoTO;
} }
override void deleteTodo(UUID _uuid) @safe override void deleteTodo(UUID _uuid) @safe
{ {
todoService.deleteTodo(_uuid); logDiagnostic("Received request to delete todo '%s'", _uuid);
todoFacade.deleteTodo(_uuid);
} }
} }

View file

@ -0,0 +1,55 @@
module d_webservice_example.controller.todo_notification_controller;
import vibe.http.websockets : WebSocket;
import vibe.web.web : path;
@path("/api/v1/rt")
class TodoNotificationController
{
import d_webservice_example.business.todo_notification_service : TodoNotificationService;
private:
TodoNotificationService todoNotificationService;
public:
this(TodoNotificationService todoNotificationService) nothrow pure @nogc @safe
{
this.todoNotificationService = todoNotificationService;
}
@path("/todos") void getWebsocket(scope WebSocket socket) @safe
{
import d_webservice_example.data.todo_notification : TodoNotification;
import d_webservice_example.transport.todo_notification_cto : TodoNotificationCTO;
import vibe.core.log : logDiagnostic;
logDiagnostic("Received request for todo notifications via web socket connection");
auto callback = (scope const TodoNotification todoNotification) {
import d_webservice_example.mapper.todo_notification_mapper : asTodoNotificationCTO;
import vibe.data.json : serializeToJsonString;
logDiagnostic("Sending todo notification to client");
socket.send(todoNotification.asTodoNotificationCTO.serializeToJsonString);
};
todoNotificationService.registerListener(callback);
dropWebsocketInput(socket);
todoNotificationService.unRegisterListener(callback);
logDiagnostic("Client disconnected from web socket connection: %s(%s)",
socket.closeReason, socket.closeCode);
}
}
private:
void dropWebsocketInput(scope WebSocket socket) @safe
{
while (socket.connected)
{
socket.waitForData;
if (socket.connected)
socket.receive((scope IncomingWebSocketMessage) {});
}
}

View file

@ -0,0 +1,10 @@
module d_webservice_example.data.todo_notification;
import d_webservice_example.enums.todo_notification_type : TodoNotificationType;
import d_webservice_example.model.todo : Todo;
struct TodoNotification
{
TodoNotificationType type;
Todo todo;
}

View file

@ -17,6 +17,11 @@ private:
Todo[string] todos; Todo[string] todos;
public: public:
this() nothrow pure @nogc @safe
{
}
override Todo save(Todo todo) override Todo save(Todo todo)
{ {
import std.range.primitives : empty; import std.range.primitives : empty;

View file

@ -0,0 +1,8 @@
module d_webservice_example.enums.todo_notification_type;
enum TodoNotificationType
{
Create = "create",
Update = "update",
Delete = "delete"
}

View file

@ -0,0 +1,51 @@
module d_webservice_example.facade.todo_facade;
class TodoFacade
{
import d_webservice_example.business.todo_notification_service : TodoNotificationService;
import d_webservice_example.business.todo_service : TodoService;
import d_webservice_example.data.todo_update_do : TodoUpdateDO;
import d_webservice_example.model.todo : Todo;
import std.uuid : UUID;
private:
TodoNotificationService todoNotificationService;
TodoService todoService;
public:
this(TodoNotificationService todoNotificationService, TodoService todoService) nothrow pure @nogc @safe
{
this.todoNotificationService = todoNotificationService;
this.todoService = todoService;
}
Todo createTodo(Todo newTodo) @safe
{
immutable todo = todoService.createTodo(newTodo);
todoNotificationService.onTodoCreated(todo);
return todo;
}
Todo[] getAllTodos() @safe
{
return todoService.getAllTodos;
}
Todo getTodoByUuid(const UUID uuid) @safe
{
return todoService.getTodoByUuid(uuid);
}
Todo updateTodo(UUID uuid, const TodoUpdateDO todoUpdate) @safe
{
immutable todo = todoService.updateTodo(uuid, todoUpdate);
todoNotificationService.onTodoUpdated(todo);
return todo;
}
void deleteTodo(UUID uuid) @safe
{
immutable todo = todoService.deleteTodo(uuid);
todoNotificationService.onTodoDeleted(todo);
}
}

View file

@ -3,7 +3,7 @@ module d_webservice_example.mapper.todo_mapper;
import d_webservice_example.model.todo : Todo; import d_webservice_example.model.todo : Todo;
import d_webservice_example.transport.todo_to : TodoTO; import d_webservice_example.transport.todo_to : TodoTO;
TodoTO asTodoTO(Todo todo) nothrow pure @safe @nogc TodoTO asTodoTO(const Todo todo) nothrow pure @safe @nogc
{ {
return TodoTO(todo.uuid, todo.title, todo.content); return TodoTO(todo.uuid, todo.title, todo.content);
} }

View file

@ -0,0 +1,11 @@
module d_webservice_example.mapper.todo_notification_mapper;
import d_webservice_example.data.todo_notification : TodoNotification;
import d_webservice_example.transport.todo_notification_cto : TodoNotificationCTO;
TodoNotificationCTO asTodoNotificationCTO(const TodoNotification todoNotification) nothrow pure @safe @nogc
{
import d_webservice_example.mapper.todo_mapper : asTodoTO;
return TodoNotificationCTO(todoNotification.type, todoNotification.todo.asTodoTO);
}

View file

@ -3,7 +3,6 @@ module d_webservice_example.model.todo;
struct Todo struct Todo
{ {
import std.uuid : UUID; import std.uuid : UUID;
import vibe.data.serialization : name;
this(string title, string content) nothrow pure @safe @nogc this(string title, string content) nothrow pure @safe @nogc
{ {
@ -28,9 +27,6 @@ struct Todo
string title; string title;
string content; string content;
UUID uuid; UUID uuid;
@name("_id")
string id; string id;
} }

View file

@ -0,0 +1,10 @@
module d_webservice_example.transport.todo_notification_cto;
import d_webservice_example.enums.todo_notification_type : TodoNotificationType;
import d_webservice_example.transport.todo_to : TodoTO;
struct TodoNotificationCTO
{
TodoNotificationType type;
TodoTO todo;
}