Mercurial > malikania
changeset 169:450d884456c4
Server: initial re-support of PostgreSQL
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 17 Aug 2018 15:44:12 +0200 |
parents | bc450538a6c4 |
children | a3af3b793da4 |
files | CMakeLists.txt libcommon/CMakeLists.txt libserver/CMakeLists.txt libserver/malikania/server/db/account.cpp libserver/malikania/server/db/account.hpp libserver/malikania/server/db/character.cpp libserver/malikania/server/db/character.hpp libserver/malikania/server/db/database.cpp libserver/malikania/server/db/database.hpp libserver/malikania/server/db/dynlib_database.cpp libserver/malikania/server/db/dynlib_database.hpp libserver/malikania/server/db/model.hpp libserver/malikania/server/db/spell.cpp libserver/malikania/server/db/spell.hpp libserver/malikania/server/net/auth_handler.cpp server/main.cpp |
diffstat | 16 files changed, 912 insertions(+), 656 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Mon Aug 06 13:01:00 2018 +0200 +++ b/CMakeLists.txt Fri Aug 17 15:44:12 2018 +0200 @@ -19,7 +19,7 @@ cmake_minimum_required(VERSION 3.0) project(malikania) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
--- a/libcommon/CMakeLists.txt Mon Aug 06 13:01:00 2018 +0200 +++ b/libcommon/CMakeLists.txt Fri Aug 17 15:44:12 2018 +0200 @@ -42,15 +42,6 @@ ${libmlk-common_SOURCE_DIR}/malikania/util.cpp ) -if (WITH_BACKEND MATCHES "SDL") - find_package(SDL2 REQUIRED) - - list(APPEND HEADERS ${libmlk-common_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/sdl_util.hpp) - list(APPEND SOURCES ${libmlk-common_SOURCE_DIR}/malikania/${WITH_BACKEND_DIR}/sdl_util.cpp) - list(APPEND INCLUDES ${SDL2_INCLUDE_DIRS}) - list(APPEND LIBRARIES ${SDL2_LIBRARIES}) -endif () - malikania_define_library( PROJECT libmlk-common TARGET libmlk-common
--- a/libserver/CMakeLists.txt Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/CMakeLists.txt Fri Aug 17 15:44:12 2018 +0200 @@ -18,24 +18,28 @@ project(libmlk-server) +find_package(PostgreSQL REQUIRED) + set( HEADERS + ${libmlk-server_SOURCE_DIR}/malikania/server/client.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.hpp + ${libmlk-server_SOURCE_DIR}/malikania/server/db/model.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.hpp - ${libmlk-server_SOURCE_DIR}/malikania/server/db/model.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.hpp - ${libmlk-server_SOURCE_DIR}/malikania/server/client.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/server.hpp ) set( SOURCES + ${libmlk-server_SOURCE_DIR}/malikania/server/client.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.cpp + ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.cpp + ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.cpp - ${libmlk-server_SOURCE_DIR}/malikania/server/client.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/server.cpp ) @@ -44,12 +48,14 @@ SOURCES ${HEADERS} ${SOURCES} LIBRARIES ${Boost_LIBRARIES} + ${PostgreSQL_LIBRARIES} OpenSSL::Crypto OpenSSL::SSL libmlk-common $<$<BOOL:${WIN32}>:mswsock> PUBLIC_INCLUDES ${Boost_INCLUDE_DIRS} + ${PostgreSQL_INCLUDE_DIRS} ${libmlk-server_SOURCE_DIR} PRIVATE_INCLUDES ${libmlk-server_SOURCE_DIR}/malikania/server
--- a/libserver/malikania/server/db/account.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/account.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -16,97 +16,227 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <cassert> + #include "account.hpp" -#include "database.hpp" + +namespace mlk::server::db { + +auto account::load(const result& res) -> account +{ + account a(PQgetvalue(res.get(), 0, 1), PQgetvalue(res.get(), 0, 2)); + + a.id_ = std::stoi(PQgetvalue(res.get(), 0, 0)); + a.firstname_ = PQgetvalue(res.get(), 0, 3); + a.lastname_ = PQgetvalue(res.get(), 0, 4); + a.email_ = PQgetvalue(res.get(), 0, 5); + a.characters_ = character::load(a); -namespace mlk { + return a; +} + +void account::clear() +{ + id_ = 0U; +} -namespace server { +account::account(std::string login, std::string password) noexcept + : login_(std::move(login)) + , password_(std::move(password)) +{ + assert(!login_.empty()); + assert(!password_.empty()); +} + +auto account::get_login() const noexcept -> const std::string& +{ + return login_; +} void account::set_password(std::string password) { + static const std::string sql( + "UPDATE account" + " SET password = $1" + " WHERE id = $2" + ); + assert(!password.empty()); if (is_published() && password_ != password) - db_.accounts().set_password(*this, password); + db::exec(sql, { password, id_ }); password_ = std::move(password); } +auto account::get_email() const noexcept -> const std::string& +{ + return email_; +} + void account::set_email(std::string email) { + static const std::string sql( + "UPDATE account" + " SET email = $1" + " WHERE id = $2" + ); + assert(!email.empty()); if (is_published() && email_ != email) - db_.accounts().set_email(*this, email); + db::exec(sql, { email, id_ }); email_ = std::move(email); } +auto account::get_firstname() const noexcept -> const std::string& +{ + return firstname_; +} + void account::set_firstname(std::string name) { + static const std::string sql( + "UPDATE account" + " SET firstname = $1" + " WHERE id = $2" + ); + if (is_published() && firstname_ != name) - db_.accounts().set_firstname(*this, name); + db::exec(sql, { name, id_ }); firstname_ = std::move(name); } +auto account::get_lastname() const noexcept -> const std::string& +{ + return lastname_; +} + void account::set_lastname(std::string name) { + static const std::string sql( + "UPDATE account" + " SET lastname = $1" + " WHERE id = $2" + ); + if (is_published() && lastname_ != name) - db_.accounts().set_lastname(*this, name); + db::exec(sql, { name, id_ }); lastname_ = std::move(name); } +auto account::get_characters() const noexcept -> const std::vector<character>& +{ + return characters_; +} + void account::add(character ch) { - assert(is_published() && ch.is_draft()); + assert(ch.is_draft()); + + characters_.reserve(characters_.size() + 1); - db_.characters().publish(ch, *this); + if (is_published()) + ch.publish(*this); + characters_.push_back(std::move(ch)); } void account::remove(std::vector<character>::iterator it) { - // TODO: assert it is in vector. - assert(is_published()); - - db_.characters().unpublish(*it); + // TODO: assert 'it' is in vector. + it->unpublish(); characters_.erase(it); } -void account::save() +void account::publish() { - if (is_draft()) - db_.accounts().publish(*this); + static const std::string sql( + "INSERT INTO account(" + " login," + " password," + " firstname," + " lastname," + " email" + ") " + "VALUES ($1, $2, $3, $4, $5) " + "RETURNING id" + ); + + assert(is_draft()); + + /* + * Recursively save the account, its characters and spells of those + * characters. + */ + transaction txn([this] { + clear(); + + for (auto& c : characters_) + c.clear(); + }); + + const auto r = select(sql, { login_, password_, firstname_, lastname_, email_ }); + + id_ = std::stoi(PQgetvalue(r.get(), 0, 0)); + + for (auto& c : characters_) + c.publish(*this); + + txn.commit(); assert(is_published()); } -void account::remove() +void account::unpublish() { - if (is_published()) - db_.accounts().unpublish(*this); + static const std::string sql( + "DELETE" + " FROM account" + " WHERE id = $1" + ); + + assert(is_published()); - id_ = 0U; - characters_.clear(); + id_ = 0; + /* + * Recursively make all characters and their spells draft. + */ + for (auto& c : characters_) + c.clear(); + assert(is_draft()); } -boost::optional<account> account_dao::authenticate(const std::string& login, - const std::string& password) +auto account::find_by_login(const std::string& login) -> std::optional<account> +{ + static const std::string sql( + "SELECT *" + " FROM account" + " WHERE login = $1" + ); + + const auto r = select(sql, { login }); + + if (PQntuples(r.get()) == 0) + return std::nullopt; + + return load(r); +} + +auto account::authenticate(const std::string& login, + const std::string& password) -> std::optional<account> { auto ac = find_by_login(login); - if (!ac || ac->get_password() != password) - return boost::none; + if (!ac || ac->password_ != password) + return std::nullopt; return ac; } - -} // !server - -} // !mlk +} // !mlk::server::db
--- a/libserver/malikania/server/db/account.hpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/account.hpp Fri Aug 17 15:44:12 2018 +0200 @@ -24,14 +24,13 @@ * \brief Database account object. */ -#include <boost/optional.hpp> +#include <optional> -#include "model.hpp" #include "character.hpp" +#include "database.hpp" +#include "model.hpp" -namespace mlk { - -namespace server { +namespace mlk::server::db { /** * \brief Database account object. @@ -45,6 +44,10 @@ std::string lastname_; std::vector<character> characters_; + static auto load(const result&) -> account; + + void clear(); + public: /** * Create a draft account. @@ -55,35 +58,14 @@ * \param password the password * \warning the password is saved as-is and **must** be hashed by the caller. */ - inline account(database& db, std::string login, std::string password) noexcept - : model(db) - , login_(std::move(login)) - , password_(std::move(password)) - { - assert(!login_.empty()); - assert(!password_.empty()); - } + account(std::string login, std::string password) noexcept; /** * Get the account login. * * \return the login */ - inline const std::string& get_login() const noexcept - { - return login_; - } - - /** - * Get the password. - * - * \note the password is returned as-is - * \return the password - */ - inline const std::string& get_password() const noexcept - { - return password_; - } + auto get_login() const noexcept -> const std::string&; /** * Set the password. @@ -98,10 +80,7 @@ * * \return the email */ - inline const std::string& get_email() const noexcept - { - return email_; - } + auto get_email() const noexcept -> const std::string&; /** * Set the account email. @@ -116,10 +95,7 @@ * * \return the name */ - inline const std::string& get_firstname() const noexcept - { - return firstname_; - } + auto get_firstname() const noexcept -> const std::string&; /** * Set the account firstname. @@ -133,10 +109,7 @@ * * \return the name */ - inline const std::string& get_lastname() const noexcept - { - return lastname_; - } + auto get_lastname() const noexcept -> const std::string&; /** * Set the account last name. @@ -150,27 +123,7 @@ * * \return the associated characters. */ - inline const std::vector<character>& get_characters() const noexcept - { - return characters_; - } - -#if defined(MALIKANIA_PRIVATE) - /** - * Overloaded function. - * - * This function is only available if MALIKANIA_PRIVATE is defined as it is - * not a end user function but is required to implement new database - * backends. - * - * \warning only use this function in database backends - * \param characters the list of characters - */ - inline std::vector<character>& get_characters() noexcept - { - return characters_; - } -#endif // !MALIKANIA_PRIVATE + auto get_characters() const noexcept -> const std::vector<character>&; /** * Add the character to the account. @@ -198,7 +151,7 @@ * \throw std::exception on errors * \post is_published() */ - void save(); + void publish(); /** * Destroy the account. @@ -208,106 +161,29 @@ * \throw std::exception on errors * \post is_draft() */ - void remove(); -}; - -/** - * \brief Account DAO. - */ -class account_dao { -public: - /** - * Authenticate the user. - * - * \param login the login - * \param password the password - * \return the account or null if does not exist - */ - boost::optional<account> authenticate(const std::string& login, - const std::string& password); + void unpublish(); /** * Find an account by login. * - * \param login the login name - * \return the account or null if not found - * \throw exception on other errors + * \param login the login + * \return the account or none if not found + * \throw std::runtime_error on database errors */ - virtual boost::optional<account> find_by_login(const std::string& login) = 0; - - /** - * Save the account. - * - * The implementation will be called only if the account is draft and must - * do the following: - * - * 1. Register the account to its database, - * 2. Create a unique and global id and set it as id_ member variable. - * - * \note called from account::save - * \throw std::exception if the operation could not succeed - */ - virtual void publish(account& ac) = 0; - - /** - * Remove the account. - * - * The implementation will be called only if the account is public and must - * do remove from the database all the associated characters along with - * their spells. - * - * \note called from account::remove - * \throw std::exception if the operation could not succeed - */ - virtual void unpublish(const account& ac) = 0; + static auto find_by_login(const std::string& login) -> std::optional<account>; /** - * Update the account password in database. - * - * Only called when the password needs to be changed, the implementation - * does not need to update password_ field. + * Find and authenticate a user. * - * \note called from set_password helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_password(const account& ac, const std::string& password) = 0; - - /** - * Update the account email in database. - * - * Only called when the email needs to be changed, the implementation does - * not need to update email_ field. - * - * \note called from set_email helper - * \throw std::exception if the operation could not succeed + * \param login the login + * \param password the password + * \return the account or none if not found + * \throw std::runtime_error on database errors */ - virtual void set_email(const account& ac, const std::string& email) = 0; - - /** - * Update the account firstname in database. - * - * Only called when the first name needs to be changed, the implementation - * does not need to update firstname_ field. - * - * \note called from set_firstname helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_firstname(const account& ac, const std::string& name) = 0; - - /** - * Update the account lastname in database. - * - * Only called when the last name needs to be changed, the implementation - * does not need to update lastname_ field. - * - * \note called from set_lastname helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_lastname(const account& ac, const std::string& name) = 0; + static auto authenticate(const std::string& login, + const std::string& password) -> std::optional<account>; }; -} // !server - -} // !mlk +} // !mlk::server::db #endif // !MALIKANIA_SERVER_DB_ACCOUNT_HPP
--- a/libserver/malikania/server/db/character.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/character.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -16,57 +16,251 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <cassert> #include <numeric> +#include "account.hpp" #include "character.hpp" #include "database.hpp" -namespace mlk { +namespace mlk::server::db { + +auto character::load(const result& result, int row) -> character +{ + character ch(PQgetvalue(result.get(), row, 2), PQgetvalue(result.get(), row, 3)); + + ch.levels_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 4)); + ch.factors_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 5)); + ch.exp_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 6)); + + ch.levels_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 7)); + ch.factors_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 8)); + ch.exp_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 9)); + + ch.levels_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 10)); + ch.factors_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 11)); + ch.exp_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 12)); + + ch.levels_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 13)); + ch.factors_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 14)); + ch.exp_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 15)); + + ch.levels_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 16)); + ch.factors_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 17)); + ch.exp_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 18)); + + ch.id_ = std::stoi(PQgetvalue(result.get(), row, 0)); + ch.spells_ = spell::load(ch); + + return ch; +} + +auto character::load(const account& parent) -> std::vector<character> +{ + static const std::string sql( + "SELECT *" + " FROM character" + " WHERE account_id = $1" + ); + + const auto r = select(sql, { parent.get_id() }); + + if (PQntuples(r.get()) == 0) + throw std::runtime_error("failed to load characters"); + + std::vector<character> characters; + + for (int i = 0; i < PQntuples(r.get()); ++i) + characters.push_back(load(r, i)); + + return characters; +} + +void character::clear() +{ + id_ = 0U; + + for (auto& s : spells_) + s.clear(); +} -namespace server { +void character::publish(account& parent) +{ + static const std::string sql( + "INSERT INTO character (" + " account_id," + " nickname," + " type," + " hp_level," + " hp_factor," + " hp_exp," + " force_level," + " force_factor," + " force_exp," + " defense_level," + " defense_factor," + " defense_exp," + " agility_level," + " agility_factor," + " agility_exp," + " luck_level," + " luck_factor," + " luck_exp" + ") " + "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) " + "RETURNING id" + ); + + const auto r = select(sql, { + parent.get_id(), + nickname_, + type_, + levels_[stat::hp], + factors_[stat::hp], + exp_[stat::hp], + levels_[stat::force], + factors_[stat::force], + exp_[stat::force], + levels_[stat::defense], + factors_[stat::defense], + exp_[stat::defense], + levels_[stat::agility], + factors_[stat::agility], + exp_[stat::agility], + levels_[stat::luck], + factors_[stat::luck], + exp_[stat::luck] + }); + + if (PQntuples(r.get()) == 0) + throw std::runtime_error("failed to save character"); + + id_ = std::stoi(PQgetvalue(r.get(), 0, 0)); + + for (auto& s : spells_) + s.publish(*this); +} + +void character::unpublish() +{ + static const std::string sql( + "DELETE" + " FROM character" + " WHERE id = $1" + ); + + exec(sql, { id_ }); + clear(); +} + +character::character(std::string nickname, std::string type) noexcept + : nickname_(std::move(nickname)) + , type_(std::move(type)) +{ + assert(!nickname_.empty()); + assert(!type_.empty()); +} + +auto character::get_nickname() const noexcept -> const std::string& +{ + return nickname_; +} + +auto character::get_type() const noexcept -> const std::string& +{ + return type_; +} + +auto character::get_spells() const noexcept -> const std::vector<spell>& +{ + return spells_; +} + +auto character::get_levels() const noexcept -> const std::array<std::uint8_t, 5>& +{ + return levels_; +} void character::set_levels(std::array<std::uint8_t, 5> levels) { + static const std::string sql( + "UPDATE character" + " SET hp_level = $1" + " , force_level = $2" + " , defense_level = $3" + " , agility_level = $4" + " , luck_level = $5" + ); + if (is_published()) - db_.characters().set_levels(*this, levels); + exec(sql, { id_, levels[0], levels[1], levels[2], levels[3], levels[4] }); levels_ = std::move(levels); } +auto character::get_factors() const noexcept -> const std::array<std::uint8_t, 5>& +{ + return factors_; +} + void character::set_factors(std::array<std::uint8_t, 5> factors) { + static const std::string sql( + "UPDATE character" + " SET hp_factor = $1" + " , force_factor = $2" + " , defense_factor = $3" + " , agility_factor = $4" + " , luck_factor = $5" + ); + assert(std::accumulate(factors.begin(), factors.end(), 0U) == 100U); if (is_published()) - db_.characters().set_factors(*this, factors); + exec(sql, { id_, factors[0], factors[1], factors[2], factors[3], factors[4] }); factors_ = std::move(factors); } +auto character::get_experience() const noexcept -> const std::array<std::uint32_t, 5>& +{ + return exp_; +} + void character::set_experience(std::array<std::uint32_t, 5> experience) { + static const std::string sql( + "UPDATE character" + " SET hp_experience = $1" + " , force_experience = $2" + " , defense_experience = $3" + " , agility_experience = $4" + " , luck_experience = $5" + ); + if (is_published()) - db_.characters().set_experience(*this, experience); + exec(sql, { id_, experience[0], experience[1], experience[2], experience[3], experience[4] }); exp_ = std::move(experience); } void character::add(spell sp) { - assert(is_published() && sp.is_draft()); + assert(sp.is_draft()); + + spells_.reserve(spells_.size() + 1); - db_.spells().publish(*this, sp); + if (is_published()) + sp.publish(*this); + spells_.push_back(std::move(sp)); } void character::remove(std::vector<spell>::iterator it) { - assert(is_published()); - - db_.spells().unpublish(*this, *it); + // TODO: assert 'it' is in vector. + it->unpublish(); spells_.erase(it); } -} // !server - -} // !mlk +} // !mlk::server::db
--- a/libserver/malikania/server/db/character.hpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/character.hpp Fri Aug 17 15:44:12 2018 +0200 @@ -28,12 +28,11 @@ #include <array> #include <vector> +#include "database.hpp" #include "model.hpp" #include "spell.hpp" -namespace mlk { - -namespace server { +namespace mlk::server::db { class account; @@ -42,6 +41,8 @@ */ class character : public model { public: + friend class account; + /** * \brief Stats index in arrays * \see set_levels @@ -58,94 +59,57 @@ private: std::string nickname_; - std::string classname_; + std::string type_; std::vector<spell> spells_; std::array<std::uint8_t, 5> levels_{1U, 1U, 1U, 1U, 1U}; std::array<std::uint8_t, 5> factors_{20U, 20U, 20U, 20U, 20U}; std::array<std::uint32_t, 5> exp_{0U, 0U, 0U, 0U, 0U}; + static auto load(const result&, int) -> character; + static auto load(const account&) -> std::vector<character>; + + void clear(); + void publish(account&); + void unpublish(); + public: /** * Construct a character, no database is modified yet. * * \pre !nickname.empty() - * \pre !classname.empty() + * \pre !type.empty() * \param nickname the nickname - * \param classname the classname + * \param type the type */ - inline character(database& db, std::string nickname, std::string classname) noexcept - : model(db) - , nickname_(std::move(nickname)) - , classname_(std::move(classname)) - { - assert(!nickname_.empty()); - assert(!classname_.empty()); - } + character(std::string nickname, std::string type) noexcept; /** * Get the character nickname. * * \return the name */ - inline const std::string& get_nickname() const noexcept - { - return nickname_; - } + auto get_nickname() const noexcept -> const std::string&; /** - * Get the character class name. + * Get the character type name. * - * \return the class name + * \return the type name */ - inline const std::string& get_classname() const noexcept - { - return classname_; - } + auto get_type() const noexcept -> const std::string&; /** * Get the list of spells. * * \return the spells */ - inline const std::vector<spell>& get_spells() const noexcept - { - return spells_; - } - -#if defined(MALIKANIA_PRIVATE) - /** - * Get the list of spells. - * - * \return the spells - */ - inline std::vector<spell>& get_spells() noexcept - { - return spells_; - } -#endif // !MALIKANIA_PRIVATE + auto get_spells() const noexcept -> const std::vector<spell>&; /** * Get the list of levels. * * \return the levels */ - inline const std::array<std::uint8_t, 5>& get_levels() const noexcept - { - return levels_; - } - -#if defined(MALIKANIA_PRIVATE) - /** - * Get the list of levels. - * - * \return the levels - * \note only available if MALIKANIA_PRIVATE macro is defined - */ - inline std::array<std::uint8_t, 5>& get_levels() noexcept - { - return levels_; - } -#endif // !MALIKANIA_PRIVATE + auto get_levels() const noexcept -> const std::array<std::uint8_t, 5>&; /** * Set the new levels. @@ -159,23 +123,7 @@ * * \return the factors */ - inline const std::array<std::uint8_t, 5>& get_factors() const noexcept - { - return factors_; - } - -#if defined(MALIKANIA_PRIVATE) - /** - * Get the list of factors. - * - * \return the factors - * \note only available if MALIKANIA_PRIVATE macro is defined - */ - inline std::array<std::uint8_t, 5>& get_factors() noexcept - { - return factors_; - } -#endif // !MALIKANIA_PRIVATE + auto get_factors() const noexcept -> const std::array<std::uint8_t, 5>&; /** * Set progression factors. @@ -190,23 +138,7 @@ * * \return the experience */ - inline const std::array<std::uint32_t, 5>& get_experience() const noexcept - { - return exp_; - } - -#if defined(MALIKANIA_PRIVATE) - /** - * Get the stats experience. - * - * \return the experience - * \note only available if MALIKANIA_PRIVATE macro is defined - */ - inline std::array<std::uint32_t, 5>& get_experience() noexcept - { - return exp_; - } -#endif // !MALIKANIA_PRIVATE + auto get_experience() const noexcept -> const std::array<std::uint32_t, 5>&; /** * Set stats experience. @@ -234,78 +166,6 @@ void remove(std::vector<spell>::iterator it); }; -/** - * \brief Character DAO. - */ -class character_dao { -public: - /** - * Default constructor. - */ - character_dao() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~character_dao() = default; - - /** - * Save this character. - * - * The implementation must save the character and update the id, account_id - * member variables. - * - * Then it will be added into account.characters_ variable. - * - * \param account the parent account - * \param ch this character - * \throw std::exception if the operation could not succeed - */ - virtual void publish(character& ch, const account& account) = 0; - - /** - * Remove this character. - * - * The implementation must remove the character from the database and update - * id, account_id member variables. - * - * \param ch this character - */ - virtual void unpublish(const character& ch) = 0; - - /** - * Update the character levels in database. - * - * \param ch the character - * \param levels the new levels - * \note called from character::set_levels helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_levels(const character& ch, const std::array<std::uint8_t, 5>& levels) = 0; - - /** - * Update the character progression factors in database. - * - * \param ch the character - * \param factors the new factors - * \note called from character::set_levels helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_factors(const character& ch, const std::array<std::uint8_t, 5>& levels) = 0; - - /** - * Update the current level experiences in database. - * - * \param ch the character - * \param experience the new experience - * \note called from character::set_levels helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_experience(const character& ch, const std::array<std::uint32_t, 5>& levels) = 0; -}; - -} // !server - -} // !mlk +} // !mlk::server::db #endif // !MALIKANIA_SERVER_DB_CHARACTER_HPP
--- a/libserver/malikania/server/db/database.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/database.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -1,10 +1,210 @@ -#include <cstdlib> -#include <stdexcept> +/* + * database.cpp -- connection to postgresql database + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> +#include <sstream> #include "database.hpp" -namespace db { +namespace mlk::server::db { + +namespace { + +struct strify { + template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> + auto operator()(T i) const -> std::string + { + return std::to_string(i); + } + + auto operator()(bool v) const -> std::string + { + return v ? "t" : "f"; + } + + auto operator()(std::string s) const -> std::string + { + return s; + } +}; + +std::unique_ptr<PGconn, void (*)(PGconn*)> connection{nullptr, nullptr}; + +auto run(const std::string& sql, std::vector<arg> args = {}) -> result +{ + std::vector<const char*> list; + + /* + * PQexecParams requires an array of C strings, convert all arguments in the + * args vector to string and then keep the C string value into a temporary + * array. + */ + for (auto& arg : args) { + arg = std::visit(strify(), arg); + list.push_back(std::get<std::string>(arg).c_str()); + } + return { + PQexecParams(connection.get(), sql.c_str(), list.size(), nullptr, + list.data(), nullptr, nullptr, 0 + ), + PQclear + }; +} + +} // !namespace + +void open(const std::string& host, + const std::string& port, + const std::string& user, + const std::string& database, + const std::string& password) +{ + assert(!connection); + + std::ostringstream oss; + + if (!host.empty()) + oss << "host=" << host << " "; + if (!port.empty()) + oss << "port=" << port << " "; + if (!user.empty()) + oss << "user=" << user << " "; + if (!database.empty()) + oss << "dbname=" << database << " "; + if (!password.empty()) + oss << "password=" << password; + + connection = { PQconnectdb(oss.str().c_str()), PQfinish }; + + if (PQstatus(connection.get()) != CONNECTION_OK) + throw std::runtime_error(PQerrorMessage(connection.get())); +} + +const std::string init_account( + "CREATE TABLE IF NOT EXISTS account(" + " id SERIAL," + " login TEXT NOT NULL," + " password TEXT NOT NULL," + " firstname TEXT," + " lastname TEXT," + " email TEXT," + " PRIMARY KEY(id)" + ")" +); +const std::string init_character( + "CREATE TABLE IF NOT EXISTS character(" + " id SERIAL," + " account_id INTEGER NOT NULL," + " nickname TEXT NOT NULL," + " type TEXT NOT NULL," + " hp_level SMALLINT NOT NULL DEFAULT 1," + " hp_factor SMALLINT NOT NULL," + " hp_exp INT NOT NULL," + " force_level SMALLINT NOT NULL DEFAULT 1," + " force_factor SMALLINT NOT NULL," + " force_exp INT NOT NULL," + " defense_level SMALLINT NOT NULL DEFAULT 1," + " defense_factor SMALLINT NOT NULL," + " defense_exp INT NOT NULL," + " agility_level SMALLINT NOT NULL DEFAULT 1," + " agility_factor SMALLINT NOT NULL," + " agility_exp INT NOT NULL," + " luck_level SMALLINT NOT NULL DEFAULT 1," + " luck_factor SMALLINT NOT NULL," + " luck_exp INT NOT NULL," + " PRIMARY KEY(id)," + " FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE" + ")" +); -} // !db +const std::string init_spell( + "CREATE TABLE IF NOT EXISTS spell(" + " id SERIAL," + " character_id INTEGER NOT NULL," + " type TEXT NOT NULL," + " level SMALLINT NOT NULL," + " PRIMARY KEY(id)," + " FOREIGN KEY (character_id) REFERENCES character(id) ON DELETE CASCADE" + ")" +); + +void init() +{ + assert(connection); + + exec(init_account); + exec(init_character); + exec(init_spell); +} + +auto select(const std::string& sql, const std::vector<arg>& args) -> result +{ + assert(connection); + + auto result = run(sql, args); + + switch (PQresultStatus(result.get())) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + break; + default: + throw std::runtime_error(PQerrorMessage(connection.get())); + } + + return result; +} + +void exec(const std::string& sql, const std::vector<arg>& args) +{ + assert(connection); + + switch (const auto result = run(sql, args); PQresultStatus(result.get())) { + case PGRES_COMMAND_OK: + break; + default: + throw std::runtime_error(PQerrorMessage(connection.get())); + } +} + +transaction::transaction(rollback fn) + : rollback_(std::move(fn)) +{ + assert(rollback_); + + exec("BEGIN"); +} + +transaction::~transaction() +{ + if (!commit_) { + rollback_(); + exec("ROLLBACK"); + } +} + +void transaction::commit() +{ + if (!commit_) { + exec("COMMIT"); + commit_ = true; + } +} + +} // !db::server::mlk
--- a/libserver/malikania/server/db/database.hpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/database.hpp Fri Aug 17 15:44:12 2018 +0200 @@ -1,5 +1,5 @@ /* - * database.hpp -- abstract database interface + * database.hpp -- connection to postgresql database * * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> * @@ -24,50 +24,128 @@ * \brief Abstract database interface. */ -namespace mlk { +#include <cstdint> +#include <functional> +#include <memory> +#include <stdexcept> +#include <string> +#include <variant> +#include <vector> -namespace server { +#include <libpq-fe.h> + +namespace mlk::server::db { -class account_dao; -class character_dao; -class spell_dao; +/** + * \brief Statement argument. + * + * When using prepared statement, you may pass any data you like using this + * variant. + */ +using arg = std::variant< + bool, + double, + std::int8_t, + std::uint8_t, + std::int16_t, + std::uint16_t, + std::int32_t, + std::uint32_t, + std::int64_t, + std::uint64_t, + std::string +>; + +/** + * \brief Convenient result type. + * + * This unique pointer holds the PostgreSQL result and free it automatically on + * out of scope. + */ +using result = std::unique_ptr<PGresult, void (*)(PGresult*)>; /** - * \brief Abstract database interface. + * Open the connection to the database. + * + * All arguments are optional. + * + * \pre connection must be closed + * \param host the hostname + * \param port the port number + * \param user the user name + * \param database the database name + * \param password the user password + * \throw std::runtime_error on errors + */ +void open(const std::string& host, + const std::string& port, + const std::string& user, + const std::string& database, + const std::string& password); + +/** + * Unconditionally init the database. + * + * \throw std::runtime_error on errors */ -class database { +void init(); + +/** + * Execute a SELECT statement. + * + * \param sql the SQL statement + * \param args the optional arguments to bind + * \return the result value + * \throw std::runtime_error on errors + */ +auto select(const std::string& sql, const std::vector<arg>& args = {}) -> result; + +/** + * Execute an UPDATE statement. + * + * \param sql the SQL statement + * \param args the optional arguments to bind + * \throw std::runtime_error on errors + */ +void exec(const std::string& sql, const std::vector<arg>& args = {}); + +class transaction { public: /** - * Default constructor. + * \brief Rollback function. */ - database() noexcept = default; + using rollback = std::function<void ()>; + +private: + rollback rollback_; + bool commit_{false}; +public: /** - * Virtual destructor defaulted. + * Start a transaction. + * + * If there is already a transaction, the returned object is no-op. + * + * \param rollback the rollback function + * \return the transaction object */ - virtual ~database() noexcept = default; + transaction(rollback fn); /** - * Initialize database. + * Rollback the transaction if commit() has not been called. */ - virtual void init() - { - } + ~transaction(); /** - * Create the account dao. + * Commit the transaction. * - * \return the account dao + * Does nothing if already committed. + * + * \throw std::exception on errors */ - virtual account_dao& accounts() = 0; - - virtual character_dao& characters() = 0; - - virtual spell_dao& spells() = 0; + void commit(); }; -} // !server - -} // !mlk +} // !mlk::server::db #endif // !MALIKANIA_SERVER_DB_DATABASE_HPP
--- a/libserver/malikania/server/db/dynlib_database.cpp Mon Aug 06 13:01:00 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* - * dynlib_database.cpp -- dynamically loadable database adapter - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "dynlib_database.hpp" - -namespace mlk { - -namespace server { - -namespace { - -using db_open_t = void (const dynlib_database::params_t&); -using db_init_t = void (); -using db_close_t = void (); - -template <typename DAO> -using db_dao_t = std::unique_ptr<DAO>(std::shared_ptr<database>); - -const std::string account_dao_name("mlk_account_dao"); -const std::string character_dao_name("mlk_character_dao"); -const std::string spell_dao_name("mlk_spell_dao"); - -} // !namespace - -void dynlib_database::open(const params_t& parameters) -{ - lib_.get<db_open_t>("mlk_db_open")(parameters); -} - -void dynlib_database::init() -{ - lib_.get<db_init_t>("mlk_db_init")(); -} - -void dynlib_database::close() -{ - lib_.get<db_close_t>("mlk_db_close")(); -} - -dynlib_database::dynlib_database(const std::string& path, const params_t& params) - : lib_(path) -{ - open(params); - init(); -} - -dynlib_database::~dynlib_database() -{ - close(); -} - -#if 0 - -std::unique_ptr<class account_dao> dynlib_database::account_dao() -{ - return lib_.get<db_dao_t<class account_dao>>(account_dao_name)(shared_from_this()); -} - -std::unique_ptr<class character_dao> dynlib_database::character_dao() -{ - return lib_.get<db_dao_t<class character_dao>>(character_dao_name)(shared_from_this()); -} - -std::unique_ptr<class spell_dao> dynlib_database::spell_dao() -{ - return lib_.get<db_dao_t<class spell_dao>>(spell_dao_name)(shared_from_this()); -} - -#endif - -} // !server - -} // !mlk
--- a/libserver/malikania/server/db/dynlib_database.hpp Mon Aug 06 13:01:00 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/* - * dynlib_database.hpp -- dynamically loadable database adapter - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MALIKANIA_SERVER_DB_DYNLIB_DATABASE_HPP -#define MALIKANIA_SERVER_DB_DYNLIB_DATABASE_HPP - -#include <string> -#include <unordered_map> - -#include <boost/dll.hpp> - -#include "database.hpp" - -namespace mlk { - -namespace server { - -class dynlib_database : public database { -public: - using params_t = std::unordered_map<std::string, std::string>; - -private: - boost::dll::shared_library lib_; - - void open(const params_t& params); - void init(); - void close(); - -public: - dynlib_database(const std::string& path, const params_t& params); - - ~dynlib_database() override; - - inline const boost::dll::shared_library& lib() const noexcept - { - return lib_; - } - - inline boost::dll::shared_library& lib() noexcept - { - return lib_; - } - -#if 0 - - std::unique_ptr<class account_dao> account_dao() override; - - std::unique_ptr<class character_dao> character_dao() override; - - std::unique_ptr<class spell_dao> spell_dao() override; -#endif -}; - -} // !server - -} // !mlk - -#endif // !MALIKANIA_SERVER_DB_DYNLIB_DATABASE_HPP
--- a/libserver/malikania/server/db/model.hpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/model.hpp Fri Aug 17 15:44:12 2018 +0200 @@ -26,32 +26,30 @@ #include <cstdint> -namespace mlk { - -namespace server { - -class database; +namespace mlk::server::db { /** * \brief Abstract database object. */ class model { +public: + /** + * Id type. + */ + using id_type = std::uint64_t; + private: model(const model&) = delete; model& operator=(const model&) = delete; protected: - database& db_; //!< database object - std::uint64_t id_{0U}; //!< object id + id_type id_{0U}; //!< object id public: /** * Default constructor. */ - inline model(database& db) noexcept - : db_(db) - { - } + model() = default; /** * Move constructor. @@ -59,8 +57,7 @@ * \param other the original value (id resets to 0) */ inline model(model&& other) noexcept - : db_(other.db_) - , id_(other.id_) + : id_(other.id_) { other.id_ = 0U; } @@ -75,11 +72,12 @@ * * \return the id */ - inline std::uint64_t get_id() const noexcept + auto get_id() const noexcept -> id_type { return id_; } +#if 0 #if defined(MALIKANIA_PRIVATE) /** * Set the internal id. @@ -91,18 +89,19 @@ * \warning only use this function in database backends * \param id the new id */ - inline void set_id(std::uint64_t id) noexcept + inline void set_id(id_type id) noexcept { id_ = id; } #endif +#endif /** * Tells if the object is not persistent. * * \return true if object was never saved */ - inline bool is_draft() const noexcept + auto is_draft() const noexcept -> bool { return id_ == 0U; } @@ -112,7 +111,7 @@ * * \return true if object is saved */ - inline bool is_published() const noexcept + auto is_published() const noexcept -> bool { return id_ > 0U; } @@ -123,7 +122,7 @@ * \param other the original value (id resets to 0) * \return *this */ - inline model& operator=(model&& other) noexcept + auto operator=(model&& other) noexcept -> model& { id_ = other.id_; other.id_ = 0U; @@ -132,8 +131,6 @@ } }; -} // !server - -} // !mlk +} // !mlk::server::db #endif // !MODEL_HPP
--- a/libserver/malikania/server/db/spell.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/spell.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -16,22 +16,109 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "database.hpp" +#include <cassert> + #include "character.hpp" #include "spell.hpp" -namespace mlk { +namespace mlk::server::db { + +auto spell::load(const result& result, int row) -> spell +{ + spell s(PQgetvalue(result.get(), row, 2)); + + s.id_ = std::stoi(PQgetvalue(result.get(), row, 0)); + s.level_ = std::stoi(PQgetvalue(result.get(), row, 3)); + + return s; +} + +auto spell::load(const character& parent) -> std::vector<spell> +{ + static const std::string sql( + "SELECT *" + " FROM spell" + " WHERE id = $1" + ); + + const auto r = select(sql, { parent.get_id() }); + + if (PQntuples(r.get()) == 0) + throw std::runtime_error("failed to load spells"); + + std::vector<spell> spells; + + for (int i = 0; i < PQntuples(r.get()); ++i) + spells.push_back(load(r, i)); + + return spells; +} + +void spell::clear() +{ + id_ = 0U; +} -namespace server { +void spell::unpublish() +{ + static const std::string sql( + "DELETE" + " FROM spell" + " WHERE id = $1" + ); + + exec(sql, { id_ }); + clear(); +} + +void spell::publish(character& parent) +{ + static const std::string sql( + "INSERT INTO spell(" + " character_id," + " type," + " level" + ") " + "VALUES ($1, $2, $3) " + "RETURNING id" + ); + + const auto r = select(sql, { parent.get_id(), type_, level_ }); + + if (PQntuples(r.get()) == 0) + throw std::runtime_error("failed to save spell"); + + id_ = std::stoi(PQgetvalue(r.get(), 0, 0)); +} + +spell::spell(std::string type) noexcept + : type_(std::move(type)) +{ + assert(!type_.empty()); +} + +auto spell::get_type() const noexcept -> const std::string& +{ + return type_; +} + +auto spell::get_level() const noexcept -> std::uint8_t +{ + return level_; +} void spell::set_level(std::uint8_t level) { + static const std::string sql( + "UPDATE spell" + " SET level = $1" + " WHERE id = $2" + ); + if (is_published() && level_ != level) - db_.spells().set_level(level); + db::exec(sql, { level_, id_ }); level_ = level; } -} // !server - -} // !mlk +} // !mlk::server::db
--- a/libserver/malikania/server/db/spell.hpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/db/spell.hpp Fri Aug 17 15:44:12 2018 +0200 @@ -24,14 +24,14 @@ * \brief Database spell object. */ -#include <cassert> +#include <cstdint> #include <string> +#include <vector> +#include "database.hpp" #include "model.hpp" -namespace mlk { - -namespace server { +namespace mlk::server::db { class character; @@ -40,32 +40,40 @@ */ class spell : public model { private: - std::string classname_; - std::uint8_t level_{1}; + friend class character; + + std::string type_; + std::uint8_t level_{1U}; + + static auto load(const result&, int) -> spell; + static auto load(const character&) -> std::vector<spell>; + + void clear(); + void unpublish(); + void publish(character&); public: /** * Constructor. * - * \pre !classname.empty() - * \param classname the class name + * \pre !type.empty() + * \param type the type name */ - inline spell(database& db, std::string classname) - : model(db) - , classname_(std::move(classname)) - { - assert(!classname_.empty()); - } + spell(std::string type) noexcept; + + /** + * Get the type. + * + * \return the type + */ + auto get_type() const noexcept -> const std::string&; /** * Get the level. * * \return the level */ - inline std::uint8_t level() const noexcept - { - return level_; - } + auto get_level() const noexcept -> std::uint8_t; /** * Set the spell level. @@ -75,53 +83,6 @@ void set_level(std::uint8_t level); }; -/** - * \brief Character DAO. - */ -class spell_dao { -public: - /** - * Default constructor. - */ - spell_dao() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~spell_dao() = default; - - /** - * Save this spell. - * - * The implementation must save the spell and update id, character_id - * member variables. - * - * Then it will be added into character.spells_ variable. - */ - virtual void publish(const character& ch, spell& sp) = 0; - - /** - * Remove this spell. - * - * The implementation must remove the spell from the database and update - * id, character_id member variables. - */ - virtual void unpublish(const character& ch, const spell& sp) = 0; - - /** - * Update the spell level in database. - * - * Only called when the level needs to be changed, the implementation does - * not need to update level_ field. - * - * \note called from set_level helper - * \throw std::exception if the operation could not succeed - */ - virtual void set_level(const character& ch, std::uint8_t level) = 0; -}; - -} // !server - -} // !mlk +} // !mlk::server::db #endif // !MALIKANIA_SERVER_DB_SPELL_HPP
--- a/libserver/malikania/server/net/auth_handler.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/libserver/malikania/server/net/auth_handler.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -31,6 +31,10 @@ void auth_handler::exec(server& server, std::shared_ptr<client> clt, nlohmann::json object) noexcept { + (void)server; + (void)clt; + (void)object; +#if 0 try { // TODO: VERIFY ALREADY LOGGED IN! auto ac = server.database().accounts().authenticate( @@ -47,6 +51,7 @@ } catch (const std::exception& ex) { clt->error("auth", ex.what()); } +#endif } } // !server
--- a/server/main.cpp Mon Aug 06 13:01:00 2018 +0200 +++ b/server/main.cpp Fri Aug 17 15:44:12 2018 +0200 @@ -16,6 +16,38 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <iostream> + +#include <malikania/server/db/database.hpp> +#include <malikania/server/db/account.hpp> + +using namespace mlk::server; +using namespace mlk::server::db; + int main() { + try { + db::open("", "", "markand", "malikaniadb", ""); + db::exec("DROP TABLE spell"); + db::exec("DROP TABLE character"); + db::exec("DROP TABLE account"); + db::init(); + + account a("markand", "plopation"); + + a.set_email("markand@malikania.fr"); + a.set_firstname("David"); + a.set_lastname("Demelier"); + + character ch("luna", "fairy"); + spell s("heal"); + + ch.add(std::move(s)); + puts("ABOUT TO ADD"); + a.add(std::move(ch)); + puts("ABOUT TO PUBLISH"); + a.publish(); + } catch (const std::exception& ex) { + std::cerr << "abort: " << ex.what() << std::endl; + } }