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