From 54a16633304c1899f4dd8231407044c133544bf9 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Tue, 8 Aug 2017 01:05:11 +0200 Subject: [PATCH 1/6] Added usage of mongodb for the storage of events and use poodinis for autowiring of components --- dub.json | 5 ++- source/app.d | 21 +++++++++- source/authenticator.d | 36 +++++++++++++++++ source/calendarwebapp.d | 56 ++++++++------------------ source/configuration.d | 24 +++++++++++ source/event.d | 88 +++++++++++++++++++++++++++++------------ views/showevents.dt | 18 ++++----- 7 files changed, 170 insertions(+), 78 deletions(-) create mode 100644 source/authenticator.d create mode 100644 source/configuration.d diff --git a/dub.json b/dub.json index 2e592e2..0f581f8 100644 --- a/dub.json +++ b/dub.json @@ -4,9 +4,10 @@ "Johannes Loher" ], "dependencies": { - "vibe-d": "~>0.7.30" + "vibe-d": "~>0.7.30", + "poodinis": "~>8.0.0" }, - "description": "A simple webapplication to edit and view calendar entries", + "description": "A simple webapplication to edit and view calendar entries", "copyright": "Copyright © 2017, Johannes Loher", "license": "MIT", "versions": [ diff --git a/source/app.d b/source/app.d index 9ce1598..07de544 100644 --- a/source/app.d +++ b/source/app.d @@ -1,6 +1,15 @@ -import calendarwebapp; +module app; + +import authenticator : Authenticator, MongoDBAuthenticator; +import calendarwebapp : CalendarWebapp; +import configuration : StringInjector; +import event : EventStore, MongoDBEventStore; + +import poodinis; import vibe.core.log : logInfo; +import vibe.db.mongo.client : MongoClient; +import vibe.db.mongo.mongo : connectMongoDB; import vibe.http.fileserver : serveStaticFiles; import vibe.http.router : URLRouter; import vibe.http.server : HTTPServerSettings, listenHTTP, MemorySessionStore; @@ -8,8 +17,16 @@ import vibe.web.web : registerWebInterface; shared static this() { + auto dependencies = new shared DependencyContainer(); + auto db = connectMongoDB("localhost"); + dependencies.register!MongoClient.existingInstance(db); + dependencies.register!(EventStore, MongoDBEventStore); + dependencies.register!(Authenticator, MongoDBAuthenticator); + dependencies.register!CalendarWebapp; + dependencies.register!(ValueInjector!string, StringInjector); + auto router = new URLRouter; - router.registerWebInterface(new CalendarWebapp); + router.registerWebInterface(dependencies.resolve!CalendarWebapp); router.get("*", serveStaticFiles("public")); auto settings = new HTTPServerSettings; diff --git a/source/authenticator.d b/source/authenticator.d new file mode 100644 index 0000000..7dceef9 --- /dev/null +++ b/source/authenticator.d @@ -0,0 +1,36 @@ +module authenticator; + +import poodinis; + +import vibe.data.bson : Bson; +import vibe.db.mongo.client : MongoClient; + +interface Authenticator +{ + bool checkUser(string username, string password); +} + +class MongoDBAuthenticator : Authenticator +{ +private: + @Autowire MongoClient mongoClient; + + @Value("Database name") + string databaseName; + + @Value("Users collection name") + string usersCollectionName; + +public: + bool checkUser(string username, string password) + { + auto users = mongoClient.getCollection(databaseName ~ "." ~ usersCollectionName); + auto result = users.findOne(["username" : username, "password" : password]); + return result != Bson(null); + } +} + +struct AuthInfo +{ + string userName; +} diff --git a/source/calendarwebapp.d b/source/calendarwebapp.d index 497ebf8..54ccc72 100644 --- a/source/calendarwebapp.d +++ b/source/calendarwebapp.d @@ -1,20 +1,17 @@ module calendarwebapp; +import authenticator : Authenticator, AuthInfo; + import core.time : days; import event; +import poodinis; + import std.datetime.date : Date; import std.exception : enforce; import std.typecons : Nullable; -import vibe.core.path : Path; - -import vibe.data.bson : Bson; - -import vibe.db.mongo.database : MongoDatabase; -import vibe.db.mongo.mongo : connectMongoDB; - import vibe.http.common : HTTPStatusException; import vibe.http.server : HTTPServerRequest, HTTPServerResponse; import vibe.http.status : HTTPStatus; @@ -22,11 +19,6 @@ import vibe.web.auth; import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar, terminateSession; -struct AuthInfo -{ - string userName; -} - @requiresAuth class CalendarWebapp { @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @@ -40,10 +32,10 @@ struct AuthInfo } public: - @anyAuth @errorDisplay!getLogin void index() + @anyAuth void index() { - auto entries = getEntriesFromFile(fileName); - render!("showevents.dt", entries); + auto events = eventStore.getAllEvents(); + render!("showevents.dt", events); } @noAuth void getLogin(string _error = null) @@ -53,7 +45,7 @@ public: @noAuth @errorDisplay!getLogin void postLogin(string username, string password) { - enforce(checkUser(username, password), "Benutzername oder Passwort ungültig"); + enforce(authenticator.checkUser(username, password), "Benutzername oder Passwort ungültig"); immutable AuthInfo authInfo = {username}; auth = authInfo; redirect("/"); @@ -73,43 +65,29 @@ public: @anyAuth @errorDisplay!getCreate void postCreate(Date begin, Nullable!Date end, string description, string name, EventType type, bool shout) { - import std.array : split, replace; + import std.array : replace, split; + import vibe.data.bson : BsonObjectID; if (!end.isNull) enforce(end - begin >= 1.days, "Mehrtägige Ereignisse müssen mindestens einen Tag dauern"); + auto event = Event(BsonObjectID.generate, begin, end, name, + description.replace("\r", "").split('\n'), type, shout); - auto entry = Entry(begin, end, Event("", name, - description.replace("\r", "").split('\n'), type, shout)); + eventStore.addEvent(event); - auto entries = getEntriesFromFile(fileName) ~ entry; - entries.writeEntriesToFile(fileName); - render!("showevents.dt", entries); + redirect("/"); } +private: struct ValidationErrorData { string msg; string field; } - this() - { - database = connectMongoDB("localhost").getDatabase("CalendarWebapp"); - } - -private: - immutable fileName = Path("events.json"); - SessionVar!(AuthInfo, "auth") auth; - MongoDatabase database; - - bool checkUser(string username, string password) - { - auto users = database["users"]; - auto result = users.findOne(["_id" : username, "password" : password]); - return result != Bson(null); - } - + @Autowire EventStore eventStore; + @Autowire Authenticator authenticator; } diff --git a/source/configuration.d b/source/configuration.d new file mode 100644 index 0000000..7d11e89 --- /dev/null +++ b/source/configuration.d @@ -0,0 +1,24 @@ +module configuration; + +import poodinis : ValueInjector; + +class StringInjector : ValueInjector!string +{ +private: + string[string] config; + +public: + this() + { + // dfmt off + config = ["Database name" : "CalendarWebapp", + "Users collection name": "users", + "Entries collection name" : "entries"]; + // dfmt on + } + + string get(string key) + { + return config[key]; + } +} diff --git a/source/event.d b/source/event.d index ebf8775..9e39e90 100644 --- a/source/event.d +++ b/source/event.d @@ -1,12 +1,68 @@ module event; -import std.datetime.date; +import poodinis; + +import std.algorithm : map; +import std.datetime.date : Date; +import std.range.interfaces : InputRange, inputRangeObject; import std.typecons : Nullable; -import vibe.core.file: existsFile, readFileUTF8, writeFileUTF8; +import vibe.core.file : existsFile, readFileUTF8, writeFileUTF8; import vibe.core.path : Path; -import vibe.data.json : deserializeJson, parseJsonString, serializeToPrettyJson; +import vibe.data.bson : Bson, BsonObjectID, deserializeBson, serializeToBson; import vibe.data.serialization : serializationName = name; +import vibe.db.mongo.client : MongoClient; +import vibe.db.mongo.collection : MongoCollection; + +interface EventStore +{ + Event getEvent(string id); + InputRange!Event getAllEvents(); + void addEvent(Event); + InputRange!Event getEventsBeginningBetween(Date begin, Date end); +} + +class MongoDBEventStore : EventStore +{ +public: + Event getEvent(string id) + { + return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) + .findOne(["_id" : id]).deserializeBson!Event; + } + + InputRange!Event getAllEvents() + { + return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) + .find().map!(deserializeBson!Event).inputRangeObject; + } + + void addEvent(Event event) + { + if (!event.id.valid) + event.id = BsonObjectID.generate; + + mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) + .insert(event.serializeToBson); + } + + InputRange!Event getEventsBeginningBetween(Date begin, Date end) + { + return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) + .find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date" + : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) + .inputRangeObject; + } + +private: + @Autowire MongoClient mongoClient; + + @Value("Database name") + string databaseName; + + @Value("Entries collection name") + string entriesCollectionName; +} enum EventType { @@ -17,33 +73,13 @@ enum EventType Any } -struct Entry -{ - @serializationName("date") Date begin; - @serializationName("end_date") Nullable!Date end; - Event event; -} - struct Event { - @serializationName("eid") string id; + @serializationName("_id") BsonObjectID id; + @serializationName("date") Date begin; + @serializationName("end_date") Nullable!Date end; string name; @serializationName("desc") string[] description; @serializationName("etype") EventType type; bool shout; } - -Entry[] getEntriesFromFile(in Path fileName) -{ - Entry[] entries; - if (fileName.existsFile) - { - deserializeJson(entries, fileName.readFileUTF8.parseJsonString); - } - return entries; -} - -void writeEntriesToFile(in Entry[] entries, in Path fileName) -{ - fileName.writeFileUTF8(entries.serializeToPrettyJson); -} diff --git a/views/showevents.dt b/views/showevents.dt index ff46ad3..bca670a 100644 --- a/views/showevents.dt +++ b/views/showevents.dt @@ -1,27 +1,27 @@ extends layout.dt block content h1 Events - - foreach (entry; entries) + - foreach (event; events) table tr td begin - td #{entry.begin} + td #{event.begin} tr td end - td #{entry.end} + td #{event.end} tr - td eid - td #{entry.event.id} + td _id + td #{event.id} tr td name - td #{entry.event.name} + td #{event.name} tr td desc - td #{entry.event.description} + td #{event.description} tr td etype - td #{entry.event.type} + td #{event.type} tr td shout - td #{entry.event.shout} + td #{event.shout} hr From b97bfea09970bfd4e726bbb0a1bd1425b909de49 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Tue, 8 Aug 2017 01:24:58 +0200 Subject: [PATCH 2/6] Added functionality to delete events --- source/calendarwebapp.d | 8 +++++++- source/event.d | 10 ++++++++-- views/showevents.dt | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/source/calendarwebapp.d b/source/calendarwebapp.d index 54ccc72..bcd1dfa 100644 --- a/source/calendarwebapp.d +++ b/source/calendarwebapp.d @@ -12,6 +12,7 @@ import std.datetime.date : Date; import std.exception : enforce; import std.typecons : Nullable; +import vibe.data.bson : BsonObjectID; import vibe.http.common : HTTPStatusException; import vibe.http.server : HTTPServerRequest, HTTPServerResponse; import vibe.http.status : HTTPStatus; @@ -66,7 +67,6 @@ public: Nullable!Date end, string description, string name, EventType type, bool shout) { import std.array : replace, split; - import vibe.data.bson : BsonObjectID; if (!end.isNull) enforce(end - begin >= 1.days, @@ -79,6 +79,12 @@ public: redirect("/"); } + @anyAuth void postRemove(BsonObjectID id) + { + eventStore.removeEvent(id); + redirect("/"); + } + private: struct ValidationErrorData { diff --git a/source/event.d b/source/event.d index 9e39e90..22dfe11 100644 --- a/source/event.d +++ b/source/event.d @@ -16,16 +16,17 @@ import vibe.db.mongo.collection : MongoCollection; interface EventStore { - Event getEvent(string id); + Event getEvent(BsonObjectID id); InputRange!Event getAllEvents(); void addEvent(Event); InputRange!Event getEventsBeginningBetween(Date begin, Date end); + void removeEvent(BsonObjectID id); } class MongoDBEventStore : EventStore { public: - Event getEvent(string id) + Event getEvent(BsonObjectID id) { return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) .findOne(["_id" : id]).deserializeBson!Event; @@ -54,6 +55,11 @@ public: .inputRangeObject; } + void removeEvent(BsonObjectID id) + { + mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName).remove(["_id" : id]); + } + private: @Autowire MongoClient mongoClient; diff --git a/views/showevents.dt b/views/showevents.dt index bca670a..e3a7877 100644 --- a/views/showevents.dt +++ b/views/showevents.dt @@ -24,4 +24,7 @@ block content tr td shout td #{event.shout} + form(action="/remove", method="post") + input#id(value="#{event.id}", name="id", type="hidden") + input#submitButton(type="submit", value="Entfernen") hr From 989e222b71a1ef4b1d21589bdfef38d2065f4f0e Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Tue, 8 Aug 2017 01:32:56 +0200 Subject: [PATCH 3/6] import cleanup in event.d --- source/event.d | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/event.d b/source/event.d index 22dfe11..12f1969 100644 --- a/source/event.d +++ b/source/event.d @@ -7,12 +7,9 @@ import std.datetime.date : Date; import std.range.interfaces : InputRange, inputRangeObject; import std.typecons : Nullable; -import vibe.core.file : existsFile, readFileUTF8, writeFileUTF8; -import vibe.core.path : Path; import vibe.data.bson : Bson, BsonObjectID, deserializeBson, serializeToBson; import vibe.data.serialization : serializationName = name; import vibe.db.mongo.client : MongoClient; -import vibe.db.mongo.collection : MongoCollection; interface EventStore { From 8473bc1930a8ea24cefc48930190be83a541deb2 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Tue, 8 Aug 2017 01:53:53 +0200 Subject: [PATCH 4/6] update to vibe-d version 0.8.0 --- dub.json | 2 +- source/calendarwebapp.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dub.json b/dub.json index 0f581f8..4f1af98 100644 --- a/dub.json +++ b/dub.json @@ -4,7 +4,7 @@ "Johannes Loher" ], "dependencies": { - "vibe-d": "~>0.7.30", + "vibe-d": "~>0.8.0", "poodinis": "~>8.0.0" }, "description": "A simple webapplication to edit and view calendar entries", diff --git a/source/calendarwebapp.d b/source/calendarwebapp.d index bcd1dfa..1c2b7d4 100644 --- a/source/calendarwebapp.d +++ b/source/calendarwebapp.d @@ -22,7 +22,7 @@ import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar, @requiresAuth class CalendarWebapp { - @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) + @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @trusted { if (!req.session || !req.session.isKeySet("auth")) { From 3d5904326105961632bcc5940cf017c7a731499e Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Thu, 10 Aug 2017 02:57:31 +0200 Subject: [PATCH 5/6] use a better way to redirect to login if not authenticated --- source/calendarwebapp.d | 6 +++--- views/layout.dt | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/source/calendarwebapp.d b/source/calendarwebapp.d index 1c2b7d4..745e5d3 100644 --- a/source/calendarwebapp.d +++ b/source/calendarwebapp.d @@ -22,12 +22,12 @@ import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar, @requiresAuth class CalendarWebapp { - @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @trusted + @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe { if (!req.session || !req.session.isKeySet("auth")) { - redirect("/login"); - throw new HTTPStatusException(HTTPStatus.forbidden, "Du musst dich erst einloggen"); + () @trusted{ redirect("/login"); }(); + return AuthInfo.init; } return req.session.get!AuthInfo("auth"); } diff --git a/views/layout.dt b/views/layout.dt index 5cabeff..b15f0ad 100644 --- a/views/layout.dt +++ b/views/layout.dt @@ -8,5 +8,3 @@ html block navigation include navigation block content - - From e510c2bacf236b3fa1d0b792908daefdf8f01222 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Sat, 12 Aug 2017 22:46:51 +0200 Subject: [PATCH 6/6] switched to vibe.d 0.8.1-rc2 and added @safe and friends where possible --- dub.json | 5 +++-- source/authenticator.d | 4 ++-- source/calendarwebapp.d | 10 +++++----- source/configuration.d | 4 ++-- source/event.d | 20 ++++++++++---------- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/dub.json b/dub.json index 4f1af98..025223d 100644 --- a/dub.json +++ b/dub.json @@ -4,13 +4,14 @@ "Johannes Loher" ], "dependencies": { - "vibe-d": "~>0.8.0", + "vibe-d": "0.8.1-rc.2", "poodinis": "~>8.0.0" }, "description": "A simple webapplication to edit and view calendar entries", "copyright": "Copyright © 2017, Johannes Loher", "license": "MIT", "versions": [ - "VibeDefaultMain" + "VibeDefaultMain", + "VibeUseOpenSSL11" ] } \ No newline at end of file diff --git a/source/authenticator.d b/source/authenticator.d index 7dceef9..94d48f1 100644 --- a/source/authenticator.d +++ b/source/authenticator.d @@ -7,7 +7,7 @@ import vibe.db.mongo.client : MongoClient; interface Authenticator { - bool checkUser(string username, string password); + bool checkUser(string username, string password) @safe; } class MongoDBAuthenticator : Authenticator @@ -22,7 +22,7 @@ private: string usersCollectionName; public: - bool checkUser(string username, string password) + bool checkUser(string username, string password) @safe { auto users = mongoClient.getCollection(databaseName ~ "." ~ usersCollectionName); auto result = users.findOne(["username" : username, "password" : password]); diff --git a/source/calendarwebapp.d b/source/calendarwebapp.d index 745e5d3..e140e6f 100644 --- a/source/calendarwebapp.d +++ b/source/calendarwebapp.d @@ -26,7 +26,7 @@ import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar, { if (!req.session || !req.session.isKeySet("auth")) { - () @trusted{ redirect("/login"); }(); + redirect("/login"); return AuthInfo.init; } return req.session.get!AuthInfo("auth"); @@ -44,7 +44,7 @@ public: render!("login.dt", _error); } - @noAuth @errorDisplay!getLogin void postLogin(string username, string password) + @noAuth @errorDisplay!getLogin void postLogin(string username, string password) @safe { enforce(authenticator.checkUser(username, password), "Benutzername oder Passwort ungültig"); immutable AuthInfo authInfo = {username}; @@ -52,7 +52,7 @@ public: redirect("/"); } - @anyAuth void getLogout() + @anyAuth void getLogout() @safe { terminateSession(); redirect("/"); @@ -64,7 +64,7 @@ public: } @anyAuth @errorDisplay!getCreate void postCreate(Date begin, - Nullable!Date end, string description, string name, EventType type, bool shout) + Nullable!Date end, string description, string name, EventType type, bool shout) @safe { import std.array : replace, split; @@ -79,7 +79,7 @@ public: redirect("/"); } - @anyAuth void postRemove(BsonObjectID id) + @anyAuth void postRemove(BsonObjectID id) @safe { eventStore.removeEvent(id); redirect("/"); diff --git a/source/configuration.d b/source/configuration.d index 7d11e89..b35a584 100644 --- a/source/configuration.d +++ b/source/configuration.d @@ -8,7 +8,7 @@ private: string[string] config; public: - this() + this() const @safe pure nothrow { // dfmt off config = ["Database name" : "CalendarWebapp", @@ -17,7 +17,7 @@ public: // dfmt on } - string get(string key) + string get(string key) const @safe pure nothrow { return config[key]; } diff --git a/source/event.d b/source/event.d index 12f1969..7678f1c 100644 --- a/source/event.d +++ b/source/event.d @@ -13,29 +13,29 @@ import vibe.db.mongo.client : MongoClient; interface EventStore { - Event getEvent(BsonObjectID id); - InputRange!Event getAllEvents(); - void addEvent(Event); - InputRange!Event getEventsBeginningBetween(Date begin, Date end); - void removeEvent(BsonObjectID id); + Event getEvent(BsonObjectID id) @safe; + InputRange!Event getAllEvents() @safe; + void addEvent(Event) @safe; + InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe; + void removeEvent(BsonObjectID id) @safe; } class MongoDBEventStore : EventStore { public: - Event getEvent(BsonObjectID id) + Event getEvent(BsonObjectID id) @safe { return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) .findOne(["_id" : id]).deserializeBson!Event; } - InputRange!Event getAllEvents() + InputRange!Event getAllEvents() @safe { return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) .find().map!(deserializeBson!Event).inputRangeObject; } - void addEvent(Event event) + void addEvent(Event event) @safe { if (!event.id.valid) event.id = BsonObjectID.generate; @@ -44,7 +44,7 @@ public: .insert(event.serializeToBson); } - InputRange!Event getEventsBeginningBetween(Date begin, Date end) + InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe { return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName) .find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date" @@ -52,7 +52,7 @@ public: .inputRangeObject; } - void removeEvent(BsonObjectID id) + void removeEvent(BsonObjectID id) @safe { mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName).remove(["_id" : id]); }