changeset 29:99792c6c8b06

Server: add initial postgresql account management, #475
author David Demelier <markand@malikania.fr>
date Thu, 26 May 2016 07:32:05 +0200
parents 80736513d699
children a1e80d991968
files CMakeLists.txt cmake/MalikaniaOptions.cmake cmake/MalikaniaVersion.cmake cmake/internal/sysconfig-tests.h database/CMakeLists.txt database/backend/postgresql/CMakeLists.txt database/backend/postgresql/account.cpp database/postgresql/CMakeLists.txt database/postgresql/script/init.sql database/postgresql/src/account.cpp database/postgresql/src/driver.cpp database/postgresql/src/driver.h libserver/CMakeLists.txt libserver/malikania/account.h libserver/malikania/dao-account.cpp libserver/malikania/dao-account.h libserver/malikania/database.cpp libserver/malikania/database.h libserver/malikania/dynlib.h server/CMakeLists.txt server/main.cpp tests/libserver/CMakeLists.txt tests/libserver/dao-account/CMakeLists.txt tests/libserver/dao-account/main.cpp
diffstat 24 files changed, 1497 insertions(+), 193 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ b/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -28,9 +28,9 @@
 
 set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
 
+include(cmake/MalikaniaVersion.cmake)
 include(cmake/MalikaniaOptions.cmake)
 include(cmake/MalikaniaFunctions.cmake)
