mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-24 18:09:28 +00:00
Use the database to store and retrieve login history for the searchlog command.
This allows the history to be persisted across restarts, whereas right now it's lost.
This commit is contained in:
parent
3d354dcd92
commit
14107b3ae6
@ -735,6 +735,20 @@ bool wildcard_string_match(const std::string& str, const std::string& match) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
void to_sql_wildcards(std::string& str, bool underscores)
|
||||
{
|
||||
std::replace(str.begin(), str.end(), '*', '%');
|
||||
if(underscores)
|
||||
{
|
||||
std::size_t n = 0;
|
||||
while((n = str.find("_", n)) != std::string::npos)
|
||||
{
|
||||
str.replace(n, 1, "\\_");
|
||||
n += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string indent(const std::string& string, std::size_t indent_size)
|
||||
{
|
||||
if(indent_size == 0) {
|
||||
|
@ -352,6 +352,14 @@ bool word_match(const std::string& message, const std::string& word);
|
||||
*/
|
||||
bool wildcard_string_match(const std::string& str, const std::string& match);
|
||||
|
||||
/**
|
||||
* Converts '*' to '%' and optionally escapes '_'.
|
||||
*
|
||||
* @param str The original string.
|
||||
* @param underscores Whether to escape underscore characters as well.
|
||||
*/
|
||||
void to_sql_wildcards(std::string& str, bool underscores = true);
|
||||
|
||||
/**
|
||||
* Check if the username contains only valid characters.
|
||||
*
|
||||
|
@ -17,7 +17,9 @@
|
||||
#include "server/common/resultsets/tournaments.hpp"
|
||||
#include "server/common/resultsets/ban_check.hpp"
|
||||
#include "server/common/resultsets/game_history.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "serialization/unicode.hpp"
|
||||
|
||||
static lg::log_domain log_sql_handler("sql_executor");
|
||||
#define ERR_SQL LOG_STREAM(err, log_sql_handler)
|
||||
@ -36,6 +38,7 @@ dbconn::dbconn(const config& c)
|
||||
, db_tournament_query_(c["db_tournament_query"].str())
|
||||
, db_topics_table_(c["db_topics_table"].str())
|
||||
, db_addon_info_table_(c["db_addon_info_table"].str())
|
||||
, db_connection_history_table_(c["db_connection_history_table"].str())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -372,6 +375,75 @@ void dbconn::insert_addon_info(const std::string& instance_version, const std::s
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long dbconn::insert_login(const std::string& username, const std::string& ip)
|
||||
{
|
||||
try
|
||||
{
|
||||
return modify(connection_, "INSERT INTO `"+db_connection_history_table_+"`(USER_NAME, IP) values(lower(?), ?)",
|
||||
username, ip);
|
||||
}
|
||||
catch(const mariadb::exception::base& e)
|
||||
{
|
||||
log_sql_exception("Unable to insert login row user `"+username+"` and ip `"+ip+"`.", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void dbconn::update_logout(unsigned long long login_id)
|
||||
{
|
||||
try
|
||||
{
|
||||
modify(connection_, "UPDATE `"+db_connection_history_table_+"` SET LOGOUT_TIME = CURRENT_TIMESTAMP WHERE LOGIN_ID = ?",
|
||||
login_id);
|
||||
}
|
||||
catch(const mariadb::exception::base& e)
|
||||
{
|
||||
log_sql_exception("Unable to update login row `"+std::to_string(login_id)+"`.", e);
|
||||
}
|
||||
}
|
||||
|
||||
void dbconn::get_users_for_ip(const std::string& ip, std::ostringstream* out)
|
||||
{
|
||||
try
|
||||
{
|
||||
mariadb::result_set_ref rslt = select(connection_, "SELECT USER_NAME, IP, date_format(LOGIN_TIME, '%Y/%m/%d %h:%i:%s'), coalesce(date_format(LOGOUT_TIME, '%Y/%m/%d %h:%i:%s'), '(not set)') FROM `"+db_connection_history_table_+"` WHERE IP LIKE ? order by LOGIN_TIME",
|
||||
ip);
|
||||
|
||||
*out << "\nCount of results for ip: " << rslt->row_count();
|
||||
|
||||
while(rslt->next())
|
||||
{
|
||||
*out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
|
||||
<< ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
|
||||
}
|
||||
}
|
||||
catch(const mariadb::exception::base& e)
|
||||
{
|
||||
log_sql_exception("Unable to select rows for ip `"+ip+"`.", e);
|
||||
}
|
||||
}
|
||||
|
||||
void dbconn::get_ips_for_user(const std::string& username, std::ostringstream* out)
|
||||
{
|
||||
try
|
||||
{
|
||||
mariadb::result_set_ref rslt = select(connection_, "SELECT USER_NAME, IP, date_format(LOGIN_TIME, '%Y/%m/%d %h:%i:%s'), coalesce(date_format(LOGOUT_TIME, '%Y/%m/%d %h:%i:%s'), '(not set)') FROM `"+db_connection_history_table_+"` WHERE USER_NAME LIKE ? order by LOGIN_TIME",
|
||||
utf8::lowercase(username));
|
||||
|
||||
*out << "\nCount of results for user: " << rslt->row_count();
|
||||
|
||||
while(rslt->next())
|
||||
{
|
||||
*out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
|
||||
<< ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
|
||||
}
|
||||
}
|
||||
catch(const mariadb::exception::base& e)
|
||||
{
|
||||
log_sql_exception("Unable to select rows for player `"+username+"`.", e);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// handle complex query results
|
||||
//
|
||||
@ -457,12 +529,12 @@ mariadb::result_set_ref dbconn::select(mariadb::connection_ref connection, const
|
||||
}
|
||||
}
|
||||
template<typename... Args>
|
||||
int dbconn::modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
|
||||
unsigned long long dbconn::modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
mariadb::statement_ref stmt = query(connection, sql, args...);
|
||||
int count = stmt->insert();
|
||||
unsigned long long count = stmt->insert();
|
||||
return count;
|
||||
}
|
||||
catch(const mariadb::exception::base& e)
|
||||
@ -509,6 +581,12 @@ int dbconn::prepare(mariadb::statement_ref stmt, int i, long arg)
|
||||
return i;
|
||||
}
|
||||
template<>
|
||||
int dbconn::prepare(mariadb::statement_ref stmt, int i, unsigned long long arg)
|
||||
{
|
||||
stmt->set_unsigned64(i++, arg);
|
||||
return i;
|
||||
}
|
||||
template<>
|
||||
int dbconn::prepare(mariadb::statement_ref stmt, int i, const char* arg)
|
||||
{
|
||||
stmt->set_string(i++, arg);
|
||||
|
@ -153,6 +153,26 @@ class dbconn
|
||||
*/
|
||||
void insert_addon_info(const std::string& instance_version, const std::string& id, const std::string& name, const std::string& type, const std::string& version, bool forum_auth, int topic_id);
|
||||
|
||||
/**
|
||||
* @see forum_user_handler::db_insert_login().
|
||||
*/
|
||||
unsigned long long insert_login(const std::string& username, const std::string& ip);
|
||||
|
||||
/**
|
||||
* @see forum_user_handler::db_update_logout().
|
||||
*/
|
||||
void update_logout(unsigned long long login_id);
|
||||
|
||||
/**
|
||||
* @see forum_user_handler::get_users_for_ip().
|
||||
*/
|
||||
void get_users_for_ip(const std::string& ip, std::ostringstream* out);
|
||||
|
||||
/**
|
||||
* @see forum_user_handler::get_ips_for_users().
|
||||
*/
|
||||
void get_ips_for_user(const std::string& username, std::ostringstream* out);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The account used to connect to the database.
|
||||
@ -183,6 +203,8 @@ class dbconn
|
||||
std::string db_topics_table_;
|
||||
/** The name of the table that contains add-on information. */
|
||||
std::string db_addon_info_table_;
|
||||
/** The name of the table that contains user connection history. */
|
||||
std::string db_connection_history_table_;
|
||||
|
||||
/**
|
||||
* This is used to write out error text when an SQL-related exception occurs.
|
||||
@ -258,7 +280,7 @@ class dbconn
|
||||
* @return The number of rows modified.
|
||||
*/
|
||||
template<typename... Args>
|
||||
int modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
|
||||
unsigned long long modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
|
||||
|
||||
/**
|
||||
* Begins recursively unpacking of the parameter pack in order to be able to call the correct parameterized setters on the query.
|
||||
|
@ -251,4 +251,20 @@ void fuh::db_insert_addon_info(const std::string& instance_version, const std::s
|
||||
conn_.insert_addon_info(instance_version, id, name, type, version, forum_auth, topic_id);
|
||||
}
|
||||
|
||||
unsigned long long fuh::db_insert_login(const std::string& username, const std::string& ip) {
|
||||
return conn_.insert_login(username, ip);
|
||||
}
|
||||
|
||||
void fuh::db_update_logout(unsigned long long login_id) {
|
||||
conn_.update_logout(login_id);
|
||||
}
|
||||
|
||||
void fuh::get_users_for_ip(const std::string& ip, std::ostringstream* out) {
|
||||
conn_.get_users_for_ip(ip, out);
|
||||
}
|
||||
|
||||
void fuh::get_ips_for_user(const std::string& username, std::ostringstream* out) {
|
||||
conn_.get_ips_for_user(username, out);
|
||||
}
|
||||
|
||||
#endif //HAVE_MYSQLPP
|
||||
|
@ -221,6 +221,40 @@ public:
|
||||
*/
|
||||
void db_insert_addon_info(const std::string& instance_version, const std::string& id, const std::string& name, const std::string& type, const std::string& version, bool forum_auth, int topic_id);
|
||||
|
||||
/**
|
||||
* Inserts into the database for when a player logs in.
|
||||
*
|
||||
* @param username The username of who logged in. The username is converted to lower case when inserting in order to allow index usage when querying.
|
||||
* @param ip The ip address of who logged in.
|
||||
*/
|
||||
unsigned long long db_insert_login(const std::string& username, const std::string& ip);
|
||||
|
||||
/**
|
||||
* Updates the database for when a player logs out.
|
||||
*
|
||||
* @param login_id The generated ID that uniquely identifies the row to be updated.
|
||||
*/
|
||||
void db_update_logout(unsigned long long login_id);
|
||||
|
||||
/**
|
||||
* Searches for all players that logged in using the ip address.
|
||||
* The '%' wildcard can be used to search for partial ip addresses.
|
||||
*
|
||||
* @param ip The ip address to search for.
|
||||
* @param out Where to output the results.
|
||||
*/
|
||||
void get_users_for_ip(const std::string& ip, std::ostringstream* out);
|
||||
|
||||
/**
|
||||
* Searches for all ip addresses used by the player.
|
||||
* The username is converted to lower case to allow a case insensitive select query to be executed while still using an index.
|
||||
* The '%' wildcard can be used to search for partial usernames.
|
||||
*
|
||||
* @param username The username to search for.
|
||||
* @param out Where to output the results.
|
||||
*/
|
||||
void get_ips_for_user(const std::string& username, std::ostringstream* out);
|
||||
|
||||
private:
|
||||
/** An instance of the class responsible for executing the queries and handling the database connection. */
|
||||
dbconn conn_;
|
||||
|
@ -144,4 +144,8 @@ public:
|
||||
virtual void async_test_query(boost::asio::io_service& io_service, int limit) = 0;
|
||||
virtual bool db_topic_id_exists(int topic_id) = 0;
|
||||
virtual void db_insert_addon_info(const std::string& instance_version, const std::string& id, const std::string& name, const std::string& type, const std::string& version, bool forum_auth, int topic_id) = 0;
|
||||
virtual unsigned long long db_insert_login(const std::string& username, const std::string& ip) = 0;
|
||||
virtual void db_update_logout(unsigned long long login_id) = 0;
|
||||
virtual void get_users_for_ip(const std::string& ip, std::ostringstream* out) = 0;
|
||||
virtual void get_ips_for_user(const std::string& username, std::ostringstream* out) = 0;
|
||||
};
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "server/wesnothd/player.hpp"
|
||||
|
||||
wesnothd::player::player(const std::string& n, simple_wml::node& cfg, int id,
|
||||
bool registered, const std::string& version, const std::string& source, const std::size_t max_messages,
|
||||
bool registered, const std::string& version, const std::string& source, unsigned long long login_id, const std::size_t max_messages,
|
||||
const std::size_t time_period,
|
||||
const bool moderator)
|
||||
: name_(n)
|
||||
@ -29,6 +29,7 @@ wesnothd::player::player(const std::string& n, simple_wml::node& cfg, int id,
|
||||
, TimePeriod(time_period)
|
||||
, status_(LOBBY)
|
||||
, moderator_(moderator)
|
||||
, login_id_(login_id)
|
||||
{
|
||||
cfg_.set_attr_dup("name", n.c_str());
|
||||
cfg_.set_attr("registered", registered ? "yes" : "no");
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
};
|
||||
|
||||
player(const std::string& n, simple_wml::node& cfg, int id, bool registered, const std::string& version, const std::string& source,
|
||||
const std::size_t max_messages=4, const std::size_t time_period=10,
|
||||
unsigned long long login_id, const std::size_t max_messages=4, const std::size_t time_period=10,
|
||||
const bool moderator=false);
|
||||
|
||||
void set_status(STATUS status);
|
||||
@ -54,6 +54,8 @@ public:
|
||||
void set_moderator(bool moderator) { moderator_ = moderator; }
|
||||
bool is_moderator() const { return moderator_; }
|
||||
|
||||
unsigned long long get_login_id() const { return login_id_; };
|
||||
|
||||
private:
|
||||
const std::string name_;
|
||||
std::string version_;
|
||||
@ -68,6 +70,7 @@ private:
|
||||
const std::time_t TimePeriod;
|
||||
STATUS status_;
|
||||
bool moderator_;
|
||||
unsigned long long login_id_;
|
||||
};
|
||||
|
||||
} //namespace wesnothd
|
||||
|
@ -709,6 +709,7 @@ void server::login_client(boost::asio::yield_context yield, socket_ptr socket)
|
||||
registered,
|
||||
client_version,
|
||||
client_source,
|
||||
user_handler_ ? user_handler_->db_insert_login(username, client_address(socket)) : 0,
|
||||
default_max_messages_,
|
||||
default_time_period_,
|
||||
is_moderator
|
||||
@ -731,14 +732,16 @@ void server::login_client(boost::asio::yield_context yield, socket_ptr socket)
|
||||
}
|
||||
|
||||
// Log the IP
|
||||
connection_log ip_name { username, client_address(socket), 0 };
|
||||
if(!user_handler_) {
|
||||
connection_log ip_name { username, client_address(socket), 0 };
|
||||
|
||||
if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
|
||||
ip_log_.push_back(ip_name);
|
||||
if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
|
||||
ip_log_.push_back(ip_name);
|
||||
|
||||
// Remove the oldest entry if the size of the IP log exceeds the maximum size
|
||||
if(ip_log_.size() > max_ip_log_size_) {
|
||||
ip_log_.pop_front();
|
||||
// Remove the oldest entry if the size of the IP log exceeds the maximum size
|
||||
if(ip_log_.size() > max_ip_log_size_) {
|
||||
ip_log_.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1888,15 +1891,18 @@ void server::remove_player(player_iterator iter)
|
||||
|
||||
games_and_users_list_.root().remove_child("user", index);
|
||||
|
||||
LOG_SERVER << ip << "\t" << iter->info().name() << "\twas logged off"
|
||||
<< "\n";
|
||||
LOG_SERVER << ip << "\t" << iter->info().name() << "\thas logged off\n";
|
||||
|
||||
// Find the matching nick-ip pair in the log and update the sign off time
|
||||
connection_log ip_name { iter->info().name(), ip, 0 };
|
||||
if(user_handler_) {
|
||||
user_handler_->db_update_logout(iter->info().get_login_id());
|
||||
} else {
|
||||
connection_log ip_name { iter->info().name(), ip, 0 };
|
||||
|
||||
auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
|
||||
if(i != ip_log_.end()) {
|
||||
i->log_off = std::time(nullptr);
|
||||
auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
|
||||
if(i != ip_log_.end()) {
|
||||
i->log_off = std::time(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
player_connections_.erase(iter);
|
||||
@ -2505,23 +2511,7 @@ void server::ban_handler(
|
||||
}
|
||||
|
||||
if(!banned) {
|
||||
// If nobody was banned yet check the ip_log but only if a
|
||||
// simple username was used to prevent accidental bans.
|
||||
// @todo FIXME: since we can have several entries now we should only ban the latest or so
|
||||
/*if (utils::isvalid_username(target)) {
|
||||
for (std::deque<connection_log>::const_iterator i = ip_log_.begin();
|
||||
i != ip_log_.end(); ++i) {
|
||||
if (i->nick == target) {
|
||||
if (banned) out << "\n";
|
||||
else banned = true;
|
||||
out << ban_manager_.ban(i->ip, parsed_time, reason, issuer_name, group, target);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if(!banned) {
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2591,22 +2581,7 @@ void server::kickban_handler(
|
||||
}
|
||||
|
||||
if(!banned) {
|
||||
// If nobody was banned yet check the ip_log but only if a
|
||||
// simple username was used to prevent accidental bans.
|
||||
// @todo FIXME: since we can have several entries now we should only ban the latest or so
|
||||
/*if (utils::isvalid_username(target)) {
|
||||
for (std::deque<connection_log>::const_iterator i = ip_log_.begin();
|
||||
i != ip_log_.end(); ++i) {
|
||||
if (i->nick == target) {
|
||||
if (banned) out << "\n";
|
||||
else banned = true;
|
||||
out << ban_manager_.ban(i->ip, parsed_time, reason, issuer_name, group, target);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if(!banned) {
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -2677,22 +2652,7 @@ void server::gban_handler(
|
||||
}
|
||||
|
||||
if(!banned) {
|
||||
// If nobody was banned yet check the ip_log but only if a
|
||||
// simple username was used to prevent accidental bans.
|
||||
// @todo FIXME: since we can have several entries now we should only ban the latest or so
|
||||
/*if (utils::isvalid_username(target)) {
|
||||
for (std::deque<connection_log>::const_iterator i = ip_log_.begin();
|
||||
i != ip_log_.end(); ++i) {
|
||||
if (i->nick == target) {
|
||||
if (banned) out << "\n";
|
||||
else banned = true;
|
||||
out << ban_manager_.ban(i->ip, parsed_time, reason, issuer_name, group, target);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if(!banned) {
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
*out << "Nickname mask '" << target << "' did not match, no bans set.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2813,33 +2773,42 @@ void server::searchlog_handler(const std::string& /*issuer_name*/,
|
||||
|
||||
*out << "IP/NICK LOG for '" << parameters << "'";
|
||||
|
||||
bool found_something = false;
|
||||
|
||||
// If this looks like an IP look up which nicks have been connected from it
|
||||
// Otherwise look for the last IP the nick used to connect
|
||||
const bool match_ip = (std::count(parameters.begin(), parameters.end(), '.') >= 1);
|
||||
|
||||
for(const auto& i : ip_log_) {
|
||||
const std::string& username = i.nick;
|
||||
const std::string& ip = i.ip;
|
||||
if(!user_handler_) {
|
||||
bool found_something = false;
|
||||
|
||||
for(const auto& i : ip_log_) {
|
||||
const std::string& username = i.nick;
|
||||
const std::string& ip = i.ip;
|
||||
|
||||
if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
|
||||
(!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
|
||||
) {
|
||||
found_something = true;
|
||||
auto player = player_connections_.get<name_t>().find(username);
|
||||
if((match_ip && utils::wildcard_string_match(ip, parameters)) ||
|
||||
(!match_ip && utils::wildcard_string_match(utf8::lowercase(username), utf8::lowercase(parameters)))
|
||||
) {
|
||||
found_something = true;
|
||||
auto player = player_connections_.get<name_t>().find(username);
|
||||
|
||||
if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
|
||||
*out << std::endl << player_status(*player);
|
||||
} else {
|
||||
*out << "\n'" << username << "' @ " << ip
|
||||
<< " last seen: " << lg::get_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
|
||||
if(player != player_connections_.get<name_t>().end() && player->client_ip() == ip) {
|
||||
*out << std::endl << player_status(*player);
|
||||
} else {
|
||||
*out << "\n'" << username << "' @ " << ip
|
||||
<< " last seen: " << lg::get_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!found_something) {
|
||||
*out << "\nNo match found.";
|
||||
if(!found_something) {
|
||||
*out << "\nNo match found.";
|
||||
}
|
||||
} else {
|
||||
if(!match_ip) {
|
||||
utils::to_sql_wildcards(parameters);
|
||||
user_handler_->get_ips_for_user(parameters, out);
|
||||
} else {
|
||||
user_handler_->get_users_for_ip(parameters, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,7 @@ CREATE INDEX START_TIME_IDX ON game_info(START_TIME);
|
||||
-- FACTION: the faction being played by this side
|
||||
-- CLIENT_VERSION: the version of the wesnoth client used to connect
|
||||
-- CLIENT_SOURCE: where the wesnoth client was downloaded from - SourceForge, Steam, etc
|
||||
-- USER_NAME: the username logged in with
|
||||
create table game_player_info
|
||||
(
|
||||
INSTANCE_UUID CHAR(36) NOT NULL,
|
||||
@ -145,3 +146,21 @@ create table addon_info
|
||||
FEEDBACK_TOPIC INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (INSTANCE_VERSION, ADDON_ID, VERSION)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- history of user sessions
|
||||
-- LOGIN_ID: auto generated ID to use as a primary key
|
||||
-- USER_NAME: the username logged in with
|
||||
-- IP: the IP address the login originated from
|
||||
-- LOGIN_TIME: when the user logged in
|
||||
-- LOGOUT_TIME: when the user logged out
|
||||
create table connection_history
|
||||
(
|
||||
LOGIN_ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
USER_NAME VARCHAR(255) NOT NULL,
|
||||
IP VARCHAR(255) NOT NULL,
|
||||
LOGIN_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
LOGOUT_TIME TIMESTAMP NULL DEFAULT NULL,
|
||||
PRIMARY KEY (LOGIN_ID)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE INDEX CONNECTION_IP_IDX ON connection_history(IP);
|
||||
CREATE INDEX CONNECTION_USERNAME_IDX ON connection_history(USER_NAME);
|
||||
|
Loading…
x
Reference in New Issue
Block a user