changeset 179:ffe8ac5c35c0

Database: implement as a separate library, closes #906 @1h
author David Demelier <markand@malikania.fr>
date Mon, 08 Oct 2018 09:51:12 +0200
parents c5274f2d4658
children 23ee2b6091e8
files CMakeLists.txt libdb/CMakeLists.txt libdb/malikania/db/account.cpp libdb/malikania/db/account.hpp libdb/malikania/db/character.cpp libdb/malikania/db/character.hpp libdb/malikania/db/database.cpp libdb/malikania/db/database.hpp libdb/malikania/db/model.hpp libdb/malikania/db/spell.cpp libdb/malikania/db/spell.hpp libserver/CMakeLists.txt libserver/malikania/server/db/account.cpp libserver/malikania/server/db/account.hpp libserver/malikania/server/db/character.cpp libserver/malikania/server/db/character.hpp libserver/malikania/server/db/database.cpp libserver/malikania/server/db/database.hpp libserver/malikania/server/db/errors.hpp libserver/malikania/server/db/model.hpp libserver/malikania/server/db/spell.cpp libserver/malikania/server/db/spell.hpp server/main.cpp
diffstat 22 files changed, 1630 insertions(+), 1606 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Aug 29 17:04:57 2018 +0200
+++ b/CMakeLists.txt	Mon Oct 08 09:51:12 2018 +0200
@@ -44,6 +44,7 @@
 
 add_subdirectory(tools)
 add_subdirectory(extern)
+add_subdirectory(libdb)
 add_subdirectory(libcommon)
 add_subdirectory(libserver)
 add_subdirectory(server)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/CMakeLists.txt	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+project(libmlk-db)
