Merge branch 'poodinis-mongo' into 'master'
Poodinis mongo See merge request !1
This commit is contained in:
commit
974a741132
8 changed files with 190 additions and 87 deletions
8
dub.json
8
dub.json
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
21
source/app.d
21
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.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
36
source/authenticator.d
Normal 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;
|
||||||
|
}
|
|
@ -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
24
source/configuration.d
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,5 +8,3 @@ html
|
||||||
block navigation
|
block navigation
|
||||||
include navigation
|
include navigation
|
||||||
block content
|
block content
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue