1
0
Fork 0
mirror of https://gitlab.com/niansa/asbots.git synced 2025-03-06 20:48:25 +01:00

First very very very basic ChanServ implementation

This commit is contained in:
Nils 2021-06-28 12:06:55 +02:00
parent 8773d0b886
commit 541239b2d5
11 changed files with 81 additions and 67 deletions

View file

@ -33,6 +33,9 @@ struct DesyncError : public std::exception {
const char *what() const throw() { const char *what() const throw() {
return "Server has desynced!!!"; return "Server has desynced!!!";
} }
DesyncError() {
abort();
}
}; };
struct InsufficientArgsError : public ParseError { struct InsufficientArgsError : public ParseError {

View file

@ -25,6 +25,4 @@ struct User;
struct Channel; struct Channel;
struct Cache; struct Cache;
struct NetworkInfo; struct NetworkInfo;
using u_User = std::unique_ptr<User>;
using u_Channel = std::unique_ptr<Channel>;
#endif #endif

View file

@ -145,8 +145,8 @@ void User::parse_euid(const Event& event, NetworkInfo& netInfo) {
realname = std::move(event.text); realname = std::move(event.text);
} }
void User::removeChannel(const u_Channel& channel) { void User::removeChannel(const Channel *channel) {
std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj == &channel;}); std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj == channel;});
} }
@ -154,13 +154,14 @@ void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo
this->server = std::get<SUID>(event.sender.id); this->server = std::get<SUID>(event.sender.id);
// Check size // Check size
argsSizeCheck("SJOIN", event.args, 3); argsSizeCheck("SJOIN", event.args, 3);
// Move values // Set values
name = std::move(event.args[1]); creationTime = std::stoull(std::string(event.args[0]));
name = event.args[1];
mode.parse<true>(event.args[2], netInfo); mode.parse<true>(event.args[2], netInfo);
// Get members // Get members
for (auto& raw_uuid : strSplit(event.text, ' ')) { for (auto& raw_uuid : strSplit(event.text, ' ')) {
// Erase prefix // Erase prefix
if (raw_uuid.size() > UUID_len) { while (raw_uuid.size() > UUID_len) {
char prefixChar = raw_uuid[0]; char prefixChar = raw_uuid[0];
raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1}; raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1};
char modeChar = netInfo.channelModes.prefixMap[prefixChar]; char modeChar = netInfo.channelModes.prefixMap[prefixChar];
@ -172,18 +173,18 @@ void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo
if (res == cache.users.end()) { if (res == cache.users.end()) {
throw DesyncError(); throw DesyncError();
} }
u_User& member = *res; User *member = res->get();
// Append member to list // Append member to list
members.push_back(&member); members.push_back(member);
} }
} }
void Channel::removeMember(const u_User& member) { void Channel::removeMember(const User *member) {
std::remove_if(members.begin(), members.end(), [&] (auto obj) {return *obj == member;}); std::remove_if(members.begin(), members.end(), [&] (auto obj) {return obj == member;});
} }
std::vector<u_User>::iterator Cache::find_user_by_nick(std::string_view nick) { std::vector<std::unique_ptr<User>>::iterator Cache::find_user_by_nick(std::string_view nick) {
for (auto it = users.begin(); ; it++) { for (auto it = users.begin(); ; it++) {
if (it == users.end() || it->get()->nick == nick) { if (it == users.end() || it->get()->nick == nick) {
return it; return it;
@ -191,7 +192,7 @@ std::vector<u_User>::iterator Cache::find_user_by_nick(std::string_view nick) {
} }
} }
std::vector<u_User>::iterator Cache::find_user_by_uid(const UUID& uid) { std::vector<std::unique_ptr<User>>::iterator Cache::find_user_by_uid(const UUID& uid) {
for (auto it = users.begin(); ; it++) { for (auto it = users.begin(); ; it++) {
if (it == users.end() || it->get()->uid == uid) { if (it == users.end() || it->get()->uid == uid) {
return it; return it;
@ -199,7 +200,7 @@ std::vector<u_User>::iterator Cache::find_user_by_uid(const UUID& uid) {
} }
} }
std::vector<u_Channel>::iterator Cache::find_channel_by_name(std::string_view name) { std::vector<std::unique_ptr<Channel>>::iterator Cache::find_channel_by_name(std::string_view name) {
for (auto it = channels.begin(); ; it++) { for (auto it = channels.begin(); ; it++) {
if (it == channels.end() || it->get()->name == name) { if (it == channels.end() || it->get()->name == name) {
return it; return it;
@ -388,7 +389,7 @@ async::result<void> Instance::process(Event event) {
} }
// Delete user from all channels // Delete user from all channels
for (auto& channel : res->get()->channels) { for (auto& channel : res->get()->channels) {
channel->get()->removeMember({*res}); channel->removeMember(res->get());
} }
// Delete user from cache // Delete user from cache
cache.users.erase(res); cache.users.erase(res);
@ -466,10 +467,10 @@ async::result<void> Instance::process(Event event) {
throw DesyncError(); throw DesyncError();
} }
// Assign user to channel and vice versa // Assign user to channel and vice versa
c_res->get()->members.push_back(&*u_res); c_res->get()->members.push_back(u_res->get());
u_res->get()->channels.push_back(&*c_res); u_res->get()->channels.push_back(c_res->get());
// Update channel modes // Update channel modes
c_res->get()->mode.parse<true>(event.args[2], netInfo); c_res->get()->mode.parse<true>(event.fromArgToEnd(event.args[2]), netInfo);
} else if (event.name == "PART" || event.name == "KICK") { } else if (event.name == "PART" || event.name == "KICK") {
// User left channel // User left channel
// Get channel from cache // Get channel from cache
@ -483,8 +484,8 @@ async::result<void> Instance::process(Event event) {
throw DesyncError(); throw DesyncError();
} }
// Remove channel from both user and channel // Remove channel from both user and channel
c_res->get()->removeMember(*u_res); c_res->get()->removeMember(u_res->get());
u_res->get()->removeChannel(*c_res); u_res->get()->removeChannel(c_res->get());
} else if (event.name == "TMODE" || event.name == "BMASK") { } else if (event.name == "TMODE" || event.name == "BMASK") {
// Channel modes changed // Channel modes changed
argsSizeCheck(event.name, event.args, 3); argsSizeCheck(event.name, event.args, 3);
@ -492,7 +493,7 @@ async::result<void> Instance::process(Event event) {
std::string_view modes, channelName; std::string_view modes, channelName;
{ {
channelName = event.args[1]; channelName = event.args[1];
modes = event.args[2]; modes = event.fromArgToEnd(event.args[2]);
} }
// Get channel from cache // Get channel from cache
auto c_res = cache.find_channel_by_name(channelName); auto c_res = cache.find_channel_by_name(channelName);
@ -500,7 +501,7 @@ async::result<void> Instance::process(Event event) {
throw DesyncError(); throw DesyncError();
} }
// Apply changes // Apply changes
c_res->get()->mode.parse<true>("+{} {}"_format(modes, event.text), netInfo); c_res->get()->mode.parse<true>("+{}"_format(modes), netInfo);
} }
// Messages // Messages
else if (event.name == "PRIVMSG") { else if (event.name == "PRIVMSG") {
@ -517,7 +518,7 @@ async::result<void> Instance::process(Event event) {
// Pass to services with ownership over target // Pass to services with ownership over target
for (auto& service : services) { for (auto& service : services) {
if (service->ready && service->uuid.str() == target.str()) { if (service->ready && service->uuid.str() == target.str()) {
co_await service->on_direct_privmsg(event.text, *res); co_await service->on_direct_privmsg(event.text, res->get());
} }
} }
} }

View file

@ -52,6 +52,9 @@ struct Event { // DON'T std::move() this class. EVER!!!
void splitArgs() { void splitArgs() {
args = Utility::strSplit(raw_args, ' '); args = Utility::strSplit(raw_args, ' ');
} }
std::string_view fromArgToEnd(std::string_view arg) {
return {arg.data(), raw_args.size() - (arg.data() - raw_args.data())};
}
}; };
struct Command { struct Command {
@ -83,17 +86,18 @@ struct Channel {
SUID server; SUID server;
std::string name; std::string name;
ModeSet mode; ModeSet mode;
time_t timeStamp = 0;
std::string topic; std::string topic;
std::vector<u_User*> members; std::vector<User*> members;
void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo); void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo);
void removeMember(const u_User& member); void removeMember(const User *member);
Event get_sjoin(const UUID& initial_user) const { Event get_sjoin(const UUID& initial_user, const SUID& my_sid) const {
return { return {
.sender = SUID(server), .sender = my_sid,
.name = "SJOIN", .name = "SJOIN",
.raw_args = fmt::format("{} {} +{}", time(nullptr), name, mode.str), .raw_args = fmt::format("{} {} +", timeStamp?timeStamp:time(nullptr), name),
.text = fmt::format("@{}", initial_user.str()) .text = fmt::format("@{}", initial_user.str())
}; };
} }
@ -105,11 +109,11 @@ struct Channel {
.text = std::string(topic) .text = std::string(topic)
}; };
} }
Event get_tmode(const ModeSet& modes, const UUID& user) const { Event get_tmode(const ModeSet& modes, const AnyUID& sender) const {
return { return {
.sender = user, .sender = AnyUID(sender),
.name = "TMODE", .name = "TMODE",
.raw_args = fmt::format("{} {} {}", time(nullptr), name, modes.fullStr()) .raw_args = fmt::format("{} {} +{}", timeStamp?timeStamp:time(nullptr), name, modes.fullStr())
}; };
} }
}; };
@ -125,11 +129,11 @@ struct User {
std::string realhost; std::string realhost;
UUID uid; UUID uid;
std::string realname; std::string realname;
std::vector<u_Channel*> channels; std::vector<Channel*> channels;
std::optional<std::string> loginName; std::optional<std::string> loginName;
void parse_euid(const Event &event, NetworkInfo& netInfo); void parse_euid(const Event &event, NetworkInfo& netInfo);
void removeChannel(const u_Channel &channel); void removeChannel(const Channel *channel);
Event get_euid() const { Event get_euid() const {
return { return {
@ -200,12 +204,12 @@ struct User {
}; };
struct Cache { struct Cache {
std::vector<u_User> users; std::vector<std::unique_ptr<User>> users;
std::vector<u_Channel> channels; std::vector<std::unique_ptr<Channel>> channels;
std::vector<u_User>::iterator find_user_by_nick(std::string_view nick); std::vector<std::unique_ptr<User>>::iterator find_user_by_nick(std::string_view nick);
std::vector<u_User>::iterator find_user_by_uid(const UUID& uid); std::vector<std::unique_ptr<User>>::iterator find_user_by_uid(const UUID& uid);
std::vector<u_Channel>::iterator find_channel_by_name(std::string_view name); std::vector<std::unique_ptr<Channel>>::iterator find_channel_by_name(std::string_view name);
}; };
struct NetworkInfo { struct NetworkInfo {

View file

@ -19,6 +19,7 @@
#include "instance.hpp" #include "instance.hpp"
#include "services/nickserv.hpp" #include "services/nickserv.hpp"
#include "services/chanserv.hpp"
#include <uvpp.hpp> #include <uvpp.hpp>
#include <async/result.hpp> #include <async/result.hpp>
@ -34,6 +35,7 @@ int main() {
Instance instance(service, parseConfig("config.inil")); Instance instance(service, parseConfig("config.inil"));
instance.services.push_back(std::make_unique<NickServ>()); instance.services.push_back(std::make_unique<NickServ>());
instance.services.push_back(std::make_unique<ChanServ>());
async::detach(instance.run()); async::detach(instance.run());
async::run_forever(rq.run_token(), loop_service_wrapper{service}); async::run_forever(rq.run_token(), loop_service_wrapper{service});

View file

@ -50,7 +50,7 @@ std::optional<std::string_view> ServiceBase::getConfig(const std::string& sectio
return {k_res->second}; return {k_res->second};
} }
async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, u_User& author) { async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, User *author) {
auto split = Utility::strSplit(msg, ' '); auto split = Utility::strSplit(msg, ' ');
User user = {.server = i->config.server.uid, .uid = uuid}; User user = {.server = i->config.server.uid, .uid = uuid};

View file

@ -38,11 +38,11 @@ public:
UUID uuid; UUID uuid;
Instance *i; Instance *i;
bool ready = false; bool ready = false;
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands; std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (User *sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands;
virtual async::result<void> intitialize() = 0; virtual async::result<void> intitialize() = 0;
virtual async::result<void> on_event(const Event& event); virtual async::result<void> on_event(const Event& event);
virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author); virtual async::result<void> on_direct_privmsg(std::string_view msg, User *author);
async::result<void> mark_ready(const User& user); async::result<void> mark_ready(const User& user);
std::optional<std::string_view> getConfig(const std::string& section, const std::string& key); std::optional<std::string_view> getConfig(const std::string& section, const std::string& key);

View file

@ -38,7 +38,8 @@ async::result<void> ChanServ::intitialize() {
.uid = uuid, .uid = uuid,
.realname = i->netInfo.name .realname = i->netInfo.name
}; };
co_await mark_ready(user);
opMe.parse<true>("+o {}"_format(user.uid.str()), i->netInfo);
using namespace sqlite_orm; using namespace sqlite_orm;
storage = std::make_unique<Storage>(make_storage( storage = std::make_unique<Storage>(make_storage(
@ -51,16 +52,16 @@ async::result<void> ChanServ::intitialize() {
storage->sync_schema(); storage->sync_schema();
commands = { commands = {
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> { {"register", {[this] (User *sender, std::vector<std::string_view> args) -> async::result<void> {
// Get channel // Get channel
u_Channel *channel; Channel *channel;
{ {
auto res = i->cache.find_channel_by_name(args[0]); auto res = i->cache.find_channel_by_name(args[0]);
if (res == i->cache.channels.end()) { if (res == i->cache.channels.end()) {
co_await i->send_event(user.get_notice("Error: {} does not exist!"_format(args[0]), sender->uid)); co_await i->send_event(user.get_notice("Error: {} does not exist!"_format(args[0]), sender->uid));
co_return; co_return;
} }
channel = &*res; channel = res->get();
} }
// Check if user is operator in the channel // Check if user is operator in the channel
// TODO // TODO
@ -68,7 +69,7 @@ async::result<void> ChanServ::intitialize() {
{ {
auto channelregs_found = storage->get_all<ChannelReg>(where(c(&ChannelReg::name) == std::string(args[0]))); auto channelregs_found = storage->get_all<ChannelReg>(where(c(&ChannelReg::name) == std::string(args[0])));
if (!channelregs_found.empty()) { if (!channelregs_found.empty()) {
co_await i->send_event(user.get_notice("Error: {} has already been registered!"_format(sender->nick), sender->uid)); co_await i->send_event(user.get_notice("Error: {} has already been registered!"_format(args[0]), sender->uid));
co_return; co_return;
} }
} }
@ -85,17 +86,21 @@ async::result<void> ChanServ::intitialize() {
}, "<channel>", 1, true} }, "<channel>", 1, true}
}, },
}; };
co_await mark_ready(user);
} }
async::result<void> ChanServ::initializeChannel(u_Channel *channel, const ChannelReg& channelReg) { async::result<void> ChanServ::initializeChannel(Channel *channel, const ChannelReg& channelReg) {
// We need to loop twice because co_await'ing inside a loop through a non-local dynamically-sized container is DANGEROUS // Get all owners nicks mode +o
std::vector<u_User*> to_mark_as_admin; ModeSet initialModes;
for (const auto member : channel->get()->members) { for (const auto& member : channel->members) {
if (member->get()->loginName.value_or("") == channelReg.owner) { if (member->loginName.has_value() && member->loginName.value() == channelReg.owner) {
to_mark_as_admin.push_back(member); initialModes.parse<true>("+o {}"_format(member->uid.str()), i->netInfo);
} }
} }
for (const auto member : to_mark_as_admin) { // Join the channel
co_await i->send_event(channel->get()->get_tmode({"o", {std::tuple<char, std::string>{'o', member->get()->uid.str()}}}, member->get()->uid)); auto je = channel->get_sjoin(user.uid, i->config.server.uid); // Needs to be done seperately to avoid some... super weird... crash
} co_await i->send_event(je);
// Grant owners nicks OP
co_await i->send_event(channel->get_tmode(initialModes, user.uid));
} }

View file

@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef _TEST_HPP #ifndef _CHANSERV_HPP
#define _TEST_HPP #define _CHANSERV_HPP
#include "../instance.hpp" #include "../instance.hpp"
#include "../serviceBase.hpp" #include "../serviceBase.hpp"
@ -32,14 +32,15 @@ struct ChannelReg {
std::string name, owner; std::string name, owner;
}; };
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<ChannelReg, sqlite_orm::internal::column_t<ChannelReg, int, const int& (ChannelReg::*)() const, void (ChannelReg::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
class ChanServ : public ServiceBase { class ChanServ : public ServiceBase {
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<ChannelReg, sqlite_orm::internal::column_t<ChannelReg, int, const int& (ChannelReg::*)() const, void (ChannelReg::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
std::unique_ptr<Storage> storage; std::unique_ptr<Storage> storage;
User user; User user;
ModeSet opMe;
virtual async::result<void> intitialize() override; virtual async::result<void> intitialize() override;
async::result<void> initializeChannel(u_Channel *channel, const ChannelReg& channelReg); async::result<void> initializeChannel(Channel *channel, const ChannelReg& channelReg);
}; };
#endif #endif

View file

@ -55,7 +55,7 @@ async::result<void> NickServ::intitialize() {
storage->sync_schema(); storage->sync_schema();
commands = { commands = {
{"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> { {"identify", {[this] (User *sender, const std::vector<std::string_view>& args) -> async::result<void> {
// Check that user is not already identified // Check that user is not already identified
if (sender->loginName.has_value()) { if (sender->loginName.has_value()) {
co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid)); co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid));
@ -88,12 +88,12 @@ async::result<void> NickServ::intitialize() {
co_await i->send_event(user.get_notice("You have been identified!", sender->uid)); co_await i->send_event(user.get_notice("You have been identified!", sender->uid));
}, "[username] <password>", 1, false} }, "[username] <password>", 1, false}
}, },
{"deidentify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> { {"deidentify", {[this] (User *sender, const std::vector<std::string_view>& args) -> async::result<void> {
co_await i->send_event(sender->get_encap_su("", i->config.server.uid)); co_await i->send_event(sender->get_encap_su("", i->config.server.uid));
co_await i->send_event(user.get_notice("You have been deidentified!", sender->uid)); co_await i->send_event(user.get_notice("You have been deidentified!", sender->uid));
}, "", 0, true} }, "", 0, true}
}, },
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> { {"register", {[this] (User *sender, std::vector<std::string_view> args) -> async::result<void> {
// Check args // Check args
if (args.size() != 2) { if (args.size() != 2) {
co_await i->send_event(user.get_notice("Error: Passwords may not contain spaces!", sender->uid)); co_await i->send_event(user.get_notice("Error: Passwords may not contain spaces!", sender->uid));

View file

@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef _TEST_HPP #ifndef _NICKSERV_HPP
#define _TEST_HPP #define _NICKSERV_HPP
#include "../instance.hpp" #include "../instance.hpp"
#include "../serviceBase.hpp" #include "../serviceBase.hpp"
@ -32,9 +32,9 @@ struct Account {
std::string name, email, password_hash; std::string name, email, password_hash;
}; };
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, int, const int& (Account::*)() const, void (Account::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
class NickServ : public ServiceBase { class NickServ : public ServiceBase {
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, int, const int& (Account::*)() const, void (Account::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
std::unique_ptr<Storage> storage; std::unique_ptr<Storage> storage;
User user; User user;