feat: Add notifications
This commit is contained in:
parent
2d2853d363
commit
f9f4bbf19f
15 changed files with 274 additions and 32 deletions
|
@ -6,7 +6,7 @@
|
|||
"botan-math": "1.0.3",
|
||||
"ddmp": "0.0.1-0.dev.3",
|
||||
"diet-ng": "1.5.0",
|
||||
"eventcore": "0.8.39",
|
||||
"eventcore": "0.8.40",
|
||||
"fluent-asserts": "0.12.3",
|
||||
"libasync": "0.8.3",
|
||||
"libdparse": "0.8.8",
|
||||
|
@ -14,9 +14,9 @@
|
|||
"memutils": "0.4.13",
|
||||
"mir-linux-kernel": "1.0.1",
|
||||
"openssl": "1.1.6+1.0.1g",
|
||||
"stdx-allocator": "2.77.4",
|
||||
"stdx-allocator": "2.77.5",
|
||||
"taggedalgebraic": "0.10.12",
|
||||
"vibe-core": "1.4.5",
|
||||
"vibe-core": "1.4.6",
|
||||
"vibe-d": "0.8.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
module d_webservice_example.application;
|
||||
|
||||
import aermicioi.aedi : locate, singleton;
|
||||
import vibe.vibe;
|
||||
import aermicioi.aedi : Container, locate, singleton;
|
||||
import vibe.http.router : URLRouter;
|
||||
|
||||
void main() @safe
|
||||
{
|
||||
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;
|
||||
scope (exit)
|
||||
|
@ -15,12 +18,38 @@ void main() @safe
|
|||
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"];
|
||||
|
||||
auto router = setupRouter(container);
|
||||
listenHTTP(settings, router);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -5,7 +5,9 @@ import std.uuid : UUID, randomUUID;
|
|||
|
||||
class TodoService
|
||||
{
|
||||
import d_webservice_example.business.todo_notification_service : TodoNotificationService;
|
||||
import d_webservice_example.data.todo_update_do : TodoUpdateDO;
|
||||
import vibe.core.log : logInfo;
|
||||
|
||||
private:
|
||||
TodoRepository todoRepository;
|
||||
|
@ -17,10 +19,12 @@ public:
|
|||
this.todoRepository = todoRepository;
|
||||
}
|
||||
|
||||
Todo createTodo(Todo newTodo) @safe
|
||||
Todo createTodo(const Todo newTodo) @safe
|
||||
{
|
||||
immutable todo = Todo(newTodo.title, newTodo.content, randomUUID);
|
||||
return todoRepository.save(todo);
|
||||
immutable createdTodo = todoRepository.save(Todo(newTodo.title,
|
||||
newTodo.content, randomUUID));
|
||||
logInfo("Created todo %s", createdTodo);
|
||||
return createdTodo;
|
||||
}
|
||||
|
||||
Todo[] getAllTodos() @safe
|
||||
|
@ -46,13 +50,17 @@ public:
|
|||
if (todoUpdate.content != null)
|
||||
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);
|
||||
todoRepository.remove(todo);
|
||||
logInfo("Deleted todo %s", todo);
|
||||
return todo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,20 @@ import aermicioi.aedi;
|
|||
|
||||
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.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.facade.todo_facade : TodoFacade;
|
||||
|
||||
container.configure.register!(TodoRepository, InMemoryTodoRepository);
|
||||
container.configure.register!TodoService.autowire;
|
||||
container.configure.register!TodoController.autowire;
|
||||
with (container.configure)
|
||||
{
|
||||
register!TodoService.autowire;
|
||||
register!TodoNotificationService.autowire;
|
||||
register!TodoController.autowire;
|
||||
register!TodoNotificationController.autowire;
|
||||
register!(TodoRepository, InMemoryTodoRepository).autowire;
|
||||
register!TodoFacade.autowire;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,25 +26,26 @@ interface 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 vibe.core.log : logDiagnostic;
|
||||
|
||||
private:
|
||||
TodoService todoService;
|
||||
TodoFacade todoFacade;
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
|
@ -52,29 +53,35 @@ public:
|
|||
import std.algorithm.iteration : map;
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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;
|
||||
if (!title.isNull)
|
||||
update.title = title.get;
|
||||
if (!content.isNull)
|
||||
update.content = content.get;
|
||||
|
||||
return todoService.updateTodo(_uuid, update).asTodoTO;
|
||||
return todoFacade.updateTodo(_uuid, update).asTodoTO;
|
||||
}
|
||||
|
||||
override void deleteTodo(UUID _uuid) @safe
|
||||
{
|
||||
todoService.deleteTodo(_uuid);
|
||||
logDiagnostic("Received request to delete todo '%s'", _uuid);
|
||||
todoFacade.deleteTodo(_uuid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {});
|
||||
}
|
||||
}
|
10
source/d_webservice_example/data/todo_notification.d
Normal file
10
source/d_webservice_example/data/todo_notification.d
Normal 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;
|
||||
}
|
|
@ -17,6 +17,11 @@ private:
|
|||
Todo[string] todos;
|
||||
|
||||
public:
|
||||
|
||||
this() nothrow pure @nogc @safe
|
||||
{
|
||||
}
|
||||
|
||||
override Todo save(Todo todo)
|
||||
{
|
||||
import std.range.primitives : empty;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module d_webservice_example.enums.todo_notification_type;
|
||||
|
||||
enum TodoNotificationType
|
||||
{
|
||||
Create = "create",
|
||||
Update = "update",
|
||||
Delete = "delete"
|
||||
}
|
51
source/d_webservice_example/facade/todo_facade.d
Normal file
51
source/d_webservice_example/facade/todo_facade.d
Normal 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);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ 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
|
||||
TodoTO asTodoTO(const Todo todo) nothrow pure @safe @nogc
|
||||
{
|
||||
return TodoTO(todo.uuid, todo.title, todo.content);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -3,7 +3,6 @@ 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
|
||||
{
|
||||
|
@ -28,9 +27,6 @@ struct Todo
|
|||
|
||||
string title;
|
||||
string content;
|
||||
|
||||
UUID uuid;
|
||||
|
||||
@name("_id")
|
||||
string id;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue