changeset 159:7362fba6ff11

Server: separate database access from model objects, #760 @4h For a cleaner object usage regarding model access, we now use plain objects instead of smart pointers of polymorphic objects. For example, account, character and spell class are static objects which have a reference to the real database backend, therefore each time a function requires database access, it is forwarded to the appropriate database functions using account_dao, character_dao and spell_dao. To avoid user destroying the data by itself, the model::set_id function is only available if MALIKANIA_PRIVATE macro is defined, thus database backends have access but usually not the user code.
author David Demelier <markand@malikania.fr>
date Sat, 03 Mar 2018 17:34:12 +0100
parents 4b292c20124c
children bbb506feb55f
files libdb-sqlite/CMakeLists.txt libdb-sqlite/malikania/server/db/sqlite_account.cpp libdb-sqlite/malikania/server/db/sqlite_account.hpp libdb-sqlite/malikania/server/db/sqlite_account_dao.cpp libdb-sqlite/malikania/server/db/sqlite_account_dao.hpp libdb-sqlite/malikania/server/db/sqlite_character.cpp libdb-sqlite/malikania/server/db/sqlite_character.hpp libdb-sqlite/malikania/server/db/sqlite_character_dao.cpp libdb-sqlite/malikania/server/db/sqlite_character_dao.hpp libdb-sqlite/malikania/server/db/sqlite_database.cpp libdb-sqlite/malikania/server/db/sqlite_database.hpp libdb-sqlite/malikania/server/db/sqlite_spell_dao.cpp libdb-sqlite/malikania/server/db/sqlite_spell_dao.hpp libserver-test/CMakeLists.txt libserver-test/malikania/server/db/broken_account.cpp libserver-test/malikania/server/db/broken_account.hpp libserver-test/malikania/server/db/broken_account_dao.cpp libserver-test/malikania/server/db/broken_account_dao.hpp libserver-test/malikania/server/db/broken_character.cpp libserver-test/malikania/server/db/broken_character.hpp libserver-test/malikania/server/db/broken_character_dao.cpp libserver-test/malikania/server/db/broken_character_dao.hpp libserver-test/malikania/server/db/broken_database.hpp libserver-test/malikania/server/db/broken_spell_dao.cpp libserver-test/malikania/server/db/broken_spell_dao.hpp libserver-test/malikania/server/db/database_fixture.hpp libserver-test/malikania/server/db/test_account.cpp libserver-test/malikania/server/db/test_account.hpp libserver-test/malikania/server/db/test_character.cpp libserver-test/malikania/server/db/test_character.hpp libserver-test/malikania/server/db/test_database.cpp libserver-test/malikania/server/db/test_database.hpp libserver-test/malikania/server/db/test_spell.cpp libserver-test/malikania/server/db/test_spell.hpp 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.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 tests/libserver/dao-account/CMakeLists.txt tests/libserver/dao-account/main.cpp tests/libserver/dao-account/test.hpp tests/libserver/dao-character/CMakeLists.txt tests/libserver/dao-character/main.cpp tests/libserver/db/account/main.cpp
diffstat 49 files changed, 1803 insertions(+), 2851 deletions(-) [+]
line wrap: on
line diff
--- a/libdb-sqlite/CMakeLists.txt	Tue Jan 09 13:15:07 2018 +0100
+++ b/libdb-sqlite/CMakeLists.txt	Sat Mar 03 17:34:12 2018 +0100
@@ -23,20 +23,24 @@
 set(
     HEADERS
     ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/common.hpp
-    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_account.hpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_account_dao.hpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_character_dao.hpp
     ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_database.hpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_spell_dao.hpp
 )
 
 set(
     SOURCES
     ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/common.cpp
-    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_account.cpp
-    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_character.cpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_account_dao.cpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_character_dao.cpp
     ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_database.cpp
+    ${libmlk-db-sqlite_SOURCE_DIR}/malikania/server/db/sqlite_spell_dao.cpp
 )
 
 malikania_define_library(
     TARGET libmlk-db-sqlite
+    FLAGS MALIKANIA_PRIVATE
     SOURCES ${HEADERS} ${SOURCES}
     LIBRARIES libmlk-server sqlite
     PUBLIC_INCLUDES ${libmlk-db-sqlite_SOURCE_DIR}
--- a/libdb-sqlite/malikania/server/db/sqlite_account.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * sqlite_account.cpp -- database account object (SQLite implementation)
- *
- * 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 "sqlite_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using sqlite_dao = sqlite_account::sqlite_dao;
-
-/*
- * sqlite_account::do_save
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_save()
-{
-    const std::string sql(
-        "INSERT INTO account(login, password, email, firstname, lastname)"
-        "VALUES (?, ?, ?, ?, ?)"
-    );
-
-    exec(db_.instance(), sql, {login_, password_, email_, firstname_, lastname_});
-    id_ = sqlite3_last_insert_rowid(db_.instance());
-}
-
-/*
- * sqlite_account::do_remove
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_remove()
-{
-    const std::string sql(
-        "DELETE"
-        "  FROM account"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {id_});
-}
-
-/*
- * sqlite_account::do_set_password
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_set_password(const std::string& password)
-{
-    const std::string sql(
-        "UPDATE account"
-        "   SET password = ?"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {password, id_});
-}
-
-/*
- * sqlite_account::do_set_email
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_set_email(const std::string& email)
-{
-    const std::string sql(
-        "UPDATE account"
-        "   SET email = ?"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {email, id_});
-}
-
-/*
- * sqlite_account::do_set_firstname
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_set_firstname(const std::string& name)
-{
-    const std::string sql(
-        "UPDATE account"
-        "   SET firstname = ?"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {name, id_});
-}
-
-/*
- * sqlite_account::do_set_lastname
- * ------------------------------------------------------------------
- */
-void sqlite_account::do_set_lastname(const std::string& name)
-{
-    const std::string sql(
-        "UPDATE account"
-        "   SET lastname = ?"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {name, id_});
-}
-
-/*
- * sqlite_account::sqlite_dao::get
- * ------------------------------------------------------------------
- */
-std::unique_ptr<sqlite_account> sqlite_dao::get(const stmt_ptr& stmt)
-{
-    auto step = sqlite3_step(stmt.get());
-
-    switch (step) {
-    case SQLITE_DONE:
-        // No row, not found so just return an empty account.
-        return nullptr;
-    case SQLITE_ERROR:
-    case SQLITE_MISUSE:
-        throw std::runtime_error(sqlite3_errmsg(db_.instance()));
-    default:
-        break;
-    }
-
-    auto ac = std::make_unique<sqlite_account>(to_string(stmt, 1), to_string(stmt, 2), db_);
-
-    ac->id_ = to_int(stmt, 0);
-    ac->email_ = to_string(stmt, 3);
-    ac->firstname_ = to_string(stmt, 4);
-    ac->lastname_ = to_string(stmt, 5);
-    ac->characters_ = db_.sqlite_character_dao().characters_for_account(ac->id_);
-
-    return ac;
-}
-
-/*
- * sqlite_account::sqlite_dao::find_by_login
- * ------------------------------------------------------------------
- */
-std::unique_ptr<account> sqlite_dao::find_by_login(const std::string& login)
-{
-    const std::string sql(
-        "SELECT *"
-        "  FROM account"
-        " WHERE login = ?"
-    );
-
-    return get(query(db_.instance(), sql, {login}));
-}
-
-} // !server
-
-} // !db
--- a/libdb-sqlite/malikania/server/db/sqlite_account.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/*
- * sqlite_account.hpp -- database account object (SQLite implementation)
- *
- * 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_DB_SQLITE_ACCOUNT_HPP
-#define MALIKANIA_DB_SQLITE_ACCOUNT_HPP
-
-/**
- * \file sqlite_account.hpp
- * \brief Database account object (SQLite implementation).
- */
-
-#include <malikania/server/db/account.hpp>
-
-#include "common.hpp"
-
-namespace mlk {
-
-namespace server {
-
-class sqlite_database;
-
-/**
- * \brief Database account object (SQLite implementation).
- */
-class sqlite_account : public account {
-public:
-    class sqlite_dao;
-
-private:
-    sqlite_database& db_;
-
-protected:
-    /**
-     * \copydoc account::do_save
-     */
-    void do_save() override;
-
-    /**
-     * \copydoc account::do_remove
-     */
-    void do_remove() override;
-
-    /**
-     * \copydoc account::do_set_password
-     */
-    void do_set_password(const std::string& password) override;
-
-    /**
-     * \copydoc account::do_set_email
-     */
-    void do_set_email(const std::string& email) override;
-
-    /**
-     * \copydoc account::do_set_firstname
-     */
-    void do_set_firstname(const std::string& name) override;
-
-    /**
-     * \copydoc account::do_set_lastname
-     */
-    void do_set_lastname(const std::string& name) override;
-
-public:
-    /**
-     * Create the sqlite account.
-     *
-     * \pre instance != nullptr
-     * \param login the login name
-     * \param password the password
-     * \param db the sqlite_database owner
-     */
-    inline sqlite_account(std::string login, std::string password, sqlite_database& db) noexcept
-        : account(std::move(login), std::move(password))
-        , db_(db)
-    {
-    }
-};
-
-/**
- * \brief DAO for accounts (SQLite implementation).
- */
-class sqlite_account::sqlite_dao : public account::dao {
-private:
-    sqlite_database& db_;
-
-    std::unique_ptr<sqlite_account> get(const stmt_ptr& stmt);
-
-protected:
-    /**
-     * \copydoc dao::find_by_login
-     */
-    std::unique_ptr<account> find_by_login(const std::string& login) override;
-
-public:
-    /**
-     * Construct the DAO.
-     *
-     * \param db the sqlite_database owner
-     */
-    inline sqlite_dao(sqlite_database& db) noexcept
-        : db_(db)
-    {
-    }
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_DB_SQLITE_ACCOUNT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_account_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,136 @@
+/*
+ * sqlite_account_dao.cpp -- database account object (SQLite implementation)
+ *
+ * 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 "sqlite_database.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void sqlite_account_dao::publish(account& ac)
+{
+    const std::string sql(
+        "INSERT INTO account(login, password, email, firstname, lastname)"
+        "VALUES (?, ?, ?, ?, ?)"
+    );
+
+    exec(db_.instance(), sql, {
+        ac.get_login(),
+        ac.get_password(),
+        ac.get_email(),
+        ac.get_firstname(),
+        ac.get_lastname()
+    });
+    ac.set_id(sqlite3_last_insert_rowid(db_.instance()));
+}
+
+void sqlite_account_dao::unpublish(const account& ac)
+{
+    const std::string sql(
+        "DELETE"
+        "  FROM account"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {ac.get_id()});
+}
+
+void sqlite_account_dao::set_password(const account& ac, const std::string& password)
+{
+    const std::string sql(
+        "UPDATE account"
+        "   SET password = ?"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {password, ac.get_id()});
+}
+
+void sqlite_account_dao::set_email(const account& ac, const std::string& email)
+{
+    const std::string sql(
+        "UPDATE account"
+        "   SET email = ?"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {email, ac.get_id()});
+}
+
+void sqlite_account_dao::set_firstname(const account& ac, const std::string& name)
+{
+    const std::string sql(
+        "UPDATE account"
+        "   SET firstname = ?"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {name, ac.get_id()});
+}
+
+void sqlite_account_dao::set_lastname(const account& ac, const std::string& name)
+{
+    const std::string sql(
+        "UPDATE account"
+        "   SET lastname = ?"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {name, ac.get_id()});
+}
+
+boost::optional<account> sqlite_account_dao::get(const stmt_ptr& stmt)
+{
+    auto step = sqlite3_step(stmt.get());
+
+    switch (step) {
+    case SQLITE_DONE:
+        // No row, not found so just return an empty account.
+        return boost::none;
+    case SQLITE_ERROR:
+    case SQLITE_MISUSE:
+        throw std::runtime_error(sqlite3_errmsg(db_.instance()));
+    default:
+        break;
+    }
+
+    account ac(db_, to_string(stmt, 1), to_string(stmt, 2));
+
+    ac.set_email(to_string(stmt, 3));
+    ac.set_firstname(to_string(stmt, 4));
+    ac.set_lastname(to_string(stmt, 5));
+    ac.set_id(to_int(stmt, 0));
+    ac.get_characters() = character_dao_.load(ac);
+
+    return ac;
+}
+
+boost::optional<account> sqlite_account_dao::find_by_login(const std::string& login)
+{
+    const std::string sql(
+        "SELECT *"
+        "  FROM account"
+        " WHERE login = ?"
+    );
+
+    return get(query(db_.instance(), sql, {login}));
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_account_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,98 @@
+/*
+ * sqlite_account_dao.hpp -- database account object (SQLite implementation)
+ *
+ * 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_DB_SQLITE_ACCOUNT_HPP
+#define MALIKANIA_DB_SQLITE_ACCOUNT_HPP
+
+/**
+ * \file sqlite_account.hpp
+ * \brief Database account object (SQLite implementation).
+ */
+
+#include <malikania/server/db/account.hpp>
+
+#include "common.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class sqlite_database;
+class sqlite_character_dao;
+
+class sqlite_account_dao : public account_dao {
+private:
+    sqlite_database& db_;
+    sqlite_character_dao& character_dao_;
+
+    boost::optional<account> get(const stmt_ptr&);
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param db the database
+     * \param character_dao the character DAO for SQLite
+     */
+    inline sqlite_account_dao(sqlite_database& db, sqlite_character_dao& character_dao) noexcept
+        : db_(db)
+        , character_dao_(character_dao)
+    {
+    }
+
+    /**
+     * \copydoc account_dao::find_by_login
+     */
+    boost::optional<account> find_by_login(const std::string& login) override;
+
+    /**
+     * \copydoc account_dao::publish
+     */
+    void publish(account& ac) override;
+
+    /**
+     * \copydoc account_dao::unpublish
+     */
+    void unpublish(const account& ac) override;
+
+    /**
+     * \copydoc account_dao::set_password
+     */
+    void set_password(const account& ac, const std::string& password) override;
+
+    /**
+     * \copydoc account_dao::set_email
+     */
+    void set_email(const account& ac, const std::string& email) override;
+
+    /**
+     * \copydoc account_dao::set_firstname
+     */
+    void set_firstname(const account& ac, const std::string& name) override;
+
+    /**
+     * \copydoc account_dao::set_lastname
+     */
+    void set_lastname(const account& ac, const std::string& name) override;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_DB_SQLITE_ACCOUNT_HPP
--- a/libdb-sqlite/malikania/server/db/sqlite_character.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/*
- * sqlite_character.cpp -- database character object (SQLite implementation)
- *
- * 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 "sqlite_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using sqlite_dao = sqlite_character::sqlite_dao;
-
-void sqlite_character::do_set_level(std::uint16_t level)
-{
-    const std::string sql(
-        "UPDATE character"
-        "   SET level = ?"
-        " WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {level, id_});
-}
-
-void sqlite_character::do_save(std::uint64_t account_id)
-{
-    const std::string sql(
-        "INSERT INTO character("
-        "   account_id,"
-        "   nickname,"
-        "   classname,"
-        "   level"
-        ") VALUES (?, ?, ?, ?)"
-    );
-
-    exec(db_.instance(), sql, {account_id, nickname_, classname_, level_});
-}
-
-void sqlite_character::do_remove()
-{
-    const std::string sql(
-        "DELETE FROM character WHERE id = ?"
-    );
-
-    exec(db_.instance(), sql, {id_});
-}
-
-std::unique_ptr<character> sqlite_dao::get(stmt_ptr& stmt)
-{
-    auto ch = std::make_unique<sqlite_character>(to_string(stmt, 2), to_string(stmt, 3), db_);
-
-    ch->account_id_ = to_int(stmt, 1);
-    ch->level_ = to_int(stmt, 4);
-    ch->id_ = to_int(stmt, 0);
-
-    return ch;
-}
-
-character_list sqlite_dao::get_all(stmt_ptr stmt)
-{
-    character_list list;
-
-    while (sqlite3_step(stmt.get()) == SQLITE_ROW)
-        list.push_back(get(stmt));
-
-    return list;
-}
-
-character_list sqlite_dao::characters_for_account(std::uint64_t account_id)
-{
-    const std::string sql(
-        "SELECT *"
-        "  FROM character"
-        " WHERE account_id = ?"
-    );
-
-    return get_all(query(db_.instance(), sql, {account_id}));
-}
-
-} // !server
-
-} // !mlk
--- a/libdb-sqlite/malikania/server/db/sqlite_character.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * sqlite_character.hpp -- database character object (SQLite implementation)
- *
- * 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_DB_SQLITE_CHARACTER_HPP
-#define MALIKANIA_DB_SQLITE_CHARACTER_HPP
-
-/**
- * \file sqlite_character.hpp
- * \brief Database character object (SQLite implementation).
- */
-
-#include <malikania/server/db/character.hpp>
-
-#include "common.hpp"
-
-namespace mlk {
-
-namespace server {
-
-class sqlite_database;
-
-/**
- * \brief Database character object (SQLite implementation).
- */
-class sqlite_character : public character {
-private:
-    sqlite_database& db_;
-
-public:
-    class sqlite_dao;
-
-protected:
-    /**
-     * \copydoc character::do_set_level
-     */
-    void do_set_level(std::uint16_t level) override;
-
-    /**
-     * \copydoc character::do_save
-     */
-    void do_save(std::uint64_t account_id) override;
-
-    /**
-     * \copydoc character::do_remove
-     */
-    void do_remove() override;
-
-public:
-    /**
-     * Construct the sqlite character.
-     *
-     * \pre instance != nullptr
-     * \param nickname the nickname
-     * \param classname the classname
-     * \param db the sqlite_database owner
-     */
-    inline sqlite_character(std::string nickname, std::string classname, sqlite_database& db) noexcept
-        : character(std::move(nickname), std::move(classname))
-        , db_(db)
-    {
-    }
-};
-
-/**
- * \brief DAO for characters (SQLite implementation).
- */
-class sqlite_character::sqlite_dao {
-private:
-    sqlite_database& db_;
-
-    std::unique_ptr<character> get(stmt_ptr& stmt);
-
-    character_list get_all(stmt_ptr stmt);
-
-public:
-    /**
-     * Construct the DAO.
-     *
-     * \param db the sqlite_database owner
-     */
-    inline sqlite_dao(sqlite_database& db) noexcept
-        : db_(db)
-    {
-    }
-
-    /**
-     * Get the set of characters for the given account.
-     */
-    character_list characters_for_account(std::uint64_t account_id);
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_DB_SQLITE_CHARACTER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_character_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,93 @@
+/*
+ * sqlite_character_dao.cpp -- database character object (SQLite implementation)
+ *
+ * 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 "sqlite_database.hpp"
+
+namespace mlk {
+
+namespace server {
+
+character sqlite_character_dao::get(stmt_ptr& stmt)
+{
+    character ch(db_, to_string(stmt, 2), to_string(stmt, 3));
+
+    ch.set_level(to_int(stmt, 4));
+    ch.set_id(to_int(stmt, 0));
+
+    return ch;
+}
+
+void sqlite_character_dao::set_level(const character& ch, std::uint16_t level)
+{
+    const std::string sql(
+        "UPDATE character"
+        "   SET level = ?"
+        " WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {level, ch.get_id()});
+}
+
+void sqlite_character_dao::publish(character& ch, const account& parent)
+{
+    const std::string sql(
+        "INSERT INTO character("
+        "   account_id,"
+        "   nickname,"
+        "   classname,"
+        "   level"
+        ") VALUES (?, ?, ?, ?)"
+    );
+
+    exec(db_.instance(), sql, {
+        parent.get_id(),
+        ch.get_nickname(),
+        ch.get_classname(),
+        ch.get_level()
+    });
+}
+
+void sqlite_character_dao::unpublish(const character& ch)
+{
+    const std::string sql(
+        "DELETE FROM character WHERE id = ?"
+    );
+
+    exec(db_.instance(), sql, {ch.get_id()});
+}
+
+std::vector<character> sqlite_character_dao::load(const account& parent)
+{
+    const std::string sql(
+        "SELECT *"
+        "  FROM character"
+        " WHERE account_id = ?"
+    );
+
+    stmt_ptr stmt = query(db_.instance(), sql, {parent.get_id()});
+    std::vector<character> characters;
+
+    while (sqlite3_step(stmt.get()) == SQLITE_ROW)
+        characters.push_back(get(stmt));
+
+    return characters;
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_character_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,86 @@
+/*
+ * sqlite_character_dao.hpp -- database character object (SQLite implementation)
+ *
+ * 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_DB_SQLITE_CHARACTER_HPP
+#define MALIKANIA_DB_SQLITE_CHARACTER_HPP
+
+/**
+ * \file sqlite_character.hpp
+ * \brief Database character object (SQLite implementation).
+ */
+
+#include <malikania/server/db/character.hpp>
+
+#include "common.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class account;
+class sqlite_database;
+
+/**
+ * \brief Database character object (SQLite implementation).
+ */
+class sqlite_character_dao : public character_dao {
+private:
+    sqlite_database& db_;
+
+    character get(stmt_ptr&);
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param db the SQLite database.
+     */
+    inline sqlite_character_dao(sqlite_database& db) noexcept
+        : db_(db)
+    {
+    }
+
+    /**
+     * Get the list of characters for this account.
+     *
+     * \param parent the parent account
+     * \return the list of characters
+     */
+    std::vector<character> load(const account& parent);
+
+    /**
+     * \copydoc character_dao::set_level
+     */
+    void set_level(const character& ch, std::uint16_t level) override;
+
+    /**
+     * \copydoc character_dao::publish
+     */
+    void publish(character& ch, const account& parent) override;
+
+    /**
+     * \copydoc character_dao::unpublish
+     */
+    void unpublish(const character& ch) override;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_DB_SQLITE_CHARACTER_HPP
--- a/libdb-sqlite/malikania/server/db/sqlite_database.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libdb-sqlite/malikania/server/db/sqlite_database.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -1,6 +1,24 @@
+/*
+ * sqlite_database.cpp -- database access (SQLite implementation)
+ *
+ * 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 "sqlite_database.hpp"
-#include "sqlite_account.hpp"
-#include "sqlite_character.hpp"
+#include "sqlite_account_dao.hpp"
+#include "sqlite_character_dao.hpp"
 
 namespace mlk {
 
@@ -66,21 +84,6 @@
     exec(instance_.get(), init_spell);
 }
 
-std::unique_ptr<account> sqlite_database::account_draft(std::string login, std::string password)
-{
-    return std::make_unique<sqlite_account>(std::move(login), std::move(password), *this);
-}
-
-account::dao& sqlite_database::account_dao()
-{
-    return account_dao_;
-}
-
-std::unique_ptr<character> sqlite_database::character_draft(std::string nickname, std::string classname)
-{
-    return std::make_unique<sqlite_character>(std::move(nickname), std::move(classname), *this);
-}
-
 } // !server
 
 } // !mlk
--- a/libdb-sqlite/malikania/server/db/sqlite_database.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libdb-sqlite/malikania/server/db/sqlite_database.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -1,3 +1,21 @@
+/*
+ * sqlite_database.hpp -- database access (SQLite implementation)
+ *
+ * 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_DB_SQLITE_DATABASE_HPP
 #define MALIKANIA_DB_SQLITE_DATABASE_HPP
 
@@ -5,8 +23,9 @@
 
 #include <malikania/server/db/database.hpp>
 
-#include "sqlite_account.hpp"
-#include "sqlite_character.hpp"
+#include "sqlite_account_dao.hpp"
+#include "sqlite_character_dao.hpp"
+#include "sqlite_spell_dao.hpp"
 
 namespace mlk {
 
@@ -18,8 +37,9 @@
 
 private:
     sqlite3_ptr instance_{nullptr, nullptr};
-    sqlite_account::sqlite_dao account_dao_{*this};
-    sqlite_character::sqlite_dao character_dao_{*this};
+    sqlite_spell_dao spell_dao_{*this};
+    sqlite_character_dao character_dao_{*this};
+    sqlite_account_dao account_dao_{*this, character_dao_};
 
     sqlite3_ptr open(const std::string&) const;
 
@@ -42,34 +62,33 @@
     }
 
     /**
-     * Get the real underlying sqlite_character::sqlite_dao.
-     *
-     * \return the character dao
+     * \copydoc database::init
+     */
+    void init() override;
+
+    /**
+     * \copydoc database::accounts
      */
-    inline sqlite_character::sqlite_dao& sqlite_character_dao() noexcept
+    account_dao& accounts() override
+    {
+        return account_dao_;
+    }
+
+    /**
+     * \copydoc database::characters
+     */
+    character_dao& characters() override
     {
         return character_dao_;
     }
 
     /**
-     * \copydoc database::init
-     */
-    void init() override;
-
-    /**
-     * \copydoc database::account_draft
+     * \copydoc database::spells
      */
-    std::unique_ptr<account> account_draft(std::string login, std::string password) override;
-
-    /**
-     * \copydoc database::account_dao
-     */
-    account::dao& account_dao() override;
-
-    /**
-     * \copydoc database::character_draft
-     */
-    std::unique_ptr<character> character_draft(std::string nickname, std::string classname) override;
+    spell_dao& spells() override
+    {
+        return spell_dao_;
+    }
 };
 
 } // !server
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_spell_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,48 @@
+/*
+ * sqlite_spell_dao.cpp -- database spell object (SQLite implementation)
+ *
+ * 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 "sqlite_database.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void sqlite_spell_dao::publish(const character& ch, spell& sp)
+{
+    // TODO: implement this.
+    (void)ch;
+    (void)sp;
+}
+
+void sqlite_spell_dao::unpublish(const character& ch, const spell& sp)
+{
+    // TODO: implement this.
+    (void)ch;
+    (void)sp;
+}
+
+void sqlite_spell_dao::set_level(const character& ch, std::uint8_t level)
+{
+    // TODO: implement this.
+    (void)ch;
+    (void)level;
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb-sqlite/malikania/server/db/sqlite_spell_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,65 @@
+/*
+ * sqlite_spell_dao.hpp -- database spell object (SQLite implementation)
+ *
+ * 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_DB_SQLITE_SPELL_HPP
+#define MALIKANIA_DB_SQLITE_SPELL_HPP
+
+#include <malikania/server/db/spell.hpp>
+
+namespace mlk {
+
+namespace server {
+
+class sqlite_database;
+
+class sqlite_spell_dao : public spell_dao {
+private:
+    sqlite_database& db_;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param db the SQLite database.
+     */
+    inline sqlite_spell_dao(sqlite_database& db) noexcept
+        : db_(db)
+    {
+    }
+
+    /**
+     * \copydoc spell_dao::publish
+     */
+    void publish(const character& ch, spell& sp) override;
+
+    /**
+     * \copydoc spell_dao::unpublish
+     */
+    void unpublish(const character& ch, const spell& sp) override;
+
+    /**
+     * \copydoc spell_dao::set_level
+     */
+    void set_level(const character& ch, std::uint8_t level) override;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_DB_SQLITE_SPELL_HPP
--- a/libserver-test/CMakeLists.txt	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver-test/CMakeLists.txt	Sat Mar 03 17:34:12 2018 +0100
@@ -20,29 +20,24 @@
 
 set(
     HEADERS
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account.hpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character.hpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account_dao.hpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character_dao.hpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_spell_dao.hpp
     ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/database_fixture.hpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_account.hpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_character.hpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_database.hpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_spell.hpp
 )
 
 set(
     SOURCES
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account.cpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character.cpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account_dao.cpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character_dao.cpp
+    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_spell_dao.cpp
     ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/database_fixture.cpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_account.cpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_character.cpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_database.cpp
-    ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_spell.cpp
 )
 
 malikania_define_library(
     TARGET libmlk-server-test
     SOURCES ${HEADERS} ${SOURCES}
+    FLAGS MALIKANIA_PRIVATE
     LIBRARIES
         libmlk-db-sqlite
         libmlk-server
--- a/libserver-test/malikania/server/db/broken_account.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * broken_account.cpp -- database account object (broken implementation)
- *
- * 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 <stdexcept>
-
-#include "broken_account.hpp"
-
-namespace mlk {
-
-namespace server {
-
-void broken_account::do_save()
-{
-    if (!bool(allow_ & allow_flags::save))
-        throw std::runtime_error("broken do_save");
-
-    id_ = 1;
-}
-
-void broken_account::do_remove()
-{
-    if (!bool(allow_& allow_flags::remove))
-        throw std::runtime_error("broken do_remove");
-}
-
-void broken_account::do_set_password(const std::string&)
-{
-    if (!bool(allow_ & allow_flags::set_password))
-        throw std::runtime_error("broken do_set_password");
-}
-
-void broken_account::do_set_email(const std::string&)
-{
-    if (!bool(allow_ & allow_flags::set_email))
-        throw std::runtime_error("broken do_set_email");
-}
-
-void broken_account::do_set_firstname(const std::string&)
-{
-    if (!bool(allow_ & allow_flags::set_firstname))
-        throw std::runtime_error("broken do_set_firstname");
-}
-
-void broken_account::do_set_lastname(const std::string&)
-{
-    if (!bool(allow_ & allow_flags::set_lastname))
-        throw std::runtime_error("broken do_set_lastname");
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/broken_account.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * broken_account.hpp -- database account object (broken implementation)
- *
- * 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_BROKEN_ACCOUNT_HPP
-#define MALIKANIA_SERVER_BROKEN_ACCOUNT_HPP
-
-/**
- * \file broken_account.hpp
- * \brief Database account object (broken implementation).
- */
-
-#include <malikania/server/db/account.hpp>
-
-namespace mlk {
-
-namespace server {
-
-/**
- * \brief Database account object (broken implementation).
- */
-class broken_account : public account {
-public:
-    /**
-     * \brief Define which function should not be broken.
-     */
-    enum class allow_flags {
-        none                = 0,            //!< everything is broken
-        save                = (1 << 0),     //!< allow do_save
-        remove              = (1 << 1),     //!< allow do_remove
-        set_password        = (1 << 2),     //!< allow do_set_password
-        set_email           = (1 << 3),     //!< allow do_set_email
-        set_firstname       = (1 << 4),     //!< allow do_set_firstname
-        set_lastname        = (1 << 5)      //!< allow do_set_lastname
-    };
-
-private:
-    allow_flags allow_;
-
-protected:
-    /**
-     * \copydoc account::do_save
-     */
-    void do_save() override;
-
-    /**
-     * \copydoc account::do_remove
-     */
-    void do_remove() override;
-
-    /**
-     * \copydoc account::do_set_password
-     */
-    void do_set_password(const std::string& password) override;
-
-    /**
-     * \copydoc account::do_set_email
-     */
-    void do_set_email(const std::string& email) override;
-
-    /**
-     * \copydoc account::do_set_firstname
-     */
-    void do_set_firstname(const std::string& name) override;
-
-    /**
-     * \copydoc account::do_set_lastname
-     */
-    void do_set_lastname(const std::string& name) override;
-
-public:
-    /**
-     * Construct a broken account.
-     *
-     * \param login the login
-     * \param password the password
-     * \param flags the flags for a less broken account
-     */
-    inline broken_account(std::string login,
-                          std::string password,
-                          allow_flags flags = allow_flags::none) noexcept
-        : account(std::move(login), std::move(password))
-        , allow_(flags)
-    {
-    }
-};
-
-/**
- * \cond ENUM_HIDDEN_SYMBOLS
- */
-
-inline broken_account::allow_flags operator^(broken_account::allow_flags v1, broken_account::allow_flags v2) noexcept
-{
-    return static_cast<broken_account::allow_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
-}
-
-inline broken_account::allow_flags operator&(broken_account::allow_flags v1, broken_account::allow_flags v2) noexcept
-{
-    return static_cast<broken_account::allow_flags>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2));
-}
-
-inline broken_account::allow_flags operator|(broken_account::allow_flags v1, broken_account::allow_flags v2) noexcept
-{
-    return static_cast<broken_account::allow_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
-}
-
-inline broken_account::allow_flags operator~(broken_account::allow_flags v) noexcept
-{
-    return static_cast<broken_account::allow_flags>(~static_cast<unsigned>(v));
-}
-
-inline broken_account::allow_flags& operator|=(broken_account::allow_flags& v1, broken_account::allow_flags v2) noexcept
-{
-    return v1 = v1 | v2;
-}
-
-inline broken_account::allow_flags& operator&=(broken_account::allow_flags& v1, broken_account::allow_flags v2) noexcept
-{
-    return v1 = v1 & v2;
-}
-
-inline broken_account::allow_flags& operator^=(broken_account::allow_flags& v1, broken_account::allow_flags v2) noexcept
-{
-    return v1 = v1 ^ v2;
-}
-
-/**
- * \endcond
- */
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_BROKEN_ACCOUNT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_account_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,72 @@
+/*
+ * broken_account_dao.cpp -- database account object (broken implementation)
+ *
+ * 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 <stdexcept>
+
+#include "broken_account_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+boost::optional<account> broken_account_dao::find_by_login(const std::string&)
+{
+    throw std::runtime_error("broken find");
+}
+
+void broken_account_dao::publish(account& ac)
+{
+    if (!bool(allow_ & allow_flags::publish))
+        throw std::runtime_error("broken publish");
+
+    ac.set_id(1U);
+}
+
+void broken_account_dao::unpublish(const account&)
+{
+    if (!bool(allow_& allow_flags::unpublish))
+        throw std::runtime_error("broken unpublish");
+}
+
+void broken_account_dao::set_password(const account&, const std::string&)
+{
+    if (!bool(allow_ & allow_flags::set_password))
+        throw std::runtime_error("broken set_password");
+}
+
+void broken_account_dao::set_email(const account&, const std::string&)
+{
+    if (!bool(allow_ & allow_flags::set_email))
+        throw std::runtime_error("broken set_email");
+}
+
+void broken_account_dao::set_firstname(const account&, const std::string&)
+{
+    if (!bool(allow_ & allow_flags::set_firstname))
+        throw std::runtime_error("broken set_firstname");
+}
+
+void broken_account_dao::set_lastname(const account&, const std::string&)
+{
+    if (!bool(allow_ & allow_flags::set_lastname))
+        throw std::runtime_error("broken set_lastname");
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_account_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,148 @@
+/*
+ * broken_account_dao.hpp -- database account object (broken implementation)
+ *
+ * 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_BROKEN_ACCOUNT_HPP
+#define MALIKANIA_SERVER_BROKEN_ACCOUNT_HPP
+
+/**
+ * \file broken_account.hpp
+ * \brief Database account object (broken implementation).
+ */
+
+#include <malikania/server/db/account.hpp>
+
+namespace mlk {
+
+namespace server {
+
+/**
+ * \brief Database account object (broken implementation).
+ */
+class broken_account_dao : public account_dao {
+public:
+    /**
+     * \brief Define which function should not be broken.
+     */
+    enum class allow_flags {
+        none                = 0,            //!< everything is broken
+        publish             = (1 << 0),     //!< allow publish
+        unpublish           = (1 << 1),     //!< allow unpublish
+        set_password        = (1 << 2),     //!< allow set_password
+        set_email           = (1 << 3),     //!< allow set_email
+        set_firstname       = (1 << 4),     //!< allow set_firstname
+        set_lastname        = (1 << 5)      //!< allow set_lastname
+    };
+
+private:
+    allow_flags allow_;
+
+public:
+    /**
+     * Set allow flags.
+     *
+     * \param flags the new flags
+     */
+    inline void set_flags(allow_flags flags) noexcept
+    {
+        allow_ = flags;
+    }
+
+    /**
+     * \copydoc account_dao::find_by_login
+     */
+    boost::optional<account> find_by_login(const std::string& login) override;
+
+    /**
+     * \copydoc account_dao::publish
+     */
+    void publish(account& ac) override;
+
+    /**
+     * \copydoc account_dao::unpublish
+     */
+    void unpublish(const account& ac) override;
+
+    /**
+     * \copydoc account_dao::set_password
+     */
+    void set_password(const account& ac, const std::string& password) override;
+
+    /**
+     * \copydoc account_dao::set_email
+     */
+    void set_email(const account& ac, const std::string& email) override;
+
+    /**
+     * \copydoc account_dao::set_firstname
+     */
+    void set_firstname(const account& ac, const std::string& name) override;
+
+    /**
+     * \copydoc account_dao::set_lastname
+     */
+    void set_lastname(const account& ac, const std::string& name) override;
+};
+
+/**
+ * \cond ENUM_HIDDEN_SYMBOLS
+ */
+
+inline broken_account_dao::allow_flags operator^(broken_account_dao::allow_flags v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_account_dao::allow_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+}
+
+inline broken_account_dao::allow_flags operator&(broken_account_dao::allow_flags v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_account_dao::allow_flags>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2));
+}
+
+inline broken_account_dao::allow_flags operator|(broken_account_dao::allow_flags v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_account_dao::allow_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+}
+
+inline broken_account_dao::allow_flags operator~(broken_account_dao::allow_flags v) noexcept
+{
+    return static_cast<broken_account_dao::allow_flags>(~static_cast<unsigned>(v));
+}
+
+inline broken_account_dao::allow_flags& operator|=(broken_account_dao::allow_flags& v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 | v2;
+}
+
+inline broken_account_dao::allow_flags& operator&=(broken_account_dao::allow_flags& v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 & v2;
+}
+
+inline broken_account_dao::allow_flags& operator^=(broken_account_dao::allow_flags& v1, broken_account_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 ^ v2;
+}
+
+/**
+ * \endcond
+ */
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_BROKEN_ACCOUNT_HPP
--- a/libserver-test/malikania/server/db/broken_character.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * broken_character.cpp -- database character object (broken implementation)
- *
- * 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 "broken_character.hpp"
-
-namespace mlk {
-
-namespace server {
-
-void broken_character::do_save(std::uint64_t)
-{
-    if (!bool(allow_ & allow_flags::save))
-        throw std::runtime_error("broken do_save");
-
-    id_ = 1;
-}
-
-void broken_character::do_remove()
-{
-    if (!bool(allow_ & allow_flags::remove))
-        throw std::runtime_error("broken do_remove");
-
-    id_ = -1;
-}
-
-void broken_character::do_set_level(std::uint16_t)
-{
-    if (!bool(allow_ & allow_flags::set_level))
-        throw std::runtime_error("broken do_set_level");
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/broken_character.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- * broken_character.hpp -- database character object (broken implementation)
- *
- * 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_BROKEN_CHARACTER_HPP
-#define MALIKANIA_SERVER_BROKEN_CHARACTER_HPP
-
-/**
- * \file broken_character.hpp
- * \brief Database character object (broken implementation).
- */
-
-#include <malikania/server/db/character.hpp>
-
-namespace mlk {
-
-namespace server {
-
-/**
- * \brief Database character object (broken implementation).
- */
-class broken_character : public character {
-public:
-    /**
-     * \brief Define which function should not be broken.
-     */
-    enum class allow_flags : unsigned {
-        none                = 0,            //!< everything is broken
-        save                = (1 << 0),     //!< allow do_save
-        remove              = (1 << 1),     //!< allow do_remove
-        set_level           = (1 << 2),     //!< allow do_set_level
-    };
-
-private:
-    allow_flags allow_;
-
-protected:
-    /**
-     * Called by account::add_character.
-     */
-    void do_save(std::uint64_t account_id) override;
-
-    /**
-     * Called by account::remove_character.
-     */
-    void do_remove() override;
-
-    /**
-     * Set the character level.
-     */
-    void do_set_level(std::uint16_t level) override;
-
-public:
-    /**
-     * Construct a broken character.
-     *
-     * \param login the login
-     * \param password the password
-     * \param flags the flags for a less broken account
-     */
-    inline broken_character(std::string nickname,
-                            std::string classname,
-                            allow_flags flags = allow_flags::none) noexcept
-        : character(std::move(nickname), std::move(classname))
-        , allow_(flags)
-    {
-    }
-};
-
-/**
- * \cond ENUM_HIDDEN_SYMBOLS
- */
-
-inline broken_character::allow_flags operator^(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept
-{
-    return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
-}
-
-inline broken_character::allow_flags operator&(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept
-{
-    return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2));
-}
-
-inline broken_character::allow_flags operator|(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept
-{
-    return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
-}
-
-inline broken_character::allow_flags operator~(broken_character::allow_flags v) noexcept
-{
-    return static_cast<broken_character::allow_flags>(~static_cast<unsigned>(v));
-}
-
-inline broken_character::allow_flags& operator|=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept
-{
-    return v1 = v1 | v2;
-}
-
-inline broken_character::allow_flags& operator&=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept
-{
-    return v1 = v1 & v2;
-}
-
-inline broken_character::allow_flags& operator^=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept
-{
-    return v1 = v1 ^ v2;
-}
-
-/**
- * \endcond
- */
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_BROKEN_CHARACTER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_character_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,49 @@
+/*
+ * broken_character_dao.cpp -- database character object (broken implementation)
+ *
+ * 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 <stdexcept>
+
+#include "broken_character_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void broken_character_dao::publish(character& ch, const account&)
+{
+    if (!bool(allow_ & allow_flags::publish))
+        throw std::runtime_error("broken publish");
+
+    ch.set_id(1U);
+}
+
+void broken_character_dao::unpublish(const character&)
+{
+    if (!bool(allow_ & allow_flags::unpublish))
+        throw std::runtime_error("broken unpublish");
+}
+
+void broken_character_dao::set_level(const character&, std::uint16_t)
+{
+    if (!bool(allow_ & allow_flags::set_level))
+        throw std::runtime_error("broken set_level");
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_character_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,125 @@
+/*
+ * broken_character_dao.hpp -- database character object (broken implementation)
+ *
+ * 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_BROKEN_CHARACTER_HPP
+#define MALIKANIA_SERVER_BROKEN_CHARACTER_HPP
+
+/**
+ * \file broken_character.hpp
+ * \brief Database character object (broken implementation).
+ */
+
+#include <malikania/server/db/character.hpp>
+
+namespace mlk {
+
+namespace server {
+
+/**
+ * \brief Database character object (broken implementation).
+ */
+class broken_character_dao : public character_dao {
+public:
+    /**
+     * \brief Define which function should not be broken.
+     */
+    enum class allow_flags : unsigned {
+        none                = 0,            //!< everything is broken
+        publish             = (1 << 0),     //!< allow publish
+        unpublish           = (1 << 1),     //!< allow unpublish
+        set_level           = (1 << 2),     //!< allow set_level
+    };
+
+private:
+    allow_flags allow_;
+
+public:
+    /**
+     * Set allow flags.
+     *
+     * \param flags the new flags
+     */
+    inline void set_flags(allow_flags flags) noexcept
+    {
+        allow_ = flags;
+    }
+
+    /**
+     * \copydoc character_dao::set_level
+     */
+    void set_level(const character& ch, std::uint16_t level) override;
+
+    /**
+     * \copydoc character_dao::publish
+     */
+    void publish(character& ch, const account& parent) override;
+
+    /**
+     * \copydoc character_dao::unpublish
+     */
+    void unpublish(const character& ch) override;
+};
+
+/**
+ * \cond ENUM_HIDDEN_SYMBOLS
+ */
+
+inline broken_character_dao::allow_flags operator^(broken_character_dao::allow_flags v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_character_dao::allow_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+}
+
+inline broken_character_dao::allow_flags operator&(broken_character_dao::allow_flags v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_character_dao::allow_flags>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2));
+}
+
+inline broken_character_dao::allow_flags operator|(broken_character_dao::allow_flags v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return static_cast<broken_character_dao::allow_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+}
+
+inline broken_character_dao::allow_flags operator~(broken_character_dao::allow_flags v) noexcept
+{
+    return static_cast<broken_character_dao::allow_flags>(~static_cast<unsigned>(v));
+}
+
+inline broken_character_dao::allow_flags& operator|=(broken_character_dao::allow_flags& v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 | v2;
+}
+
+inline broken_character_dao::allow_flags& operator&=(broken_character_dao::allow_flags& v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 & v2;
+}
+
+inline broken_character_dao::allow_flags& operator^=(broken_character_dao::allow_flags& v1, broken_character_dao::allow_flags v2) noexcept
+{
+    return v1 = v1 ^ v2;
+}
+
+/**
+ * \endcond
+ */
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_BROKEN_CHARACTER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_database.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,98 @@
+/*
+ * broken_database.hpp -- database access (broken implementation)
+ *
+ * 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_BROKEN_DATABASE_HPP
+#define MALIKANIA_SERVER_BROKEN_DATABASE_CPP
+
+#include <malikania/server/db/database.hpp>
+
+#include "broken_account_dao.hpp"
+#include "broken_character_dao.hpp"
+#include "broken_spell_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+class broken_database : public database {
+private:
+    broken_account_dao account_dao_;
+    broken_character_dao character_dao_;
+    broken_spell_dao spell_dao_;
+
+public:
+    /**
+     * Update allow flags on broken_account_dao.
+     *
+     * \param flags the new flags
+     */
+    inline void set_account_flags(broken_account_dao::allow_flags flags) noexcept
+    {
+        account_dao_.set_flags(flags);
+    }
+
+    /**
+     * Update allow flags on broken_character_dao.
+     *
+     * \param flags the new flags
+     */
+    inline void set_character_flags(broken_character_dao::allow_flags flags) noexcept
+    {
+        character_dao_.set_flags(flags);
+    }
+
+    /**
+     * Update allow flags on broken_spell_dao.
+     *
+     * \param flags the new flags
+     */
+    inline void set_spell_flags(broken_spell_dao::allow_flags flags) noexcept
+    {
+        spell_dao_.set_flags(flags);
+    }
+
+    /**
+     * \copydoc database::accounts
+     */
+    account_dao& accounts() override
+    {
+        return account_dao_;
+    }
+
+    /**
+     * \copydoc database::characters
+     */
+    character_dao& characters() override
+    {
+        return character_dao_;
+    }
+
+    /**
+     * \copydoc database::spells
+     */
+    spell_dao& spells() override
+    {
+        return spell_dao_;
+    }
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_BROKEN_DATABASE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_spell_dao.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,45 @@
+/*
+ * broken_spell_dao.cpp -- database spell object (broken implementation)
+ *
+ * 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 "broken_spell_dao.hpp"
+
+namespace mlk {
+
+namespace server {
+
+void broken_spell_dao::publish(const character& ch, spell& sp)
+{
+    (void)ch;
+    (void)sp;
+}
+
+void broken_spell_dao::unpublish(const character& ch, const spell& sp)
+{
+    (void)ch;
+    (void)sp;
+}
+
+void broken_spell_dao::set_level(const character& ch, std::uint8_t level)
+{
+    (void)ch;
+    (void)level;
+}
+
+} // !server
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver-test/malikania/server/db/broken_spell_dao.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -0,0 +1,74 @@
+/*
+ * broken_spell_dao.hpp -- database spell object (broken implementation)
+ *
+ * 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_BROKEN_SPELL_DAO_HPP
+#define MALIKANIA_SERVER_BROKEN_SPELL_DAO_HPP
+
+#include <malikania/server/db/spell.hpp>
+
+namespace mlk {
+
+namespace server {
+
+class broken_spell_dao : public spell_dao {
+public:
+    /**
+     * \brief Define which function should not be broken.
+     */
+    enum class allow_flags : unsigned {
+        none                = 0,            //!< everything is broken
+        publish             = (1 << 0),     //!< allow publish
+        unpublish           = (1 << 1),     //!< allow unpublish
+        set_level           = (1 << 2),     //!< allow set_level
+    };
+
+private:
+    allow_flags allow_;
+
+public:
+    /**
+     * Set allow flags.
+     *
+     * \param flags the new flags
+     */
+    inline void set_flags(allow_flags flags) noexcept
+    {
+        allow_ = flags;
+    }
+
+    /**
+     * \copydoc spell_dao::publish
+     */
+    void publish(const character& ch, spell& sp) override;
+
+    /**
+     * \copydoc spell_dao::unpublish
+     */
+    void unpublish(const character& ch, const spell& sp) override;
+
+    /**
+     * \copydoc spell_dao::set_level
+     */
+    void set_level(const character& ch, std::uint8_t level) override;
+};
+
+} // !server
+
+} // !mlk
+
+#endif // !MALIKANIA_SERVER_BROKEN_SPELL_DAO_HPP
--- a/libserver-test/malikania/server/db/database_fixture.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver-test/malikania/server/db/database_fixture.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -23,7 +23,6 @@
 
 #include <boost/mpl/list.hpp>
 
-#include <malikania/server/db/test_database.hpp>
 #include <malikania/server/db/sqlite_database.hpp>
 
 namespace mlk {
@@ -58,28 +57,10 @@
 };
 
 /**
- * \brief Create a test database fixture.
- */
-class test_fixture {
-private:
-    test_database db_;
-
-public:
-    /**
-     * Access the database object.
-     */
-    inline database& db() noexcept
-    {
-        return db_;
-    }
-};
-
-/**
  * \brief List to use with BOOST_AUTO_TEST_CASE_TEMPLATE
  */
 using database_types = boost::mpl::list<
-    sqlite_fixture,
-    test_fixture
+    sqlite_fixture
 >;
 
 } // !server
--- a/libserver-test/malikania/server/db/test_account.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/*
- * test_account.cpp -- database account object (memory implementation)
- *
- * 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 "test_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using test_dao = test_account::test_dao;
-
-void test_account::do_save()
-{
-    assert(!db_.exists(*this));
-
-    id_ = db_.next_account_id();
-    db_.dict(*this)[db_.sid(*this)] = serialize();
-}
-
-void test_account::do_remove()
-{
-    assert(db_.exists(*this));
-
-    db_.dict(*this).erase(db_.sid(*this));
-}
-
-void test_account::do_set_password(const std::string& password)
-{
-    assert(db_.exists(*this));
-
-    db_.ref(*this)["password"] = password;
-}
-
-void test_account::do_set_email(const std::string& email)
-{
-    assert(db_.exists(*this));
-
-    db_.ref(*this)["email"] = email;
-}
-
-void test_account::do_set_firstname(const std::string& name)
-{
-    assert(db_.exists(*this));
-
-
-    db_.ref(*this)["firstname"] = name;
-}
-
-void test_account::do_set_lastname(const std::string& name)
-{
-    assert(db_.exists(*this));
-
-    db_.ref(*this)["lastname"] = name;
-}
-
-test_account::test_account(nlohmann::json json, test_database& db)
-    : test_account(json["login"], json["password"], db)
-{
-    id_ = json["id"];
-    login_ = json["login"];
-    password_ = json["password"];
-    email_ = json["email"];
-    firstname_ = json["firstname"];
-    lastname_ = json["lastname"];
-
-    for (const auto& cdata : json["characters"])
-        characters_.push_back(std::make_unique<test_character>(cdata, db_));
-}
-
-nlohmann::json test_account::serialize() const
-{
-    return {
-        { "id", id_ },
-        { "login", login_ },
-        { "password", password_ },
-        { "email", email_ },
-        { "firstname", firstname_ },
-        { "lastname", lastname_ },
-        { "characters", nlohmann::json::object() }
-    };
-}
-
-std::unique_ptr<account> test_dao::find_by_login(const std::string& login)
-{
-    for (const auto& v : db_.data()["accounts"])
-        if (v["login"].get<std::string>() == login)
-            return std::make_unique<test_account>(v, db_);
-
-    return nullptr;
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/test_account.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/*
- * test_account.hpp -- database account object (memory implementation)
- *
- * 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_TEST_ACCOUNT_HPP
-#define MALIKANIA_SERVER_TEST_ACCOUNT_HPP
-
-/**
- * \file test_account.hpp
- * \brief Database account object (memory implementation)
- */
-
-#include <unordered_map>
-
-#include <json.hpp>
-
-#include <malikania/server/db/account.hpp>
-
-#include "test_character.hpp"
-
-namespace mlk {
-
-namespace server {
-
-class test_database;
-
-/**
- * \brief Database account object (memory implementation)
- */
-class test_account : public account {
-public:
-    class test_dao;
-
-private:
-    test_database& db_;
-
-public:
-    /**
-     * Parse the JSON as account.
-     *
-     * This also load recursively characters.
-     *
-     * \warning json input is not verified
-     * \param json the JSON input
-     * \param db the database
-     */
-    test_account(nlohmann::json json, test_database& db);
-
-    /**
-     * Construct a test_account.
-     *
-     * \param login the login
-     * \param password the password
-     * \param db the test_database
-     */
-    inline test_account(std::string login, std::string password, test_database& db) noexcept
-        : account(std::move(login), std::move(password))
-        , db_(db)
-    {
-    }
-
-    /**
-     * Path where accounts are saved in JSON.
-     *
-     * \return the path as JSON pointer
-     */
-    inline std::string path() const noexcept
-    {
-        return "/accounts";
-    }
-
-    /**
-     * Dump this account as json.
-     *
-     * \return the json
-     */
-    nlohmann::json serialize() const;
-
-    /**
-     * \copydoc account::do_save
-     */
-    void do_save() override;
-
-    /**
-     * \copydoc account::do_remove
-     */
-    void do_remove() override;
-
-    /**
-     * \copydoc account::do_set_password
-     */
-    void do_set_password(const std::string& password) override;
-
-    /**
-     * \copydoc account::do_set_email
-     */
-    void do_set_email(const std::string& email) override;
-
-    /**
-     * \copydoc account::do_set_firstname
-     */
-    void do_set_firstname(const std::string& name) override;
-
-    /**
-     * \copydoc account::do_set_lastname
-     */
-    void do_set_lastname(const std::string& name) override;
-};
-
-/**
- * \brief Account dao for test_account.
- *
- * This class saves accounts in memory, thus once deleted the no accounts are
- * available anymore.
- */
-class test_account::test_dao : public account::dao {
-private:
-    test_database& db_;
-
-public:
-    /**
-     * Construct the dao, requires character_dao to load characters.
-     *
-     * \param db the test databasse
-     */
-    inline test_dao(test_database& db) noexcept
-        : db_(db)
-    {
-    }
-
-    /**
-     * \copydoc dao::find_by_login
-     */
-    std::unique_ptr<account> find_by_login(const std::string& login) override;
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_TEST_ACCOUNT_HPP
--- a/libserver-test/malikania/server/db/test_character.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * test_character.cpp -- database account object (memory implementation)
- *
- * 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 "test_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using test_dao = test_character::test_dao;
-
-void test_character::do_save(std::uint64_t account_id)
-{
-    id_ = db_.next_character_id();
-    account_id_ = account_id;
-    db_.dict(*this)[db_.sid(*this)] = serialize();
-}
-
-void test_character::do_remove()
-{
-    db_.dict(*this).erase(db_.sid(*this));
-}
-
-void test_character::do_set_level(std::uint16_t level)
-{
-    db_.ref(*this)["level"] = level;
-}
-
-nlohmann::json test_character::serialize() const
-{
-    return {
-        { "id", id_ },
-        { "account_id", account_id_ },
-        { "nickname", nickname_ },
-        { "classname", classname_ },
-        { "level", level_ }
-    };
-}
-
-test_character::test_character(nlohmann::json json, test_database& db)
-    : test_character(json["nickname"], json["classname"], db)
-{
-    id_ = json["id"];
-    account_id_ = json["account_id"];
-    nickname_ = json["nickname"];
-    classname_ = json["classname"];
-    level_ = json["level"];
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/test_character.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-/*
- * test_character.hpp -- database account object (memory implementation)
- *
- * 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_TEST_CHARACTER_HPP
-#define MALIKANIA_SERVER_TEST_CHARACTER_HPP
-
-/**
- * \file test_account.hpp
- * \brief Database account object (memory implementation)
- */
-
-#include <unordered_map>
-#include <sstream>
-
-#include <json.hpp>
-
-#include <malikania/server/db/character.hpp>
-
-namespace mlk {
-
-namespace server {
-
-class test_database;
-
-/**
- * \brief Database account object (memory implementation)
- */
-class test_character : public character {
-public:
-    class test_dao;
-
-private:
-    test_database& db_;
-
-protected:
-    /**
-     * \copydoc character::do_save
-     */
-    void do_save(std::uint64_t account_id) override;
-
-    /**
-     * \copydoc character::do_remove
-     */
-    void do_remove() override;
-
-    /**
-     * \copydoc character::do_set_level
-     */
-    void do_set_level(std::uint16_t level) override;
-
-public:
-    /**
-     * Parse the JSON as character.
-     *
-     * This also load recursively spells.
-     *
-     * \warning json input is not verified
-     * \param json the JSON input
-     * \param db the database
-     */
-    test_character(nlohmann::json json, test_database& db);
-
-    /**
-     * Construct a test account.
-     *
-     * \param nickname the nickname
-     * \param classname the classname
-     * \param db reference to test_database
-     */
-    inline test_character(std::string nickname, std::string classname, test_database& db) noexcept
-        : character(std::move(nickname), std::move(classname))
-        , db_(db)
-    {
-    }
-
-    /**
-     * Path where characters are saved in JSON.
-     *
-     * \return the path as JSON pointer
-     */
-    inline std::string path() const
-    {
-        assert(account_id_ > 0);
-
-        std::ostringstream oss;
-
-        oss << "/accounts/" << account_id_;
-        oss << "/characters";
-
-        return oss.str();
-    }
-
-    /**
-     * Dump this account as json.
-     *
-     * \return the json
-     */
-    nlohmann::json serialize() const;
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_TEST_CHARACTER_HPP
--- a/libserver-test/malikania/server/db/test_database.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * test_database.cpp -- database test (memory implementation)
- *
- * Copyright (c) 2013-2018 Alexis Dörr <nanahara@malikania.fr>
- * 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 "test_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-/*
- * test_database::test_database
- * ------------------------------------------------------------------
- *
- * We can't use the in-class initialization because the JSON object will be
- * constructed as an array.
- */
-test_database::test_database()
-    : data_(nlohmann::json::value_t::object)
-{
-    data_["accounts"] = nlohmann::json::object();
-}
-
-std::unique_ptr<account> test_database::account_draft(std::string login, std::string password)
-{
-    return std::make_unique<test_account>(std::move(login), std::move(password), *this);
-}
-
-account::dao& test_database::account_dao()
-{
-    return account_dao_;
-}
-
-std::unique_ptr<character> test_database::character_draft(std::string nickname, std::string classname)
-{
-    return std::make_unique<test_character>(std::move(nickname), std::move(classname), *this);
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/test_database.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-/*
- * test_database.hpp -- database test (memory implementation)
- *
- * Copyright (c) 2013-2018 Alexis Dörr <nanahara@malikania.fr>
- * 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_TEST_DATABASE_HPP
-#define MALIKANIA_SERVER_TEST_DATABASE_HPP
-
-/**
- * \file test_database.hpp
- * \brief In memory database implementation for test purposes.
- */
-
-#include <malikania/server/db/database.hpp>
-
-#include <json.hpp>
-
-#include "test_account.hpp"
-#include "test_character.hpp"
-#include "test_spell.hpp"
-
-namespace mlk {
-
-namespace server {
-
-/**
- * \brief In memory database implementation for test purposes.
- */
-class test_database : public database {
-private:
-    nlohmann::json data_;
-    test_spell::test_dao spell_dao_{*this};
-    test_account::test_dao account_dao_{*this};
-    std::uint64_t seq_account_id_{1};
-    std::uint64_t seq_character_id_{1};
-
-public:
-    /**
-     * Construct the database and initialize the JSON tree.
-     */
-    test_database();
-
-    /**
-     * Get the next account id.
-     *
-     * \return the next id
-     */
-    inline std::uint64_t next_account_id() noexcept
-    {
-        return seq_account_id_ ++;
-    }
-
-    /**
-     * Get the next character id.
-     *
-     * \return the next character id
-     */
-    inline std::uint64_t next_character_id() noexcept
-    {
-        return seq_character_id_ ++;
-    }
-
-    /**
-     * Access the underlying data.
-     *
-     * \return the data
-     */
-    inline const nlohmann::json& data() const noexcept
-    {
-        return data_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the data
-     */
-    inline nlohmann::json& data() noexcept
-    {
-        return data_;
-    }
-
-    /**
-     * Get the real underlying test_dao type for accounts.
-     *
-     * \return the test_dao instance
-     */
-    inline test_account::test_dao& test_account_dao() noexcept
-    {
-        return account_dao_;
-    }
-
-    /**
-     * Get the real underlying test_dao type for spells.
-     *
-     * \return the test_spell::test_dao instance
-     */
-    inline test_spell::test_dao& test_spell_dao() noexcept
-    {
-        return spell_dao_;
-    }
-
-    /**
-     * \copydoc database::account_draft
-     */
-    std::unique_ptr<account> account_draft(std::string login, std::string password) override;
-
-    /**
-     * \copydoc database::account_dao
-     */
-    account::dao& account_dao() override;
-
-    /**
-     * \copydoc database::character_draft
-     */
-    std::unique_ptr<character> character_draft(std::string nickname, std::string classname) override;
-
-    /**
-     * Convert the model id into a string to be used as JSON keys.
-     *
-     * \return the string id
-     */
-    inline std::string sid(const model& m) const noexcept
-    {
-        return std::to_string(m.id());
-    }
-
-    /**
-     * Overload that takes an id.
-     *
-     * \return the id
-     */
-    inline std::string sid(std::uint64_t id) const noexcept
-    {
-        return std::to_string(id);
-    }
-
-    /**
-     * Check if the specified object exists in JSON database.
-     *
-     * \param obj the object
-     * \return true if exists
-     */
-    template <typename Object>
-    inline bool exists(const Object& obj) const noexcept
-    {
-        return data_[nlohmann::json::json_pointer(obj.path())].count(sid(obj)) > 0;
-    }
-
-    /**
-     * Get the parent JSON object.
-     *
-     * \param obj the object
-     * \return the parent JSON object
-     */
-    template <typename Object>
-    inline const nlohmann::json& dict(Object& obj) const noexcept
-    {
-        return data_[nlohmann::json::json_pointer(obj.path())];
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param obj the object
-     * \return the parent JSON object
-     */
-    template <typename Object>
-    inline nlohmann::json& dict(Object& obj) noexcept
-    {
-        return data_[nlohmann::json::json_pointer(obj.path())];
-    }
-
-    /**
-     * Get the reference to the object in JSON database.
-     *
-     * \param obj the object
-     * \return the reference to the object
-     */
-    template <typename Object>
-    inline nlohmann::json& ref(Object& obj) noexcept
-    {
-        return data_[nlohmann::json::json_pointer(obj.path() + std::string("/") + sid(obj))];
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \param obj the object
-     * \return the reference to the object
-     */
-    template <typename Object>
-    inline const nlohmann::json& ref(const Object& obj) noexcept
-    {
-        return data_[nlohmann::json::json_pointer(obj.path() + std::string("/") + sid(obj))];
-    }
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_TEST_DATABASE_HPP
--- a/libserver-test/malikania/server/db/test_spell.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-#include "test_database.hpp"
-
-namespace mlk {
-
-namespace server {
-
-using test_dao = test_spell::test_dao;
-
-void test_spell::do_save(std::uint64_t character_id)
-{
-    character_id_ = character_id;
-}
-
-void test_spell::do_remove()
-{
-}
-
-void test_spell::do_set_level(std::uint8_t)
-{
-}
-
-nlohmann::json test_spell::serialize() const
-{
-    return {
-        { "id", id_ },
-        { "character_id", character_id_ },
-        { "classname", classname_ },
-        { "level", level_ }
-    };
-}
-
-void test_spell::unserialize(const nlohmann::json& json)
-{
-    id_ = json["id"];
-    character_id_ = json["character_id"];
-    classname_ = json["classname"];
-    level_ = json["level"];
-}
-
-spell_list test_dao::spells_for_charater(std::uint64_t character_id)
-{
-#if 0
-    spell_list list;
-
-    for (const auto& pair : spells_) {
-        if (pair.second["character_id"].get<int>() == character_id) {
-            auto s = std::make_unique<test_spell>(pair.second["classname"], db_);
-
-            s->unserialize(pair.second);
-            list.push_back(std::move(s));
-        }
-    }
-
-    return list;
-#endif
-    spell_list list;
-
-    (void)character_id;
-
-    return list;
-}
-
-void test_dao::remove_all(std::uint64_t character_id)
-{
-#if 0
-    for (auto it = spells_.begin(); it != spells_.end(); ) {
-        if (it->second["character_id"].get<int>() == character_id)
-            it = spells_.erase(it);
-        else
-            it++;
-    }
-#endif
-    (void)character_id;
-}
-
-} // !server
-
-} // !mlk
--- a/libserver-test/malikania/server/db/test_spell.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * test_spell.hpp -- database spell object (memory implementation)
- *
- * 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_TEST_SPELL_HPP
-#define MALIKANIA_SERVER_TEST_SPELL_HPP
-
-/**
- * \file test_spell.hpp
- * \brief Database spell object (memory implementation).
- */
-
-#include <unordered_map>
-
-#include <malikania/server/db/spell.hpp>
-
-#include <json.hpp>
-
-namespace mlk {
-
-namespace server {
-
-class test_database;
-
-/**
- * \brief Database spell object (memory implementation).
- */
-class test_spell : public spell {
-public:
-    class test_dao;
-
-protected:
-    void do_save(std::uint64_t character_id) override;
-    void do_remove() override;
-    void do_set_level(std::uint8_t level) override;
-
-public:
-    inline test_spell(std::string classname, test_database&) noexcept
-        : spell(std::move(classname))
-    {
-    }
-
-    /**
-     * Dump this character as json.
-     *
-     * \return the json
-     */
-    nlohmann::json serialize() const;
-
-    /**
-     * Fill this character with json input.
-     *
-     * \warning json input must be valid, no checks are performed
-     * \param input the json input
-     */
-    void unserialize(const nlohmann::json& json);
-};
-
-class test_spell::test_dao {
-public:
-    inline test_dao(test_database&) noexcept
-    {
-    }
-
-    /**
-     * Get the set of spells for the given character.
-     */
-    spell_list spells_for_charater(std::uint64_t character_id);
-
-    /**
-     * Remove all characters for the specified character.
-     *
-     * \param character_id the character id
-     */
-    void remove_all(std::uint64_t character_id);
-};
-
-} // !server
-
-} // !mlk
-
-#endif // !MALIKANIA_SERVER_TEST_SPELL_HPP
--- a/libserver/malikania/server/db/account.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/account.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -23,55 +23,90 @@
 
 namespace server {
 
-/*
- * account::add_character
- * ------------------------------------------------------------------
- */
-std::unique_ptr<character>& account::add_character(std::unique_ptr<character> ch)
+void account::set_password(std::string password)
 {
-    assert(is_published());
-    assert(ch && ch->is_draft());
+    assert(!password.empty());
+
+    if (is_published() && password_ != password)
+        db_.accounts().set_password(*this, password);
+
+    password_ = std::move(password);
+}
 
-    ch->do_save(id_);
-    characters_.push_back(std::move(ch));
+void account::set_email(std::string email)
+{
+    assert(!email.empty());
 
-    return characters_.back();
+    if (is_published() && email_ != email)
+        db_.accounts().set_email(*this, email);
+
+    email_ = std::move(email);
 }
 
-/*
- * account::remove_character
- * ------------------------------------------------------------------
- */
-void account::remove_character(std::unique_ptr<character>& ch)
+void account::set_firstname(std::string name)
+{
+    if (is_published() && firstname_ != name)
+        db_.accounts().set_firstname(*this, name);
+
+    firstname_ = std::move(name);
+}
+
+void account::set_lastname(std::string name)
 {
+    if (is_published() && lastname_ != name)
+        db_.accounts().set_lastname(*this, name);
+
+    lastname_ = std::move(name);
+}
+
+void account::add(character ch)
+{
+    assert(is_published() && ch.is_draft());
+
+    db_.characters().publish(ch, *this);
+    characters_.push_back(std::move(ch));
+}
+
+void account::remove(std::vector<character>::iterator it)
+{
+    // TODO: assert it is in vector.
     assert(is_published());
-    assert(ch && ch->is_published());
 
-    auto it = std::find(characters_.begin(), characters_.end(), ch);
-
-    if (it == characters_.end())
-        return;
-
-    ch->do_remove();
-    ch = nullptr;
+    db_.characters().unpublish(*it);
     characters_.erase(it);
 }
 
-/*
- * account::dao::authenticate
- * ------------------------------------------------------------------
- */
-std::unique_ptr<account> account::dao::authenticate(const std::string& login,
-                                                    const std::string& password)
+void account::save()
+{
+    if (is_draft())
+        db_.accounts().publish(*this);
+
+    assert(is_published());
+}
+
+void account::remove()
+{
+    if (is_published())
+        db_.accounts().unpublish(*this);
+
+    id_ = 0U;
+    characters_.clear();
+
+    assert(is_draft());
+}
+
+boost::optional<account> account_dao::authenticate(const std::string& login,
+                                                   const std::string& password)
 {
     auto ac = find_by_login(login);
 
-    if (!ac || ac->password() != password)
-        return nullptr;
+    if (!ac || ac->get_password() != password)
+        return boost::none;
 
     return ac;
 }
 
+
 } // !server
 
 } // !mlk
--- a/libserver/malikania/server/db/account.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/account.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -24,7 +24,7 @@
  * \brief Database account object.
  */
 
-#include <algorithm>
+#include <boost/optional.hpp>
 
 #include "model.hpp"
 #include "character.hpp"
@@ -37,16 +37,203 @@
  * \brief Database account object.
  */
 class account : public model {
-public:
-    class dao;
-
 protected:
     std::string login_;                 //!< unique login (not null)
     std::string password_;              //!< password stored as-is
     std::string email_;                 //!< raw email
     std::string firstname_;             //!< optional first name
     std::string lastname_;              //!< optional last name
-    character_list characters_;         //!< list of characters
+    std::vector<character> characters_; //!< list of characters
+
+public:
+    /**
+     * Create a draft account.
+     *
+     * \pre !login.empty()
+     * \pre !password.empty()
+     * \param login the login name
+     * \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());
+    }
+
+    /**
+     * 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_;
+    }
+
+    /**
+     * Set the password.
+     *
+     * \pre !password.empty()
+     * \warning the password is saved as-is and **must** be hashed by the caller.
+     */
+    void set_password(std::string password);
+
+    /**
+     * Get the account email.
+     *
+     * \return the email
+     */
+    inline const std::string& get_email() const noexcept
+    {
+        return email_;
+    }
+
+    /**
+     * Set the account email.
+     *
+     * \pre !email.empty()
+     * \param email the new email
+     */
+    void set_email(std::string email);
+
+    /**
+     * Get the account first name.
+     *
+     * \return the name
+     */
+    inline const std::string& get_firstname() const noexcept
+    {
+        return firstname_;
+    }
+
+    /**
+     * Set the account firstname.
+     *
+     * \param name the new name
+     */
+    void set_firstname(std::string name);
+
+    /**
+     * Get the account last name.
+     *
+     * \return the name
+     */
+    inline const std::string& get_lastname() const noexcept
+    {
+        return lastname_;
+    }
+
+    /**
+     * Set the account last name.
+     *
+     * \param name the new name
+     */
+    void set_lastname(std::string name);
+
+    /**
+     * Get the caracter list.
+     *
+     * \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
+
+    /**
+     * Add the character to the account.
+     *
+     * Account takes ownership of character.
+     *
+     * \pre ch.is_draft()
+     * \param ch the character
+     * \post ch.is_published()
+     */
+    void add(character ch);
+
+    /**
+     * Remove the character from the account.
+     *
+     * \pre it is in the the account character list
+     * \param ch the character
+     * \post ch == nullptr
+     */
+    void remove(std::vector<character>::iterator it);
+
+    /**
+     * Save the account, does nothing if is_published().
+     *
+     * \throw std::exception on errors
+     * \post is_published()
+     */
+    void save();
+
+    /**
+     * Destroy the account.
+     *
+     * The account will contains no characters anymore.
+     *
+     * \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);
+
+    /**
+     * Find an account by login.
+     *
+     * \param login the login name
+     * \return the account or null if not found
+     * \throw exception on other errors
+     */
+    virtual boost::optional<account> find_by_login(const std::string& login) = 0;
 
     /**
      * Save the account.
@@ -60,7 +247,7 @@
      * \note called from account::save
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_save() = 0;
+    virtual void publish(account& ac) = 0;
 
     /**
      * Remove the account.
@@ -72,7 +259,7 @@
      * \note called from account::remove
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_remove() = 0;
+    virtual void unpublish(const account& ac) = 0;
 
     /**
      * Update the account password in database.
@@ -83,7 +270,7 @@
      * \note called from set_password helper
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_set_password(const std::string& password) = 0;
+    virtual void set_password(const account& ac, const std::string& password) = 0;
 
     /**
      * Update the account email in database.
@@ -94,7 +281,7 @@
      * \note called from set_email helper
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_set_email(const std::string& email) = 0;
+    virtual void set_email(const account& ac, const std::string& email) = 0;
 
     /**
      * Update the account firstname in database.
@@ -105,7 +292,7 @@
      * \note called from set_firstname helper
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_set_firstname(const std::string& name) = 0;
+    virtual void set_firstname(const account& ac, const std::string& name) = 0;
 
     /**
      * Update the account lastname in database.
@@ -116,244 +303,7 @@
      * \note called from set_lastname helper
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_set_lastname(const std::string& name) = 0;
-
-    /**
-     * Create a draft account.
-     *
-     * \pre !login.empty()
-     * \pre !password.empty()
-     * \param login the login name
-     * \param password the password
-     * \warning the password is saved as-is and **must** be hashed by the caller.
-     */
-    inline account(std::string login, std::string password) noexcept
-        : login_(std::move(login))
-        , password_(std::move(password))
-    {
-        assert(!login_.empty());
-        assert(!password_.empty());
-    }
-
-public:
-    /**
-     * Get the account login.
-     *
-     * \return the login
-     */
-    inline const std::string& login() const noexcept
-    {
-        return login_;
-    }
-
-    /**
-     * Get the password.
-     *
-     * \note the password is returned as-is
-     * \return the password
-     */
-    inline const std::string& password() const noexcept
-    {
-        return password_;
-    }
-
-    /**
-     * Set the password.
-     *
-     * \pre !password.empty()
-     * \warning the password is saved as-is and **must** be hashed by the caller.
-     */
-    inline void set_password(std::string password)
-    {
-        assert(!password.empty());
-
-        if (is_published() && password_ != password)
-            do_set_password(password);
-
-        password_ = std::move(password);
-    }
-
-    /**
-     * Get the account email.
-     *
-     * \return the email
-     */
-    inline const std::string& email() const noexcept
-    {
-        return email_;
-    }
-
-    /**
-     * Set the account email.
-     *
-     * \pre !email.empty()
-     * \param email the new email
-     */
-    inline void set_email(std::string email)
-    {
-        assert(!email.empty());
-
-        if (is_published() && email_ != email)
-            do_set_email(email);
-
-        email_ = std::move(email);
-    }
-
-    /**
-     * Get the account first name.
-     *
-     * \return the name
-     */
-    inline const std::string& firstname() const noexcept
-    {
-        return firstname_;
-    }
-
-    /**
-     * Set the account firstname.
-     *
-     * \param name the new name
-     */
-    inline void set_firstname(std::string name)
-    {
-        if (is_published() && firstname_ != name)
-            do_set_firstname(name);
-
-        firstname_ = std::move(name);
-    }
-
-    /**
-     * Get the account last name.
-     *
-     * \return the name
-     */
-    inline const std::string& lastname() const noexcept
-    {
-        return lastname_;
-    }
-
-    /**
-     * Set the account last name.
-     *
-     * \param name the new name
-     */
-    inline void set_lastname(std::string name)
-    {
-        if (is_published() && lastname_ != name)
-            do_set_lastname(name);
-
-        lastname_ = std::move(name);
-    }
-
-    /**
-     * Get the caracter set.
-     *
-     * \return the associated characters.
-     */
-    inline const character_list& characters() const noexcept
-    {
-        return characters_;
-    }
-
-    /**
-     * Add the character to the account.
-     *
-     * Account takes ownership of character.
-     *
-     * \pre ch->is_draft()
-     * \param ch the character
-     * \return the attached character
-     * \post ch->is_published()
-     */
-    std::unique_ptr<character>& add_character(std::unique_ptr<character> ch);
-
-    /**
-     * Find a character by a predicate.
-     *
-     * The predicate must be the same as one use in std::find_if, the unique
-     * value given as argument is the const std::unique_ptr<character>&.
-     *
-     * \param pred the unary predicate
-     * \return the character or null if not found.
-     */
-    template <typename Predicate>
-    inline std::unique_ptr<character>& find_character(Predicate&& pred) noexcept
-    {
-        static std::unique_ptr<character> none;
-
-        auto it = std::find_if(characters_.begin(), characters_.end(), std::forward<Predicate>(pred));
-
-        if (it != characters_.end())
-            return *it;
-
-        return none;
-    }
-
-    /**
-     * Remove the character from the account.
-     *
-     * \param ch the character
-     * \post ch == nullptr
-     */
-    void remove_character(std::unique_ptr<character>& ch);
-
-    /**
-     * Save the account, does nothing if is_published().
-     *
-     * \throw std::exception on errors
-     * \post is_published()
-     */
-    inline void save()
-    {
-        if (is_draft())
-            do_save();
-
-        assert(is_published());
-    }
-
-    /**
-     * Destroy the account.
-     *
-     * The account will contains no characters anymore.
-     *
-     * \throw std::exception on errors
-     * \post is_draft()
-     */
-    inline void remove()
-    {
-        if (is_published())
-            do_remove();
-
-        id_ = -1;
-        characters_.clear();
-
-        assert(is_draft());
-    }
-};
-
-/**
- * \brief DAO for accounts.
- */
-class account::dao {
-public:
-    /**
-     * Find an account by login.
-     *
-     * \param login the login name
-     * \return the account or null if not found
-     * \throw exception on other errors
-     */
-    virtual std::unique_ptr<account> find_by_login(const std::string& login) = 0;
-
-    /**
-     * Authenticate the user.
-     *
-     * \param login the login
-     * \param password the password
-     * \return the account or null if does not exist
-     */
-    std::unique_ptr<account> authenticate(const std::string& login,
-                                          const std::string& password);
+    virtual void set_lastname(const account& ac, const std::string& name) = 0;
 };
 
 } // !server
--- a/libserver/malikania/server/db/character.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/character.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -17,42 +17,33 @@
  */
 
 #include "character.hpp"
+#include "database.hpp"
 
 namespace mlk {
 
 namespace server {
 
-/*
- * character::add_spell
- * ------------------------------------------------------------------
- */
-std::unique_ptr<spell>& character::add_spell(std::unique_ptr<spell> sp)
+void character::set_level(std::uint16_t level)
 {
-    assert(is_published());
-    assert(sp && sp->is_draft());
+    if (is_published() && level_ != level)
+        db_.characters().set_level(*this, level);
 
-    sp->do_save(id_);
-    spells_.push_back(std::move(sp));
-
-    return spells_.back();
+    level_ = level;
 }
 
-/*
- * character::remove_spell
- * ------------------------------------------------------------------
- */
-void character::remove_spell(std::unique_ptr<spell>& sp)
+void character::add(spell sp)
+{
+    assert(is_published() && sp.is_draft());
+
+    db_.spells().publish(*this, sp);
+    spells_.push_back(std::move(sp));
+}
+
+void character::remove(std::vector<spell>::iterator it)
 {
     assert(is_published());
-    assert(sp && sp->is_published());
 
-    auto it = std::find(spells_.begin(), spells_.end(), sp);
-
-    if (it == spells_.end())
-        return;
-
-    sp->do_remove();
-    sp = nullptr;
+    db_.spells().unpublish(*this, *it);
     spells_.erase(it);
 }
 
--- a/libserver/malikania/server/db/character.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/character.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -24,7 +24,7 @@
  * \brief Database character object.
  */
 
-#include <algorithm>
+#include <vector>
 
 #include "model.hpp"
 #include "spell.hpp"
@@ -33,19 +33,128 @@
 
 namespace server {
 
+class account;
+
 /*
  * \brief Database character object.
  */
 class character : public model {
-private:
-    friend class account;
-
 protected:
-    std::uint64_t account_id_{0U};      //!< parent account
     std::string nickname_;              //!< nickname (non null)
     std::string classname_;             //!< class type to instanciate
     std::uint16_t level_{1};            //!< character level
-    spell_list spells_;                 //!< list of spells
+    std::vector<spell> spells_;         //!< list of spells
+
+public:
+    /**
+     * Construct a character, no database is modified yet.
+     *
+     * \pre !nickname.empty()
+     * \pre !classname.empty()
+     * \param nickname the nickname
+     * \param classname the classname
+     */
+    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());
+    }
+
+    /**
+     * Get the character nickname.
+     *
+     * \return the name
+     */
+    inline const std::string& get_nickname() const noexcept
+    {
+        return nickname_;
+    }
+
+    /**
+     * Get the character class name.
+     *
+     * \return the class name
+     */
+    inline const std::string& get_classname() const noexcept
+    {
+        return classname_;
+    }
+
+    /**
+     * 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
+
+    /**
+     * Get the account level.
+     *
+     * \return the level
+     */
+    inline std::uint16_t get_level() const noexcept
+    {
+        return level_;
+    }
+
+    /**
+     * Set the account level.
+     *
+     * \param level the level
+     */
+    void set_level(std::uint16_t level);
+
+    /**
+     * Add a spell.
+     *
+     * Character takes ownership of spell.
+     *
+     * \pre sp.is_draft()
+     * \param sp the spell (moved)
+     */
+    void add(spell sp);
+
+    /**
+     * Remove the spell from the character.
+     *
+     * \pre it must be valid
+     * \param it the spell iterator
+     */
+    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.
@@ -55,11 +164,11 @@
      *
      * Then it will be added into account.characters_ variable.
      *
-     * \param account_id the parent account
-     * \note called by account::add_character.
+     * \param account the parent account
+     * \param ch this character
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_save(std::uint64_t account_id) = 0;
+    virtual void publish(character& ch, const account& account) = 0;
 
     /**
      * Remove this character.
@@ -67,9 +176,9 @@
      * The implementation must remove the character from the database and update
      * id, account_id member variables.
      *
-     * \note called by account::remove_character.
+     * \param ch this character
      */
-    virtual void do_remove() = 0;
+    virtual void unpublish(const character& ch) = 0;
 
     /**
      * Update the character level in database.
@@ -77,106 +186,14 @@
      * Only called when the level needs to be changed, the implementation does
      * not need to update level_ field.
      *
-     * \note called from set_level helper
+     * \param ch the character
+     * \param level the new level
+     * \note called from character::set_level helper
      * \throw std::exception if the operation could not succeed
      */
-    virtual void do_set_level(std::uint16_t level) = 0;
-
-    /**
-     * Construct a character, no database is modified yet.
-     *
-     * \pre !nickname.empty()
-     * \pre !classname.empty()
-     * \param nickname the nickname
-     * \param classname the classname
-     */
-    inline character(std::string nickname, std::string classname)
-        : nickname_(std::move(nickname))
-        , classname_(std::move(classname))
-    {
-        assert(!nickname_.empty());
-        assert(!classname_.empty());
-    }
-
-public:
-    /**
-     * Get the character nickname.
-     *
-     * \return the name
-     */
-    inline const std::string& nickname() const noexcept
-    {
-        return nickname_;
-    }
-
-    /**
-     * Get the character class name.
-     *
-     * \return the class name
-     */
-    inline const std::string& classname() const noexcept
-    {
-        return classname_;
-    }
-
-    /**
-     * Get the account level.
-     *
-     * \return the level
-     */
-    inline std::uint16_t level() const noexcept
-    {
-        return level_;
-    }
-
-    /**
-     * Get the set of spells.
-     *
-     * \return the spells
-     */
-    inline const spell_list& spells() const noexcept
-    {
-        return spells_;
-    }
-
-    /**
-     * Add a spell.
-     *
-     * Character takes ownership of spell.
-     *
-     * \pre sp->is_draft()
-     * \param sp the spell
-     * \return the attached spell
-     */
-    std::unique_ptr<spell>& add_spell(std::unique_ptr<spell> sp);
-
-    /**
-     * Remove the spell from the character.
-     *
-     * \param sp the spell
-     * \post sp == nullptr
-     */
-    void remove_spell(std::unique_ptr<spell>& sp);
-
-    /**
-     * Set the account level.
-     *
-     * \param level the level
-     */
-    inline void set_level(std::uint16_t level)
-    {
-        if (is_published() && level_ != level)
-            do_set_level(level);
-
-        level_ = level;
-    }
+    virtual void set_level(const character& ch, std::uint16_t level) = 0;
 };
 
-/**
- * Type for storing characters.
- */
-using character_list = std::vector<std::unique_ptr<character>>;
-
 } // !server
 
 } // !mlk
--- a/libserver/malikania/server/db/database.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/database.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -24,14 +24,14 @@
  * \brief Abstract database interface.
  */
 
-#include "account.hpp"
-#include "character.hpp"
-#include "spell.hpp"
-
 namespace mlk {
 
 namespace server {
 
+class account_dao;
+class character_dao;
+class spell_dao;
+
 /**
  * \brief Abstract database interface.
  */
@@ -55,32 +55,15 @@
     }
 
     /**
-     * Create a draft account.
-     *
-     * \param login the login name
-     * \param password the password
-     * \return a draft account
-     * \post value->is_draft()
-     * \see account::account
-     */
-    virtual std::unique_ptr<account> account_draft(std::string login, std::string password) = 0;
-
-    /**
      * Create the account dao.
      *
      * \return the account dao
      */
-    virtual account::dao& account_dao() = 0;
+    virtual account_dao& accounts() = 0;
 
-    /**
-     * Create a draft character.
-     *
-     * \param nickname the character nickname
-     * \param classname the character class type
-     * \return the draft character
-     * \post value->is_draft()
-     */
-    virtual std::unique_ptr<character> character_draft(std::string nickname, std::string classname) = 0;
+    virtual character_dao& characters() = 0;
+
+    virtual spell_dao& spells() = 0;
 };
 
 } // !server
--- a/libserver/malikania/server/db/model.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/model.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -31,6 +31,8 @@
 
 namespace server {
 
+class database;
+
 /**
  * \brief Abstract database object.
  */
@@ -39,17 +41,30 @@
     model(const model&) = delete;
     model& operator=(const model&) = delete;
 
-    model(model&&) = delete;
-    model& operator=(model&&) = delete;
-
 protected:
+    database& db_;                      //!< database object
     std::uint64_t id_{0U};              //!< object id
 
 public:
     /**
      * Default constructor.
      */
-    model() noexcept = default;
+    inline model(database& db) noexcept
+        : db_(db)
+    {
+    }
+
+    /**
+     * Move constructor.
+     *
+     * \param other the original value (id resets to 0)
+     */
+    inline model(model&& other) noexcept
+        : db_(other.db_)
+        , id_(other.id_)
+    {
+        other.id_ = 0U;
+    }
 
     /**
      * Virtual destructor defaulted.
@@ -61,11 +76,28 @@
      *
      * \return the id
      */
-    inline std::uint64_t id() const noexcept
+    inline std::uint64_t get_id() const noexcept
     {
         return id_;
     }
 
+#if defined(MALIKANIA_PRIVATE)
+    /**
+     * Set the internal id.
+     *
+     * 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 id the new id
+     */
+    inline void set_id(std::uint64_t id) noexcept
+    {
+        id_ = id;
+    }
+#endif
+
     /**
      * Tells if the object is not persistent.
      *
@@ -85,6 +117,20 @@
     {
         return id_ > 0U;
     }
+
+    /**
+     * Move operator.
+     *
+     * \param other the original value (id resets to 0)
+     * \return *this
+     */
+    inline model& operator=(model&& other) noexcept
+    {
+        id_ = other.id_;
+        other.id_ = 0U;
+
+        return *this;
+    }
 };
 
 } // !server
--- a/libserver/malikania/server/db/spell.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/spell.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -24,6 +24,14 @@
 
 namespace server {
 
+void spell::set_level(std::uint8_t level)
+{
+    if (is_published() && level_ != level)
+        db_.spells().set_level(level);
+
+    level_ = level;
+}
+
 } // !server
 
 } // !mlk
--- a/libserver/malikania/server/db/spell.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/db/spell.hpp	Sat Mar 03 17:34:12 2018 +0100
@@ -24,77 +24,38 @@
  * \brief Database spell object.
  */
 
-#include <memory>
 #include <string>
-#include <vector>
 
 #include "model.hpp"
 
 namespace mlk {
 
-/**
- * \brief Database spell object.
- */
 namespace server {
 
+class character;
+
 /**
  * \brief Describe a spell.
  */
 class spell : public model {
-private:
-    friend class character;
-
 protected:
-    std::uint64_t character_id_{0U};    //!< parent character
     std::string classname_;             //!< class type to instanciate
     std::uint8_t level_{1};             //!< spell level
 
-    /**
-     * 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.
-     *
-     * \note called by character::add_spell.
-     */
-    virtual void do_save(std::uint64_t character_id) = 0;
-
-    /**
-     * Remove this spell.
-     *
-     * The implementation must remove the spell from the database and update
-     * id, character_id member variables.
-     *
-     * \note called by character::remove_spell
-     */
-    virtual void do_remove() = 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 do_set_level(std::uint8_t level) = 0;
-
+public:
     /**
      * Constructor.
      *
      * \pre !classname.empty()
      * \param classname the class name
      */
-    inline spell(std::string classname)
-        : classname_(std::move(classname))
+    inline spell(database& db, std::string classname)
+        : model(db)
+        , classname_(std::move(classname))
     {
         assert(!classname_.empty());
     }
 
-public:
     /**
      * Get the level.
      *
@@ -110,19 +71,53 @@
      *
      * \param level the level
      */
-    inline void set_level(std::uint8_t level)
-    {
-        if (is_published() && level_ != level)
-            do_set_level(level);
-
-        level_ = level;
-    }
+    void set_level(std::uint8_t level);
 };
 
 /**
- * Type for storing spells.
+ * \brief Character DAO.
  */
-using spell_list = std::vector<std::unique_ptr<spell>>;
+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
 
--- a/libserver/malikania/server/net/auth_handler.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/libserver/malikania/server/net/auth_handler.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -33,7 +33,7 @@
 {
     try {
         // TODO: VERIFY ALREADY LOGGED IN!
-        auto ac = server.database().account_dao().authenticate(
+        auto ac = server.database().accounts().authenticate(
             util::json::require_string(object, "/login"_json_pointer),
             util::json::require_string(object, "/password"_json_pointer)
         );
--- a/tests/libserver/dao-account/CMakeLists.txt	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for malikania
-#
-# 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.
-#
-
-malikania_create_test(
-    NAME dao-account
-    LIBRARIES libmlk-server
-    SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
-    FLAGS
-        SQLITE_SHARED_LIBRARY="$<TARGET_FILE:db-sqlite>"
-)
--- a/tests/libserver/dao-account/main.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * main.cpp -- test account_dao
- *
- * Copyright (c) 2013-2016 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.
- */
-
-#define BOOST_TEST_MODULE "account_dao"
-#include <boost/test/unit_test.hpp>
-
-#include <malikania/server/db/dynlib_database.hpp>
-#include <malikania/server/db/account_dao.hpp>
-#include <malikania/server/db/account.hpp>
-
-namespace mlk {
-
-namespace server {
-
-class fixture {
-protected:
-    std::shared_ptr<dynlib_database> db_;
-
-    fixture()
-        : db_(new dynlib_database())
-    {
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(account_dao_suite, fixture)
-
-BOOST_AUTO_TEST_CASE(save)
-{
-    auto ac = std::make_shared<account>("francis", nullptr);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !server
-
-} // !mlk
--- a/tests/libserver/dao-account/test.hpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-class @TEST_CLASS_NAME@ : public testing::Test {
-protected:
-    mlk::database m_database;
-    mlk::account_dao m_dao;
-
-    std::string kind()
-    {
-        static const std::string name = "@TEST_CLASS_NAME@";
-
-        return name;
-    }
-
-    std::unordered_map<std::string, std::string> parameters()
-    {
-        assert(kind() == "TestAccountSqlite");
-
-        if (kind() == "TestAccountSqlite") {
-            return {{ "path", "@test-dao-account_BINARY_DIR@/test-account-dao.db" }};
-        }
-
-        return {};
-    }
-
-    std::string file()
-    {
-        assert(kind() == "TestAccountSqlite");
-
-        if (kind() == "TestAccountSqlite") {
-            return DRIVERDIR "sqlite.so";
-        }
-
-        return "";
-    }
-
-public:
-    @TEST_CLASS_NAME@()
-        : m_database(file(), parameters())
-        , m_dao(m_database)
-    {
-        m_dao.clear();
-
-        assert(m_dao.count() == 0U);
-    }
-};
-
-TEST_F(@TEST_CLASS_NAME@, create)
-{
-    try {
-        mlk::account ac;
-
-        ac.name = "jean";
-        ac.email = "jean@christophe.fr";
-        ac.first_name = "Jean";
-        ac.last_name = "Christophe";
-        ac.password = "raw";
-
-        m_dao.create(ac);
-
-        ASSERT_EQ(1U, m_dao.count());
-        ASSERT_EQ(ac, m_dao.list()[0]);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
-}
-
-TEST_F(@TEST_CLASS_NAME@, update)
-{
-    try {
-        mlk::account ac;
-
-        ac.name = "jean";
-        ac.email = "jean@christophe.fr";
-        ac.first_name = "Jean";
-        ac.last_name = "Christophe";
-        ac.password = "raw";
-
-        m_dao.create(ac);
-
-        ac.email = "benoit@christophe.fr";
-        ac.first_name = "Benoit";
-
-        m_dao.update(ac);
-
-        mlk::account ac2 = m_dao.get(ac.id);
-
-        ASSERT_EQ("jean", ac2.name);
-        ASSERT_EQ("benoit@christophe.fr", ac2.email);
-        ASSERT_EQ("Benoit", ac2.first_name);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
-}
--- a/tests/libserver/dao-character/CMakeLists.txt	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for malikania
-#
-# 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.
-#
-
-malikania_create_test(
-    NAME dao-character
-    LIBRARIES libmlk-server
-    SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
-    FLAGS
-        SQLITE_SHARED_LIBRARY="$<TARGET_FILE:db-sqlite>"
-)
--- a/tests/libserver/dao-character/main.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/*
- * main.cpp -- test character_dao
- *
- * Copyright (c) 2013-2016 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.
- */
-
-#define BOOST_TEST_MODULE "character_dao"
-#include <boost/test/unit_test.hpp>
-
-#include <malikania/server/db/dynlib_database.hpp>
-#include <malikania/server/db/account.hpp>
-#include <malikania/server/db/account_dao.hpp>
-#include <malikania/server/db/character_dao.hpp>
-#include <malikania/server/db/character.hpp>
-
-namespace mlk {
-
-namespace server {
-
-class fixture {
-protected:
-    std::shared_ptr<dynlib_database> db_;
-
-    fixture()
-        : db_(new dynlib_database(SQLITE_SHARED_LIBRARY, {
-            { "path", CMAKE_CURRENT_BINARY_DIR "/character.db" }
-        }))
-    {
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(character_dao_suite, fixture)
-
-BOOST_AUTO_TEST_CASE(save)
-{
-    {
-        auto ac = std::make_shared<account>(db_, "francis");
-        ac->set_email("francis@world.org");
-        ac->set_firstname("Francis");
-        ac->set_lastname("Meyer");
-        ac->save();
-
-        auto ch = std::make_shared<character>(db_, "blackmage");
-        ac->add_character(ch);
-
-        BOOST_TEST(ac->is_published());
-        BOOST_TEST(ac == ch->account());
-        BOOST_TEST(ac->characters().size() == 1U);
-        BOOST_TEST(ch->is_published());
-    }
-
-    auto ac = db_->account_dao()->find("francis");
-
-    BOOST_TEST(ac->email() == "francis@world.org");
-    BOOST_TEST(ac->firstname() == "Francis");
-    BOOST_TEST(ac->lastname() == "Meyer");
-}
-
-BOOST_AUTO_TEST_CASE(direct_save)
-{
-    auto ac = std::make_shared<account>(db_, "francis");
-    ac->save();
-
-    auto ch = std::make_shared<character>(db_, "blackmage");
-    ch->save(ac);
-
-    BOOST_TEST(ac->is_published());
-    BOOST_TEST(ch->is_published());
-    BOOST_TEST(ac == ch->account());
-}
-
-BOOST_AUTO_TEST_CASE(remove)
-{
-    auto ac = std::make_shared<account>(db_, "francis");
-    ac->save();
-    auto ch = std::make_shared<character>(db_, "blackmage");
-    ac->add_character(ch);
-    ac->remove_character(ch);
-
-    BOOST_TEST(ch->is_draft());
-    BOOST_TEST(ac->characters().size() == 0U);
-}
-
-BOOST_AUTO_TEST_CASE(direct_remove)
-{
-    auto ac = std::make_shared<account>(db_, "francis");
-    ac->save();
-    auto ch = std::make_shared<character>(db_, "blackmage");
-    ch->save(ac);
-    ch->remove();
-
-    BOOST_TEST(ch->is_draft());
-    BOOST_TEST(ac->characters().size() == 0U);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !server
-
-} // !mlk
--- a/tests/libserver/db/account/main.cpp	Tue Jan 09 13:15:07 2018 +0100
+++ b/tests/libserver/db/account/main.cpp	Sat Mar 03 17:34:12 2018 +0100
@@ -19,103 +19,28 @@
 #define BOOST_TEST_MODULE "database account"
 #include <boost/test/unit_test.hpp>
 
-#include <malikania/server/db/test_database.hpp>
-#include <malikania/server/db/broken_account.hpp>
-#include <malikania/server/db/broken_character.hpp>
+#include <malikania/server/db/broken_database.hpp>
 #include <malikania/server/db/database_fixture.hpp>
 
 namespace mlk {
 
 namespace server {
 
-class account_fixture {
-protected:
-    test_database db_;
-
-    const nlohmann::json& accounts() const noexcept
-    {
-        return db_.data()["accounts"];
-    }
-
-    const nlohmann::json& get(std::int64_t id) const noexcept
-    {
-        return accounts().at(std::to_string(id));
-    }
-};
-
-/*
- * Basic suite using test database.
- * ------------------------------------------------------------------
- */
-BOOST_FIXTURE_TEST_SUITE(draft_suite, account_fixture)
-
-/*
- * account::set_password.
- * ------------------------------------------------------------------
- */
-BOOST_AUTO_TEST_CASE(set_password)
-{
-    auto ac = db_.account_draft("markand", "nopassword");
-
-    ac->set_password("temporarypassword");
-
-    BOOST_TEST(accounts().empty());
-}
-
-/*
- * account::set_email.
- * ------------------------------------------------------------------
- */
-BOOST_AUTO_TEST_CASE(set_email)
-{
-    auto ac = db_.account_draft("markand", "nopassword");
-
-    ac->set_email("fake@malikania.fr");
-
-    BOOST_TEST(accounts().empty());
-}
-
-/*
- * account::set_firstname.
- * ------------------------------------------------------------------
- */
-BOOST_AUTO_TEST_CASE(set_firstname)
-{
-    auto ac = db_.account_draft("markand", "nopassword");
-
-    ac->set_firstname("Jean");
-
-    BOOST_TEST(accounts().empty());
-}
-
-/*
- * account::set_lastname.
- * ------------------------------------------------------------------
- */
-BOOST_AUTO_TEST_CASE(set_lastname)
-{
-    auto ac = db_.account_draft("markand", "nopassword");
-
-    ac->set_lastname("Bertrand");
-
-    BOOST_TEST(accounts().empty());
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
 /*
  * Test behaviour with broken implementation.
  * ------------------------------------------------------------------
  */
-BOOST_AUTO_TEST_SUITE(broken)
 
-/*
- * account::save.
- * ------------------------------------------------------------------
- */
+class broken_fixture {
+protected:
+    broken_database db_;
+};
+
+BOOST_FIXTURE_TEST_SUITE(broken_fixture_suite, broken_fixture)
+
 BOOST_AUTO_TEST_CASE(save)
 {
-    broken_account ac("markand", "nopassword");
+    account ac(db_, "markand", "nopassword");
 
     try {
         ac.save();
@@ -124,13 +49,10 @@
     BOOST_TEST(ac.is_draft());
 }
 
-/*
- * account::remove.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(remove)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
@@ -141,13 +63,10 @@
     BOOST_TEST(ac.is_published());
 }
 
-/*
- * account::set_password.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(set_password)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
@@ -155,16 +74,13 @@
         ac.set_password("newpassword");
     } catch (...) {}
 
-    BOOST_TEST(ac.password() == "nopassword");
+    BOOST_TEST(ac.get_password() == "nopassword");
 }
 
-/*
- * account::set_email.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(set_email)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
@@ -172,16 +88,13 @@
         ac.set_email("markand@malikania.fr");
     } catch (...) {}
 
-    BOOST_TEST(ac.email() == "");
+    BOOST_TEST(ac.get_email() == "");
 }
 
-/*
- * account::set_firstname.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(set_firstname)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
@@ -189,16 +102,13 @@
         ac.set_firstname("David");
     } catch (...) {}
 
-    BOOST_TEST(ac.firstname() == "");
+    BOOST_TEST(ac.get_firstname() == "");
 }
 
-/*
- * account::set_lastname.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(set_lastname)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
@@ -206,24 +116,21 @@
         ac.set_lastname("David");
     } catch (...) {}
 
-    BOOST_TEST(ac.lastname() == "");
+    BOOST_TEST(ac.get_lastname() == "");
 }
 
-/*
- * account::add_character.
- * ------------------------------------------------------------------
- */
 BOOST_AUTO_TEST_CASE(add_character)
 {
-    broken_account ac("markand", "nopassword", broken_account::allow_flags::save);
+    db_.set_account_flags(broken_account_dao::allow_flags::publish);
+    account ac(db_, "markand", "nopassword");
 
     ac.save();
 
     try {
-        ac.add_character(std::make_unique<broken_character>("erekin", "fire"));
+        ac.add(character(db_, "erekin", "fire"));
     } catch (...) {}
 
-    BOOST_TEST(ac.characters().size() == 0U);
+    BOOST_TEST(ac.get_characters().size() == 0U);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
@@ -236,58 +143,60 @@
 
 void fill(database& db)
 {
-    auto ac = db.account_draft("markand", "nopassword");
+    account ac(db, "markand", "nopassword");
 
-    ac->set_email("markand@malikania.fr");
-    ac->set_firstname("David");
-    ac->set_lastname("Demelier");
-    ac->save();
+    ac.set_email("markand@malikania.fr");
+    ac.set_firstname("David");
+    ac.set_lastname("Demelier");
+    ac.save();
 
     {
-        auto ch = db.character_draft("erekin", "blackmage");
-        ch->set_level(75);
-        ac->add_character(std::move(ch));
+        character ch(db, "erekin", "blackmage");
+
+        ch.set_level(100);
+        ac.add(std::move(ch));
     }
 
     {
-        auto ch = db.character_draft("luna", "fairy");
-        ch->set_level(45);
-        ac->add_character(std::move(ch));
+        character ch(db, "luna", "fairy");
+
+        ch.set_level(45);
+        ac.add(std::move(ch));
     }
 }
 
+
 BOOST_AUTO_TEST_CASE_TEMPLATE(load, Database, database_types)
 {
     Database db;
 
     fill(db.db());
 
-    auto ac = db.db().account_dao().find_by_login("markand");
+    auto ac = db.db().accounts().find_by_login("markand");
 
-    BOOST_TEST(ac->login() == "markand");
-    BOOST_TEST(ac->password() == "nopassword");
-    BOOST_TEST(ac->email() == "markand@malikania.fr");
-    BOOST_TEST(ac->firstname() == "David");
-    BOOST_TEST(ac->lastname() == "Demelier");
-    BOOST_TEST(ac->characters().size() == 2U);
+    BOOST_TEST(ac->get_login() == "markand");
+    BOOST_TEST(ac->get_password() == "nopassword");
+    BOOST_TEST(ac->get_email() == "markand@malikania.fr");
+    BOOST_TEST(ac->get_firstname() == "David");
+    BOOST_TEST(ac->get_lastname() == "Demelier");
+    BOOST_TEST(ac->get_characters().size() == 2U);
 
-    auto& erekin = ac->find_character([] (const auto& c) {
-        return c->nickname() == "erekin";
+    const auto& characters = ac->get_characters();
+    const auto erekin = std::find_if(characters.begin(), characters.end(), [] (const auto& c) {
+        return c.get_nickname() == "erekin";
     });
 
-    BOOST_TEST(erekin.get());
-    BOOST_TEST(erekin->nickname() == "erekin");
-    BOOST_TEST(erekin->classname() == "blackmage");
-    BOOST_TEST(erekin->level() == 75U);
+    BOOST_TEST(erekin->get_nickname() == "erekin");
+    BOOST_TEST(erekin->get_classname() == "blackmage");
+    BOOST_TEST(erekin->get_level() == 100U);
 
-    auto& luna = ac->find_character([] (const auto& c) {
-        return c->nickname() == "luna";
+    const auto luna = std::find_if(characters.begin(), characters.end(), [] (const auto& c) {
+        return c.get_nickname() == "luna";
     });
 
-    BOOST_TEST(luna.get());
-    BOOST_TEST(luna->nickname() == "luna");
-    BOOST_TEST(luna->classname() == "fairy");
-    BOOST_TEST(luna->level() == 45U);
+    BOOST_TEST(luna->get_nickname() == "luna");
+    BOOST_TEST(luna->get_classname() == "fairy");
+    BOOST_TEST(luna->get_level() == 45U);
 }
 
 BOOST_AUTO_TEST_CASE_TEMPLATE(set_password, Database, database_types)
@@ -295,9 +204,9 @@
     Database db;
 
     fill(db.db());
-    db.db().account_dao().find_by_login("markand")->set_password("hello");
+    db.db().accounts().find_by_login("markand")->set_password("hello");
 
-    BOOST_TEST(db.db().account_dao().find_by_login("markand")->password() == "hello");
+    BOOST_TEST(db.db().accounts().find_by_login("markand")->get_password() == "hello");
 }
 
 BOOST_AUTO_TEST_CASE_TEMPLATE(set_email, Database, database_types)
@@ -305,9 +214,9 @@
     Database db;
 
     fill(db.db());
-    db.db().account_dao().find_by_login("markand")->set_email("no@spam.org");
+    db.db().accounts().find_by_login("markand")->set_email("no@spam.org");
 
-    BOOST_TEST(db.db().account_dao().find_by_login("markand")->email() == "no@spam.org");
+    BOOST_TEST(db.db().accounts().find_by_login("markand")->get_email() == "no@spam.org");
 }
 
 BOOST_AUTO_TEST_CASE_TEMPLATE(set_firstname, Database, database_types)
@@ -315,9 +224,9 @@
     Database db;
 
     fill(db.db());
-    db.db().account_dao().find_by_login("markand")->set_firstname("Francis");
+    db.db().accounts().find_by_login("markand")->set_firstname("Francis");
 
-    BOOST_TEST(db.db().account_dao().find_by_login("markand")->firstname() == "Francis");
+    BOOST_TEST(db.db().accounts().find_by_login("markand")->get_firstname() == "Francis");
 }
 
 BOOST_AUTO_TEST_CASE_TEMPLATE(set_lastname, Database, database_types)
@@ -325,36 +234,9 @@
     Database db;
 
     fill(db.db());
-    db.db().account_dao().find_by_login("markand")->set_lastname("Lalanne");
-
-    BOOST_TEST(db.db().account_dao().find_by_login("markand")->lastname() == "Lalanne");
-}
-
-BOOST_AUTO_TEST_CASE_TEMPLATE(remove_character, Database, database_types)
-{
-    Database db;
-
-    fill(db.db());
-
-    {
-        auto ac = db.db().account_dao().find_by_login("markand");
+    db.db().accounts().find_by_login("markand")->set_lastname("Lalanne");
 
-        ac->remove_character(
-            ac->find_character([] (const auto& c) {
-                return c->nickname() == "luna";
-            })
-        );
-    }
-
-    auto ac = db.db().account_dao().find_by_login("markand");
-
-    BOOST_TEST(ac->characters().size() == 1U);
-
-    auto& luna = ac->find_character([] (const auto& c) {
-        return c->nickname() == "luna";
-    });
-
-    BOOST_TEST(!luna.get());
+    BOOST_TEST(db.db().accounts().find_by_login("markand")->get_lastname() == "Lalanne");
 }
 
 BOOST_AUTO_TEST_SUITE_END()