#include "subscription_store.hpp"
#include <string>
#include <utility>
#include "database.hpp"
#include "service_error.hpp"
namespace telegrammer
{
namespace
{
mw::E<void> execute(mw::SQLite& db, const std::string& sql)
{
auto result = db.execute(sql);
if(!result.has_value())
{
return std::unexpected(result.error());
}
return {};
}
mw::Error databaseError([[maybe_unused]] const mw::Error& error)
{
return serviceError(503, "DATABASE_UNAVAILABLE",
"The state database is unavailable");
}
mw::E<Subscription> readSubscription(mw::SQLite& db, int64_t id)
{
auto statement = db.statementFromStr(
"SELECT id, owner_id, chat_id, callback_url, created_at "
"FROM subscriptions WHERE id = ?;");
if(!statement.has_value())
{
return std::unexpected(databaseError(statement.error()));
}
auto bind_result = statement->bind(id);
if(!bind_result.has_value())
{
return std::unexpected(databaseError(bind_result.error()));
}
auto rows = db.eval<int64_t, int64_t, int64_t, std::string, int64_t>(
std::move(*statement));
if(!rows.has_value())
{
return std::unexpected(databaseError(rows.error()));
}
if(rows->empty())
{
return std::unexpected(serviceError(
500, "DATABASE_INCONSISTENT", "Subscription disappeared"));
}
const auto& row = (*rows)[0];
return Subscription{std::get<0>(row), std::get<1>(row),
std::get<2>(row), std::get<3>(row),
std::get<4>(row)};
}
} // namespace
SubscriptionStore::SubscriptionStore(std::string db_path)
: db_path_(std::move(db_path))
{}
mw::E<Subscription> SubscriptionStore::add(
int64_t owner_id, int64_t chat_id, const std::string& callback_url) const
{
auto db_result = openDatabase(db_path_);
if(!db_result.has_value())
{
return std::unexpected(db_result.error());
}
mw::SQLite& db = **db_result;
auto begin_result = execute(db, "BEGIN IMMEDIATE;");
if(!begin_result.has_value())
{
return std::unexpected(databaseError(begin_result.error()));
}
auto rollback = [&db]()
{
[[maybe_unused]] auto result = db.execute("ROLLBACK;");
};
auto existing = db.statementFromStr(
"SELECT id FROM subscriptions "
"WHERE owner_id = ? AND chat_id = ? AND callback_url = ?;");
if(!existing.has_value())
{
rollback();
return std::unexpected(databaseError(existing.error()));
}
auto existing_bind = existing->bind(owner_id, chat_id, callback_url);
if(!existing_bind.has_value())
{
rollback();
return std::unexpected(databaseError(existing_bind.error()));
}
auto existing_rows = db.eval<int64_t>(std::move(*existing));
if(!existing_rows.has_value())
{
rollback();
return std::unexpected(databaseError(existing_rows.error()));
}
if(!existing_rows->empty())
{
auto subscription = readSubscription(
db, std::get<0>((*existing_rows)[0]));
if(!subscription.has_value())
{
rollback();
return std::unexpected(subscription.error());
}
auto commit_result = execute(db, "COMMIT;");
if(!commit_result.has_value())
{
rollback();
return std::unexpected(databaseError(commit_result.error()));
}
return *subscription;
}
auto owner_count = db.statementFromStr(
"SELECT COUNT(*) FROM subscriptions WHERE owner_id = ?;");
if(!owner_count.has_value())
{
rollback();
return std::unexpected(databaseError(owner_count.error()));
}
auto owner_bind = owner_count->bind(owner_id);
if(!owner_bind.has_value())
{
rollback();
return std::unexpected(databaseError(owner_bind.error()));
}
auto owner_rows = db.evalToValue<int64_t>(std::move(*owner_count));
if(!owner_rows.has_value())
{
rollback();
return std::unexpected(databaseError(owner_rows.error()));
}
if(*owner_rows >= 100)
{
rollback();
return std::unexpected(serviceError(
409, "SUBSCRIPTION_LIMIT", "The key has too many subscriptions"));
}
auto total_rows = db.evalToValue<int64_t>(
"SELECT COUNT(*) FROM subscriptions;");
if(!total_rows.has_value())
{
rollback();
return std::unexpected(databaseError(total_rows.error()));
}
if(*total_rows >= 1000)
{
rollback();
return std::unexpected(serviceError(
409, "SUBSCRIPTION_LIMIT", "The daemon has too many subscriptions"));
}
auto statement = db.statementFromStr(
"INSERT INTO subscriptions "
"(owner_id, chat_id, callback_url, created_at) "
"VALUES (?, ?, ?, ?);");
if(!statement.has_value())
{
rollback();
return std::unexpected(databaseError(statement.error()));
}
auto bind_result = statement->bind(owner_id, chat_id, callback_url,
nowSeconds());
if(!bind_result.has_value())
{
rollback();
return std::unexpected(databaseError(bind_result.error()));
}
auto insert_result = db.execute(std::move(*statement));
if(!insert_result.has_value())
{
rollback();
return std::unexpected(databaseError(insert_result.error()));
}
int64_t id = db.lastInsertRowID();
auto subscription = readSubscription(db, id);
if(!subscription.has_value())
{
rollback();
return std::unexpected(subscription.error());
}
auto commit_result = execute(db, "COMMIT;");
if(!commit_result.has_value())
{
rollback();
return std::unexpected(databaseError(commit_result.error()));
}
return *subscription;
}
mw::E<std::vector<Subscription>> SubscriptionStore::list(
int64_t owner_id) const
{
auto db_result = openDatabase(db_path_);
if(!db_result.has_value())
{
return std::unexpected(db_result.error());
}
auto statement = (*db_result)->statementFromStr(
"SELECT id, owner_id, chat_id, callback_url, created_at "
"FROM subscriptions WHERE owner_id = ? ORDER BY id;");
if(!statement.has_value())
{
return std::unexpected(databaseError(statement.error()));
}
auto bind_result = statement->bind(owner_id);
if(!bind_result.has_value())
{
return std::unexpected(databaseError(bind_result.error()));
}
auto rows = (*db_result)->eval<int64_t, int64_t, int64_t, std::string,
int64_t>(std::move(*statement));
if(!rows.has_value())
{
return std::unexpected(databaseError(rows.error()));
}
std::vector<Subscription> result;
result.reserve(rows->size());
for(const auto& row: *rows)
{
result.push_back({std::get<0>(row), std::get<1>(row),
std::get<2>(row), std::get<3>(row),
std::get<4>(row)});
}
return result;
}
mw::E<bool> SubscriptionStore::remove(int64_t owner_id,
int64_t subscription_id) const
{
auto db_result = openDatabase(db_path_);
if(!db_result.has_value())
{
return std::unexpected(db_result.error());
}
auto statement = (*db_result)->statementFromStr(
"DELETE FROM subscriptions WHERE id = ? AND owner_id = ?;");
if(!statement.has_value())
{
return std::unexpected(databaseError(statement.error()));
}
auto bind_result = statement->bind(subscription_id, owner_id);
if(!bind_result.has_value())
{
return std::unexpected(databaseError(bind_result.error()));
}
auto delete_result = (*db_result)->execute(std::move(*statement));
if(!delete_result.has_value())
{
return std::unexpected(databaseError(delete_result.error()));
}
return (*db_result)->changedRowsCount() > 0;
}
} // namespace telegrammer