changeset 101:51dd7a4914ec

Server: add database stubs, #687 @1h
author David Demelier <markand@malikania.fr>
date Mon, 21 Aug 2017 13:17:07 +0200
parents 0e9c01d3e0d1
children 03b3f520e270
files libserver/CMakeLists.txt libserver/malikania/server/account.hpp libserver/malikania/server/account_dao.cpp libserver/malikania/server/account_dao.hpp libserver/malikania/server/client.hpp libserver/malikania/server/database.cpp libserver/malikania/server/database.hpp libserver/malikania/server/db/account.cpp libserver/malikania/server/db/account.hpp libserver/malikania/server/db/account_dao.hpp libserver/malikania/server/db/character.cpp libserver/malikania/server/db/character.hpp libserver/malikania/server/db/character_dao.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/errors.hpp libserver/malikania/server/db/model.hpp libserver/malikania/server/db/spell.cpp libserver/malikania/server/db/spell.hpp libserver/malikania/server/db/spell_dao.hpp libserver/malikania/server/net/auth_handler.cpp server/main.cpp
diffstat 23 files changed, 1184 insertions(+), 455 deletions(-) [+]
line wrap: on
line diff
--- a/libserver/CMakeLists.txt	Mon Aug 21 07:39:41 2017 +0200
+++ b/libserver/CMakeLists.txt	Mon Aug 21 13:17:07 2017 +0200
@@ -20,20 +20,28 @@
 
 set(
     HEADERS
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/account_dao.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/character_dao.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/dynlib_database.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.hpp
+    ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell_dao.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/account.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/account_dao.hpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/client.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/database.hpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/server.hpp
 )
 
 set(
     SOURCES
+    ${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/dynlib_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/account_dao.cpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/client.cpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/database.cpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/server.cpp
 )
 
--- a/libserver/malikania/server/account.hpp	Mon Aug 21 07:39:41 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * account.hpp -- account model
- *
- * Copyright (c) 2013-2017 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_ACCOUNT_HPP
-#define MALIKANIA_SERVER_ACCOUNT_HPP
-
-/**
- * \file account.hpp
- * \brief Account model.
- */
-
-#include <cstdint>
-#include <string>
-
-namespace mlk {
-
-namespace server {
-
-/**
- * \brief Account model
- */
-class account {
-public:
-    std::uint64_t id;
-    std::string name;
-    std::string email;
-    std::string first_name;
-    std::string last_name;
-    std::string password;
-};
-
-inline bool operator==(const account &ac1, const account &ac2) noexcept
-{
-    return ac1.id == ac2.id;
-}
-
-inline bool operator!=(const account &ac1, const account &ac2) noexcept
-{
-    return !(ac1 == ac2);
-}
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_ACCOUNT_HPP
--- a/libserver/malikania/server/account_dao.cpp	Mon Aug 21 07:39:41 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * dao-account.cpp -- database account management
- *
- * Copyright (c) 2013-2017 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 "account.hpp"
-#include "account_dao.hpp"
-#include "database.hpp"
-#include "hash.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using create_func = void (account&);
-using remove_func = void (const account&);
-using update_func = void (account&);
-using get_func = boost::optional<account> (std::uint64_t);
-using find_by_name_func = boost::optional<account> (const std::string&);
-using list_func = std::vector<account> ();
-using count_func = std::uint64_t ();
-using clear_func = void ();
-
-bool account_dao::authenticate(const std::string& login, const std::string& pass)
-{
-    auto ac = find_by_name(login);
-    auto sha512 = hash::sha512(pass);
-
-    return ac && ac->password == sha512;
-}
-
-void account_dao::create(account& account)
-{
-    m_database.handle().get<create_func>("malikania_account_create")(account);
-}
-
-void account_dao::remove(const account& account)
-{
-    m_database.handle().get<remove_func>("malikania_account_remove")(account);
-}
-
-void account_dao::update(account& account)
-{
-    m_database.handle().get<update_func>("malikania_account_update")(account);
-}
-
-boost::optional<account> account_dao::get(uint64_t id)
-{
-    return m_database.handle().get<get_func>("malikania_account_get")(id);
-}
-
-boost::optional<account> account_dao::find_by_name(const std::string& name)
-{
-    return m_database.handle().get<find_by_name_func>("malikania_account_find_by_name")(name);
-}
-
-std::vector<account> account_dao::list()
-{
-    return m_database.handle().get<list_func>("malikania_account_list")();
-}
-
-std::uint64_t account_dao::count()
-{
-    return m_database.handle().get<count_func>("malikania_account_count")();
-}
-
-void account_dao::clear()
-{
-    m_database.handle().get<clear_func>("malikania_account_clear")();
-}
-
-} // !server
-
-} // !mlk
--- a/libserver/malikania/server/account_dao.hpp	Mon Aug 21 07:39:41 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/*
- * account_dao.hpp -- database account management
- *
- * Copyright (c) 2013-2017 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_ACCOUNT_DAO_HPP
-#define MALIKANIA_SERVER_ACCOUNT_DAO_HPP
-
-/**
- * \file account_dao.hpp
- * \brief Database account management.
- */
-
-#include <cstdint>
-#include <vector>
-
-#include <boost/optional.hpp>
-
-namespace mlk {
-
-namespace server {
-
-class database;
-class account;
-
-/**
- * \brief Account DAO.
- */
-class account_dao {
-private:
-    database& m_database;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param database the database
-     */
-    inline account_dao(database& database) noexcept
-        : m_database(database)
-    {
-    }
-
-    /**
-     * Check if the user authentication is valid.
-     *
-     * \param login the login name
-     * \param pass the clear password
-     * \return true if authentication was successful
-     */
-    bool authenticate(const std::string& login, const std::string& pass);
-
-    /**
-     * Create the given account.
-     *
-     * The object will be modified in place.
-     *
-     * \param account the account to add
-     * \throw std::exception on errors
-     */
-    void create(account& account);
-
-    /**
-     * Remove the given account, all data that references this account is
-     * deleted too.
-     *
-     * \param account the account
-     * \throw std::exception on errors
-     */
-    void remove(const account& account);
-
-    /**
-     * Update account only, does not recurse into objects that references the
-     * account.
-     *
-     * \param account the account
-     * \throw std::exception on errors
-     */
-    void update(account& account);
-
-    /**
-     * Get an account.
-     *
-     * \param id the account id
-     * \return the account
-     * \throw std::exception if not found
-     */
-    boost::optional<account> get(std::uint64_t id);
-
-    /**
-     * Find an account by name.
-     *
-     * \param name the account name
-     * \return the account or empty one if not found
-     * \throw std::exception on other errors
-     */
-    boost::optional<account> find_by_name(const std::string& name);
-
-    /**
-     * Get the list of account.
-     *
-     * \throw std::exception on errors
-     */
-    std::vector<account> list();
-
-    /**
-     * Get the number of accounts.
-     *
-     * \return the number of account.
-     */
-    std::uint64_t count();
-
-    /**
-     * Remove all accounts recursively.
-     *
-     * \throw std::exception on errors
-     * \warning use with care
-     */
-    void clear();
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_ACCOUNT_DAO_HPP
--- a/libserver/malikania/server/client.hpp	Mon Aug 21 07:39:41 2017 +0200
+++ b/libserver/malikania/server/client.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -35,6 +35,8 @@
 
 #include <json.hpp>
 
+#include "db/account.hpp"
+
 namespace mlk {
 
 namespace server {
@@ -78,6 +80,7 @@
     boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
     boost::asio::streambuf input_;
     std::deque<std::string> output_;
+    std::shared_ptr<class account> account_;
 
     void handle_read(boost::system::error_code, std::size_t);
     void handle_write(boost::system::error_code, std::size_t);
@@ -119,6 +122,16 @@
     }
 
     /**
+     * Set the client account.
+     *
+     * \param account the account
+     */
+    inline void set_account(std::shared_ptr<class account> account) noexcept
+    {
+        account_ = std::move(account);
+    }
+
+    /**
      * Send a command and its arguments.
      *
      * \pre !cmd.empty()
--- a/libserver/malikania/server/database.cpp	Mon Aug 21 07:39:41 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * database.cpp -- generic database loader
- *
- * Copyright (c) 2013-2017 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 <boost/filesystem.hpp>
-
-#include <stdexcept>
-
-#include "database.hpp"
-#include "util.hpp"
-
-namespace mlk {
-
-namespace server {
-
-namespace {
-
-using load_func = void (const database_settings&);
-using unload_func = void ();
-
-boost::filesystem::path path(const database_settings& params)
-{
-    auto it = params.find("type");
-
-    if (it == params.end()) {
-        throw std::runtime_error("missing 'type' property");
-    }
-
-    boost::filesystem::path ret(util::basedir());
-
-    ret /= "lib";
-    ret /= "malikania";
-    ret /= "0.1.0";         // TODO: change this with an appropriate sysconfig.h
-    ret /= it->second;
-
-    return ret;
-}
-
-} // !namespace
-
-database::database(const database_settings& params)
-    : m_dso(path(params), boost::dll::load_mode::append_decorations)
-{
-    m_dso.get<load_func>("malikania_driver_load")(params);
-}
-
-database::~database()
-{
-    m_dso.get<unload_func>("malikania_driver_unload")();
-}
-
-} // !server
-
-} // !mlk
--- a/libserver/malikania/server/database.hpp	Mon Aug 21 07:39:41 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * database.hpp -- generic database loader
- *
- * Copyright (c) 2013-2017 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_DATABASE_HPP
-#define MALIKANIA_SERVER_DATABASE_HPP
-
-/**
- * \file database.hpp
- * \brief Generic database loader.
- */
-
-#include <boost/dll.hpp>
-
-#include <string>
-#include <unordered_map>
-
-namespace mlk {
-
-namespace server {
-
-/**
- * Generic settings for database.
- */
-using database_settings = std::unordered_map<std::string, std::string>;
-
-/**
- * \brief Generic database.
- */
-class database {
-private:
-    boost::dll::shared_library m_dso;
-
-public:
-    /**
-     * Load the database driver dynamically.
-     *
-     * \param params the parameters to pass to the driver
-     * \throw std::exception on errors
-     */
-    database(const database_settings& params);
-
-    /**
-     * Close the database.
-     */
-    ~database();
-
-    /**
-     * Get the associated dso handle.
-     *
-     * \return the handle
-     */
-    inline const boost::dll::shared_library& handle() const noexcept
-    {
-        return m_dso;
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \return the handle
-     */
-    inline boost::dll::shared_library& handle() noexcept
-    {
-        return m_dso;
-    }
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_DATABASE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/account.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,53 @@
+/*
+ * account.cpp -- database account object
+ *
+ * Copyright (c) 2013-2017 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 "database.hpp"
+#include "account_dao.hpp"
+#include "account.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void account::set_email(std::string email)
+{
+    if (is_published())
+        db_->account_dao()->set_email(*this, email);
+
+    email_ = std::move(email);
+}
+
+void account::set_firstname(std::string name)
+{
+    if (is_published())
+        db_->account_dao()->set_firstname(*this, name);
+
+    firstname_ = std::move(name);
+}
+
+void account::set_lastname(std::string name)
+{
+    if (is_published())
+        db_->account_dao()->set_lastname(*this, name);
+
+    lastname_ = std::move(name);
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/account.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,86 @@
+/*
+ * account.hpp -- database account object
+ *
+ * Copyright (c) 2013-2017 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_ACCOUNT_HPP
+#define MALIKANIA_SERVER_DB_ACCOUNT_HPP
+
+#include <string>
+
+#include "model.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class character;
+
+class account : public model<account> {
+private:
+    std::string login_;
+    std::string email_;
+    std::string firstname_;
+    std::string lastname_;
+    std::shared_ptr<class character> character_;
+
+public:
+    inline account(std::shared_ptr<database> db,
+                   std::string login,
+                   std::shared_ptr<class character> ch)
+        : model(std::move(db))
+        , login_(std::move(login))
+        , character_(std::move(ch))
+    {
+    }
+
+    inline const std::string& login() const noexcept
+    {
+        return login_;
+    }
+
+    inline const std::string& email() const noexcept
+    {
+        return email_;
+    }
+
+    void set_email(std::string email);
+
+    inline const std::string& firstname() const noexcept
+    {
+        return firstname_;
+    }
+
+    void set_firstname(std::string firstname);
+
+    inline const std::string& lastname() const noexcept
+    {
+        return lastname_;
+    }
+
+    void set_lastname(std::string lastname);
+
+    inline const std::shared_ptr<class character>& character() const noexcept
+    {
+        return character_;
+    }
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_ACCOUNT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/account_dao.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,45 @@
+/*
+ * account_dao.hpp -- database spell interface
+ *
+ * Copyright (c) 2013-2017 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_ACCOUNT_DAO_HPP
+#define MALIKANIA_SERVER_DB_ACCOUNT_DAO_HPP
+
+#include <memory>
+#include <string>
+
+namespace mlk {
+
+namespace server {
+
+class account;
+
+class account_dao {
+public:
+    virtual std::shared_ptr<account> authenticate(const std::string& login,
+                                                  const std::string& password) = 0;
+
+    virtual void set_email(account& ac, const std::string& email) = 0;
+    virtual void set_firstname(account& ac, const std::string& name) = 0;
+    virtual void set_lastname(account& ac, const std::string& name) = 0;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_ACCOUNT_DAO_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/character.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,89 @@
+/*
+ * character.cpp -- database character object
+ *
+ * Copyright (c) 2013-2017 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 <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+#include "character.hpp"
+#include "character_dao.hpp"
+#include "database.hpp"
+#include "spell.hpp"
+#include "spell_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void character::set_name(std::string name)
+{
+    if (is_published())
+        db_->character_dao()->set_name(*this, name);
+
+    name_ = std::move(name);
+}
+
+void character::add_spell(std::shared_ptr<spell> spell)
+{
+    assert(spell);
+
+    if (std::find(spells_.begin(), spells_.end(), spell) != spells_.end())
+        return;
+
+    if (spell->is_published())
+        throw referenced_error(shared_from_this(), spell);
+
+    db_->spell_dao()->save(*spell, *this);
+    spells_.push_back(std::move(spell));
+}
+
+void character::remove_spell(std::shared_ptr<spell> spell)
+{
+    assert(spell);
+
+    if (std::find(spells_.begin(), spells_.end(), spell) == spells_.end())
+        return;
+
+    db_->spell_dao()->remove(*spell);
+    spell->set_id(-1);
+    spell->set_character(nullptr);
+    spells_.erase(std::remove(spells_.begin(), spells_.end(), spell), spells_.end());
+}
+
+void character::save()
+{
+    if (is_published())
+        return;
+
+    db_->character_dao()->save(*this);
+}
+
+void character::remove()
+{
+    if (is_draft())
+        return;
+
+    db_->character_dao()->remove(*this);
+    id_ = -1;
+    spells_.clear();
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/character.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,114 @@
+/*
+ * character.hpp -- database character object
+ *
+ * Copyright (c) 2013-2017 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_CHARACTER_HPP
+#define MALIKANIA_SERVER_DB_CHARACTER_HPP
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "model.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class spell;
+
+/**
+ * \brief Describe an account character.
+ */
+class character : public model<character> {
+private:
+    std::string name_;
+    std::vector<std::shared_ptr<spell>> spells_;
+
+public:
+    /**
+     * Construct a character, no database is modified yet.
+     *
+     * \param db the database object
+     * \param name the account name
+     */
+    inline character(std::shared_ptr<database> db, std::string name) noexcept
+        : model(std::move(db))
+        , name_(std::move(name))
+    {
+    }
+
+    /**
+     * Get the character name.
+     *
+     * \return the name
+     */
+    inline const std::string& name() const noexcept
+    {
+        return name_;
+    }
+
+    /**
+     * Set and update the database name.
+     *
+     * \param name the name
+     */
+    void set_name(std::string name);
+
+    /**
+     * Add a spell into the database and the object.
+     *
+     * \param spell the spell
+     * \throw referenced_error if spell is public
+     * \throw phase_error if character is not public
+     */
+    void add_spell(std::shared_ptr<spell> spell);
+
+    /**
+     * Remove the specified spell.
+     *
+     * \param spell the spell to remove
+     */
+    void remove_spell(std::shared_ptr<spell> spell);
+
+    /**
+     * \copydoc model::table
+     */
+    std::string table() const override
+    {
+        return "character";
+    }
+
+    /**
+     * Create the character object as an insert statement.
+     *
+     * No-op if is_published().
+     */
+    void save();
+
+    /**
+     * Remove the character from the database and all objects that belongs to
+     * it.
+     */
+    void remove();
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_CHARACTER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/character_dao.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,39 @@
+/*
+ * character_dao.hpp -- database character interface
+ *
+ * Copyright (c) 2013-2017 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_CHARACTER_DAO_HPP
+#define MALIKANIA_SERVER_DB_CHARACTER_DAO_HPP
+
+namespace mlk {
+
+namespace server {
+
+class character;
+
+class character_dao {
+public:
+    virtual void save(character&) = 0;
+    virtual void remove(character&) = 0;
+    virtual void set_name(character&, const std::string&) = 0;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_CHARACTER_DAO_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/database.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,10 @@
+#include <cstdlib>
+#include <stdexcept>
+
+#include "database.hpp"
+
+namespace db {
+
+
+
+} // !db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/database.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,69 @@
+/*
+ * database.hpp -- abstract database interface
+ *
+ * Copyright (c) 2013-2017 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_DATABASE_HPP
+#define MALIKANIA_SERVER_DB_DATABASE_HPP
+
+#include <memory>
+
+namespace mlk {
+
+namespace server {
+
+class account_dao;
+class character_dao;
+
+class database {
+public:
+    /**
+     * Default constructor.
+     */
+    database() noexcept = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~database() noexcept = default;
+
+    /**
+     * Create the account dao.
+     *
+     * \return the account dao
+     */
+    virtual std::unique_ptr<class account_dao> account_dao() = 0;
+
+    /**
+     * Create the character dao.
+     *
+     * \return the account dao
+     */
+    virtual std::unique_ptr<class character_dao> character_dao() = 0;
+
+    /**
+     * Create the spell dao.
+     *
+     * \return the account dao
+     */
+    virtual std::unique_ptr<class spell_dao> spell_dao() = 0;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_DATABASE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/dynlib_database.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,87 @@
+/*
+ * dynlib_database.cpp -- dynamically loadable database adapter
+ *
+ * Copyright (c) 2013-2017 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 "account_dao.hpp"
+#include "character_dao.hpp"
+#include "spell_dao.hpp"
+#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>();
+
+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();
+}
+
+std::unique_ptr<class account_dao> dynlib_database::account_dao()
+{
+    return lib_.get<db_dao_t<class account_dao>>(account_dao_name)();
+}
+
+std::unique_ptr<class character_dao> dynlib_database::character_dao()
+{
+    return lib_.get<db_dao_t<class character_dao>>(character_dao_name)();
+}
+
+std::unique_ptr<class spell_dao> dynlib_database::spell_dao()
+{
+    return lib_.get<db_dao_t<class spell_dao>>(spell_dao_name)();
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/dynlib_database.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,70 @@
+/*
+ * dynlib_database.hpp -- dynamically loadable database adapter
+ *
+ * Copyright (c) 2013-2017 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_;
+    }
+
+    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;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_DYNLIB_DATABASE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/model.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,292 @@
+/*
+ * model.hpp -- abstract database object
+ *
+ * Copyright (c) 2013-2017 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_MODEL_HPP
+#define MALIKANIA_SERVER_DB_MODEL_HPP
+
+#include <cassert>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+
+namespace mlk {
+
+namespace server {
+
+class database;
+
+/**
+ * \brief Describe availability of an object.
+ */
+enum class model_phase {
+    draft,                  //!< object is not saved
+    published               //!< object is saved
+};
+
+/**
+ * Show the model_phase.
+ *
+ * \param out the output stream
+ * \param phase the object phase
+ * \return out
+ */
+inline std::ostream& operator<<(std::ostream& out, model_phase phase)
+{
+    out << (phase == model_phase::draft ? "draft" : "published");
+
+    return out;
+}
+
+/**
+ * \brief Base class for database object.
+ */
+class basic_model {
+public:
+protected:
+    std::int64_t id_{-1};                   //!< object id
+    std::shared_ptr<database> db_;          //!< database object
+
+public:
+    inline basic_model(std::shared_ptr<database> db) noexcept
+        : db_(std::move(db))
+    {
+    }
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~basic_model() noexcept = default;
+
+    /**
+     * Get the id.
+     *
+     * \return the id
+     */
+    inline std::int64_t id() const noexcept
+    {
+        return id_;
+    }
+
+    /**
+     * Set the model id.
+     *
+     * \warning use this function with care
+     */
+    inline void set_id(std::int64_t id) noexcept
+    {
+        id_ = id;
+    }
+
+    /**
+     * Tells if the object is not persistent.
+     *
+     * \return true if object was never saved
+     */
+    inline bool is_draft() const noexcept
+    {
+        return id_ == -1;
+    }
+
+    /**
+     * Tells if the object is present in database
+     *
+     * \return true if object is saved
+     */
+    inline bool is_published() const noexcept
+    {
+        return id_ >= 0;
+    }
+
+    /**
+     * Get the current phase.
+     *
+     * \return the phase
+     */
+    inline model_phase phase() const noexcept
+    {
+        return is_draft() ? model_phase::draft : model_phase::published;
+    }
+
+    /**
+     * Get the associated database table.
+     *
+     * \return the table
+     */
+    virtual std::string table() const = 0;
+};
+
+/**
+ * \brief Class helper for std::enabled_shared_from_this.
+ */
+template <typename T>
+class model
+    : public basic_model
+    , public std::enable_shared_from_this<T>
+{
+public:
+    /**
+     * Inherited constructors.
+     */
+    using basic_model::basic_model;
+};
+
+/**
+ * \brief Already referenced error.
+ *
+ * This class is used when an operation attempted to add a child object that
+ * already have a parent.
+ */
+class referenced_error : public std::exception {
+private:
+    std::string message_;
+    std::shared_ptr<basic_model> parent_;
+    std::shared_ptr<basic_model> object_;
+
+public:
+    /**
+     * Construct a referenced_error.
+     *
+     * \pre parent != nullptr && object != nullptr
+     * \param parent the parent that already have object
+     * \param object the child object of parent
+     */
+    inline referenced_error(std::shared_ptr<basic_model> parent,
+                            std::shared_ptr<basic_model> object)
+        : parent_(std::move(parent))
+        , object_(std::move(object))
+    {
+        assert(parent_ && object_);
+
+        std::ostringstream oss;
+
+        oss << "object " << object_->table() << "(" << object_->id() << ") is ";
+        oss << "already referenced in " << parent_->table() << "(" << parent_->id() << ")";
+
+        message_ = oss.str();
+    }
+
+    /**
+     * Get the parent object
+     *
+     * \return the parent
+     */
+    inline const std::shared_ptr<basic_model>& parent() const noexcept
+    {
+        return parent_;
+    }
+
+    /**
+     * Get the child object that already have a parent.
+     *
+     * \return the object
+     */
+    inline const std::shared_ptr<basic_model>& object() const noexcept
+    {
+        return object_;
+    }
+
+    /**
+     * Get the error message.
+     *
+     * \return the message
+     */
+    const char* what() const noexcept
+    {
+        return message_.c_str();
+    }
+};
+
+/**
+ * \brief Error when an operation required a different phase.
+ */
+class phase_error : public std::exception {
+private:
+    std::string message_;
+    std::shared_ptr<basic_model> object_;
+    model_phase expected_phase_;
+
+public:
+    /**
+     * Create a phase_error
+     *
+     * \pre object != nullptr
+     * \param object the object model
+     * \param expected_phase the required phase
+     */
+    inline phase_error(std::shared_ptr<basic_model> object, model_phase expected_phase) noexcept
+        : object_(std::move(object))
+        , expected_phase_(expected_phase)
+    {
+        assert(object_);
+
+        std::ostringstream oss;
+
+        oss << "object '" << object_->table() << "' has invalid phase ";
+        oss << "(phase " << expected_phase_ << " expected, ";
+        oss << "got " << object_->phase() << ")";
+    }
+
+    /**
+     * Get the object.
+     *
+     * \return the object
+     */
+    inline const std::shared_ptr<basic_model>& object() const noexcept
+    {
+        return object_;
+    }
+
+    /**
+     * Get the expected phase.
+     *
+     * \return the expected phase
+     */
+    inline model_phase expected_phase() const noexcept
+    {
+        return expected_phase_;
+    }
+
+    /**
+     * Get the current object phase.
+     *
+     * \return the object phase
+     */
+    inline model_phase acquired_phase() const noexcept
+    {
+        return object_->phase();
+    }
+
+    /**
+     * Get the error message.
+     *
+     * \return the message
+     */
+    const char* what() const noexcept
+    {
+        return message_.c_str();
+    }
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MODEL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/spell.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,46 @@
+/*
+ * spell.cpp -- database spell object
+ *
+ * Copyright (c) 2013-2017 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 "database.hpp"
+#include "character.hpp"
+#include "spell.hpp"
+#include "spell_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void spell::set_level(std::uint8_t level)
+{
+    if (is_published())
+        db_->spell_dao()->set_level(*this, level);
+
+    level_ = level;
+}
+
+void spell::set_character(std::shared_ptr<class character> ch)
+{
+    if (is_published())
+        throw std::runtime_error("attempt to reparent spell");
+
+    character_ = std::move(ch);
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/spell.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,102 @@
+/*
+ * spell.hpp -- database spell object
+ *
+ * Copyright (c) 2013-2017 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_SPELL_HPP
+#define MALIKANIA_SERVER_DB_SPELL_HPP
+
+#include <cstdint>
+#include <memory>
+
+#include "model.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class character;
+
+/**
+ * \brief Describe a spell.
+ */
+class spell : public model<spell> {
+private:
+    std::weak_ptr<class character> character_;
+    std::uint8_t level_{0};
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param db the database
+     * \param level the spell level
+     */
+    inline spell(std::shared_ptr<database> db, std::uint8_t level = 0)
+        : model(std::move(db))
+        , level_(level)
+    {
+    }
+
+    /**
+     * Get the level.
+     *
+     * \return the level
+     */
+    inline std::uint8_t level() const noexcept
+    {
+        return level_;
+    }
+
+    /**
+     * Set the spell level.
+     *
+     * \param level the level
+     */
+    void set_level(std::uint8_t level);
+
+    /**
+     * Get the owner.
+     *
+     * \return the owner (may be null)
+     */
+    inline std::shared_ptr<class character> character() const noexcept
+    {
+        return character_.lock();
+    }
+
+    /**
+     * Set the character owner.
+     *
+     * \param ch the character (may be null)
+     * \throw referenced_error if is_public()
+     */
+    void set_character(std::shared_ptr<class character> ch);
+
+    /**
+     * \copydoc model::table
+     */
+    std::string table() const override
+    {
+        return "character";
+    }
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_SPELL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/server/db/spell_dao.hpp	Mon Aug 21 13:17:07 2017 +0200
@@ -0,0 +1,42 @@
+/*
+ * spell_dao.hpp -- database spell interface
+ *
+ * Copyright (c) 2013-2017 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_SPELL_DAO_HPP
+#define MALIKANIA_SERVER_DB_SPELL_DAO_HPP
+
+#include <cstdint>
+
+namespace mlk {
+
+namespace server {
+
+class character;
+class spell;
+
+class spell_dao {
+public:
+    virtual void save(spell&, character&) = 0;
+    virtual void set_level(spell&, std::uint8_t) = 0;
+    virtual void remove(spell&) = 0;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_DB_SPELL_DAO_HPP
--- a/libserver/malikania/server/net/auth_handler.cpp	Mon Aug 21 07:39:41 2017 +0200
+++ b/libserver/malikania/server/net/auth_handler.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -18,7 +18,9 @@
 
 #include <util.hpp>
 
-#include "account_dao.hpp"
+#include "db/database.hpp"
+#include "db/account_dao.hpp"
+
 #include "auth_handler.hpp"
 #include "client.hpp"
 #include "server.hpp"
@@ -30,14 +32,16 @@
 void auth_handler::exec(server& server, std::shared_ptr<client> clt, nlohmann::json object) noexcept
 {
     try {
-        auto login = util::json::require_string(object, "/login"_json_pointer);
-        auto password = util::json::require_string(object, "/password"_json_pointer);
+        auto dao = server.database().account_dao();
+        auto ac = dao->authenticate(
+            util::json::require_string(object, "/login"_json_pointer),
+            util::json::require_string(object, "/password"_json_pointer)
+        );
 
-        account_dao dao(server.database());
-
-        if (!dao.authenticate(login, password)) {
+        if (ac) {
             clt->error("auth", "invalid credentials");
         } else {
+            clt->set_account(ac);
             clt->set_state(client::state::ready);
             clt->ok("auth");
         }
--- a/server/main.cpp	Mon Aug 21 07:39:41 2017 +0200
+++ b/server/main.cpp	Mon Aug 21 13:17:07 2017 +0200
@@ -18,18 +18,19 @@
 
 #include <iostream>
 
-#include <malikania/server/database.hpp>
+#include <malikania/server/db/dynlib_database.hpp>
 #include <malikania/server/server.hpp>
 
 int main()
 {
+#if 0
     mlk::server::settings sv_params{
         3320,
         "/home/markand/null/server.crt",
         "/home/markand/null/server.key",
     };
 
-    mlk::server::database db({
+    mlk::server::dynlib_database db({
         { "type", "sqlite" },
         { "path", "/home/markand/kingdom.db" }
     });
@@ -45,4 +46,5 @@
     } catch (const std::exception& ex) {
         std::cerr << "fatal: " << ex.what() << std::endl;
     }
+#endif
 }