Refactor chat system: Introduce ChatRoom and ChatUser classes

- Created ChatRoom class to manage chat room functionalities, including user management, message handling, and game mechanics.
- Developed ChatUser class to represent individual users, handling user-specific actions and interactions within chat rooms.
- Implemented a Config class for loading configuration settings from a JSON file.
- Established a Server class to manage connections, handle requests, and facilitate communication between users and chat rooms.
- Introduced a Database class for database interactions, utilizing PostgreSQL for user and room data management.
- Added utility functions in the Base class for JSON handling and socket communication.
- Created Object classes for Room and User to encapsulate their properties and behaviors.
- Updated main function to initialize server and load chat rooms from configuration.
This commit is contained in:
Torsten Schulz (local)
2025-08-11 16:07:15 +02:00
parent 6ecdbda9de
commit 864d86aa09
28 changed files with 693 additions and 343 deletions

275
src/core/chat_user.cpp Normal file
View File

@@ -0,0 +1,275 @@
// entfernt: #include "user.h"
#include "chat_user.h"
#include "server.h"
#include "lib/tools.h"
#include <json/json.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "chat_room.h"
#include <iostream>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sstream>
namespace Yc
{
namespace Lib
{
ChatUser::ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket)
: _parent(std::move(parent)),
_name(name),
_color(color),
_socket(socket),
_stop(false)
{
// Hole DB-Connection
auto server = _parent->getServer();
auto db = server->_database;
// Suche Community-User
std::string query = "SELECT * FROM community.\"user\" WHERE username = '" + name + "' LIMIT 1;";
auto result = db->exec(query);
Json::Value userJson;
if (result.empty()) {
// Kein Community-User, lege Dummy an
userJson["display_name"] = name;
userJson["color"] = "#000000";
} else {
const auto& row = result[0];
int falukant_user_id = row["id"].as<int>();
// Suche Chat-User
std::string chatUserQuery = "SELECT * FROM chat.\"user\" WHERE falukant_user_id = " + std::to_string(falukant_user_id) + " LIMIT 1;";
auto chatUserResult = db->exec(chatUserQuery);
if (chatUserResult.empty()) {
// Chat-User anlegen
std::string insert = "INSERT INTO chat.\"user\" (falukant_user_id, display_name, color, show_gender, show_age, created_at, updated_at) VALUES (" +
std::to_string(falukant_user_id) + ", '" + name + "', '#000000', true, true, NOW(), NOW()) RETURNING *;";
auto newUser = db->exec(insert);
if (!newUser.empty()) {
const auto& u = newUser[0];
userJson["id"] = u["id"].as<int>();
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
userJson["display_name"] = u["display_name"].c_str();
userJson["color"] = u["color"].c_str();
userJson["show_gender"] = u["show_gender"].as<bool>();
userJson["show_age"] = u["show_age"].as<bool>();
userJson["created_at"] = u["created_at"].c_str();
userJson["updated_at"] = u["updated_at"].c_str();
}
} else {
const auto& u = chatUserResult[0];
userJson["id"] = u["id"].as<int>();
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
userJson["display_name"] = u["display_name"].c_str();
userJson["color"] = u["color"].c_str();
userJson["show_gender"] = u["show_gender"].as<bool>();
userJson["show_age"] = u["show_age"].as<bool>();
userJson["created_at"] = u["created_at"].c_str();
userJson["updated_at"] = u["updated_at"].c_str();
}
// Rechte laden
std::string rightsQuery = "SELECT r.tr FROM chat.user_rights ur JOIN chat.rights r ON ur.chat_right_id = r.id WHERE ur.chat_user_id = " + std::to_string(userJson["id"].asInt()) + ";";
auto rightsResult = db->exec(rightsQuery);
Json::Value rights(Json::arrayValue);
for (const auto& r : rightsResult) {
rights.append(r["tr"].c_str());
}
userJson["rights"] = rights;
}
_user = Yc::Object::User(userJson);
_token = Yc::Lib::Tools::generateRandomString(32);
sendMsg(token, _token, "", "");
thread = std::make_unique<std::thread>(&ChatUser::checkerTask, this);
}
ChatUser::~ChatUser()
{
_parent->addMessage(ChatUser::system, std::string("leaved_chat"), std::string(_name), std::string(_color));
}
std::string ChatUser::name() const
{
return _name;
}
bool ChatUser::validateToken(std::string token)
{
return (token == _token);
}
bool ChatUser::isUser(std::shared_ptr<ChatUser> toValidate)
{
return (toValidate.get() == this);
}
void ChatUser::sendMsg(MsgType type, const char *message, std::string userName, std::string color)
{
sendMsg(type, std::string(message), userName, color);
}
void ChatUser::sendMsg(MsgType type, std::string message, std::string userName, std::string color)
{
Json::Value sendMessage;
sendMessage["type"] = type;
sendMessage["message"] = message;
sendMessage["userName"] = userName;
sendMessage["color"] = color;
send(sendMessage);
}
void ChatUser::sendMsg(MsgType type, Json::Value message, std::string userName, std::string color)
{
Json::Value sendMessage;
sendMessage["type"] = type;
sendMessage["message"] = message;
sendMessage["userName"] = userName;
sendMessage["color"] = color;
send(sendMessage);
}
void ChatUser::checkerTask()
{
while (!_stop)
{
fd_set readSd;
FD_ZERO(&readSd);
FD_SET(_socket, &readSd);
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
int selectResult = select(_socket + 1, &readSd, NULL, NULL, &tv);
if (selectResult == 1 && FD_ISSET(_socket, &readSd) == 1)
{
_parent->removeUser(_token);
_stop = true;
delete this;
return;
}
std::string msg = readSocket(_socket);
if (msg == "")
{
continue;
}
handleMessage(msg);
}
}
void ChatUser::stop()
{
_stop = true;
}
std::string ChatUser::color() const
{
return _color;
}
void ChatUser::setParent(std::shared_ptr<ChatRoom> parent)
{
_parent = std::move(parent);
}
void ChatUser::send(std::string out)
{
Base::send(_socket, out);
}
void ChatUser::send(Json::Value out)
{
Base::send(_socket, out);
}
void ChatUser::handleMessage(std::string message)
{
Json::Value jsonTree = getJsonTree(message);
if (jsonTree["token"].asString() != _token)
{
return;
}
if (jsonTree["type"].asString() == "message")
{
checkString(jsonTree["message"].asString());
}
else if (jsonTree["type"].asString() == "dice")
{
doDice();
}
else if (jsonTree["type"].asString() == "scream")
{
_parent->addMessage(ChatUser::scream, jsonTree["message"].asString(), std::string(_name), std::string(_color));
}
else if (jsonTree["type"].asString() == "do")
{
_parent->addMessage(ChatUser::dosomething, jsonTree["message"].asString(), std::string(_name), std::string(_color));
}
else if (jsonTree["type"].asString() == "join")
{
changeRoom(jsonTree["newroom"].asString(), jsonTree["password"].asString());
}
else if (jsonTree["type"].asString() == "userlist")
{
sendUserList();
}
}
void ChatUser::checkString(std::string message)
{
if (message.substr(0, 6) == "/join ")
{
message = message.substr(6);
if (message.find(" ") == std::string::npos)
{
changeRoom(message, "");
}
else
{
std::string room = message.substr(0, message.find(" "));
std::string password = message.substr(message.find(" ") + 1);
changeRoom(room, password);
}
}
else if (message == "/dice")
{
doDice();
}
else
{
_parent->addMessage(ChatUser::message, std::string(message), std::string(_name), std::string(_color));
}
}
void ChatUser::sendUserList()
{
Json::Value userList = _parent->userList();
Json::Value msg = Json::objectValue;
msg["userlist"] = userList;
sendMsg(userListe, msg, "", "");
}
void ChatUser::doDice()
{
switch (_parent->addDice(shared_from_this(), (rand() % 6) + 1))
{
case 1:
sendMsg(system, "dice_not_possible", "", "");
break;
case 2:
sendMsg(system, "dice_allready_done", "", "");
break;
default:
break;
}
}
void ChatUser::changeRoom(std::string newRoom, std::string password)
{
if (!_parent->userToNewRoom(shared_from_this(), newRoom, password))
{
sendMsg(ChatUser::system, "room_not_possible", "", "");
}
}
} // namespace Lib
} // namespace Yc