+
+find_package(PostgreSQL REQUIRED)
+
+set(
+	HEADERS
+	${libmlk-db_SOURCE_DIR}/malikania/db/account.hpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/character.hpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/database.hpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/model.hpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/spell.hpp
+)
+
+set(
+	SOURCES
+	${libmlk-db_SOURCE_DIR}/malikania/db/account.cpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/character.cpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/database.cpp
+	${libmlk-db_SOURCE_DIR}/malikania/db/spell.cpp
+)
+
+malikania_define_library(
+	TARGET libmlk-db
+	SOURCES ${HEADERS} ${SOURCES}
+	LIBRARIES
+		${PostgreSQL_LIBRARIES}
+	PUBLIC_INCLUDES
+		${Boost_INCLUDE_DIRS}
+		${libmlk-db_SOURCE_DIR}/malikania
+	PRIVATE_INCLUDES
+		${PostgreSQL_INCLUDE_DIRS}
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/account.cpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,242 @@
+/*
+ * account.cpp -- database account object
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include "account.hpp"
+
+namespace mlk::db {
+
+auto account::load(const result& res) -> account
+{
+	account a(PQgetvalue(res.get(), 0, 1), PQgetvalue(res.get(), 0, 2));
+
+	a.id_ = std::stoi(PQgetvalue(res.get(), 0, 0));
+	a.firstname_ = PQgetvalue(res.get(), 0, 3);
+	a.lastname_ = PQgetvalue(res.get(), 0, 4);
+	a.email_ = PQgetvalue(res.get(), 0, 5);
+	a.characters_ = character::load(a);
+
+	return a;
+}
+
+void account::clear()
+{
+	id_ = 0U;
+}
+
+account::account(std::string login, std::string password) noexcept
+	: login_(std::move(login))
+	, password_(std::move(password))
+{
+	assert(!login_.empty());
+	assert(!password_.empty());
+}
+
+auto account::get_login() const noexcept -> const std::string&
+{
+	return login_;
+}
+
+void account::set_password(std::string password)
+{
+	static const std::string sql(
+		"UPDATE account"
+		"   SET password = $1"
+		" WHERE id = $2"
+	);
+
+	assert(!password.empty());
+
+	if (is_published() && password_ != password)
+		db::exec(sql, { password, id_ });
+
+	password_ = std::move(password);
+}
+
+auto account::get_email() const noexcept -> const std::string&
+{
+	return email_;
+}
+
+void account::set_email(std::string email)
+{
+	static const std::string sql(
+		"UPDATE account"
+		"   SET email = $1"
+		" WHERE id = $2"
+	);
+
+	assert(!email.empty());
+
+	if (is_published() && email_ != email)
+		db::exec(sql, { email, id_ });
+
+	email_ = std::move(email);
+}
+
+auto account::get_firstname() const noexcept -> const std::string&
+{
+	return firstname_;
+}
+
+void account::set_firstname(std::string name)
+{
+	static const std::string sql(
+		"UPDATE account"
+		"   SET firstname = $1"
+		" WHERE id = $2"
+	);
+
+	if (is_published() && firstname_ != name)
+		db::exec(sql, { name, id_ });
+
+	firstname_ = std::move(name);
+}
+
+auto account::get_lastname() const noexcept -> const std::string&
+{
+	return lastname_;
+}
+
+void account::set_lastname(std::string name)
+{
+	static const std::string sql(
+		"UPDATE account"
+		"   SET lastname = $1"
+		" WHERE id = $2"
+	);
+
+	if (is_published() && lastname_ != name)
+		db::exec(sql, { name, id_ });
+
+	lastname_ = std::move(name);
+}
+
+auto account::get_characters() const noexcept -> const std::vector<character>&
+{
+	return characters_;
+}
+
+void account::add(character ch)
+{
+	assert(ch.is_draft());
+
+	characters_.reserve(characters_.size() + 1);
+
+	if (is_published())
+		ch.publish(*this);
+
+	characters_.push_back(std::move(ch));
+}
+
+void account::remove(std::vector<character>::iterator it)
+{
+	// TODO: assert 'it' is in vector.
+	it->unpublish();
+	characters_.erase(it);
+}
+
+void account::publish()
+{
+	static const std::string sql(
+		"INSERT INTO account("
+		"  login,"
+		"  password,"
+		"  firstname,"
+		"  lastname,"
+		"  email"
+		") "
+		"VALUES ($1, $2, $3, $4, $5) "
+		"RETURNING id"
+	);
+
+	assert(is_draft());
+
+	/*
+	 * Recursively save the account, its characters and spells of those
+	 * characters.
+	 */
+	transaction txn([this] {
+		clear();
+
+		for (auto& c : characters_)
+			c.clear();
+	});
+
+	const auto r = select(sql, { login_, password_, firstname_, lastname_, email_ });
+
+	id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
+
+	for (auto& c : characters_)
+		c.publish(*this);
+
+	txn.commit();
+
+	assert(is_published());
+}
+
+void account::unpublish()
+{
+	static const std::string sql(
+		"DELETE"
+		"  FROM account"
+		" WHERE id = $1"
+	);
+
+	assert(is_published());
+
+	id_ = 0;
+
+	/*
+	 * Recursively make all characters and their spells draft.
+	 */
+	for (auto& c : characters_)
+		c.clear();
+
+	assert(is_draft());
+}
+
+auto account::find_by_login(const std::string& login) -> std::optional<account>
+{
+	static const std::string sql(
+		"SELECT *"
+		"  FROM account"
+		" WHERE login = $1"
+	);
+
+	const auto r = select(sql, { login });
+
+	if (PQntuples(r.get()) == 0)
+		return std::nullopt;
+
+	return load(r);
+}
+
+auto account::authenticate(const std::string& login,
+                           const std::string& password) -> std::optional<account>
+{
+	auto ac = find_by_login(login);
+
+	if (!ac || ac->password_ != password)
+		return std::nullopt;
+
+	return ac;
+}
+
+} // !mlk::db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/account.hpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,189 @@
+/*
+ * account.hpp -- database account object
+ *
+ * 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_ACCOUNT_HPP
+#define MALIKANIA_DB_ACCOUNT_HPP
+
+/**
+ * \file account.hpp
+ * \brief Database account object.
+ */
+
+#include <optional>
+
+#include "character.hpp"
+#include "database.hpp"
+#include "model.hpp"
+
+namespace mlk::db {
+
+/**
+ * \brief Database account object.
+ */
+class account : public model {
+private:
+	std::string login_;
+	std::string password_;
+	std::string email_;
+	std::string firstname_;
+	std::string lastname_;
+	std::vector<character> characters_;
+
+	static auto load(const result&) -> account;
+
+	void clear();
+
+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.
+	 */
+	account(std::string login, std::string password) noexcept;
+
+	/**
+	 * Get the account login.
+	 *
+	 * \return the login
+	 */
+	auto get_login() const noexcept -> const std::string&;
+
+	/**
+	 * 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
+	 */
+	auto get_email() const noexcept -> const std::string&;
+
+	/**
+	 * 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
+	 */
+	auto get_firstname() const noexcept -> const std::string&;
+
+	/**
+	 * Set the account firstname.
+	 *
+	 * \param name the new name
+	 */
+	void set_firstname(std::string name);
+
+	/**
+	 * Get the account last name.
+	 *
+	 * \return the name
+	 */
+	auto get_lastname() const noexcept -> const std::string&;
+
+	/**
+	 * Set the account last name.
+	 *
+	 * \param name the new name
+	 */
+	void set_lastname(std::string name);
+
+	/**
+	 * Get the caracter list.
+	 *
+	 * \return the associated characters.
+	 */
+	auto get_characters() const noexcept -> const std::vector<character>&;
+
+	/**
+	 * 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 publish();
+
+	/**
+	 * Destroy the account.
+	 *
+	 * The account will contains no characters anymore.
+	 *
+	 * \throw std::exception on errors
+	 * \post is_draft()
+	 */
+	void unpublish();
+
+	/**
+	 * Find an account by login.
+	 *
+	 * \param login the login
+	 * \return the account or none if not found
+	 * \throw std::runtime_error on database errors
+	 */
+	static auto find_by_login(const std::string& login) -> std::optional<account>;
+
+	/**
+	 * Find and authenticate a user.
+	 *
+	 * \param login the login
+	 * \param password the password
+	 * \return the account or none if not found
+	 * \throw std::runtime_error on database errors
+	 */
+	static auto authenticate(const std::string& login,
+                                 const std::string& password) -> std::optional<account>;
+};
+
+} // !mlk::db
+
+#endif // !MALIKANIA_DB_ACCOUNT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/character.cpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,266 @@
+/*
+ * character.cpp -- database character object
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <numeric>
+
+#include "account.hpp"
+#include "character.hpp"
+#include "database.hpp"
+
+namespace mlk::db {
+
+auto character::load(const result& result, int row) -> character
+{
+	character ch(PQgetvalue(result.get(), row, 2), PQgetvalue(result.get(), row, 3));
+
+	ch.levels_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 4));
+	ch.factors_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 5));
+	ch.exp_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 6));
+
+	ch.levels_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 7));
+	ch.factors_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 8));
+	ch.exp_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 9));
+
+	ch.levels_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 10));
+	ch.factors_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 11));
+	ch.exp_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 12));
+
+	ch.levels_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 13));
+	ch.factors_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 14));
+	ch.exp_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 15));
+
+	ch.levels_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 16));
+	ch.factors_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 17));
+	ch.exp_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 18));
+
+	ch.id_ = std::stoi(PQgetvalue(result.get(), row, 0));
+	ch.spells_ = spell::load(ch);
+
+	return ch;
+}
+
+auto character::load(const account& parent) -> std::vector<character>
+{
+	static const std::string sql(
+		"SELECT *"
+		"  FROM character"
+		" WHERE account_id = $1"
+	);
+
+	const auto r = select(sql, { parent.get_id() });
+
+	if (PQntuples(r.get()) == 0)
+		throw std::runtime_error("failed to load characters");
+
+	std::vector<character> characters;
+
+	for (int i = 0; i < PQntuples(r.get()); ++i)
+		characters.push_back(load(r, i));
+
+	return characters;
+}
+
+void character::clear()
+{
+	id_ = 0U;
+
+	for (auto& s : spells_)
+		s.clear();
+}
+
+void character::publish(account& parent)
+{
+	static const std::string sql(
+		"INSERT INTO character ("
+		"  account_id,"
+		"  nickname,"
+		"  type,"
+		"  hp_level,"
+		"  hp_factor,"
+		"  hp_exp,"
+		"  force_level,"
+		"  force_factor,"
+		"  force_exp,"
+		"  defense_level,"
+		"  defense_factor,"
+		"  defense_exp,"
+		"  agility_level,"
+		"  agility_factor,"
+		"  agility_exp,"
+		"  luck_level,"
+		"  luck_factor,"
+		"  luck_exp"
+		") "
+		"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) "
+		"RETURNING id"
+	);
+
+	const auto r = select(sql, {
+		parent.get_id(),
+		nickname_,
+		type_,
+		levels_[stat::hp],
+		factors_[stat::hp],
+		exp_[stat::hp],
+		levels_[stat::force],
+		factors_[stat::force],
+		exp_[stat::force],
+		levels_[stat::defense],
+		factors_[stat::defense],
+		exp_[stat::defense],
+		levels_[stat::agility],
+		factors_[stat::agility],
+		exp_[stat::agility],
+		levels_[stat::luck],
+		factors_[stat::luck],
+		exp_[stat::luck]
+	});
+
+	if (PQntuples(r.get()) == 0)
+		throw std::runtime_error("failed to save character");
+
+	id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
+
+	for (auto& s : spells_)
+		s.publish(*this);
+}
+
+void character::unpublish()
+{
+	static const std::string sql(
+		"DELETE"
+		"  FROM character"
+		" WHERE id = $1"
+	);
+
+	exec(sql, { id_ });
+	clear();
+}
+
+character::character(std::string nickname, std::string type) noexcept
+	: nickname_(std::move(nickname))
+	, type_(std::move(type))
+{
+	assert(!nickname_.empty());
+	assert(!type_.empty());
+}
+
+auto character::get_nickname() const noexcept -> const std::string&
+{
+	return nickname_;
+}
+
+auto character::get_type() const noexcept -> const std::string&
+{
+	return type_;
+}
+
+auto character::get_spells() const noexcept -> const std::vector<spell>&
+{
+	return spells_;
+}
+
+auto character::get_levels() const noexcept -> const std::array<std::uint8_t, 5>&
+{
+	return levels_;
+}
+
+void character::set_levels(std::array<std::uint8_t, 5> levels)
+{
+	static const std::string sql(
+		"UPDATE character"
+		"   SET hp_level = $1"
+		"	 , force_level = $2"
+		"	 , defense_level = $3"
+		"	 , agility_level = $4"
+		"	 , luck_level = $5"
+	);
+
+	if (is_published())
+		exec(sql, { id_, levels[0], levels[1], levels[2], levels[3], levels[4] });
+
+	levels_ = std::move(levels);
+}
+
+auto character::get_factors() const noexcept -> const std::array<std::uint8_t, 5>&
+{
+	return factors_;
+}
+
+void character::set_factors(std::array<std::uint8_t, 5> factors)
+{
+	static const std::string sql(
+		"UPDATE character"
+		"   SET hp_factor = $1"
+		"	 , force_factor = $2"
+		"	 , defense_factor = $3"
+		"	 , agility_factor = $4"
+		"	 , luck_factor = $5"
+	);
+
+	assert(std::accumulate(factors.begin(), factors.end(), 0U) == 100U);
+
+	if (is_published())
+		exec(sql, { id_, factors[0], factors[1], factors[2], factors[3], factors[4] });
+
+	factors_ = std::move(factors);
+}
+
+auto character::get_experience() const noexcept -> const std::array<std::uint32_t, 5>&
+{
+	return exp_;
+}
+
+void character::set_experience(std::array<std::uint32_t, 5> experience)
+{
+	static const std::string sql(
+		"UPDATE character"
+		"   SET hp_experience = $1"
+		"	 , force_experience = $2"
+		"	 , defense_experience = $3"
+		"	 , agility_experience = $4"
+		"	 , luck_experience = $5"
+	);
+
+	if (is_published())
+		exec(sql, { id_, experience[0], experience[1], experience[2], experience[3], experience[4] });
+
+	exp_ = std::move(experience);
+}
+
+void character::add(spell sp)
+{
+	assert(sp.is_draft());
+
+	spells_.reserve(spells_.size() + 1);
+
+	if (is_published())
+		sp.publish(*this);
+
+	spells_.push_back(std::move(sp));
+}
+
+void character::remove(std::vector<spell>::iterator it)
+{
+	// TODO: assert 'it' is in vector.
+	it->unpublish();
+	spells_.erase(it);
+}
+
+} // !mlk::db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/character.hpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,171 @@
+/*
+ * character.hpp -- database character object
+ *
+ * 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_CHARACTER_HPP
+#define MALIKANIA_DB_CHARACTER_HPP
+
+/*
+ * \file character.hpp
+ * \brief Database character object.
+ */
+
+#include <cstdint>
+#include <array>
+#include <vector>
+
+#include "database.hpp"
+#include "model.hpp"
+#include "spell.hpp"
+
+namespace mlk::db {
+
+class account;
+
+/*
+ * \brief Database character object.
+ */
+class character : public model {
+public:
+	friend class account;
+
+	/**
+	 * \brief Stats index in arrays
+	 * \see set_levels
+	 * \see set_factors
+	 * \see set_experience
+	 */
+	enum stat {
+		hp = 0,                 //!< hp index
+		force,                  //!< force index
+		defense,                //!< defense index
+		agility,                //!< agility index
+		luck                    //!< luck index
+	};
+
+private:
+	std::string nickname_;
+	std::string type_;
+	std::vector<spell> spells_;
+	std::array<std::uint8_t, 5> levels_{1U, 1U, 1U, 1U, 1U};
+	std::array<std::uint8_t, 5> factors_{20U, 20U, 20U, 20U, 20U};
+	std::array<std::uint32_t, 5> exp_{0U, 0U, 0U, 0U, 0U};
+
+	static auto load(const result&, int) -> character;
+	static auto load(const account&) -> std::vector<character>;
+
+	void clear();
+	void publish(account&);
+	void unpublish();
+
+public:
+	/**
+	 * Construct a character, no database is modified yet.
+	 *
+	 * \pre !nickname.empty()
+	 * \pre !type.empty()
+	 * \param nickname the nickname
+	 * \param type the type
+	 */
+	character(std::string nickname, std::string type) noexcept;
+
+	/**
+	 * Get the character nickname.
+	 *
+	 * \return the name
+	 */
+	auto get_nickname() const noexcept -> const std::string&;
+
+	/**
+	 * Get the character type name.
+	 *
+	 * \return the type name
+	 */
+	auto get_type() const noexcept -> const std::string&;
+
+	/**
+	 * Get the list of spells.
+	 *
+	 * \return the spells
+	 */
+	auto get_spells() const noexcept -> const std::vector<spell>&;
+
+	/**
+	 * Get the list of levels.
+	 *
+	 * \return the levels
+	 */
+	auto get_levels() const noexcept -> const std::array<std::uint8_t, 5>&;
+
+	/**
+	 * Set the new levels.
+	 *
+	 * \param levels the levels
+	 */
+	void set_levels(std::array<std::uint8_t, 5> levels);
+
+	/**
+	 * Get the list of factors.
+	 *
+	 * \return the factors
+	 */
+	auto get_factors() const noexcept -> const std::array<std::uint8_t, 5>&;
+
+	/**
+	 * Set progression factors.
+	 *
+	 * \pre factors sum must be 100
+	 * \param factors the factors
+	 */
+	void set_factors(std::array<std::uint8_t, 5> factors);
+
+	/**
+	 * Get the stats experience.
+	 *
+	 * \return the experience
+	 */
+	auto get_experience() const noexcept -> const std::array<std::uint32_t, 5>&;
+
+	/**
+	 * Set stats experience.
+	 *
+	 * \param experience the experience
+	 */
+	void set_experience(std::array<std::uint32_t, 5> experience);
+
+	/**
+	 * 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);
+};
+
+} // !mlk::db
+
+#endif // !MALIKANIA_DB_CHARACTER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/database.cpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,210 @@
+/*
+ * database.cpp -- connection to postgresql database
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <sstream>
+
+#include "database.hpp"
+
+namespace mlk::db {
+
+namespace {
+
+struct strify {
+	template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+	auto operator()(T i) const -> std::string
+	{
+		return std::to_string(i);
+	}
+
+	auto operator()(bool v) const -> std::string
+	{
+		return v ? "t" : "f";
+	}
+
+	auto operator()(std::string s) const -> std::string
+	{
+		return s;
+	}
+};
+
+std::unique_ptr<PGconn, void (*)(PGconn*)> connection{nullptr, nullptr};
+
+auto run(const std::string& sql, std::vector<arg> args = {}) -> result
+{
+	std::vector<const char*> list;
+
+	/*
+	 * PQexecParams requires an array of C strings, convert all arguments in the
+	 * args vector to string and then keep the C string value into a temporary
+	 * array.
+	 */
+	for (auto& arg : args) {
+		arg = std::visit(strify(), arg);
+		list.push_back(std::get<std::string>(arg).c_str());
+	}
+
+	return {
+		PQexecParams(connection.get(), sql.c_str(), list.size(), nullptr,
+			list.data(), nullptr, nullptr, 0
+		),
+		PQclear
+	};
+}
+
+} // !namespace
+
+void open(const std::string& host,
+          const std::string& port,
+          const std::string& user,
+          const std::string& database,
+          const std::string& password)
+{
+	assert(!connection);
+
+	std::ostringstream oss;
+
+	if (!host.empty())
+		oss << "host=" << host << " ";
+	if (!port.empty())
+		oss << "port=" << port << " ";
+	if (!user.empty())
+		oss << "user=" << user << " ";
+	if (!database.empty())
+		oss << "dbname=" << database << " ";
+	if (!password.empty())
+		oss << "password=" << password;
+
+	connection = { PQconnectdb(oss.str().c_str()), PQfinish };
+
+	if (PQstatus(connection.get()) != CONNECTION_OK)
+		throw std::runtime_error(PQerrorMessage(connection.get()));
+}
+
+const std::string init_account(
+	"CREATE TABLE IF NOT EXISTS account("
+	"  id SERIAL,"
+	"  login TEXT NOT NULL,"
+	"  password TEXT NOT NULL,"
+	"  firstname TEXT,"
+	"  lastname TEXT,"
+	"  email TEXT,"
+	"  PRIMARY KEY(id)"
+	")"
+);
+
+const std::string init_character(
+	"CREATE TABLE IF NOT EXISTS character("
+	"  id SERIAL,"
+	"  account_id INTEGER NOT NULL,"
+	"  nickname TEXT NOT NULL,"
+	"  type TEXT NOT NULL,"
+	"  hp_level SMALLINT NOT NULL DEFAULT 1,"
+	"  hp_factor SMALLINT NOT NULL,"
+	"  hp_exp INT NOT NULL,"
+	"  force_level SMALLINT NOT NULL DEFAULT 1,"
+	"  force_factor SMALLINT NOT NULL,"
+	"  force_exp INT NOT NULL,"
+	"  defense_level SMALLINT NOT NULL DEFAULT 1,"
+	"  defense_factor SMALLINT NOT NULL,"
+	"  defense_exp INT NOT NULL,"
+	"  agility_level SMALLINT NOT NULL DEFAULT 1,"
+	"  agility_factor SMALLINT NOT NULL,"
+	"  agility_exp INT NOT NULL,"
+	"  luck_level SMALLINT NOT NULL DEFAULT 1,"
+	"  luck_factor SMALLINT NOT NULL,"
+	"  luck_exp INT NOT NULL,"
+	"  PRIMARY KEY(id),"
+	"  FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE"
+	")"
+);
+
+const std::string init_spell(
+	"CREATE TABLE IF NOT EXISTS spell("
+	"  id SERIAL,"
+	"  character_id INTEGER NOT NULL,"
+	"  type TEXT NOT NULL,"
+	"  level SMALLINT NOT NULL,"
+	"  PRIMARY KEY(id),"
+	"  FOREIGN KEY (character_id) REFERENCES character(id) ON DELETE CASCADE"
+	")"
+);
+
+void init()
+{
+	assert(connection);
+
+	exec(init_account);
+	exec(init_character);
+	exec(init_spell);
+}
+
+auto select(const std::string& sql, const std::vector<arg>& args) -> result
+{
+	assert(connection);
+
+	auto result = run(sql, args);
+
+	switch (PQresultStatus(result.get())) {
+	case PGRES_COMMAND_OK:
+	case PGRES_TUPLES_OK:
+		break;
+	default:
+		throw std::runtime_error(PQerrorMessage(connection.get()));
+	}
+
+	return result;
+}
+
+void exec(const std::string& sql, const std::vector<arg>& args)
+{
+	assert(connection);
+
+	switch (const auto result = run(sql, args); PQresultStatus(result.get())) {
+	case PGRES_COMMAND_OK:
+		break;
+	default:
+		throw std::runtime_error(PQerrorMessage(connection.get()));
+	}
+}
+
+transaction::transaction(rollback fn)
+	: rollback_(std::move(fn))
+{
+	assert(rollback_);
+
+	exec("BEGIN");
+}
+
+transaction::~transaction()
+{
+	if (!commit_) {
+		rollback_();
+		exec("ROLLBACK");
+	}
+}
+
+void transaction::commit()
+{
+	if (!commit_) {
+		exec("COMMIT");
+		commit_ = true;
+	}
+}
+
+} // !db::mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/database.hpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,151 @@
+/*
+ * database.hpp -- connection to postgresql database
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_DB_DATABASE_HPP
+#define MALIKANIA_DB_DATABASE_HPP
+
+/**
+ * \file database.hpp
+ * \brief Abstract database interface.
+ */
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include <libpq-fe.h>
+
+namespace mlk::db {
+
+/**
+ * \brief Statement argument.
+ *
+ * When using prepared statement, you may pass any data you like using this
+ * variant.
+ */
+using arg = std::variant<
+	bool,
+	double,
+	std::int8_t,
+	std::uint8_t,
+	std::int16_t,
+	std::uint16_t,
+	std::int32_t,
+	std::uint32_t,
+	std::int64_t,
+	std::uint64_t,
+	std::string
+>;
+
+/**
+ * \brief Convenient result type.
+ *
+ * This unique pointer holds the PostgreSQL result and free it automatically on
+ * out of scope.
+ */
+using result = std::unique_ptr<PGresult, void (*)(PGresult*)>;
+
+/**
+ * Open the connection to the database.
+ *
+ * All arguments are optional.
+ *
+ * \pre connection must be closed
+ * \param host the hostname
+ * \param port the port number
+ * \param user the user name
+ * \param database the database name
+ * \param password the user password
+ * \throw std::runtime_error on errors
+ */
+void open(const std::string& host,
+          const std::string& port,
+          const std::string& user,
+          const std::string& database,
+          const std::string& password);
+
+/**
+ * Unconditionally init the database.
+ *
+ * \throw std::runtime_error on errors
+ */
+void init();
+
+/**
+ * Execute a SELECT statement.
+ *
+ * \param sql the SQL statement
+ * \param args the optional arguments to bind
+ * \return the result value
+ * \throw std::runtime_error on errors
+ */
+auto select(const std::string& sql, const std::vector<arg>& args = {}) -> result;
+
+/**
+ * Execute an UPDATE statement.
+ *
+ * \param sql the SQL statement
+ * \param args the optional arguments to bind
+ * \throw std::runtime_error on errors
+ */
+void exec(const std::string& sql, const std::vector<arg>& args = {});
+
+class transaction {
+public:
+	/**
+	 * \brief Rollback function.
+	 */
+	using rollback = std::function<void ()>;
+
+private:
+	rollback rollback_;
+	bool commit_{false};
+
+public:
+	/**
+	 * Start a transaction.
+	 *
+	 * If there is already a transaction, the returned object is no-op.
+	 *
+	 * \param rollback the rollback function
+	 * \return the transaction object
+	 */
+	transaction(rollback fn);
+
+	/**
+	 * Rollback the transaction if commit() has not been called.
+	 */
+	~transaction();
+
+	/**
+	 * Commit the transaction.
+	 *
+	 * Does nothing if already committed.
+	 *
+	 * \throw std::exception on errors
+	 */
+	void commit();
+};
+
+} // !mlk::db
+
+#endif // !MALIKANIA_DB_DATABASE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/model.hpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,120 @@
+/*
+ * model.hpp -- abstract database object
+ *
+ * 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_MODEL_HPP
+#define MALIKANIA_DB_MODEL_HPP
+
+/**
+ * \file model.hpp
+ * \brief Abstract database object.
+ */
+
+#include <cstdint>
+
+namespace mlk::db {
+
+/**
+ * \brief Abstract database object.
+ */
+class model {
+public:
+	/**
+	 * Id type.
+	 */
+	using id_type = std::uint64_t;
+
+private:
+	model(const model&) = delete;
+	model& operator=(const model&) = delete;
+
+protected:
+	/**
+	 * Object id.
+	 */
+	id_type id_{0U};
+
+public:
+	/**
+	 * Default constructor.
+	 */
+	model() = default;
+
+	/**
+	 * Move constructor.
+	 *
+	 * \param other the original value (id resets to 0)
+	 */
+	inline model(model&& other) noexcept
+		: id_(other.id_)
+	{
+		other.id_ = 0U;
+	}
+
+	/**
+	 * Virtual destructor defaulted.
+	 */
+	virtual ~model() noexcept = default;
+
+	/**
+	 * Get the id.
+	 *
+	 * \return the id
+	 */
+	auto get_id() const noexcept -> id_type
+	{
+		return id_;
+	}
+
+	/**
+	 * Tells if the object is not persistent.
+	 *
+	 * \return true if object was never saved
+	 */
+	auto is_draft() const noexcept -> bool
+	{
+		return id_ == 0U;
+	}
+
+	/**
+	 * Tells if the object is present in database
+	 *
+	 * \return true if object is saved
+	 */
+	auto is_published() const noexcept -> bool
+	{
+		return id_ > 0U;
+	}
+
+	/**
+	 * Move operator.
+	 *
+	 * \param other the original value (id resets to 0)
+	 * \return *this
+	 */
+	auto operator=(model&& other) noexcept -> model&
+	{
+		id_ = other.id_;
+		other.id_ = 0U;
+
+		return *this;
+	}
+};
+
+} // !mlk::db
+
+#endif // !MALIKANIA_DB_MODEL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/spell.cpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,124 @@
+/*
+ * spell.cpp -- database spell object
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include "character.hpp"
+#include "spell.hpp"
+
+namespace mlk::db {
+
+auto spell::load(const result& result, int row) -> spell
+{
+	spell s(PQgetvalue(result.get(), row, 2));
+
+	s.id_ = std::stoi(PQgetvalue(result.get(), row, 0));
+	s.level_ = std::stoi(PQgetvalue(result.get(), row, 3));
+
+	return s;
+}
+
+auto spell::load(const character& parent) -> std::vector<spell>
+{
+	static const std::string sql(
+		"SELECT *"
+		"  FROM spell"
+		" WHERE id = $1"
+	);
+
+	const auto r = select(sql, { parent.get_id() });
+
+	if (PQntuples(r.get()) == 0)
+		throw std::runtime_error("failed to load spells");
+
+	std::vector<spell> spells;
+
+	for (int i = 0; i < PQntuples(r.get()); ++i)
+		spells.push_back(load(r, i));
+
+	return spells;
+}
+
+void spell::clear()
+{
+	id_ = 0U;
+}
+
+void spell::unpublish()
+{
+	static const std::string sql(
+		"DELETE"
+		"  FROM spell"
+		" WHERE id = $1"
+	);
+
+	exec(sql, { id_ });
+	clear();
+}
+
+void spell::publish(character& parent)
+{
+	static const std::string sql(
+		"INSERT INTO spell("
+		"  character_id,"
+		"  type,"
+		"  level"
+		") "
+		"VALUES ($1, $2, $3) "
+		"RETURNING id"
+	);
+
+	const auto r = select(sql, { parent.get_id(), type_, level_ });
+
+	if (PQntuples(r.get()) == 0)
+		throw std::runtime_error("failed to save spell");
+
+	id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
+}
+
+spell::spell(std::string type) noexcept
+	: type_(std::move(type))
+{
+	assert(!type_.empty());
+}
+
+auto spell::get_type() const noexcept -> const std::string&
+{
+	return type_;
+}
+
+auto spell::get_level() const noexcept -> std::uint8_t
+{
+	return level_;
+}
+
+void spell::set_level(std::uint8_t level)
+{
+	static const std::string sql(
+		"UPDATE spell"
+		"   SET level = $1"
+		" WHERE id = $2"
+	);
+
+	if (is_published() && level_ != level)
+		db::exec(sql, { level_, id_ });
+
+	level_ = level;
+}
+
+} // !mlk::db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdb/malikania/db/spell.hpp	Mon Oct 08 09:51:12 2018 +0200
@@ -0,0 +1,88 @@
+/*
+ * spell.hpp -- database spell object
+ *
+ * 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_SPELL_HPP
+#define MALIKANIA_DB_SPELL_HPP
+
+/**
+ * \file spell.hpp
+ * \brief Database spell object.
+ */
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "database.hpp"
+#include "model.hpp"
+
+namespace mlk::db {
+
+class character;
+
+/**
+ * \brief Describe a spell.
+ */
+class spell : public model {
+private:
+	friend class character;
+
+	std::string type_;
+	std::uint8_t level_{1U};
+
+	static auto load(const result&, int) -> spell;
+	static auto load(const character&) -> std::vector<spell>;
+
+	void clear();
+	void unpublish();
+	void publish(character&);
+
+public:
+	/**
+	 * Constructor.
+	 *
+	 * \pre !type.empty()
+	 * \param type the type name
+	 */
+	spell(std::string type) noexcept;
+
+	/**
+	 * Get the type.
+	 *
+	 * \return the type
+	 */
+	auto get_type() const noexcept -> const std::string&;
+
+	/**
+	 * Get the level.
+	 *
+	 * \return the level
+	 */
+	auto get_level() const noexcept -> std::uint8_t;
+
+	/**
+	 * Set the spell level.
+	 *
+	 * \param level the level
+	 */
+	void set_level(std::uint8_t level);
+};
+
+} // !mlk::db
+
+#endif // !MALIKANIA_DB_SPELL_HPP
--- a/libserver/CMakeLists.txt	Wed Aug 29 17:04:57 2018 +0200
+++ b/libserver/CMakeLists.txt	Mon Oct 08 09:51:12 2018 +0200
@@ -18,17 +18,11 @@
 
 project(libmlk-server)
 
