summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2015-06-03 00:09:09 +0200
committerJoel Klinghed <the_jk@yahoo.com>2015-06-03 00:23:19 +0200
commit41c45d4a4d6f8bea9b45d76ddef5d8bac71cfc5a (patch)
tree70f5e4a20a7659529a48174a50ff24da92a01e44
parent823dc403a9b56e00be4982d0082d30b8df5e53d6 (diff)
A start for an event handler
-rw-r--r--configure.ac3
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am7
-rw-r--r--src/event.cc282
-rw-r--r--src/event.hh51
-rw-r--r--src/event_main.cc191
-rw-r--r--src/http.cc52
-rw-r--r--src/http.hh19
-rw-r--r--src/signup.cc19
9 files changed, 602 insertions, 24 deletions
diff --git a/configure.ac b/configure.ac
index ea86ca8..7a691e0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,7 +10,8 @@ AM_PROG_CC_C_O
AC_LANG([C++])
DEFINES=
-AX_APPEND_COMPILE_FLAGS([-fno-rtti -fno-exceptions],DEFINES)
+#-fno-exceptions
+AX_APPEND_COMPILE_FLAGS([-fno-rtti],DEFINES)
# Test c++11
OLDCXXFLAGS="$CXXFLAGS"
diff --git a/src/.gitignore b/src/.gitignore
index 896e388..4a2bd2c 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -4,4 +4,4 @@
/stamp-h1
/libcgi.la
/libdb.la
-/signup
+/event
diff --git a/src/Makefile.am b/src/Makefile.am
index 54ff40d..45bca7d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,17 +2,18 @@ MAINTAINERCLEANFILES = Makefile.in
AM_CPPFLAGS = @DEFINES@
-bin_PROGRAMS = signup
+bin_PROGRAMS = event
noinst_LTLIBRARIES = libdb.la libcgi.la
-signup_SOURCES = signup.cc common.hh cgi.hh db.hh
-signup_LDADD = libdb.la libcgi.la
+event_SOURCES = event.cc event.hh event_main.cc common.hh cgi.hh db.hh
+event_LDADD = libdb.la libcgi.la
libcgi_la_SOURCES = cgi.hh common.hh cgi.cc \
query_parser.hh query_parser.cc \
header_parser.hh header_parser.cc \
strutils.hh strutils.cc \
args.hh args.cc \
+ http.hh http.cc \
multipart_formdata_parser.hh multipart_formdata_parser.cc
libcgi_la_CPPFLAGS = $(AM_CPPFLAGS) @FASTCGI_CFLAGS@
libcgi_la_LIBADD = @FASTCGI_LIBS@
diff --git a/src/event.cc b/src/event.cc
new file mode 100644
index 0000000..8606819
--- /dev/null
+++ b/src/event.cc
@@ -0,0 +1,282 @@
+#include "common.hh"
+
+#include "db.hh"
+#include "event.hh"
+
+namespace stuff {
+
+namespace {
+
+const std::string kEventTable = "events";
+const std::string kEventGoingTable = "events_going";
+
+class EventImpl : public Event {
+public:
+ ~EventImpl() override {
+ }
+
+ const std::string& name() const override {
+ return name_;
+ }
+ void set_name(const std::string& name) override {
+ if (name_ == name) return;
+ edit();
+ editor_->set("name", name);
+ name_ = name;
+ }
+
+ const std::string& text() const override {
+ return text_;
+ }
+ void set_text(const std::string& text) override {
+ if (text_ == text) return;
+ edit();
+ editor_->set("text", text);
+ text_ = text;
+ }
+
+ time_t start() const override {
+ return start_;
+ }
+ void set_start(time_t start) override {
+ if (start_ == start) return;
+ edit();
+ editor_->set("start", static_cast<int64_t>(start));
+ start_ = start;
+ }
+
+ void going(std::set<std::string>* going,
+ std::set<std::string>* not_going) const override {
+ if (going) {
+ going->clear();
+ going->insert(going_.begin(), going_.end());
+ }
+ if (not_going) {
+ not_going->clear();
+ not_going->insert(not_going_.begin(), not_going_.end());
+ }
+ }
+
+ bool is_going(const std::string& name) const override {
+ return going_.count(name) > 0;
+ }
+
+ void update_going(const std::string& name, bool going) override {
+ auto it = going_.find(name);
+ if (it != going_.end()) {
+ if (going) return;
+ going_.erase(it);
+ not_going_.insert(name);
+ going_uptodate_ = false;
+ } else {
+ it = not_going_.find(name);
+ if (it != not_going_.end()) {
+ if (!going) return;
+ going_.insert(name);
+ not_going_.erase(it);
+ going_uptodate_ = false;
+ } else {
+ if (going) {
+ going_.insert(name);
+ } else {
+ not_going_.insert(name);
+ }
+ }
+ }
+ going_uptodate_ = false;
+ }
+
+ bool store() override {
+ if (editor_) {
+ DB::Transaction transaction(db_);
+ if (!editor_->commit()) return false;
+ if (new_) {
+ id_ = editor_->last_insert_rowid();
+ }
+ if (store_going() && transaction.commit()) {
+ new_ = false;
+ editor_.reset();
+ return true;
+ } else {
+ if (new_) id_ = 0;
+ return false;
+ }
+ } else {
+ if (new_) return false;
+ return store_going();
+ }
+ }
+
+ bool remove() override {
+ if (new_) return true;
+ DB::Transaction transaction(db_);
+ if (!db_->remove(kEventTable,
+ DB::Condition("id", DB::Condition::EQUAL, id_)))
+ return false;
+ if (!db_->remove(kEventGoingTable,
+ DB::Condition("event", DB::Condition::EQUAL, id_)))
+ return false;
+ if (transaction.commit()) {
+ new_ = true;
+ id_ = 0;
+ return true;
+ }
+ return false;
+ }
+
+ bool load(DB::Snapshot* snapshot) {
+ editor_.reset();
+ new_ = false;
+ if (snapshot->bad()) return false;
+ if (!snapshot->get(0, &id_)) return false;
+ if (!snapshot->get(1, &name_)) return false;
+ int64_t tmp;
+ if (!snapshot->get(2, &tmp)) return false;
+ start_ = tmp;
+ if (!snapshot->get(3, &text_)) {
+ text_ = "";
+ }
+ return load_going();
+ }
+
+ EventImpl(std::shared_ptr<DB> db)
+ : db_(db), id_(0), new_(true), going_uptodate_(true) {
+ }
+
+private:
+ void edit() {
+ if (editor_) return;
+ if (new_) {
+ editor_ = db_->insert(kEventTable);
+ } else {
+ editor_ = db_->update(kEventTable,
+ DB::Condition("id",
+ DB::Condition::EQUAL,
+ DB::Value(id_)));
+ }
+ }
+
+ bool store_going(const std::set<std::string>& names, bool going) const {
+ for (const auto& name : names) {
+ auto editor = db_->insert(kEventGoingTable);
+ editor->set("event", id_);
+ editor->set("name", name);
+ editor->set("going", going);
+ if (!editor->commit()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool store_going() {
+ if (going_uptodate_) return true;
+ DB::Transaction transaction(db_);
+ if (!db_->remove(kEventGoingTable,
+ DB::Condition("event", DB::Condition::EQUAL, id_)))
+ return false;
+ if (!store_going(going_, true) || !store_going(not_going_, false))
+ return false;
+ if (transaction.commit()) {
+ going_uptodate_ = true;
+ return true;
+ }
+ return false;
+ }
+ bool load_going() {
+ going_.clear();
+ not_going_.clear();
+ going_uptodate_ = true;
+ auto snapshot = db_->select(kEventGoingTable,
+ DB::Condition("event",
+ DB::Condition::EQUAL,
+ id_));
+ if (!snapshot) return true;
+ do {
+ bool is_going;
+ std::string name;
+ if (!snapshot->get(0, &name) || !snapshot->get(1, &is_going))
+ return false;
+ if (is_going) {
+ going_.insert(name);
+ } else {
+ not_going_.insert(name);
+ }
+ } while (snapshot->next());
+ return !snapshot->bad();
+ }
+
+ std::shared_ptr<DB> db_;
+ std::shared_ptr<DB::Editor> editor_;
+ int64_t id_;
+ std::string name_;
+ std::string text_;
+ time_t start_;
+ bool new_;
+ bool going_uptodate_;
+ std::set<std::string> going_;
+ std::set<std::string> not_going_;
+};
+
+} // namespace
+
+// static
+bool Event::setup(DB* db) {
+ DB::Declaration decl;
+ decl.push_back(std::make_pair("id", DB::PrimaryKey(DB::Type::INT64)));
+ decl.push_back(std::make_pair("name", DB::NotNull(DB::Type::STRING)));
+ decl.push_back(std::make_pair("start", DB::NotNull(DB::Type::INT64)));
+ decl.push_back(std::make_pair("text", DB::Type::STRING));
+ if (!db->insert_table(kEventTable, decl)) return false;
+ decl.clear();
+ decl.push_back(std::make_pair("event", DB::NotNull(DB::Type::INT64)));
+ decl.push_back(std::make_pair("name", DB::NotNull(DB::Type::STRING)));
+ decl.push_back(std::make_pair("going", DB::NotNull(DB::Type::BOOL)));
+ return db->insert_table(kEventGoingTable, decl);
+}
+
+// static
+std::unique_ptr<Event> Event::next(std::shared_ptr<DB> db) {
+ auto snapshot =
+ db->select(kEventTable, DB::Condition(), DB::OrderBy("start"));
+ if (snapshot) {
+ do {
+ auto ev = new EventImpl(db);
+ if (ev->load(snapshot.get())) {
+ return std::unique_ptr<Event>(ev);
+ }
+ delete ev;
+ } while (snapshot->next());
+ }
+ return nullptr;
+}
+
+// static
+std::vector<std::unique_ptr<Event>> Event::all(std::shared_ptr<DB> db) {
+ auto snapshot =
+ db->select(kEventTable, DB::Condition(), DB::OrderBy("start"));
+ std::vector<std::unique_ptr<Event>> ret;
+ if (snapshot) {
+ do {
+ auto ev = new EventImpl(db);
+ if (ev->load(snapshot.get())) {
+ ret.emplace_back(ev);
+ } else {
+ delete ev;
+ }
+ } while (snapshot->next());
+ }
+ return ret;
+}
+
+// static
+std::unique_ptr<Event> Event::create(std::shared_ptr<DB> db,
+ const std::string& name, time_t start) {
+ std::unique_ptr<Event> ev(new EventImpl(db));
+ ev->set_name(name);
+ ev->set_start(start);
+ return ev;
+}
+
+
+} // namespace stuff
diff --git a/src/event.hh b/src/event.hh
new file mode 100644
index 0000000..425a70f
--- /dev/null
+++ b/src/event.hh
@@ -0,0 +1,51 @@
+#ifndef EVENT_HH
+#define EVENT_HH
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace stuff {
+
+class DB;
+
+class Event {
+public:
+ virtual ~Event() {}
+
+ virtual const std::string& name() const = 0;
+ virtual void set_name(const std::string& name) = 0;
+
+ virtual const std::string& text() const = 0;
+ virtual void set_text(const std::string& text) = 0;
+
+ virtual time_t start() const = 0;
+ virtual void set_start(time_t start) = 0;
+
+ virtual void going(std::set<std::string>* going,
+ std::set<std::string>* not_going) const = 0;
+ virtual bool is_going(const std::string& name) const = 0;
+
+ virtual void update_going(const std::string& name, bool going) = 0;
+
+ virtual bool store() = 0;
+
+ virtual bool remove() = 0;
+
+ static bool setup(DB* db);
+
+ static std::unique_ptr<Event> next(std::shared_ptr<DB> db);
+ static std::vector<std::unique_ptr<Event>> all(std::shared_ptr<DB> db);
+ static std::unique_ptr<Event> create(std::shared_ptr<DB> db,
+ const std::string& name, time_t start);
+
+protected:
+ Event() { }
+ Event(const Event&) = delete;
+ Event& operator=(const Event&) = delete;
+};
+
+} // namespace stuff
+
+#endif /* EVENT_HH */
diff --git a/src/event_main.cc b/src/event_main.cc
new file mode 100644
index 0000000..99b7d1a
--- /dev/null
+++ b/src/event_main.cc
@@ -0,0 +1,191 @@
+#include "common.hh"
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "args.hh"
+#include "cgi.hh"
+#include "event.hh"
+#include "db.hh"
+#include "http.hh"
+#include "sqlite3_db.hh"
+
+#define DB_PATH "/var/stuff/"
+
+ /*
+token=gIkuvaNzQIHg97ATvDxqgjtO
+team_id=T0001
+team_domain=example
+channel_id=C2147483705
+channel_name=test
+user_id=U2147483697
+user_name=Steve
+command=/weather
+text=94070
+ */
+
+using namespace stuff;
+
+namespace {
+
+std::shared_ptr<DB> open(const std::string& channel) {
+ std::string tmp = channel;
+ for (auto it = tmp.begin(); it != tmp.end(); ++it) {
+ if (!((*it >= 'a' && *it <= 'z') ||
+ (*it >= 'A' && *it <= 'Z') ||
+ (*it >= '0' && *it <= '9') ||
+ *it == '-' || *it == '_' || *it == '.')) {
+ *it = '.';
+ }
+ }
+ auto db = SQLite3::open(DB_PATH + tmp + ".db");
+ if (!db) {
+ Http::response(200, "Unable to open database");
+ }
+ return std::move(db);
+}
+
+bool parse(const std::string& text, std::vector<std::string>* args) {
+ if (Args::parse(text, args)) return true;
+ Http::response(200, "Invalid parameter format");
+ return false;
+}
+
+bool create(const std::string& channel,
+ std::map<std::string, std::string>& data) {
+ std::vector<std::string> args;
+ if (!parse(data["text"], &args) || args.empty()) return true;
+
+ return true;
+}
+
+bool cancel(const std::string& channel,
+ std::map<std::string, std::string>& data) {
+ std::vector<std::string> args;
+ if (!parse(data["text"], &args)) return true;
+ std::vector<unsigned long> indexes;
+ if (args.empty()) {
+ indexes.push_back(0);
+ } else {
+ for (const auto& arg : args) {
+ try {
+ size_t end;
+ auto tmp = std::stoul(arg, &end);
+ if (end != arg.size()) {
+ Http::response(200, "Bad index: " + arg);
+ return true;
+ }
+ indexes.push_back(tmp);
+ } catch (std::invalid_argument& e) {
+ Http::response(200, "Bad index: " + arg);
+ return true;
+ }
+ }
+ std::sort(indexes.begin(), indexes.end(),
+ std::greater<unsigned long>());
+ std::unique(indexes.begin(), indexes.end());
+ }
+ auto db = open(channel);
+ if (!db) return true;
+ auto events = Event::all(db);
+ if (indexes.front() >= events.size()) {
+ if (events.empty()) {
+ Http::response(200, "There are no events");
+ } else {
+ std::ostringstream ss;
+ ss << "There are only " << events.size() << " events";
+ Http::response(200, ss.str());
+ }
+ return true;
+ }
+ bool signal_channel = false;
+ for (const auto& index : indexes) {
+ if (index == 0) signal_channel = true;
+ events[index]->remove();
+ }
+ if (indexes.size() > 1) {
+ Http::response(200, "Events removed");
+ } else {
+ Http::response(200, "Event removed");
+ }
+ if (signal_channel) {
+
+ }
+ return true;
+}
+
+bool update(const std::string& channel,
+ std::map<std::string, std::string>& data) {
+ std::vector<std::string> args;
+ if (!parse(data["text"], &args)) return true;
+
+ return true;
+}
+
+bool show(const std::string& channel,
+ std::map<std::string, std::string>& data) {
+ std::vector<std::string> args;
+ if (!parse(data["text"], &args)) return true;
+
+ return true;
+}
+
+bool going(const std::string& channel,
+ std::map<std::string, std::string>& data,
+ bool going) {
+ std::vector<std::string> args;
+ if (!parse(data["text"], &args)) return true;
+
+ return true;
+}
+
+bool handle_request(CGI* cgi) {
+ switch (cgi->request_type()) {
+ case CGI::GET:
+ case CGI::POST:
+ break;
+ default:
+ Http::response(500, "Unsupported request");
+ return true;
+ }
+
+ std::map<std::string, std::string> data;
+ cgi->get_data(&data);
+ const auto& channel = data["channel"];
+ if (channel.empty()) {
+ Http::response(500, "No channel");
+ return true;
+ }
+ auto command = data["command"];
+ if (command.front() == '/') command = command.substr(1);
+ if (command == "create") {
+ return create(channel, data);
+ }
+ if (command == "cancel") {
+ return cancel(channel, data);
+ }
+ if (command == "update") {
+ return update(channel, data);
+ }
+ if (command == "show") {
+ return show(channel, data);
+ }
+ if (command == "going") {
+ return going(channel, data, true);
+ }
+ if (command == "!going") {
+ return going(channel, data, false);
+ }
+ Http::response(500, "Unknown command: " + command);
+ return true;
+}
+
+} // namespace
+
+int main() {
+ return CGI::run(handle_request);
+}
diff --git a/src/http.cc b/src/http.cc
new file mode 100644
index 0000000..248bd6b
--- /dev/null
+++ b/src/http.cc
@@ -0,0 +1,52 @@
+#include "common.hh"
+
+#include <iostream>
+
+#include "http.hh"
+
+namespace stuff {
+
+namespace {
+
+const std::string EOL = "\r\n";
+
+const char* get_status_message(unsigned int status) {
+ switch (status) {
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Partial information";
+ case 204: return "No response";
+ case 301: return "Moved";
+ case 302: return "Found";
+ case 304: return "Not modified";
+ case 400: return "Bad request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment required";
+ case 403: return "Forbidden";
+ case 404: return "Not found";
+ case 500: return "Internal error";
+ case 501: return "Not implemented";
+ }
+ return nullptr;
+}
+
+} // namespace
+
+// static
+void Http::response(unsigned int status, const std::string& content) {
+ if (status != 200) {
+ std::cout << "Status: " << status;
+ const char* msg = get_status_message(status);
+ if (msg) {
+ std::cout << ' ' << msg;
+ }
+ std::cout << EOL;
+ }
+ std::cout << "Content-Type: text/plain; charset=utf-8" << EOL;
+ std::cout << "Content-Length: " << content.size() << EOL;
+ std::cout << EOL;
+ std::cout << content;
+}
+
+} // namespace stuff
diff --git a/src/http.hh b/src/http.hh
new file mode 100644
index 0000000..58bfb92
--- /dev/null
+++ b/src/http.hh
@@ -0,0 +1,19 @@
+#ifndef HTTP_HH
+#define HTTP_HH
+
+#include <string>
+
+namespace stuff {
+
+class Http {
+public:
+ static void response(unsigned int status, const std::string& content);
+
+private:
+ Http() = delete;
+ ~Http() = delete;
+};
+
+} // namespace stuff
+
+#endif /* HTTP_HH */
diff --git a/src/signup.cc b/src/signup.cc
deleted file mode 100644
index a3eafd4..0000000
--- a/src/signup.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "common.hh"
-
-#include "cgi.hh"
-#include "db.hh"
-
-using namespace stuff;
-
-namespace {
-
-bool handle_request(CGI* cgi) {
-
- return false;
-}
-
-} // namespace
-
-int main() {
- return CGI::run(handle_request);
-}