diff --git a/dub.json b/dub.json index 74add46..cf00d50 100644 --- a/dub.json +++ b/dub.json @@ -4,6 +4,7 @@ "Johannes Loher" ], "dependencies": { + "mysql-native": "~>1.1.2", "botan": "~>1.12.9", "vibe-d": "~>0.8.1", "vibe-d:tls": "~>0.8.1", diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..fac9180 --- /dev/null +++ b/schema.sql @@ -0,0 +1,23 @@ +CREATE DATABASE CalendarWebapp; +USE CalendarWebapp; + +CREATE TABLE users ( + id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, + username CHAR(30) NOT NULL UNIQUE, + password CHAR(60) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE events ( + id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, + begin DATE NOT NULL, + end DATE, + name CHAR(128) NOT NULL, + description TEXT NOT NULL, + type TINYINT UNSIGNED NOT NULL, + shout BOOLEAN NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO users (username, password) VALUES ('foo', +'$2a$10$9LBqOZV99ARiE4Nx.2b7GeYfqk2.0A32PWGu2cRGyW2hRJ0xeDfnO'); diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d index b48d83b..c9a9a11 100644 --- a/source/calendarwebapp/authenticator.d +++ b/source/calendarwebapp/authenticator.d @@ -30,6 +30,32 @@ public: } } +class MySQLAuthenticator : Authenticator +{ +private: + import mysql; + + @Autowire MySQLPool pool; + +public: + bool checkUser(string username, string password) @trusted + { + import botan.passhash.bcrypt : checkBcrypt; + + auto cn = pool.lockConnection(); + scope (exit) + cn.close(); + auto prepared = cn.prepare("SELECT password FROM users WHERE username = ?"); + prepared.setArg(0, username); + auto result = prepared.query(); + + /* checkBcrypt should be called using vibe.core.concurrency.async to + avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is + blocking this */ + return !result.empty && (() @trusted => checkBcrypt(password, result.front[0].get!string))(); + } +} + struct AuthInfo { string userName; diff --git a/source/calendarwebapp/calendarwebapp.d b/source/calendarwebapp/calendarwebapp.d index 21a652e..f6a15bd 100644 --- a/source/calendarwebapp/calendarwebapp.d +++ b/source/calendarwebapp/calendarwebapp.d @@ -63,22 +63,21 @@ public: } @anyAuth @errorDisplay!getCreate void postCreate(Date begin, - Nullable!Date end, string description, string name, EventType type, bool shout) @safe + Nullable!Date end, string description, string name, EventType type, bool shout) { import std.array : replace, split; 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", ""), type, shout); + auto event = Event("", begin, end, name, description.replace("\r", ""), type, shout); eventStore.addEvent(event); redirect("/"); } - @anyAuth void postRemove(BsonObjectID id) @safe + @anyAuth void postRemove(string id) { eventStore.removeEvent(id); redirect("/"); diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d index c2c0959..1fadeb1 100644 --- a/source/calendarwebapp/configuration.d +++ b/source/calendarwebapp/configuration.d @@ -1,8 +1,10 @@ module calendarwebapp.configuration; -import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator; +import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator, MySQLAuthenticator; import calendarwebapp.calendarwebapp : CalendarWebapp; -import calendarwebapp.event : EventStore, MongoDBEventStore; +import calendarwebapp.event : EventStore, MongoDBEventStore, MySQLEventStore; + +import mysql : MySQLPool; import poodinis; @@ -16,9 +18,11 @@ public: override void registerDependencies(shared(DependencyContainer) container) { auto mongoClient = connectMongoDB("localhost"); + auto pool = new MySQLPool("localhost", "username", "password", "CalendarWebapp"); + container.register!MySQLPool.existingInstance(pool); container.register!MongoClient.existingInstance(mongoClient); - container.register!(EventStore, MongoDBEventStore!()); - container.register!(Authenticator, MongoDBAuthenticator!()); + container.register!(EventStore, MySQLEventStore); + container.register!(Authenticator, MySQLAuthenticator); container.register!CalendarWebapp; container.register!(ValueInjector!string, StringInjector); container.register!(ValueInjector!MongoCollection, MongoCollectionInjector); diff --git a/source/calendarwebapp/event.d b/source/calendarwebapp/event.d index e03ee43..3f8e2e0 100644 --- a/source/calendarwebapp/event.d +++ b/source/calendarwebapp/event.d @@ -3,6 +3,7 @@ module calendarwebapp.event; import poodinis; import std.algorithm : map; +import std.conv : to; import std.datetime : Date; import std.range.interfaces : InputRange, inputRangeObject; import std.typecons : Nullable; @@ -13,17 +14,17 @@ import vibe.db.mongo.collection : MongoCollection; interface EventStore { - 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; + Event getEvent(string id); + InputRange!Event getAllEvents(); + void addEvent(Event); + /* InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe; */ + void removeEvent(string id); } class MongoDBEventStore(Collection = MongoCollection) : EventStore { public: - Event getEvent(BsonObjectID id) @safe + Event getEvent(string id) @safe { return events.findOne(["_id" : id]).deserializeBson!Event; } @@ -35,20 +36,29 @@ public: void addEvent(Event event) @safe { - if (!event.id.valid) - event.id = BsonObjectID.generate; + import std.conv : ConvException; + + try + { + if (!BsonObjectID.fromString(event.id).valid) + throw new ConvException("invalid BsonObjectID."); + } + catch (ConvException) + { + event.id = BsonObjectID.generate.to!string; + } events.insert(event.serializeToBson); } InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe { - return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date" - : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) + return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], + ["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) .inputRangeObject; } - void removeEvent(BsonObjectID id) @safe + void removeEvent(string id) @safe { events.remove(["_id" : id]); } @@ -58,6 +68,82 @@ private: Collection events; } +class MySQLEventStore : EventStore +{ +private: + import mysql; + +public: + Event getEvent(string id) + { + auto cn = pool.lockConnection(); + scope (exit) + cn.close; + auto prepared = cn.prepare( + "SELECT id begin end name description type shout FROM events WHERE id = ?"); + prepared.setArg(0, id.to!uint); + return toEvent(prepared.query.front); + } + + InputRange!Event getAllEvents() + { + auto cn = pool.lockConnection(); + scope (exit) + cn.close; + auto prepared = cn.prepare( + "SELECT id, begin, end, name, description, type, shout FROM events"); + return prepared.querySet.map!(r => toEvent(r)).inputRangeObject; + } + + void addEvent(Event event) + { + auto cn = pool.lockConnection(); + scope (exit) + cn.close; + auto prepared = cn.prepare( + "INSERT INTO events (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)"); + prepared.setArgs(event.begin, event.end, event.name, event.description, + event.type.to!uint, event.shout); + prepared.exec(); + } + + /* InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe + { + return events.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], + ["date" : ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event) + .inputRangeObject; + } */ + + void removeEvent(string id) + { + auto cn = pool.lockConnection(); + scope (exit) + cn.close; + auto prepared = cn.prepare("DELETE FROM events WHERE id = ?"); + prepared.setArg(0, id.to!uint); + prepared.exec(); + } + +private: + @Autowire MySQLPool pool; + + Event toEvent(Row r) + { + import std.conv : to; + + Event event; + event.id = r[0].get!uint.to!string; + event.begin = r[1].get!Date; + if (r[2].hasValue) + event.end = r[2].get!Date; + event.name = r[3].get!string; + event.description = r[4].get!string; + event.type = r[5].get!uint.to!EventType; + event.shout = r[6].get!byte.to!bool; + return event; + } +} + enum EventType { Holiday, @@ -69,7 +155,7 @@ enum EventType struct Event { - @serializationName("_id") BsonObjectID id; + @serializationName("_id") string id; @serializationName("date") Date begin; @serializationName("end_date") Nullable!Date end; string name; diff --git a/test/calendarwebapp/testevent.d b/test/calendarwebapp/testevent.d index e351881..4f0024b 100644 --- a/test/calendarwebapp/testevent.d +++ b/test/calendarwebapp/testevent.d @@ -10,15 +10,15 @@ import std.algorithm : map; import unit_threaded.mock; import unit_threaded.should; -import vibe.data.bson : Bson, BsonObjectID, serializeToBson; +import vibe.data.bson : Bson, serializeToBson; interface Collection { - Bson findOne(BsonObjectID[string] query) @safe; + Bson findOne(string[string] query) @safe; Bson[] find() @safe; Bson[] find(Bson[string][string][][string] query) @safe; void insert(Bson document) @safe; - void remove(BsonObjectID[string] selector) @safe; + void remove(string[string] selector) @safe; } class CollectionInjector : ValueInjector!Collection @@ -50,7 +50,7 @@ public: collection.returnValue!"findOne"(Bson(null)); - auto id = BsonObjectID.fromString("599090de97355141140fc698"); + auto id = "599090de97355141140fc698"; collection.expect!"findOne"(["_id" : id]); auto eventStore = container.resolve!(EventStore); @@ -68,7 +68,7 @@ public: container.register!(EventStore, MongoDBEventStore!(Collection))( RegistrationOption.doNotAddConcreteTypeRegistration); - auto id = BsonObjectID.fromString("599090de97355141140fc698"); + auto id = "599090de97355141140fc698"; Event event; event.id = id; @@ -91,7 +91,7 @@ public: container.register!(EventStore, MongoDBEventStore!(Collection))( RegistrationOption.doNotAddConcreteTypeRegistration); - auto id = BsonObjectID.fromString("599090de97355141140fc698"); + auto id = "599090de97355141140fc698"; Event event; event.id = id; auto serializedEvent = event.serializeToBson; @@ -121,7 +121,7 @@ public: container.register!(EventStore, MongoDBEventStore!(Collection))( RegistrationOption.doNotAddConcreteTypeRegistration); - auto id = BsonObjectID.fromString("599090de97355141140fc698"); + auto id = "599090de97355141140fc698"; Event event; event.id = id; @@ -151,8 +151,7 @@ public: RegistrationOption.doNotAddConcreteTypeRegistration); immutable ids = [ - BsonObjectID.fromString("599090de97355141140fc698"), BsonObjectID.fromString("599090de97355141140fc698"), - BsonObjectID.fromString("59cb9ad8fc0ba5751c0df02b") + "599090de97355141140fc698", "599090de97355141140fc698", "59cb9ad8fc0ba5751c0df02b" ]; auto events = ids.map!(id => Event(id)).array;