-find_package(PostgreSQL REQUIRED)
 find_package(Threads REQUIRED)
 
 set(
     HEADERS
     ${libmlk-server_SOURCE_DIR}/malikania/server/client.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/model.hpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.hpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.hpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/server.hpp
 )
@@ -36,10 +30,6 @@
 set(
     SOURCES
     ${libmlk-server_SOURCE_DIR}/malikania/server/client.cpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.cpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.cpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.cpp
-    ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.cpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.cpp
     ${libmlk-server_SOURCE_DIR}/malikania/server/server.cpp
 )
@@ -49,15 +39,14 @@
     SOURCES ${HEADERS} ${SOURCES}
     LIBRARIES
         ${Boost_LIBRARIES}
-        ${PostgreSQL_LIBRARIES}
         Threads::Threads
         OpenSSL::Crypto
         OpenSSL::SSL
         libmlk-common
+        libmlk-db
         $<$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>:mswsock>
     PUBLIC_INCLUDES
         ${Boost_INCLUDE_DIRS}
-        ${PostgreSQL_INCLUDE_DIRS}
         ${libmlk-server_SOURCE_DIR}
     PRIVATE_INCLUDES
         ${libmlk-server_SOURCE_DIR}/malikania/server
--- a/libserver/malikania/server/db/account.cpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,242 +0,0 @@
-/*
- * account.cpp -- database account object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include "account.hpp"
-
-namespace mlk::server::db {
-
-auto account::load(const result& res) -> account
-{
-    account a(PQgetvalue(res.get(), 0, 1), PQgetvalue(res.get(), 0, 2));
-
-    a.id_ = std::stoi(PQgetvalue(res.get(), 0, 0));
-    a.firstname_ = PQgetvalue(res.get(), 0, 3);
-    a.lastname_ = PQgetvalue(res.get(), 0, 4);
-    a.email_ = PQgetvalue(res.get(), 0, 5);
-    a.characters_ = character::load(a);
-
-    return a;
-}
-
-void account::clear()
-{
-    id_ = 0U;
-}
-
-account::account(std::string login, std::string password) noexcept
-    : login_(std::move(login))
-    , password_(std::move(password))
-{
-    assert(!login_.empty());
-    assert(!password_.empty());
-}
-
-auto account::get_login() const noexcept -> const std::string&
-{
-    return login_;
-}
-
-void account::set_password(std::string password)
-{
-    static const std::string sql(
-        "UPDATE account"
-        "   SET password = $1"
-        " WHERE id = $2"
-    );
-
-    assert(!password.empty());
-
-    if (is_published() && password_ != password)
-        db::exec(sql, { password, id_ });
-
-    password_ = std::move(password);
-}
-
-auto account::get_email() const noexcept -> const std::string&
-{
-    return email_;
-}
-
-void account::set_email(std::string email)
-{
-    static const std::string sql(
-        "UPDATE account"
-        "   SET email = $1"
-        " WHERE id = $2"
-    );
-
-    assert(!email.empty());
-
-    if (is_published() && email_ != email)
-        db::exec(sql, { email, id_ });
-
-    email_ = std::move(email);
-}
-
-auto account::get_firstname() const noexcept -> const std::string&
-{
-    return firstname_;
-}
-
-void account::set_firstname(std::string name)
-{
-    static const std::string sql(
-        "UPDATE account"
-        "   SET firstname = $1"
-        " WHERE id = $2"
-    );
-
-    if (is_published() && firstname_ != name)
-        db::exec(sql, { name, id_ });
-
-    firstname_ = std::move(name);
-}
-
-auto account::get_lastname() const noexcept -> const std::string&
-{
-    return lastname_;
-}
-
-void account::set_lastname(std::string name)
-{
-    static const std::string sql(
-        "UPDATE account"
-        "   SET lastname = $1"
-        " WHERE id = $2"
-    );
-
-    if (is_published() && lastname_ != name)
-        db::exec(sql, { name, id_ });
-
-    lastname_ = std::move(name);
-}
-
-auto account::get_characters() const noexcept -> const std::vector<character>&
-{
-    return characters_;
-}
-
-void account::add(character ch)
-{
-    assert(ch.is_draft());
-
-    characters_.reserve(characters_.size() + 1);
-
-    if (is_published())
-        ch.publish(*this);
-
-    characters_.push_back(std::move(ch));
-}
-
-void account::remove(std::vector<character>::iterator it)
-{
-    // TODO: assert 'it' is in vector.
-    it->unpublish();
-    characters_.erase(it);
-}
-
-void account::publish()
-{
-    static const std::string sql(
-        "INSERT INTO account("
-        "  login,"
-        "  password,"
-        "  firstname,"
-        "  lastname,"
-        "  email"
-        ") "
-        "VALUES ($1, $2, $3, $4, $5) "
-        "RETURNING id"
-    );
-
-    assert(is_draft());
-
-    /*
-     * Recursively save the account, its characters and spells of those
-     * characters.
-     */
-    transaction txn([this] {
-        clear();
-
-        for (auto& c : characters_)
-            c.clear();
-    });
-
-    const auto r = select(sql, { login_, password_, firstname_, lastname_, email_ });
-
-    id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
-
-    for (auto& c : characters_)
-        c.publish(*this);
-
-    txn.commit();
-
-    assert(is_published());
-}
-
-void account::unpublish()
-{
-    static const std::string sql(
-        "DELETE"
-        "  FROM account"
-        " WHERE id = $1"
-    );
-
-    assert(is_published());
-
-    id_ = 0;
-
-    /*
-     * Recursively make all characters and their spells draft.
-     */
-    for (auto& c : characters_)
-        c.clear();
-    
-    assert(is_draft());
-}
-
-auto account::find_by_login(const std::string& login) -> std::optional<account>
-{
-    static const std::string sql(
-        "SELECT *"
-        "  FROM account"
-        " WHERE login = $1"
-    );
-
-    const auto r = select(sql, { login });
-
-    if (PQntuples(r.get()) == 0)
-        return std::nullopt;
-
-    return load(r);
-}
-
-auto account::authenticate(const std::string& login,
-                           const std::string& password) -> std::optional<account>
-{
-    auto ac = find_by_login(login);
-
-    if (!ac || ac->password_ != password)
-        return std::nullopt;
-
-    return ac;
-}
-
-} // !mlk::server::db
--- a/libserver/malikania/server/db/account.hpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,189 +0,0 @@
-/*
- * account.hpp -- database account object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_SERVER_DB_ACCOUNT_HPP
-#define MALIKANIA_SERVER_DB_ACCOUNT_HPP
-
-/**
- * \file account.hpp
- * \brief Database account object.
- */
-
-#include <optional>
-
-#include "character.hpp"
-#include "database.hpp"
-#include "model.hpp"
-
-namespace mlk::server::db {
-
-/**
- * \brief Database account object.
- */
-class account : public model {
-private:
-    std::string login_;
-    std::string password_;
-    std::string email_;
-    std::string firstname_;
-    std::string lastname_;
-    std::vector<character> characters_;
-
-    static auto load(const result&) -> account;
-
-    void clear();
-
-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.
-     */
-    account(std::string login, std::string password) noexcept;
-
-    /**
-     * Get the account login.
-     *
-     * \return the login
-     */
-    auto get_login() const noexcept -> const std::string&;
-
-    /**
-     * 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
-     */
-    auto get_email() const noexcept -> const std::string&;
-
-    /**
-     * 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
-     */
-    auto get_firstname() const noexcept -> const std::string&;
-
-    /**
-     * Set the account firstname.
-     *
-     * \param name the new name
-     */
-    void set_firstname(std::string name);
-
-    /**
-     * Get the account last name.
-     *
-     * \return the name
-     */
-    auto get_lastname() const noexcept -> const std::string&;
-
-    /**
-     * Set the account last name.
-     *
-     * \param name the new name
-     */
-    void set_lastname(std::string name);
-
-    /**
-     * Get the caracter list.
-     *
-     * \return the associated characters.
-     */
-    auto get_characters() const noexcept -> const std::vector<character>&;
-
-    /**
-     * 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 publish();
-
-    /**
-     * Destroy the account.
-     *
-     * The account will contains no characters anymore.
-     *
-     * \throw std::exception on errors
-     * \post is_draft()
-     */
-    void unpublish();
-
-    /**
-     * Find an account by login.
-     *
-     * \param login the login
-     * \return the account or none if not found
-     * \throw std::runtime_error on database errors
-     */
-    static auto find_by_login(const std::string& login) -> std::optional<account>;
-
-    /**
-     * Find and authenticate a user.
-     *
-     * \param login the login
-     * \param password the password
-     * \return the account or none if not found
-     * \throw std::runtime_error on database errors
-     */
-    static auto authenticate(const std::string& login,
-                             const std::string& password) -> std::optional<account>;
-};
-
-} // !mlk::server::db
-
-#endif // !MALIKANIA_SERVER_DB_ACCOUNT_HPP
--- a/libserver/malikania/server/db/character.cpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-/*
- * character.cpp -- database character object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <numeric>
-
-#include "account.hpp"
-#include "character.hpp"
-#include "database.hpp"
-
-namespace mlk::server::db {
-
-auto character::load(const result& result, int row) -> character
-{
-    character ch(PQgetvalue(result.get(), row, 2), PQgetvalue(result.get(), row, 3));
-
-    ch.levels_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 4));
-    ch.factors_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 5));
-    ch.exp_[stat::hp] = std::stoi(PQgetvalue(result.get(), row, 6));
-
-    ch.levels_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 7));
-    ch.factors_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 8));
-    ch.exp_[stat::force] = std::stoi(PQgetvalue(result.get(), row, 9));
-
-    ch.levels_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 10));
-    ch.factors_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 11));
-    ch.exp_[stat::defense] = std::stoi(PQgetvalue(result.get(), row, 12));
-
-    ch.levels_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 13));
-    ch.factors_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 14));
-    ch.exp_[stat::agility] = std::stoi(PQgetvalue(result.get(), row, 15));
-
-    ch.levels_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 16));
-    ch.factors_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 17));
-    ch.exp_[stat::luck] = std::stoi(PQgetvalue(result.get(), row, 18));
-
-    ch.id_ = std::stoi(PQgetvalue(result.get(), row, 0));
-    ch.spells_ = spell::load(ch);
-
-    return ch;
-}
-
-auto character::load(const account& parent) -> std::vector<character>
-{
-    static const std::string sql(
-        "SELECT *"
-        "  FROM character"
-        " WHERE account_id = $1"
-    );
-
-    const auto r = select(sql, { parent.get_id() });
-
-    if (PQntuples(r.get()) == 0)
-        throw std::runtime_error("failed to load characters");
-
-    std::vector<character> characters;
-
-    for (int i = 0; i < PQntuples(r.get()); ++i)
-        characters.push_back(load(r, i));
-
-    return characters;
-}
-
-void character::clear()
-{
-    id_ = 0U;
-
-    for (auto& s : spells_)
-        s.clear();
-}
-
-void character::publish(account& parent)
-{
-    static const std::string sql(
-        "INSERT INTO character ("
-        "  account_id,"
-        "  nickname,"
-        "  type,"
-        "  hp_level,"
-        "  hp_factor,"
-        "  hp_exp,"
-        "  force_level,"
-        "  force_factor,"
-        "  force_exp,"
-        "  defense_level,"
-        "  defense_factor,"
-        "  defense_exp,"
-        "  agility_level,"
-        "  agility_factor,"
-        "  agility_exp,"
-        "  luck_level,"
-        "  luck_factor,"
-        "  luck_exp"
-        ") "
-        "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) "
-        "RETURNING id"
-    );
-
-    const auto r = select(sql, {
-        parent.get_id(),
-        nickname_,
-        type_,
-        levels_[stat::hp],
-        factors_[stat::hp],
-        exp_[stat::hp],
-        levels_[stat::force],
-        factors_[stat::force],
-        exp_[stat::force],
-        levels_[stat::defense],
-        factors_[stat::defense],
-        exp_[stat::defense],
-        levels_[stat::agility],
-        factors_[stat::agility],
-        exp_[stat::agility],
-        levels_[stat::luck],
-        factors_[stat::luck],
-        exp_[stat::luck]
-    });
-
-    if (PQntuples(r.get()) == 0)
-        throw std::runtime_error("failed to save character");
-
-    id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
-
-    for (auto& s : spells_)
-        s.publish(*this);
-}
-
-void character::unpublish()
-{
-    static const std::string sql(
-        "DELETE"
-        "  FROM character"
-        " WHERE id = $1"
-    );
-
-    exec(sql, { id_ });
-    clear();
-}
-
-character::character(std::string nickname, std::string type) noexcept
-    : nickname_(std::move(nickname))
-    , type_(std::move(type))
-{
-    assert(!nickname_.empty());
-    assert(!type_.empty());
-}
-
-auto character::get_nickname() const noexcept -> const std::string&
-{
-    return nickname_;
-}
-
-auto character::get_type() const noexcept -> const std::string&
-{
-    return type_;
-}
-
-auto character::get_spells() const noexcept -> const std::vector<spell>&
-{
-    return spells_;
-}
-
-auto character::get_levels() const noexcept -> const std::array<std::uint8_t, 5>&
-{
-    return levels_;
-}
-
-void character::set_levels(std::array<std::uint8_t, 5> levels)
-{
-    static const std::string sql(
-        "UPDATE character"
-        "   SET hp_level = $1"
-        "     , force_level = $2"
-        "     , defense_level = $3"
-        "     , agility_level = $4"
-        "     , luck_level = $5"
-    );
-
-    if (is_published())
-        exec(sql, { id_, levels[0], levels[1], levels[2], levels[3], levels[4] });
-
-    levels_ = std::move(levels);
-}
-
-auto character::get_factors() const noexcept -> const std::array<std::uint8_t, 5>&
-{
-    return factors_;
-}
-
-void character::set_factors(std::array<std::uint8_t, 5> factors)
-{
-    static const std::string sql(
-        "UPDATE character"
-        "   SET hp_factor = $1"
-        "     , force_factor = $2"
-        "     , defense_factor = $3"
-        "     , agility_factor = $4"
-        "     , luck_factor = $5"
-    );
-
-    assert(std::accumulate(factors.begin(), factors.end(), 0U) == 100U);
-
-    if (is_published())
-        exec(sql, { id_, factors[0], factors[1], factors[2], factors[3], factors[4] });
-
-    factors_ = std::move(factors);
-}
-
-auto character::get_experience() const noexcept -> const std::array<std::uint32_t, 5>&
-{
-    return exp_;
-}
-
-void character::set_experience(std::array<std::uint32_t, 5> experience)
-{
-    static const std::string sql(
-        "UPDATE character"
-        "   SET hp_experience = $1"
-        "     , force_experience = $2"
-        "     , defense_experience = $3"
-        "     , agility_experience = $4"
-        "     , luck_experience = $5"
-    );
-
-    if (is_published())
-        exec(sql, { id_, experience[0], experience[1], experience[2], experience[3], experience[4] });
-
-    exp_ = std::move(experience);
-}
-
-void character::add(spell sp)
-{
-    assert(sp.is_draft());
-
-    spells_.reserve(spells_.size() + 1);
-
-    if (is_published())
-        sp.publish(*this);
-
-    spells_.push_back(std::move(sp));
-}
-
-void character::remove(std::vector<spell>::iterator it)
-{
-    // TODO: assert 'it' is in vector.
-    it->unpublish();
-    spells_.erase(it);
-}
-
-} // !mlk::server::db
--- a/libserver/malikania/server/db/character.hpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-/*
- * character.hpp -- database character object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_SERVER_DB_CHARACTER_HPP
-#define MALIKANIA_SERVER_DB_CHARACTER_HPP
-
-/*
- * \file character.hpp
- * \brief Database character object.
- */
-
-#include <cstdint>
-#include <array>
-#include <vector>
-
-#include "database.hpp"
-#include "model.hpp"
-#include "spell.hpp"
-
-namespace mlk::server::db {
-
-class account;
-
-/*
- * \brief Database character object.
- */
-class character : public model {
-public:
-    friend class account;
-
-    /**
-     * \brief Stats index in arrays
-     * \see set_levels
-     * \see set_factors
-     * \see set_experience
-     */
-    enum stat {
-        hp = 0,             //!< hp index
-        force,              //!< force index
-        defense,            //!< defense index
-        agility,            //!< agility index
-        luck                //!< luck index
-    };
-
-private:
-    std::string nickname_;
-    std::string type_;
-    std::vector<spell> spells_;
-    std::array<std::uint8_t, 5> levels_{1U, 1U, 1U, 1U, 1U};
-    std::array<std::uint8_t, 5> factors_{20U, 20U, 20U, 20U, 20U};
-    std::array<std::uint32_t, 5> exp_{0U, 0U, 0U, 0U, 0U};
-
-    static auto load(const result&, int) -> character;
-    static auto load(const account&) -> std::vector<character>;
-
-    void clear();
-    void publish(account&);
-    void unpublish();
-
-public:
-    /**
-     * Construct a character, no database is modified yet.
-     *
-     * \pre !nickname.empty()
-     * \pre !type.empty()
-     * \param nickname the nickname
-     * \param type the type
-     */
-    character(std::string nickname, std::string type) noexcept;
-
-    /**
-     * Get the character nickname.
-     *
-     * \return the name
-     */
-    auto get_nickname() const noexcept -> const std::string&;
-
-    /**
-     * Get the character type name.
-     *
-     * \return the type name
-     */
-    auto get_type() const noexcept -> const std::string&;
-
-    /**
-     * Get the list of spells.
-     *
-     * \return the spells
-     */
-    auto get_spells() const noexcept -> const std::vector<spell>&;
-
-    /**
-     * Get the list of levels.
-     *
-     * \return the levels
-     */
-    auto get_levels() const noexcept -> const std::array<std::uint8_t, 5>&;
-
-    /**
-     * Set the new levels.
-     *
-     * \param levels the levels
-     */
-    void set_levels(std::array<std::uint8_t, 5> levels);
-
-    /**
-     * Get the list of factors.
-     *
-     * \return the factors
-     */
-    auto get_factors() const noexcept -> const std::array<std::uint8_t, 5>&;
-
-    /**
-     * Set progression factors.
-     *
-     * \pre factors sum must be 100
-     * \param factors the factors
-     */
-    void set_factors(std::array<std::uint8_t, 5> factors);
-
-    /**
-     * Get the stats experience.
-     *
-     * \return the experience
-     */
-    auto get_experience() const noexcept -> const std::array<std::uint32_t, 5>&;
-
-    /**
-     * Set stats experience.
-     *
-     * \param experience the experience
-     */
-    void set_experience(std::array<std::uint32_t, 5> experience);
-
-    /**
-     * 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);
-};
-
-} // !mlk::server::db
-
-#endif // !MALIKANIA_SERVER_DB_CHARACTER_HPP
--- a/libserver/malikania/server/db/database.cpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/*
- * database.cpp -- connection to postgresql database
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <sstream>
-
-#include "database.hpp"
-
-namespace mlk::server::db {
-
-namespace {
-
-struct strify {
-    template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
-    auto operator()(T i) const -> std::string
-    {
-        return std::to_string(i);
-    }
-
-    auto operator()(bool v) const -> std::string
-    {
-        return v ? "t" : "f";
-    }
-
-    auto operator()(std::string s) const -> std::string
-    {
-        return s;
-    }
-};
-
-std::unique_ptr<PGconn, void (*)(PGconn*)> connection{nullptr, nullptr};
-
-auto run(const std::string& sql, std::vector<arg> args = {}) -> result
-{
-    std::vector<const char*> list;
-
-    /*
-     * PQexecParams requires an array of C strings, convert all arguments in the
-     * args vector to string and then keep the C string value into a temporary
-     * array.
-     */
-    for (auto& arg : args) {
-        arg = std::visit(strify(), arg);
-        list.push_back(std::get<std::string>(arg).c_str());
-    }
-
-    return {
-        PQexecParams(connection.get(), sql.c_str(), list.size(), nullptr,
-            list.data(), nullptr, nullptr, 0
-        ),
-        PQclear
-    };
-}
-
-} // !namespace
-
-void open(const std::string& host,
-          const std::string& port,
-          const std::string& user,
-          const std::string& database,
-          const std::string& password)
-{
-    assert(!connection);
-
-    std::ostringstream oss;
-
-    if (!host.empty())
-        oss << "host=" << host << " ";
-    if (!port.empty())
-        oss << "port=" << port << " ";
-    if (!user.empty())
-        oss << "user=" << user << " ";
-    if (!database.empty())
-        oss << "dbname=" << database << " ";
-    if (!password.empty())
-        oss << "password=" << password;
-
-    connection = { PQconnectdb(oss.str().c_str()), PQfinish };
-
-    if (PQstatus(connection.get()) != CONNECTION_OK)
-        throw std::runtime_error(PQerrorMessage(connection.get()));
-}
-
-const std::string init_account(
-    "CREATE TABLE IF NOT EXISTS account("
-    "  id SERIAL,"
-    "  login TEXT NOT NULL,"
-    "  password TEXT NOT NULL,"
-    "  firstname TEXT,"
-    "  lastname TEXT,"
-    "  email TEXT,"
-    "  PRIMARY KEY(id)"
-    ")"
-);
-
-const std::string init_character(
-    "CREATE TABLE IF NOT EXISTS character("
-    "  id SERIAL,"
-    "  account_id INTEGER NOT NULL,"
-    "  nickname TEXT NOT NULL,"
-    "  type TEXT NOT NULL,"
-    "  hp_level SMALLINT NOT NULL DEFAULT 1,"
-    "  hp_factor SMALLINT NOT NULL,"
-    "  hp_exp INT NOT NULL,"
-    "  force_level SMALLINT NOT NULL DEFAULT 1,"
-    "  force_factor SMALLINT NOT NULL,"
-    "  force_exp INT NOT NULL,"
-    "  defense_level SMALLINT NOT NULL DEFAULT 1,"
-    "  defense_factor SMALLINT NOT NULL,"
-    "  defense_exp INT NOT NULL,"
-    "  agility_level SMALLINT NOT NULL DEFAULT 1,"
-    "  agility_factor SMALLINT NOT NULL,"
-    "  agility_exp INT NOT NULL,"
-    "  luck_level SMALLINT NOT NULL DEFAULT 1,"
-    "  luck_factor SMALLINT NOT NULL,"
-    "  luck_exp INT NOT NULL,"
-    "  PRIMARY KEY(id),"
-    "  FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE"
-    ")"
-);
-
-const std::string init_spell(
-    "CREATE TABLE IF NOT EXISTS spell("
-    "  id SERIAL,"
-    "  character_id INTEGER NOT NULL,"
-    "  type TEXT NOT NULL,"
-    "  level SMALLINT NOT NULL,"
-    "  PRIMARY KEY(id),"
-    "  FOREIGN KEY (character_id) REFERENCES character(id) ON DELETE CASCADE"
-    ")"
-);
-
-void init()
-{
-    assert(connection);
-
-    exec(init_account);
-    exec(init_character);
-    exec(init_spell);
-}
-
-auto select(const std::string& sql, const std::vector<arg>& args) -> result
-{
-    assert(connection);
-
-    auto result = run(sql, args);
-
-    switch (PQresultStatus(result.get())) {
-    case PGRES_COMMAND_OK:
-    case PGRES_TUPLES_OK:
-        break;
-    default:
-        throw std::runtime_error(PQerrorMessage(connection.get()));
-    }
-
-    return result;
-}
-
-void exec(const std::string& sql, const std::vector<arg>& args)
-{
-    assert(connection);
-
-    switch (const auto result = run(sql, args); PQresultStatus(result.get())) {
-    case PGRES_COMMAND_OK:
-        break;
-    default:
-        throw std::runtime_error(PQerrorMessage(connection.get()));
-    }
-}
-
-transaction::transaction(rollback fn)
-    : rollback_(std::move(fn))
-{
-    assert(rollback_);
-
-    exec("BEGIN");
-}
-
-transaction::~transaction()
-{
-    if (!commit_) {
-        rollback_();
-        exec("ROLLBACK");
-    }
-}
-
-void transaction::commit()
-{
-    if (!commit_) {
-        exec("COMMIT");
-        commit_ = true;
-    }
-}
-
-} // !db::server::mlk
--- a/libserver/malikania/server/db/database.hpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-/*
- * database.hpp -- connection to postgresql database
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_SERVER_DB_DATABASE_HPP
-#define MALIKANIA_SERVER_DB_DATABASE_HPP
-
-/**
- * \file database.hpp
- * \brief Abstract database interface.
- */
-
-#include <cstdint>
-#include <functional>
-#include <memory>
-#include <stdexcept>
-#include <string>
-#include <variant>
-#include <vector>
-
-#include <libpq-fe.h>
-
-namespace mlk::server::db {
-
-/**
- * \brief Statement argument.
- *
- * When using prepared statement, you may pass any data you like using this
- * variant.
- */
-using arg = std::variant<
-    bool,
-    double,
-    std::int8_t,
-    std::uint8_t,
-    std::int16_t,
-    std::uint16_t,
-    std::int32_t,
-    std::uint32_t,
-    std::int64_t,
-    std::uint64_t,
-    std::string
->;
-
-/**
- * \brief Convenient result type.
- *
- * This unique pointer holds the PostgreSQL result and free it automatically on
- * out of scope.
- */
-using result = std::unique_ptr<PGresult, void (*)(PGresult*)>;
-
-/**
- * Open the connection to the database.
- *
- * All arguments are optional.
- *
- * \pre connection must be closed
- * \param host the hostname
- * \param port the port number
- * \param user the user name
- * \param database the database name
- * \param password the user password
- * \throw std::runtime_error on errors
- */
-void open(const std::string& host,
-          const std::string& port,
-          const std::string& user,
-          const std::string& database,
-          const std::string& password);
-
-/**
- * Unconditionally init the database.
- *
- * \throw std::runtime_error on errors
- */
-void init();
-
-/**
- * Execute a SELECT statement.
- *
- * \param sql the SQL statement
- * \param args the optional arguments to bind
- * \return the result value
- * \throw std::runtime_error on errors
- */
-auto select(const std::string& sql, const std::vector<arg>& args = {}) -> result;
-
-/**
- * Execute an UPDATE statement.
- *
- * \param sql the SQL statement
- * \param args the optional arguments to bind
- * \throw std::runtime_error on errors
- */
-void exec(const std::string& sql, const std::vector<arg>& args = {});
-
-class transaction {
-public:
-    /**
-     * \brief Rollback function.
-     */
-    using rollback = std::function<void ()>;
-
-private:
-    rollback rollback_;
-    bool commit_{false};
-
-public:
-    /**
-     * Start a transaction.
-     *
-     * If there is already a transaction, the returned object is no-op.
-     *
-     * \param rollback the rollback function
-     * \return the transaction object
-     */
-    transaction(rollback fn);
-
-    /**
-     * Rollback the transaction if commit() has not been called.
-     */
-    ~transaction();
-
-    /**
-     * Commit the transaction.
-     *
-     * Does nothing if already committed.
-     *
-     * \throw std::exception on errors
-     */
-    void commit();
-};
-
-} // !mlk::server::db
-
-#endif // !MALIKANIA_SERVER_DB_DATABASE_HPP
--- a/libserver/malikania/server/db/model.hpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- * model.hpp -- abstract database object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_SERVER_DB_MODEL_HPP
-#define MALIKANIA_SERVER_DB_MODEL_HPP
-
-/**
- * \file model.hpp
- * \brief Abstract database object.
- */
-
-#include <cstdint>
-
-namespace mlk::server::db {
-
-/**
- * \brief Abstract database object.
- */
-class model {
-public:
-    /**
-     * Id type.
-     */
-    using id_type = std::uint64_t;
-
-private:
-    model(const model&) = delete;
-    model& operator=(const model&) = delete;
-
-protected:
-    id_type id_{0U};              //!< object id
-
-public:
-    /**
-     * Default constructor.
-     */
-    model() = default;
-
-    /**
-     * Move constructor.
-     *
-     * \param other the original value (id resets to 0)
-     */
-    inline model(model&& other) noexcept
-        : id_(other.id_)
-    {
-        other.id_ = 0U;
-    }
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~model() noexcept = default;
-
-    /**
-     * Get the id.
-     *
-     * \return the id
-     */
-    auto get_id() const noexcept -> id_type
-    {
-        return id_;
-    }
-
-#if 0
-#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(id_type id) noexcept
-    {
-        id_ = id;
-    }
-#endif
-#endif
-
-    /**
-     * Tells if the object is not persistent.
-     *
-     * \return true if object was never saved
-     */
-    auto is_draft() const noexcept -> bool
-    {
-        return id_ == 0U;
-    }
-
-    /**
-     * Tells if the object is present in database
-     *
-     * \return true if object is saved
-     */
-    auto is_published() const noexcept -> bool
-    {
-        return id_ > 0U;
-    }
-
-    /**
-     * Move operator.
-     *
-     * \param other the original value (id resets to 0)
-     * \return *this
-     */
-    auto operator=(model&& other) noexcept -> model&
-    {
-        id_ = other.id_;
-        other.id_ = 0U;
-
-        return *this;
-    }
-};
-
-} // !mlk::server::db
-
-#endif // !MODEL_HPP
--- a/libserver/malikania/server/db/spell.cpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * spell.cpp -- database spell object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include "character.hpp"
-#include "spell.hpp"
-
-namespace mlk::server::db {
-
-auto spell::load(const result& result, int row) -> spell
-{
-    spell s(PQgetvalue(result.get(), row, 2));
-
-    s.id_ = std::stoi(PQgetvalue(result.get(), row, 0));
-    s.level_ = std::stoi(PQgetvalue(result.get(), row, 3));
-
-    return s;
-}
-
-auto spell::load(const character& parent) -> std::vector<spell>
-{
-    static const std::string sql(
-        "SELECT *"
-        "  FROM spell"
-        " WHERE id = $1"
-    );
-
-    const auto r = select(sql, { parent.get_id() });
-
-    if (PQntuples(r.get()) == 0)
-        throw std::runtime_error("failed to load spells");
-
-    std::vector<spell> spells;
-
-    for (int i = 0; i < PQntuples(r.get()); ++i)
-        spells.push_back(load(r, i));
-
-    return spells;
-}
-
-void spell::clear()
-{
-    id_ = 0U;
-}
-
-void spell::unpublish()
-{
-    static const std::string sql(
-        "DELETE"
-        "  FROM spell"
-        " WHERE id = $1"
-    );
-
-    exec(sql, { id_ });
-    clear();
-}
-
-void spell::publish(character& parent)
-{
-    static const std::string sql(
-        "INSERT INTO spell("
-        "  character_id,"
-        "  type,"
-        "  level"
-        ") "
-        "VALUES ($1, $2, $3) "
-        "RETURNING id"
-    );
-
-    const auto r = select(sql, { parent.get_id(), type_, level_ });
-
-    if (PQntuples(r.get()) == 0)
-        throw std::runtime_error("failed to save spell");
-
-    id_ = std::stoi(PQgetvalue(r.get(), 0, 0));
-}
-
-spell::spell(std::string type) noexcept
-    : type_(std::move(type))
-{
-    assert(!type_.empty());
-}
-
-auto spell::get_type() const noexcept -> const std::string&
-{
-    return type_;
-}
-
-auto spell::get_level() const noexcept -> std::uint8_t
-{
-    return level_;
-}
-
-void spell::set_level(std::uint8_t level)
-{
-    static const std::string sql(
-        "UPDATE spell"
-        "   SET level = $1"
-        " WHERE id = $2"
-    );
-
-    if (is_published() && level_ != level)
-        db::exec(sql, { level_, id_ });
-
-    level_ = level;
-}
-
-} // !mlk::server::db
--- a/libserver/malikania/server/db/spell.hpp	Wed Aug 29 17:04:57 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * spell.hpp -- database spell object
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_SERVER_DB_SPELL_HPP
-#define MALIKANIA_SERVER_DB_SPELL_HPP
-
-/**
- * \file spell.hpp
- * \brief Database spell object.
- */
-
-#include <cstdint>
-#include <string>
-#include <vector>
-
-#include "database.hpp"
-#include "model.hpp"
-
-namespace mlk::server::db {
-
-class character;
-
-/**
- * \brief Describe a spell.
- */
-class spell : public model {
-private:
-    friend class character;
-
-    std::string type_;
-    std::uint8_t level_{1U};
-
-    static auto load(const result&, int) -> spell;
-    static auto load(const character&) -> std::vector<spell>;
-
-    void clear();
-    void unpublish();
-    void publish(character&);
-
-public:
-    /**
-     * Constructor.
-     *
-     * \pre !type.empty()
-     * \param type the type name
-     */
-    spell(std::string type) noexcept;
-
-    /**
-     * Get the type.
-     *
-     * \return the type
-     */
-    auto get_type() const noexcept -> const std::string&;
-
-    /**
-     * Get the level.
-     *
-     * \return the level
-     */
-    auto get_level() const noexcept -> std::uint8_t;
-
-    /**
-     * Set the spell level.
-     *
-     * \param level the level
-     */
-    void set_level(std::uint8_t level);
-};
-
-} // !mlk::server::db
-
-#endif // !MALIKANIA_SERVER_DB_SPELL_HPP
--- a/server/main.cpp	Wed Aug 29 17:04:57 2018 +0200
+++ b/server/main.cpp	Mon Oct 08 09:51:12 2018 +0200
@@ -18,8 +18,8 @@
 
 #include <iostream>
 
