Merge branch 'poodinis-mongo' into 'master'

Poodinis mongo



See merge request !1
This commit is contained in:
Johannes Loher 2017-08-12 22:50:20 +02:00
commit 974a741132
8 changed files with 190 additions and 87 deletions

View file

@ -4,12 +4,14 @@
"Johannes Loher" "Johannes Loher"
], ],
"dependencies": { "dependencies": {
"vibe-d": "~>0.7.30" "vibe-d": "0.8.1-rc.2",
"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", "copyright": "Copyright © 2017, Johannes Loher",
"license": "MIT", "license": "MIT",
"versions": [ "versions": [
"VibeDefaultMain" "VibeDefaultMain",
"VibeUseOpenSSL11"
] ]
} }

View file

@ -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.core.log : logInfo;
import vibe.db.mongo.client : MongoClient;
import vibe.db.mongo.mongo : connectMongoDB;
import vibe.http.fileserver : serveStaticFiles; import vibe.http.fileserver : serveStaticFiles;
import vibe.http.router : URLRouter; import vibe.http.router : URLRouter;
import vibe.http.server : HTTPServerSettings, listenHTTP, MemorySessionStore; import vibe.http.server : HTTPServerSettings, listenHTTP, MemorySessionStore;
@ -8,8 +17,16 @@ import vibe.web.web : registerWebInterface;
shared static this() 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; auto router = new URLRouter;
router.registerWebInterface(new CalendarWebapp); router.registerWebInterface(dependencies.resolve!CalendarWebapp);
router.get("*", serveStaticFiles("public")); router.get("*", serveStaticFiles("public"));
auto settings = new HTTPServerSettings; auto settings = new HTTPServerSettings;

36
source/authenticator.d Normal file
View file

@ -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) @safe;
}
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) @safe
{
auto users = mongoClient.getCollection(databaseName ~ "." ~ usersCollectionName);
auto result = users.findOne(["username" : username, "password" : password]);
return result != Bson(null);
}
}
struct AuthInfo
{
string userName;
}

View file

@ -1,20 +1,18 @@
module calendarwebapp; module calendarwebapp;
import authenticator : Authenticator, AuthInfo;
import core.time : days; import core.time : days;
import event; import event;
import poodinis;
import std.datetime.date : Date; import std.datetime.date : Date;
import std.exception : enforce; import std.exception : enforce;
import std.typecons : Nullable; import std.typecons : Nullable;
import vibe.core.path : Path; import vibe.data.bson : BsonObjectID;
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.common : HTTPStatusException;
import vibe.http.server : HTTPServerRequest, HTTPServerResponse; import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
import vibe.http.status : HTTPStatus; import vibe.http.status : HTTPStatus;
@ -22,28 +20,23 @@ import vibe.web.auth;
import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar, import vibe.web.web : errorDisplay, noRoute, redirect, render, SessionVar,
terminateSession; terminateSession;
struct AuthInfo
{
string userName;
}
@requiresAuth class CalendarWebapp @requiresAuth class CalendarWebapp
{ {
@noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @noRoute AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe
{ {
if (!req.session || !req.session.isKeySet("auth")) if (!req.session || !req.session.isKeySet("auth"))
{ {
redirect("/login"); redirect("/login");
throw new HTTPStatusException(HTTPStatus.forbidden, "Du musst dich erst einloggen"); return AuthInfo.init;
} }
return req.session.get!AuthInfo("auth"); return req.session.get!AuthInfo("auth");
} }
public: public:
@anyAuth @errorDisplay!getLogin void index() @anyAuth void index()
{ {
auto entries = getEntriesFromFile(fileName); auto events = eventStore.getAllEvents();
render!("showevents.dt", entries); render!("showevents.dt", events);
} }
@noAuth void getLogin(string _error = null) @noAuth void getLogin(string _error = null)
@ -51,15 +44,15 @@ public:
render!("login.dt", _error); render!("login.dt", _error);
} }
@noAuth @errorDisplay!getLogin void postLogin(string username, string password) @noAuth @errorDisplay!getLogin void postLogin(string username, string password) @safe
{ {
enforce(checkUser(username, password), "Benutzername oder Passwort ungültig"); enforce(authenticator.checkUser(username, password), "Benutzername oder Passwort ungültig");
immutable AuthInfo authInfo = {username}; immutable AuthInfo authInfo = {username};
auth = authInfo; auth = authInfo;
redirect("/"); redirect("/");
} }
@anyAuth void getLogout() @anyAuth void getLogout() @safe
{ {
terminateSession(); terminateSession();
redirect("/"); redirect("/");
@ -71,45 +64,36 @@ public:
} }
@anyAuth @errorDisplay!getCreate void postCreate(Date begin, @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 : split, replace; import std.array : replace, split;
if (!end.isNull) if (!end.isNull)
enforce(end - begin >= 1.days, enforce(end - begin >= 1.days,
"Mehrtägige Ereignisse müssen mindestens einen Tag dauern"); "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, eventStore.addEvent(event);
description.replace("\r", "").split('\n'), type, shout));
auto entries = getEntriesFromFile(fileName) ~ entry; redirect("/");
entries.writeEntriesToFile(fileName);
render!("showevents.dt", entries);
} }
@anyAuth void postRemove(BsonObjectID id) @safe
{
eventStore.removeEvent(id);
redirect("/");
}
private:
struct ValidationErrorData struct ValidationErrorData
{ {
string msg; string msg;
string field; string field;
} }
this()
{
database = connectMongoDB("localhost").getDatabase("CalendarWebapp");
}
private:
immutable fileName = Path("events.json");
SessionVar!(AuthInfo, "auth") auth; SessionVar!(AuthInfo, "auth") auth;
MongoDatabase database; @Autowire EventStore eventStore;
@Autowire Authenticator authenticator;
bool checkUser(string username, string password)
{
auto users = database["users"];
auto result = users.findOne(["_id" : username, "password" : password]);
return result != Bson(null);
}
} }