-include(cmake/MalikaniaVersion.cmake)
 include(cmake/MalikaniaSystem.cmake)
 
 configure_file(
@@ -45,6 +45,7 @@
 find_package(OpenSSL REQUIRED)
 
 add_subdirectory(extern)
+add_subdirectory(database)
 add_subdirectory(docs)
 add_subdirectory(libcommon)
 add_subdirectory(libclient)
--- a/cmake/MalikaniaOptions.cmake	Tue Apr 19 21:13:23 2016 +0200
+++ b/cmake/MalikaniaOptions.cmake	Thu May 26 07:32:05 2016 +0200
@@ -43,6 +43,31 @@
 	set(WITH_DOXYGEN Off)
 endif ()
 
+set(WITH_DRIVERDIR "lib/malikania/${MALIKANIA_VERSION}"
+	CACHE STRING "Database driver directory")
+set(WITH_BINDIR "libexec/malikania/${MALIKANIA_VERSION}"
+	CACHE STRING "Front end directory")
+
+if (IS_ABSOLUTE ${WITH_DRIVERDIR})
+	message(FATAL_ERROR "WITH_DRIVERDIR must not be absolute")
+elseif (IS_ABSOLUTE ${WITH_BINDIR})
+	message(FATAL_ERROR "WITH_BINDIR must not be absolute")
+endif ()
+
+#
+# Options for unit tests
+# -------------------------------------------------------------------
+#
+
+set(WITH_TEST_PGSQL_HOST "localhost"
+	CACHE STRING "Hostname for PostgreSQL driver")
+set(WITH_TEST_PGSQL_PORT "5432"
+	CACHE STRING "Port for PostgreSQL driver")
+set(WITH_TEST_PGSQL_DATABASE "malikaniadb"
+	CACHE STRING "Database for PostgreSQL driver")
+set(WITH_TEST_PGSQL_USER "malikania"
+	CACHE STRING "Username for PostgreSQL driver")
+
 #
 # Targets to build
 # -------------------------------------------------------------------
@@ -61,3 +86,8 @@
 else ()
 	message(FATAL_ERROR "Unknown backend selected: ${WITH_BACKEND}")
 endif ()
+
+configure_file(
+	${malikania_SOURCE_DIR}/cmake/internal/sysconfig-tests.h
+	${malikania_BINARY_DIR}/sysconfig-tests.h
+)
--- a/cmake/MalikaniaVersion.cmake	Tue Apr 19 21:13:23 2016 +0200
+++ b/cmake/MalikaniaVersion.cmake	Thu May 26 07:32:05 2016 +0200
@@ -19,3 +19,4 @@
 set(MALIKANIA_VERSION_MAJOR 0)
 set(MALIKANIA_VERSION_MINOR 1)
 set(MALIKANIA_VERSION_PATCH 0)
+set(MALIKANIA_VERSION ${MALIKANIA_VERSION_MAJOR}.${MALIKANIA_VERSION_MINOR}.${MALIKANIA_VERSION_PATCH})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/internal/sysconfig-tests.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,13 @@
+#ifndef MALIKANIA_SYSCONFIG_TESTS_H
+#define MALIKANIA_SYSCONFIG_TESTS_H
+
+/*
+ * PostgreSQL configuration.
+ */
+
+#define WITH_TEST_PGSQL_HOST "@WITH_TEST_PGSQL_HOST@"
+#define WITH_TEST_PGSQL_PORT "@WITH_TEST_PGSQL_PORT@"
+#define WITH_TEST_PGSQL_DATABASE "@WITH_TEST_PGSQL_DATABASE@"
+#define WITH_TEST_PGSQL_USER "@WITH_TEST_PGSQL_USER@"
+
+#endif // !MALIKANIA_SYSCONFIG_TESTS_H
--- a/database/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ b/database/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -18,4 +18,4 @@
 
 project(database)
 
-add_subdirectory(backend/postgresql)
+add_subdirectory(postgresql)
--- a/database/backend/postgresql/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for malikania
-#
-# Copyright (c) 2013, 2014, 2015 Malikania Authors
-#
-# 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.
-#
-
-add_library(
-	malikania-db-postgresql
-	MODULE
-		account.cpp
-)
--- a/database/backend/postgresql/account.cpp	Tue Apr 19 21:13:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#include <string>
-
-class Database;
-class Account;
-
-extern "C" {
-
-void account_create(Database &database, Account &account)
-{
-}
-
-void account_update(Database &database, Account &account)
-{
-}
-
-void account_delete(Database &database, Account &account)
-{
-}
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/database/postgresql/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,39 @@
+#
+# CMakeLists.txt -- CMake build system for malikania
+#
+# Copyright (c) 2013, 2014, 2015 Malikania Authors
+#
+# 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.
+#
+
+find_package(PostgreSQL REQUIRED)
+
+add_library(
+	mlk-driver-postgresql
+	MODULE
+		script/init.sql
+		src/account.cpp
+		src/driver.cpp
+		src/driver.h
+)
+
+target_link_libraries(mlk-driver-postgresql libmlk-server ${PostgreSQL_LIBRARIES})
+target_include_directories(mlk-driver-postgresql PRIVATE ${PostgreSQL_INCLUDE_DIRS})
+target_compile_options(mlk-driver-postgresql PRIVATE -Wno-return-type-c-linkage)
+set_target_properties(
+	mlk-driver-postgresql
+	PROPERTIES
+		PREFIX ""
+		OUTPUT_NAME pgsql
+		LIBRARY_OUTPUT_DIRECTORY ${malikania_BINARY_DIR}/fakeroot/${WITH_DRIVERDIR}
+)
--- a/database/postgresql/script/init.sql	Tue Apr 19 21:13:23 2016 +0200
+++ b/database/postgresql/script/init.sql	Thu May 26 07:32:05 2016 +0200
@@ -1,115 +1,139 @@
-CREATE TABLE IF NOT EXISTS mk_account (
-  ac_id serial,
-  ac_name varchar[32],
-  ac_email varchar[128],
-  ac_firstname varchar[32],
-  ac_lastname varchar[32],
-  ac_salt varchar[8],
-  ac_password varchar[256],
-  ac_joindate date,
-  CONSTRAINT pk_ac_id PRIMARY KEY(ac_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_log_history (
-  log_id serial,
-  log_ac_id serial,
-  CONSTRAINT pk_log_id PRIMARY KEY(log_id),
-  CONSTRAINT fk_log_ac_id FOREIGN KEY(log_ac_id) REFERENCES mk_account(ac_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_character (
-  ch_id serial,
-  ch_ac_id serial,
-  ch_class varchar[32],
-  ch_createdate date,
-  CONSTRAINT pk_ch_id PRIMARY KEY(ch_id),
-  CONSTRAINT fk_ch_ac_id FOREIGN KEY(ch_ac_id) REFERENCES mk_account(ac_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_log_history_item (
-  lhi_id serial,
-  lhi_log_id serial,
-  lhi_date time,
-  CONSTRAINT pk_lhi_id PRIMARY KEY(lhi_id),
-  CONSTRAINT fk_lhi_log_id FOREIGN KEY(lhi_log_id) REFERENCES mk_log_history(log_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_status (
-  st_id serial,
-  st_ch_id serial,
-  st_hp int,
-  st_mp int,
-  CONSTRAINT pk_st_id PRIMARY KEY(st_id),
-  CONSTRAINT fk_st_ch_id FOREIGN KEY(st_ch_id) REFERENCES mk_character(ch_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_points (
-  pt_id serial,
-  pt_st_id serial,
-  pt_avail smallint,
-  pt_force smallint,
-  pt_defense smallint,
-  pt_agility smallint,
-  CONSTRAINT pk_pt_id PRIMARY KEY(pt_id),
-  CONSTRAINT fk_st_id FOREIGN KEY(pt_st_id) REFERENCES mk_status(st_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_build (
-  bd_id serial,
-  bd_ac_id serial,
-  bd_position int,
-  bd_title varchar[32],
-  bd_password varchar[25],
-  bd_kind varchar[32],
-  CONSTRAINT pk_bd_id PRIMARY KEY(bd_id),
-  CONSTRAINT fk_bd_ac_id FOREIGN KEY(bd_ac_id) REFERENCES mk_account(ac_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_chest (
-  cht_id serial,
-  cht_bd_id serial,
-  cht_name varchar[32],
-  CONSTRAINT pk_cht_id PRIMARY KEY(cht_id),
-  CONSTRAINT fk_cht_bd_id FOREIGN KEY(cht_bd_id) REFERENCES mk_build(bd_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_inventory (
-  inv_id serial,
-  inv_ch_id serial,
-  CONSTRAINT pk_inv_id PRIMARY KEY(inv_id),
-  CONSTRAINT fk_inv_ch_id FOREIGN KEY(inv_ch_id) REFERENCES mk_character(ch_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_artefact (
-  atf_id serial,
-  atf_ch_id serial,
-  atf_name varchar[32],
-  CONSTRAINT pk_atf_id PRIMARY KEY(atf_id),
-  CONSTRAINT fk_atf_ch_id FOREIGN KEY(atf_ch_id) REFERENCES mk_character(ch_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_quest (
-  qs_id serial,
-  qs_ch_id serial,
-  qs_name varchar[32],
-  CONSTRAINT pk_qs_id PRIMARY KEY(qs_id),
-  CONSTRAINT fk_qs_ch_id FOREIGN KEY(qs_ch_id) REFERENCES mk_character(ch_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_inventory_object (
-  invo_id serial,
-  invo_inv_id serial,
-  invo_name varchar[32],
-  invo_count smallint,
-  CONSTRAINT pk_invo_id PRIMARY KEY(invo_id),
-  CONSTRAINT fk_invo_inv_id FOREIGN KEY(invo_inv_id) REFERENCES mk_inventory(inv_id)
-);
-
-CREATE TABLE IF NOT EXISTS mk_quest_property (
-  qp_id serial,
-  qp_qs_id serial,
-  qp_name varchar[32],
-  qp_value text,
-  CONSTRAINT pk_qp_id PRIMARY KEY(qp_id),
-  CONSTRAINT fk_qp_qs_id FOREIGN KEY(qp_qs_id) REFERENCES mk_quest(qs_id)
-);
\ No newline at end of file
+--
+-- init.sql -- initialize PostgreSQL database
+--
+-- Copyright (c) 2013-2016 Malikania Authors
+--
+-- 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.
+--
+
+create table if not exists mk_info(
+	if_id varchar(32),
+	if_value varchar(256),
+	CONSTRAINT pk_if_id PRIMARY KEY(if_id)
+);
+
+create table if not exists mk_account(
+	ac_id serial,
+	ac_name varchar(32),
+	ac_email varchar(128),
+	ac_firstname varchar(32),
+	ac_lastname varchar(32),
+	ac_salt varchar(8),
+	ac_password varchar(256),
+	ac_joindate date,
+	CONSTRAINT pk_ac_id PRIMARY KEY(ac_id)
+);
+
+create table if not exists mk_log_history(
+	log_id serial,
+	log_ac_id serial,
+	CONSTRAINT pk_log_id PRIMARY KEY(log_id),
+	CONSTRAINT fk_log_ac_id FOREIGN KEY(log_ac_id) REFERENCES mk_account(ac_id)
+);
+
+create table if not exists mk_character(
+	ch_id serial,
+	ch_ac_id serial,
+	ch_class varchar(32),
+	ch_createdate date,
+	CONSTRAINT pk_ch_id PRIMARY KEY(ch_id),
+	CONSTRAINT fk_ch_ac_id FOREIGN KEY(ch_ac_id) REFERENCES mk_account(ac_id)
+);
+
+create table if not exists mk_log_history_item(
+	lhi_id serial,
+	lhi_log_id serial,
+	lhi_date time,
+	CONSTRAINT pk_lhi_id PRIMARY KEY(lhi_id),
+	CONSTRAINT fk_lhi_log_id FOREIGN KEY(lhi_log_id) REFERENCES mk_log_history(log_id)
+);
+
+create table if not exists mk_status(
+	st_id serial,
+	st_ch_id serial,
+	st_hp int,
+	st_mp int,
+	CONSTRAINT pk_st_id PRIMARY KEY(st_id),
+	CONSTRAINT fk_st_ch_id FOREIGN KEY(st_ch_id) REFERENCES mk_character(ch_id)
+);
+
+create table if not exists mk_points(
+	pt_id serial,
+	pt_st_id serial,
+	pt_avail smallint,
+	pt_force smallint,
+	pt_defense smallint,
+	pt_agility smallint,
+	CONSTRAINT pk_pt_id PRIMARY KEY(pt_id),
+	CONSTRAINT fk_st_id FOREIGN KEY(pt_st_id) REFERENCES mk_status(st_id)
+);
+
+create table if not exists mk_build(
+	bd_id serial,
+	bd_ac_id serial,
+	bd_position int,
+	bd_title varchar(32),
+	bd_password varchar(25),
+	bd_kind varchar(32),
+	CONSTRAINT pk_bd_id PRIMARY KEY(bd_id),
+	CONSTRAINT fk_bd_ac_id FOREIGN KEY(bd_ac_id) REFERENCES mk_account(ac_id)
+);
+
+create table if not exists mk_chest(
+	cht_id serial,
+	cht_bd_id serial,
+	cht_name varchar(32),
+	CONSTRAINT pk_cht_id PRIMARY KEY(cht_id),
+	CONSTRAINT fk_cht_bd_id FOREIGN KEY(cht_bd_id) REFERENCES mk_build(bd_id)
+);
+
+create table if not exists mk_inventory(
+	inv_id serial,
+	inv_ch_id serial,
+	CONSTRAINT pk_inv_id PRIMARY KEY(inv_id),
+	CONSTRAINT fk_inv_ch_id FOREIGN KEY(inv_ch_id) REFERENCES mk_character(ch_id)
+);
+
+create table if not exists mk_artefact(
+	atf_id serial,
+	atf_ch_id serial,
+	atf_name varchar(32),
+	CONSTRAINT pk_atf_id PRIMARY KEY(atf_id),
+	CONSTRAINT fk_atf_ch_id FOREIGN KEY(atf_ch_id) REFERENCES mk_character(ch_id)
+);
+
+create table if not exists mk_quest(
+	qs_id serial,
+	qs_ch_id serial,
+	qs_name varchar(32),
+	CONSTRAINT pk_qs_id PRIMARY KEY(qs_id),
+	CONSTRAINT fk_qs_ch_id FOREIGN KEY(qs_ch_id) REFERENCES mk_character(ch_id)
+);
+
+create table if not exists mk_inventory_object(
+	invo_id serial,
+	invo_inv_id serial,
+	invo_name varchar(32),
+	invo_count smallint,
+	CONSTRAINT pk_invo_id PRIMARY KEY(invo_id),
+	CONSTRAINT fk_invo_inv_id FOREIGN KEY(invo_inv_id) REFERENCES mk_inventory(inv_id)
+);
+
+create table if not exists mk_quest_property(
+	qp_id serial,
+	qp_qs_id serial,
+	qp_name varchar(32),
+	qp_value text,
+	CONSTRAINT pk_qp_id PRIMARY KEY(qp_id),
+	CONSTRAINT fk_qp_qs_id FOREIGN KEY(qp_qs_id) REFERENCES mk_quest(qs_id)
+);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/database/postgresql/src/account.cpp	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,130 @@
+/*
+ * account.cpp -- account management
+ *
+ * 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.
+ */
+
+#include <cassert>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+#include <malikania/dynlib.h>
+#include <malikania/account.h>
+
+#include "driver.h"
+
+using namespace malikania;
+
+extern "C" {
+
+namespace {
+
+Account toAccount(PGresult *result, int i)
+{
+	assert(i < PQntuples(result));
+
+	Account account;
+
+	account.setId(static_cast<std::uint64_t>(std::stoll(PQgetvalue(result, i, 0))));
+	account.setName(PQgetvalue(result, i, 1));
+	account.setEmail(PQgetvalue(result, i, 2));
+	account.setFirstName(PQgetvalue(result, i, 3));
+	account.setLastName(PQgetvalue(result, i, 4));
+	account.setPassword(PQgetvalue(result, i, 6));
+
+	return account;
+}
+
+} // !namespace
+
+DYNLIB_EXPORT void malikania_account_create(Account &account)
+{
+	std::ostringstream oss;
+
+	oss << "insert into mk_account(ac_name, ac_email, ac_firstname, ac_lastname, ac_password) values (";
+	oss << pgsql::escape(account.name()) << ", ";
+	oss << pgsql::escape(account.email()) << ", ";
+	oss << pgsql::escape(account.firstName()) << ", ";
+	oss << pgsql::escape(account.lastName()) << ", ";
+	oss << pgsql::escape(account.password()) << ") returning ac_id";
+
+	std::shared_ptr<PGresult> result = pgsql::exec(oss.str());
+
+	account.setId(static_cast<std::uint64_t>(std::stoll(PQgetvalue(result.get(), 0, 0))));
+}
+
+DYNLIB_EXPORT void malikania_account_update(Account &account)
+{
+	std::ostringstream oss;
+
+	oss << "update mk_account set ";
+	oss << "ac_name = " << pgsql::escape(account.name()) << ", ";
+	oss << "ac_email = " << pgsql::escape(account.email()) << ", ";
+	oss << "ac_firstname = " << pgsql::escape(account.firstName()) << ", ";
+	oss << "ac_lastname = " << pgsql::escape(account.lastName()) << ", ";
+	oss << "ac_password = " << pgsql::escape(account.password()) << " ";
+	oss << "where ac_id = " << account.id();
+
+	pgsql::exec(oss.str());
+}
+
+DYNLIB_EXPORT void malikania_account_remove(const Account &account)
+{
+	std::ostringstream oss;
+
+	oss << "delete from mk_account where ac_id = " << account.id();
+
+	pgsql::exec(oss.str());
+}
+
+DYNLIB_EXPORT Account malikania_account_get(std::uint64_t id)
+{
+	std::ostringstream oss;
+
+	oss << "select * from mk_account where ac_id = " << id;
+
+	std::shared_ptr<PGresult> result = pgsql::exec(oss.str());
+
+	if (PQntuples(result.get()) != 1)
+		throw std::out_of_range("account not found");
+
+	return toAccount(result.get(), 0);
+}
+
+DYNLIB_EXPORT std::vector<malikania::Account> malikania_account_list()
+{
+	std::vector<Account> accounts;
+	std::shared_ptr<PGresult> result = pgsql::exec("select * from mk_account");
+
+	for (int i = 0; i < PQntuples(result.get()); ++i)
+		accounts.push_back(toAccount(result.get(), i));
+
+	return accounts;
+}
+
+DYNLIB_EXPORT std::uint64_t malikania_account_count()
+{
+	std::shared_ptr<PGresult> result = pgsql::exec("select count(*) from mk_account");
+
+	return static_cast<std::uint64_t>(std::stoll(PQgetvalue(result.get(), 0, 0)));
+}
+
+DYNLIB_EXPORT void malikania_account_clear()
+{
+	pgsql::exec("delete from mk_account");
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/database/postgresql/src/driver.cpp	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,113 @@
+/*
+ * driver.cpp -- PostgreSQL database driver
+ *
+ * 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.
+ */
+
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#include <malikania/dynlib.h>
+
+#include "driver.h"
+
+namespace pgsql {
+
+PGconn *connection{nullptr};
+
+std::shared_ptr<PGresult> exec(const std::string &sql)
+{
+	std::shared_ptr<PGresult> ptr(PQexec(connection, sql.c_str()), PQclear);
+
+	if (PQresultStatus(ptr.get()) != PGRES_TUPLES_OK && PQresultStatus(ptr.get()) != PGRES_COMMAND_OK)
+		throw std::runtime_error(PQresultErrorMessage(ptr.get()));
+
+	return ptr;
+}
+
+std::string escape(const std::string &input)
+{
+	auto text = PQescapeLiteral(connection, input.c_str(), input.length());
+
+	if (!text)
+		throw std::runtime_error(PQerrorMessage(connection));
+
+	std::string result(text);
+
+	PQfreemem(text);
+
+	return result;
+}
+
+} // !pgsql
+
+namespace {
+
+std::string parameters(const std::unordered_map<std::string, std::string> &params)
+{
+	std::ostringstream oss;
+	std::unordered_map<std::string, std::string>::const_iterator it;
+
+	// Host.
+	if ((it = params.find("host")) == params.end())
+		throw std::runtime_error("missing 'host' parameter");
+
+	oss << "host = " << it->second << " ";
+
+	// Database.
+	if ((it = params.find("database")) == params.end())
+		throw std::runtime_error("missing 'database' parameter");
+
+	oss << "dbname = " << it->second << " ";
+
+	// User.
+	if ((it = params.find("user")) == params.end())
+		throw std::runtime_error("missing 'user' parameter");
+
+	oss << "user = " << it->second << " ";
+
+	// Port (optional).
+	if ((it = params.find("port")) != params.end())
+		oss << "port = " << it->second << " ";
+
+	// Password (optional).
+	if ((it = params.find("password")) != params.end())
+		oss << "password = " << it->second << " ";
+
+	return oss.str();
+}
+
+} // !namespace
+
+extern "C" {
+
+DYNLIB_EXPORT void malikania_driver_load(const std::unordered_map<std::string, std::string> &params)
+{
+	pgsql::connection = PQconnectdb(parameters(params).c_str());
+
+	if (!pgsql::connection)
+		throw std::runtime_error(PQerrorMessage(pgsql::connection));
+}
+
+DYNLIB_EXPORT void malikania_driver_unload()
+{
+	if (pgsql::connection) {
+		PQfinish(pgsql::connection);
+		pgsql::connection = nullptr;
+	}
+}
+
+} // !C
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/database/postgresql/src/driver.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,37 @@
+/*
+ * driver.h -- PostgreSQL database driver
+ *
+ * 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.
+ */
+
+#ifndef MALIKANIA_POSTGRESQL_DRIVER_H
+#define MALIKANIA_POSTGRESQL_DRIVER_H
+
+#include <memory>
+#include <string>
+
+#include <libpq-fe.h>
+
+namespace pgsql {
+
+extern PGconn *connection;
+
+std::shared_ptr<PGresult> exec(const std::string &sql);
+
+std::string escape(const std::string &input);
+
+} // !pgsql
+
+#endif // !MALIKANIA_POSTGRESQL_DRIVER_H
--- a/libserver/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ b/libserver/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -16,24 +16,26 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
-# set(
-# 	HEADERS
-# 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.h
-# 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.h
-# )
-#
-# set(
-# 	SOURCES
-# 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/Server.cpp
-# 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/ServerApp.cpp
-# )
-#
-# malikania_create_library(
-# 	PROJECT libserver
-# 	TARGET libserver
-# 	SOURCES ${HEADERS} ${SOURCES}
-# 	PUBLIC_INCLUDES
-# 		${CMAKE_CURRENT_SOURCE_DIR}
-# 	LIBRARIES
-# 		libcommon
-# )
+project(libmlk-server)
+
+set(
+	HEADERS
+	${libmlk-server_SOURCE_DIR}/malikania/account.h
+	${libmlk-server_SOURCE_DIR}/malikania/dao-account.h
+	${libmlk-server_SOURCE_DIR}/malikania/database.h
+	${libmlk-server_SOURCE_DIR}/malikania/dynlib.h
+)
+
+set(
+	SOURCES
+	${libmlk-server_SOURCE_DIR}/malikania/dao-account.cpp
+	${libmlk-server_SOURCE_DIR}/malikania/database.cpp
+)
+
+malikania_create_library(
+	TARGET libmlk-server
+	SOURCES ${HEADERS} ${SOURCES}
+	LIBRARIES libcommon
+)
+
+target_include_directories(libmlk-server PUBLIC ${libmlk-server_SOURCE_DIR})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/account.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,180 @@
+/*
+ * account.h -- account model
+ *
+ * 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.
+ */
+
+#ifndef MALIKANIA_ACCOUNT_H
+#define MALIKANIA_ACCOUNT_H
+
+/**
+ * \file account.h
+ * \brief Account model.
+ */
+
+#include <cstdint>
+#include <string>
+
+namespace malikania {
+
+/**
+ * \brief Account model
+ */
+class Account {
+private:
+	std::uint64_t m_id{0};
+	std::string m_name;
+	std::string m_email;
+	std::string m_firstName;
+	std::string m_lastName;
+	std::string m_password;
+
+public:
+	/**
+	 * Get the id.
+	 *
+	 * \return the id
+	 */
+	inline std::uint64_t id() const noexcept
+	{
+		return m_id;
+	}
+
+	/**
+	 * Set the id.
+	 *
+	 * \param id the id
+	 */
+	inline void setId(std::uint64_t id) noexcept
+	{
+		m_id = id;
+	}
+
+	/**
+	 * Get the account name.
+	 *
+	 * \return the name
+	 */
+	inline const std::string &name() const noexcept
+	{
+		return m_name;
+	}
+
+	/**
+	 * Set the account name.
+	 *
+	 * \param name the name
+	 */
+	inline void setName(std::string name) noexcept
+	{
+		m_name = std::move(name);
+	}
+
+	/**
+	 * Get the email.
+	 *
+	 * \return the email address
+	 */
+	inline const std::string &email() const noexcept
+	{
+		return m_email;
+	}
+
+	/**
+	 * Set the email.
+	 *
+	 * \param email the email address
+	 */
+	inline void setEmail(std::string email) noexcept
+	{
+		m_email = std::move(email);
+	}
+
+	/**
+	 * Get the first name.
+	 *
+	 * \return the first name
+	 */
+	inline const std::string &firstName() const noexcept
+	{
+		return m_firstName;
+	}
+
+	/**
+	 * Set the first name.
+	 *
+	 * \param firstName the first name
+	 */
+	inline void setFirstName(std::string firstName) noexcept
+	{
+		m_firstName = std::move(firstName);
+	}
+
+	/**
+	 * Get the last name.
+	 *
+	 * \return the last name
+	 */
+	inline const std::string &lastName() const noexcept
+	{
+		return m_lastName;
+	}
+
+	/**
+	 * Set the last name.
+	 *
+	 * \param lastName the last name
+	 */
+	inline void setLastName(std::string lastName) noexcept
+	{
+		m_lastName = std::move(lastName);
+	}
+
+	/**
+	 * Get the password.
+	 *
+	 * \return the password
+	 */
+	inline const std::string &password() const noexcept
+	{
+		return m_password;
+	}
+
+	/**
+	 * Set the password.
+	 *
+	 * \param password the password
+	 */
+	inline void setPassword(std::string password) noexcept
+	{
+		m_password = std::move(password);
+	}
+};
+
+inline bool operator==(const Account &ac1, const Account &ac2) noexcept
+{
+	return ac1.id() == ac2.id() && ac1.name() == ac2.name() && ac1.email() == ac2.email() &&
+	       ac1.firstName() == ac2.firstName() && ac1.lastName() == ac2.lastName() &&
+	       ac1.password() == ac2.password();
+}
+
+inline bool operator!=(const Account &ac1, const Account &ac2) noexcept
+{
+	return !(ac1 == ac2);
+}
+
+} // !malikania
+
+#endif // !MALIKANIA_ACCOUNT_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/dao-account.cpp	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * dao-account.cpp -- database account management
+ *
+ * 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.
+ */
+
+#include "account.h"
+#include "dao-account.h"
+#include "database.h"
+
+namespace malikania {
+
+using Create = void (*)(Account &);
+using Remove = void (*)(const Account &);
+using Update = void (*)(Account &);
+using Get = Account (*)(std::uint64_t);
+using List = std::vector<Account> (*)();
+using Count = std::uint64_t (*)();
+using Clear = void (*)();
+
+void AccountDao::create(Account &account)
+{
+	m_database.handle().sym<Create>("malikania_account_create")(account);
+}
+
+void AccountDao::remove(const Account &account)
+{
+	m_database.handle().sym<Remove>("malikania_account_remove")(account);
+}
+
+void AccountDao::update(Account &account)
+{
+	m_database.handle().sym<Update>("malikania_account_update")(account);
+}
+
+Account AccountDao::get(uint64_t id)
+{
+	return m_database.handle().sym<Get>("malikania_account_get")(id);
+}
+
+std::vector<Account> AccountDao::list()
+{
+	return m_database.handle().sym<List>("malikania_account_list")();
+}
+
+std::uint64_t AccountDao::count()
+{
+	return m_database.handle().sym<Count>("malikania_account_count")();
+}
+
+void AccountDao::clear()
+{
+	m_database.handle().sym<Clear>("malikania_account_clear")();
+}
+
+} // !malikania
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/dao-account.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,113 @@
+/*
+ * dao-account.h -- database account management
+ *
+ * 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.
+ */
+
+#ifndef MALIKANIA_DAO_ACCOUNT_H
+#define MALIKANIA_DAO_ACCOUNT_H
+
+/**
+ * \file dao-account.h
+ * \brief Database account management.
+ */
+
+#include <cstdint>
+#include <vector>
+
+namespace malikania {
+
+class Database;
+class Account;
+
+/**
+ * \brief Account DAO.
+ */
+class AccountDao {
+private:
+	Database &m_database;
+
+public:
+	/**
+	 * Constructor.
+	 *
+	 * \param database the database
+	 */
+	inline AccountDao(Database &database) noexcept
+		: m_database(database)
+	{
+	}
+
+	/**
+	 * Create the given account.
+	 *
+	 * The object will be modified in place.
+	 *
+	 * \param account the account to add
+	 * \throw std::exception on errors
+	 */
+	void create(Account &account);
+
+	/**
+	 * Remove the given account, all data that references this account is deleted too.
+	 *
+	 * \param account the account
+	 * \throw std::exception on errors
+	 */
+	void remove(const Account &account);
+
+	/**
+	 * Update account only, does not recurse into objects that references the account.
+	 *
+	 * \param account the account
+	 * \throw std::exception on errors
+	 */
+	void update(Account &account);
+
+	/**
+	 * Get an account.
+	 *
+	 * \param id the account id
+	 * \return the account
+	 * \throw std::exception if not found
+	 */
+	Account get(std::uint64_t id);
+
+	/**
+	 * Get the list of account.
+	 *
+	 * \throw std::exception on errors
+	 */
+	std::vector<Account> list();
+
+	/**
+	 * Get the number of accounts.
+	 *
+	 * \return the number of account.
+	 */
+	std::uint64_t count();
+
+	/**
+	 * Remove all accounts recursively.
+	 *
+	 * \throw std::exception on errors
+	 * \warning use with care
+	 */
+	void clear();
+};
+
+} // !malikania
+
+#endif // !MALIKANIA_DAO_ACCOUNT_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/database.cpp	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,39 @@
+/*
+ * database.cpp -- generic database loader
+ *
+ * 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.
+ */
+
+#include "database.h"
+
+namespace malikania {
+
+using Load = void (*)(const std::unordered_map<std::string, std::string> &);
+using Unload = void (*)();
+
+// TODO: is path is relative, search for it
+
+Database::Database(const std::string &path, const std::unordered_map<std::string, std::string> &params)
+	: m_dso(path)
+{
+	m_dso.sym<Load>("malikania_driver_load")(params);
+}
+
+Database::~Database()
+{
+	m_dso.sym<Unload>("malikania_driver_unload")();
+}
+
+} // !malikania
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/database.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,69 @@
+/*
+ * database.h -- generic database loader
+ *
+ * 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.
+ */
+
+#ifndef MALIKANIA_DATABASE_H
+#define MALIKANIA_DATABASE_H
+
+/**
+ * \file database.h
+ * \brief Generic database loader.
+ */
+
+#include <string>
+#include <unordered_map>
+
+#include "dynlib.h"
+
+namespace malikania {
+
+/**
+ * \brief Generic database.
+ */
+class Database {
+private:
+	Dynlib m_dso;
+
+public:
+	/**
+	 * Load the database driver dynamically.
+	 *
+	 * The type is the canonical file name (e.g. "pgsql").
+	 *
+	 * \param type the database name
+	 * \param params the parameters to pass to the driver
+	 * \throw std::exception on errors
+	 */
+	Database(const std::string &type, const std::unordered_map<std::string, std::string> &params);
+
+	/**
+	 * Close the database.
+	 */
+	~Database();
+
+	/**
+	 * Get the associated dynlib handle.
+	 */
+	inline Dynlib &handle() noexcept
+	{
+		return m_dso;
+	}
+};
+
+} // !malikania
+
+#endif // !MALIKANIA_DATABASE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libserver/malikania/dynlib.h	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,338 @@
+/*
+ * dynlib.hpp -- portable shared library loader
+ *
+ * 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.
+ */
+
+#ifndef DYNLIB_HPP
+#define DYNLIB_HPP
+
+/**
+ * \file dynlib.hpp
+ * \brief Portable shared library loader.
+ * \author David Demelier <markand@malikania.fr>
+ */
+
+/**
+ * \page Dynlib Dynlib
+ * \brief Portable shared library loader.
+ *
+ * The dynlib module let you open shared libraries dynamically at runtime.
+ *
+ * ## Operating system support
+ *
+ * | System  | Support | Remarks            |
+ * |---------|---------|--------------------|
+ * | Apple   | Ok      |                    |
+ * | FreeBSD | Ok      |                    |
+ * | Linux   | Ok      | Needs -ldl library |
+ * | Windows | Ok      |                    |
+ *
+ * ## How to export symbols
+ *
+ * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if
+ * not they will be [mangled][name-mangling].
+ *
+ * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function
+ * overloading.
+ *
+ * Example of **plugin.cpp**:
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void plugin_load()
+ * {
+ *   std::cout << "Loading plugin" << std::endl;
+ * }
+ *
+ * DYNLIB_EXPORT void plugin_unload()
+ * {
+ *   std::cout << "Unloading plugin" << std::endl;
+ * }
+ *
+ * }
+ * ````
+ *
+ * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always
+ * add it before any function.
+ *
+ * To compile, see your compiler documentation or build system. For gcc you can use the following:
+ *
+ * ````
+ * gcc -std=c++14 -shared plugin.cpp -o plugin.so
+ * ````
+ *
+ * ## How to load the library
+ *
+ * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure
+ * that the library can be found. Otherwise, just use an absolute path to the file.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * int main()
+ * {
+ *   try {
+ *     Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *   } catch (const std::exception &ex) {
+ *     std::cerr << ex.what() << std::endl;
+ *   }
+ *
+ *   return 0;
+ * }
+ * ````
+ *
+ * ## How to load symbol
+ *
+ * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * using PluginLoad = void (*)();
+ * using PluginUnload = void (*)();
+ *
+ * int main()
+ * {
+ *	try {
+ *		Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *
+ *		dso.sym<PluginLoad>("plugin_load")();
+ *		dso.sym<PluginUnload>("plugin_unload")();
+ *	} catch (const std::exception &ex) {
+ *		std::cerr << ex.what() << std::endl;
+ *	}
+ *
+ *	return 0;
+ * }
+ * ````
+ *
+ * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling
+ */
+
+#include <stdexcept>
+#include <string>
+
+#if defined(_WIN32)
+#  include <Windows.h>
+#else
+#  include <dlfcn.h>
+#endif
+
+/**
+ * \brief Export the symbol.
+ *
+ * This is required on some platforms and you should put it before your function signature.
+ *
+ * \code{.cpp}
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void my_function()
+ * {
+ * }
+ *
+ * }
+ * \endcode
+ */
+#if defined(_WIN32)
+#  define DYNLIB_EXPORT	__declspec(dllexport)
+#else
+#  define DYNLIB_EXPORT
+#endif
+
+/**
+ * \brief Usual suffix for the library.
+ *
+ * This macro expands to the suffix convention for this platform.
+ *
+ * \code{.cpp}
+ * Dynlib library("./myplugin" DYNLIB_SUFFIX);
+ * \endcode
+ *
+ * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong.
+ */
+#if defined(_WIN32)
+#  define DYNLIB_SUFFIX ".dll"
+#elif defined(__APPLE__)
+#  define DYNLIB_SUFFIX ".dylib"
+#else
+#  define DYNLIB_SUFFIX ".so"
+#endif
+
+namespace malikania {
+
+/**
+ * \class Dynlib
+ * \brief Load a dynamic module.
+ *
+ * This class is a portable wrapper to load shared libraries on supported systems.
+ */
+class Dynlib {
+private:
+#if defined(_WIN32)
+	using Handle	= HMODULE;
+	using Sym	= FARPROC;
+#else
+	using Handle	= void *;
+	using Sym	= void *;
+#endif
+
+public:
+	/**
+	 * \brief Policy for symbol resolution.
+	 */
+	enum Policy {
+		Immediately,		//!< load symbols immediately
+		Lazy			//!< load symbols when needed
+	};
+
+private:
+	Handle	m_handle;
+
+	Dynlib(const Dynlib &) = delete;
+	Dynlib &operator=(const Dynlib &) = delete;
+
+	Dynlib(Dynlib &&) = delete;
+	Dynlib &operator=(Dynlib &&) = delete;
+
+#if defined(_WIN32)
+	std::string error()
+	{
+		LPSTR error = nullptr;
+		std::string errmsg;
+
+		FormatMessageA(
+			FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+			NULL,
+			GetLastError(),
+			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+			(LPSTR)&error, 0, NULL);
+
+		if (error) {
+			errmsg = std::string(error);
+			LocalFree(error);
+		}
+
+		return errmsg;
+	}
+#endif
+
+public:
+	/**
+	 * Constructor to load a shared module.
+	 *
+	 * \param path the absolute path
+	 * \param policy the policy to load
+	 * \throw std::runtime_error on error
+	 */
+	Dynlib(const std::string &path, Policy policy = Immediately);
+
+	/**
+	 * Close the library automatically.
+	 */
+	~Dynlib();
+
+	/**
+	 * Get a symbol from the library.
+	 *
+	 * On some platforms the symbol must be manually exported.
+	 *
+	 * \param name the symbol
+	 * \return the symbol
+	 * \throw std::runtime_error on error
+	 * \see DYNLIB_EXPORT
+	 */
+	template <typename T>
+	T sym(const std::string &name);
+};
+
+#if defined(_WIN32)
+
+/*
+ * Windows implementation
+ * ------------------------------------------------------------------
+ */
+
+inline Dynlib::Dynlib(const std::string &path, Policy)
+{
+	m_handle = LoadLibraryA(path.c_str());
+
+	if (m_handle == nullptr)
+		throw std::runtime_error(error());
+}
+
+inline Dynlib::~Dynlib()
+{
+	FreeLibrary(m_handle);
+	m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+	Sym sym = GetProcAddress(m_handle, name.c_str());
+
+	if (sym == nullptr)
+		throw std::runtime_error(error());
+
+	return reinterpret_cast<T>(sym);
+}
+
+#else
+
+/*
+ * Unix implementation
+ * ------------------------------------------------------------------
+ */
+
+inline Dynlib::Dynlib(const std::string &path, Policy policy)
+{
+	m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY);
+
+	if (m_handle == nullptr)
+		throw std::runtime_error(dlerror());
+}
+
+inline Dynlib::~Dynlib()
+{
+	dlclose(m_handle);
+	m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+	Sym sym = dlsym(m_handle, name.c_str());
+
+	if (sym == nullptr)
+		throw std::runtime_error(dlerror());
+
+	return reinterpret_cast<T>(sym);
+}
+
+#endif
+
+} // !malikania
+
+#endif // !DYNLIB_HPP
--- a/server/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ b/server/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -16,9 +16,7 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
-# project(server)
-# 
-# add_executable(server main.cpp)
-# 
-# target_include_directories(server PRIVATE ${Mongo_INCLUDE_DIRS})
-# target_link_libraries(server libserver ${Mongo_LIBRARIES})
+project(mlk-server)
+
+add_executable(mlk-server main.cpp)
+target_link_libraries(mlk-server libmlk-server)
--- a/server/main.cpp	Tue Apr 19 21:13:23 2016 +0200
+++ b/server/main.cpp	Thu May 26 07:32:05 2016 +0200
@@ -18,17 +18,38 @@
 
 #include <iostream>
 
-#include <malikania/Game.h>
-#include <malikania/ResourcesLocator.h>
-#include <malikania/ResourcesLoader.h>
+#include "malikania/account.h"
+#include "malikania/dao-account.h"
+#include "malikania/database.h"
 
 using namespace malikania;
 
 int main(int, char **)
 {
-	ResourcesLocatorDirectory locator("/home/markand/mygame");
-	ResourcesLoader loader(locator);
-	Game game = loader.loadGame();
+	try {
+		Database database("./pgsql", {
+			{ "user", "kingdom" },
+			{ "host", "localhost" },
+			{ "database", "kingdomdb" }
+		});
+
+		AccountDao act(database);
+		Account account;
+
+		account.setId(2);
+
+		act.remove(account);
+
+		for (const Account &ac : act.list()) {
+			std::cout << "id          : " << ac.id() << std::endl;
+			std::cout << "name        : " << ac.name() << std::endl;
+			std::cout << "email       : " << ac.email() << std::endl;
+			std::cout << "firstname   : " << ac.firstName() << std::endl;
+			std::cout << "lastname    : " << ac.lastName() << std::endl;
+		}
+	} catch (const std::exception &ex) {
+		std::cerr << ex.what() << std::endl;
+	}
 
 	return 0;
 }
--- a/tests/libserver/CMakeLists.txt	Tue Apr 19 21:13:23 2016 +0200
+++ b/tests/libserver/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -17,3 +17,4 @@
 #
 
 add_subdirectory(id)
+add_subdirectory(dao-account)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/libserver/dao-account/CMakeLists.txt	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,25 @@
+#
+# CMakeLists.txt -- CMake build system for malikania
+#
+# Copyright (c) 2013-2016 Malikania Authors
+#
+# 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 main.cpp
+)
+
+target_compile_definitions(test-dao-account PRIVATE DRIVERDIR=\"${CMAKE_BINARY_DIR}/fakeroot/${WITH_DRIVERDIR}/\")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/libserver/dao-account/main.cpp	Thu May 26 07:32:05 2016 +0200
@@ -0,0 +1,105 @@
+/*
+ * main.cpp -- test AccountDao
+ *
+ * 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.
+ */
+
+#include <cassert>
+
+#include <gtest/gtest.h>
+
+#include <sysconfig-tests.h>
+
+#include <malikania/account.h>
+#include <malikania/dao-account.h>
+#include <malikania/database.h>
+
+using namespace malikania;
+
+class TestAccountDao : public testing::Test {
+protected:
+	Database m_database;
+	AccountDao m_dao;
+
+public:
+	TestAccountDao()
+		: m_database(DRIVERDIR "pgsql.so", {
+			{ "host", WITH_TEST_PGSQL_HOST },
+			{ "port", WITH_TEST_PGSQL_PORT },
+			{ "user", WITH_TEST_PGSQL_USER },
+			{ "database", WITH_TEST_PGSQL_DATABASE }
+		})
+		, m_dao(m_database)
+	{
+		m_dao.clear();
+
+		assert(m_dao.count() == 0U);
+	}
+};
+
+TEST_F(TestAccountDao, create)
+{
+	try {
+		Account ac;
+
+		ac.setName("jean");
+		ac.setEmail("jean@christophe.fr");
+		ac.setFirstName("Jean");
+		ac.setLastName("Christophe");
+		ac.setPassword("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(TestAccountDao, update)
+{
+	try {
+		Account ac;
+
+		ac.setName("jean");
+		ac.setEmail("jean@christophe.fr");
+		ac.setFirstName("Jean");
+		ac.setLastName("Christophe");
+		ac.setPassword("raw");
+
+		m_dao.create(ac);
+
+		ac.setEmail("benoit@christophe.fr");
+		ac.setFirstName("Benoit");
+
+		m_dao.update(ac);
+
+		Account ac2 = m_dao.get(ac.id());
+
+		ASSERT_EQ("jean", ac2.name());
+		ASSERT_EQ("benoit@christophe.fr", ac2.email());
+		ASSERT_EQ("Benoit", ac2.firstName());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+int main(int argc, char **argv)
+{
+	testing::InitGoogleTest(&argc, argv);
+
+	return RUN_ALL_TESTS();
+}