-#include <malikania/server/db/database.hpp>
-#include <malikania/server/db/account.hpp>
+#include <malikania/db/database.hpp>
+#include <malikania/db/account.hpp>
 
 #include <malikania/server/server.hpp>
 
@@ -27,22 +27,22 @@
 
 int main()
 {
-    boost::asio::io_context ctx;
+	boost::asio::io_context ctx;
 
-    try {
-        server::settings settings;
+	try {
+		server::settings settings;
 
-        settings.key = "/home/markand/server.key";
-        settings.certificate = "/home/markand/server.crt";
-        settings.port = 3320;
-        
-        server::server sv(ctx, settings);
+		settings.key = "/home/markand/server.key";
+		settings.certificate = "/home/markand/server.crt";
+		settings.port = 3320;
+		
+		server::server sv(ctx, settings);
 
-        server::db::open("", "", "markand", "malikaniadb", "");
-        server::db::init();
-        
-        ctx.run();
-    } catch (const std::exception& ex) {
-        std::cerr << "abort: " << ex.what() << std::endl;
-    }
+		db::open("", "", "markand", "malikaniadb", "");
+		db::init();
+		
+		ctx.run();
+	} catch (const std::exception& ex) {
+		std::cerr << "abort: " << ex.what() << std::endl;
+	}
 }