24
source/configuration.d Normal file
View file

@ -0,0 +1,24 @@
module configuration;
import poodinis : ValueInjector;
class StringInjector : ValueInjector!string
{
private:
string[string] config;
public:
this() const @safe pure nothrow
{
// dfmt off
config = ["Database name" : "CalendarWebapp",
"Users collection name": "users",
"Entries collection name" : "entries"];
// dfmt on
}
string get(string key) const @safe pure nothrow
{
return config[key];
}
}

View file

@ -1,12 +1,71 @@
module event; 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 std.typecons : Nullable;
import vibe.core.file: existsFile, readFileUTF8, writeFileUTF8; import vibe.data.bson : Bson, BsonObjectID, deserializeBson, serializeToBson;
import vibe.core.path : Path;
import vibe.data.json : deserializeJson, parseJsonString, serializeToPrettyJson;
import vibe.data.serialization : serializationName = name; import vibe.data.serialization : serializationName = name;
import vibe.db.mongo.client : MongoClient;
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;
}
class MongoDBEventStore : EventStore
{
public:
Event getEvent(BsonObjectID id) @safe
{
return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName)
.findOne(["_id" : id]).deserializeBson!Event;
}
InputRange!Event getAllEvents() @safe
{
return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName)
.find().map!(deserializeBson!Event).inputRangeObject;
}
void addEvent(Event event) @safe
{
if (!event.id.valid)
event.id = BsonObjectID.generate;
mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName)
.insert(event.serializeToBson);
}
InputRange!Event getEventsBeginningBetween(Date begin, Date end) @safe
{
return mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName)
.find(["$and" : [["date" : ["$gte" : begin.serializeToBson]], ["date"
: ["$lte" : end.serializeToBson]]]]).map!(deserializeBson!Event)
.inputRangeObject;
}
void removeEvent(BsonObjectID id) @safe
{
mongoClient.getCollection(databaseName ~ "." ~ entriesCollectionName).remove(["_id" : id]);
}
private:
@Autowire MongoClient mongoClient;
@Value("Database name")
string databaseName;
@Value("Entries collection name")
string entriesCollectionName;
}
enum EventType enum EventType
{ {
@ -17,33 +76,13 @@ enum EventType
Any Any
} }
struct Entry
{
@serializationName("date") Date begin;
@serializationName("end_date") Nullable!Date end;
Event event;
}
struct Event struct Event
{ {
@serializationName("eid") string id; @serializationName("_id") BsonObjectID id;
@serializationName("date") Date begin;
@serializationName("end_date") Nullable!Date end;
string name; string name;
@serializationName("desc") string[] description; @serializationName("desc") string[] description;
@serializationName("etype") EventType type; @serializationName("etype") EventType type;
bool shout; 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);
}

View file

@ -8,5 +8,3 @@ html
block navigation block navigation
include navigation include navigation
block content block content

View file

@ -1,27 +1,30 @@
extends layout.dt extends layout.dt
block content block content
h1 Events h1 Events
- foreach (entry; entries) - foreach (event; events)
table table
tr tr
td begin td begin
td #{entry.begin} td #{event.begin}
tr tr
td end td end
td #{entry.end} td #{event.end}
tr tr
td eid td _id
td #{entry.event.id} td #{event.id}
tr tr
td name td name
td #{entry.event.name} td #{event.name}
tr tr
td desc td desc
td #{entry.event.description} td #{event.description}
tr tr
td etype td etype
td #{entry.event.type} td #{event.type}
tr tr
td shout td shout
td #{entry.event.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 hr