diff --git a/exes/themis/inc/alarm_database.hpp b/exes/themis/inc/alarm_database.hpp index e121002900..38c2658fe0 100644 --- a/exes/themis/inc/alarm_database.hpp +++ b/exes/themis/inc/alarm_database.hpp @@ -12,7 +12,7 @@ namespace tfc::themis { using enum tfc::snitch::level_e; -using enum tfc::snitch::api::active_e; +using tfc::snitch::api::time_point; inline auto config_file_name_populate_dir() -> std::string { auto const file{ base::make_config_file_name(base::get_exe_name(), "db") }; @@ -48,10 +48,10 @@ CREATE TABLE IF NOT EXISTS AlarmTranslation( // And because sqlite does not offer a native timestamp type. db_ << R"( CREATE TABLE IF NOT EXISTS AlarmActivations( - activation_id INTEGER PRIMARY KEY, + activation_id LONG INTEGER PRIMARY KEY, alarm_id INTEGER NOT NULL, activation_time LONG INTEGER NOT NULL, - activation_level INTEGER NOT NULL, + activation_level BOOLEAN NOT NULL, FOREIGN KEY(alarm_id) REFERENCES Alarms(alarm_id) ); )"; @@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS AlarmActivations( CREATE TABLE IF NOT EXISTS AlarmAcks( activation_id INTEGER, ack_timestamp LONG INTEGER NOT NULL, + UNIQUE(activation_id) ON CONFLICT IGNORE, FOREIGN KEY(activation_id) REFERENCES AlarmActivations(activation_id) ); )"; @@ -150,6 +151,77 @@ ON Alarms.sha1sum = AlarmTranslation.sha1sum; locale, short_msg, msg); } + auto set_alarm(std::uint64_t alarm_id, + std::unordered_map variables, + std::optional tp = {}) -> void { + db_ << fmt::format("INSERT INTO AlarmActivations(alarm_id, activation_time, activation_level) VALUES({},{},1)", alarm_id, + milliseconds_since_epoch(tp)); + std::int64_t last_insert_rowid = db_.last_insert_rowid(); + if (last_insert_rowid < 0) { + throw std::runtime_error("Failed to insert activation into database"); + } + + for (auto& [key, value] : variables) { + db_ << fmt::format("INSERT INTO AlarmVariables(activation_id, variable_key, variable_value) VALUES({},{},'{}');", + last_insert_rowid, key, value); + } + } + auto reset_alarm(std::uint64_t alarm_id, std::optional tp = {}) -> void { + db_ << fmt::format("INSERT INTO AlarmActivations(alarm_id, activation_time, activation_level) VALUES({},{},0)", alarm_id, + milliseconds_since_epoch(tp)); + } + + auto ack_alarm(std::uint64_t activation_id, std::optional tp = {}) -> void { + db_ << fmt::format("INSERT INTO AlarmAcks(activation_id, ack_timestamp) VALUES({},{});", activation_id, + milliseconds_since_epoch(tp)); + } + + auto ack_all_alarms(std::optional tp = {}) -> void { + // TODO: This is not 100% but we should be able to do this in a single query. Very fast. + // Gonna build up some more elaborate test cases and dig into this when that is complete + db_ << fmt::format( + "INSERT INTO AlarmAcks(activation_id, ack_timestamp) SELECT DISTINCT activation_id, {} FROM AlarmActivations;", + milliseconds_since_epoch(tp)); + } + + [[nodiscard]] auto list_activations(std::string_view locale, + std::uint64_t start_count, + std::uint64_t count, + tfc::snitch::level_e level, + tfc::snitch::api::active_e active, + std::optional start, + std::optional end) + -> std::vector { + std::vector activations; + std::string populated_query = fmt::format(R"( +SELECT activation_id, Alarms.alarm_id, activation_time, activation_level, Alarms.short_msg, Alarms.msg, AlarmTranslation.short_msg, AlarmTranslation.msg, Alarms.alarm_latching, Alarms.alarm_level +FROM AlarmActivations +JOIN Alarms on (Alarm.alarm_id = AlarmActivations.alarm_id) +LEFT OUTER JOIN AlarmTranslation on (Alarms.sha1sum = AlarmTranslation.sha1sum) +WHERE AlarmTranslation.locale = '{}' AND activation_time >= {} AND activation_time <= {} +)", + locale, milliseconds_since_epoch(start), milliseconds_since_epoch(end)); + if (level != tfc::snitch::level_e::unknown) { + populated_query += fmt::format("AND alarm_level = {}", static_cast(level)); + } + if (active != tfc::snitch::api::active_e::all) { + populated_query += fmt::format("AND activation_level = {}", static_cast(active)); + } + populated_query += fmt::format("LIMIT {} OFFSET {};", count, start_count); + db_ << populated_query >> + [&](std::uint64_t activation_id, std::uint64_t alarm_id, std::int64_t activation_time, bool activation_level, + std::string short_msg_en, std::string msg_en, std::unique_ptr translated_short_msg, + std::unique_ptr translated_msg, bool alarm_latching, std::uint8_t alarm_level) { + std::string final_short_msg = translated_short_msg == nullptr ? short_msg_en : *translated_short_msg; + std::string final_msg = translated_msg == nullptr ? msg_en : *translated_msg; + tfc::snitch::api::activation temp{ alarm_id, activation_id, final_short_msg, final_msg, activation_level, + static_cast(alarm_level), alarm_latching, + timepoint_from_milliseconds(activation_time) }; + activations.emplace_back(temp); + }; + return activations; + } + // Note. Use `echo -n "value" | sha1sum` to not hash the newline character and // match the results from this function. [[nodiscard]] static auto get_sha1(std::string_view msg) -> std::string { @@ -167,6 +239,22 @@ ON Alarms.sha1sum = AlarmTranslation.sha1sum; } private: + static std::int64_t milliseconds_since_epoch(std::optional tp) { + tfc::snitch::api::time_point value = + tp.value_or(std::chrono::time_point_cast(std::chrono::system_clock::now())); + return milliseconds_since_epoch_base(value); + } + + static constexpr std::int64_t milliseconds_since_epoch_base(tfc::snitch::api::time_point tp) { + return std::chrono::duration_cast(tp.time_since_epoch()).count(); + } + + static constexpr tfc::snitch::api::time_point timepoint_from_milliseconds(std::int64_t ms) { + return tfc::snitch::api::time_point(std::chrono::milliseconds(ms)); + } + + static_assert(milliseconds_since_epoch_base(timepoint_from_milliseconds(1000)) == 1000); + sqlite::database db_; }; } // namespace tfc::themis diff --git a/exes/themis/tests/themis_database_test.cpp b/exes/themis/tests/themis_database_test.cpp index bc53c48e57..52a881ade9 100644 --- a/exes/themis/tests/themis_database_test.cpp +++ b/exes/themis/tests/themis_database_test.cpp @@ -60,5 +60,9 @@ auto main(int argc, char** argv) -> int { expect(iterator != alarms.at(0).translations.end()); expect(iterator->second.description == "Some icelandic really"); expect(iterator->second.details == "This is really some icelandic"); + + auto activations = alarm_db.list_activations("es", 0, 10, tfc::snitch::level_e::unknown, tfc::snitch::api::active_e::all, + , std::chrono::system_clock::now()); + expect(activations.size() == 0); }; } diff --git a/libs/snitch/inc/public/tfc/snitch/common.hpp b/libs/snitch/inc/public/tfc/snitch/common.hpp index 8e0d52d036..480a0d1cd0 100644 --- a/libs/snitch/inc/public/tfc/snitch/common.hpp +++ b/libs/snitch/inc/public/tfc/snitch/common.hpp @@ -6,8 +6,9 @@ namespace tfc::snitch { -enum struct level_e : std::uint8_t { - unknown = 0, +enum struct level_e : std::int8_t { + all = -1, + unknown, info, warning, error, @@ -18,7 +19,7 @@ namespace api { using time_point = std::chrono::time_point; using alarm_id_t = std::int64_t; -enum struct active_e : std::int32_t { +enum struct active_e : std::int8_t { all = -1, inactive = 0, active = 1 @@ -41,6 +42,8 @@ struct alarm { }; struct activation { + std::uint64_t alarm_id; + std::uint64_t activation_id; std::string description; std::string details; bool active{};