diff options
| author | Joel Klinghed <the_jk@yahoo.com> | 2015-06-03 00:09:09 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@yahoo.com> | 2015-06-03 00:23:19 +0200 |
| commit | 41c45d4a4d6f8bea9b45d76ddef5d8bac71cfc5a (patch) | |
| tree | 70f5e4a20a7659529a48174a50ff24da92a01e44 /src | |
| parent | 823dc403a9b56e00be4982d0082d30b8df5e53d6 (diff) | |
A start for an event handler
Diffstat (limited to 'src')
| -rw-r--r-- | src/.gitignore | 2 | ||||
| -rw-r--r-- | src/Makefile.am | 7 | ||||
| -rw-r--r-- | src/event.cc | 282 | ||||
| -rw-r--r-- | src/event.hh | 51 | ||||
| -rw-r--r-- | src/event_main.cc | 191 | ||||
| -rw-r--r-- | src/http.cc | 52 | ||||
| -rw-r--r-- | src/http.hh | 19 | ||||
| -rw-r--r-- | src/signup.cc | 19 |
8 files changed, 600 insertions, 23 deletions
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); -} |
