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

0
src/chat_room.cpp Normal file
View File

0
src/chat_room.h Normal file
View File

0
src/chat_user.cpp Normal file
View File

0
src/chat_user.h Normal file
View File

297
src/core/chat_room.cpp Executable file
View File

@@ -0,0 +1,297 @@
#include "chat_room.h"
#include <algorithm>
#include "server.h"
#include <unistd.h>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <utility>
#include <thread>
namespace Yc
{
namespace Lib
{
ChatRoom::ChatRoom(std::shared_ptr<Server> parent, Json::Value roomParams)
: _room(roomParams),
_parent(std::move(parent)),
_blocked(false),
_stop(false),
_roundRunning(false)
{
_name = roomParams["name"].asString();
_password = roomParams["password"].asString();
for (auto &user : roomParams["allowed"])
{
_allowedUsers.push_back(user.asString());
}
_type = (RoomType)roomParams["type"].asInt();
_roundLength = roomParams["roundlength"].asInt();
_lastRoundEnd = std::time(NULL);
thread = std::make_unique<std::thread>(&ChatRoom::run, this);
}
ChatRoom::~ChatRoom() = default;
void ChatRoom::run()
{
while (!_stop)
{
if (_msgQueue.size() > 0 && !_blocked)
{
_blocked = true;
while (_msgQueue.size() > 0)
{
Message message = _msgQueue.front();
for (auto &user : _users)
{
user->sendMsg(message.type, message.messageTr, message.userName, message.color);
}
_msgQueue.pop();
}
_blocked = false;
}
if ((_type & dice) == dice)
{
_handleDice();
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
bool ChatRoom::addUser(std::string _userName, std::string color, std::string _password, int socket)
{
if (_password != "" && _password == _password && std::find(std::begin(_allowedUsers), std::end(_allowedUsers), _userName) == std::end(_allowedUsers))
{
return false;
}
auto newUser = std::make_shared<ChatUser>(shared_from_this(), _userName, color, socket);
_users.push_back(newUser);
newUser->sendMsg(ChatUser::roomList, _parent->jsonRoomList(), "", "");
addMessage(ChatUser::system, "room_entered", newUser->name(), newUser->color());
_initRound();
return true;
}
bool ChatRoom::addUser(std::shared_ptr<ChatUser> user, std::string password)
{
if (password == _password)
{
_users.push_back(user);
user->setParent(shared_from_this());
_initRound();
return true;
}
return false;
}
bool ChatRoom::userNameExists(std::string userName)
{
for (const auto &user : _users)
{
if (user->name() == userName)
{
return true;
}
}
return false;
}
void ChatRoom::removeUser(std::string _token, bool silent)
{
for (auto it = _users.begin(); it != _users.end(); ++it)
{
if ((*it)->validateToken(_token))
{
if (!silent)
{
addMessage(ChatUser::system, (*it)->name(), (*it)->color());
}
_users.erase(it);
break;
}
}
}
void ChatRoom::removeUser(std::shared_ptr<ChatUser> userToRemove, bool silent)
{
for (auto it = _users.begin(); it != _users.end(); ++it)
{
if (*it == userToRemove)
{
if (!silent)
{
addMessage(ChatUser::system, (*it)->name(), (*it)->color());
}
_users.erase(it);
break;
}
}
}
void ChatRoom::setStop()
{
_stop = true;
}
void ChatRoom::addMessage(ChatUser::MsgType type, const char *messageText, std::string userName, std::string color)
{
addMessage(type, (std::string)messageText, userName, color);
}
void ChatRoom::addMessage(ChatUser::MsgType type, std::string messageText, std::string userName, std::string color)
{
Message message;
message.type = type;
message.messageTr = messageText;
message.userName = userName;
message.color = color;
_msgQueue.push(message);
}
void ChatRoom::addMessage(ChatUser::MsgType type, Json::Value messageText, std::string userName, std::string color)
{
addMessage(type, getJsonString(messageText), userName, color);
}
void ChatRoom::addUserWhenQueueEmpty(std::shared_ptr<ChatUser> user)
{
while (_msgQueue.size() > 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
_users.push_back(user);
user->setParent(shared_from_this());
}
bool ChatRoom::userToNewRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password)
{
return _parent->changeRoom(user, newRoom, password);
}
unsigned int ChatRoom::flags()
{
unsigned int value = (unsigned int)_type;
if (_password != "")
{
value += (unsigned int)ChatRoom::password;
}
return value;
}
Json::Value ChatRoom::userList()
{
Json::Value users = Json::arrayValue;
for (auto &user : _users)
{
Json::Value jsonUser = Json::objectValue;
jsonUser["name"] = user->name();
users.append(jsonUser);
}
return users;
}
ChatRoom::RoomType ChatRoom::type()
{
return _type;
}
bool ChatRoom::isType(ChatRoom::RoomType type)
{
return ((_type & type) == type);
}
bool ChatRoom::canDice()
{
return (_roundRunning || (_type & rounds) == rounds) && (_type & dice) == dice;
}
unsigned int ChatRoom::addDice(std::shared_ptr<ChatUser> user, int diceValue)
{
if (!canDice())
{
return 1;
}
for (auto &listUser : _diceValues)
{
if (listUser.first == user)
{
return 2;
}
}
_diceValues.push_back(std::make_pair(user, diceValue));
addMessage(ChatUser::dice, std::to_string(diceValue), user->name(), user->color());
return 0;
}
bool ChatRoom::accessAllowed(std::string userName, std::string password)
{
return (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end());
}
bool ChatRoom::userIsInRoom(std::string userName)
{
for (auto &user : _users)
{
if (userName == user->name())
{
return true;
}
}
return false;
}
void ChatRoom::_handleDice()
{
if (((_type & rounds) == rounds))
{
if (_roundRunning && (_users.size() < 2 || _roundStart + _roundLength >= time(NULL)))
{
_lastRoundEnd = time(NULL);
_roundRunning = false;
addMessage(ChatUser::system, "round_ends");
_showDiceRoundResults();
}
else if (!_roundRunning && _lastRoundEnd <= time(NULL) - 15 && _users.size() >= 2)
{
_roundStart = time(NULL);
_roundRunning = true;
_diceValues.clear();
addMessage(ChatUser::system, "next_round_starts_now");
}
}
}
void ChatRoom::_initRound()
{
if (_users.size() == 2)
{
_lastRoundEnd = time(NULL);
addMessage(ChatUser::system, "next_round_starts_soon");
}
}
void ChatRoom::_showDiceRoundResults()
{
std::sort(_diceValues.begin(), _diceValues.end(), [=](const std::pair<std::shared_ptr<ChatUser>, int> &val1, const std::pair<std::shared_ptr<ChatUser>, int> &val2)
{ return (val1.second > val2.second); });
Json::Value userList = Json::arrayValue;
for (auto &user : _diceValues)
{
Json::Value entry = Json::objectValue;
entry["name"] = user.first->name();
entry["value"] = user.second;
userList.append(entry);
}
addMessage(ChatUser::result, userList);
}
std::string ChatRoom::name()
{
return _name;
}
} // namespace Lib
} // namespace Yc

96
src/core/chat_room.h Executable file
View File

@@ -0,0 +1,96 @@
// renamed from room.h
#ifndef YC_LIB_CHAT_ROOM_H
#define YC_LIB_CHAT_ROOM_H
#include <vector>
#include <string>
#include <queue>
#include <thread>
#include <future>
#include <utility>
#include <vector>
#include "lib/base.h"
#include "chat_user.h"
#include "object/room.h"
namespace Yc
{
namespace Lib
{
class Server;
class ChatRoom : public Base, public std::enable_shared_from_this<ChatRoom>
{
public:
enum RoomType
{
none = 0,
dice = 1,
poker = 2,
rounds = 4,
password = 8
};
ChatRoom(std::shared_ptr<Server> parent, Json::Value roomParams);
~ChatRoom();
std::shared_ptr<Server> getServer() const { return _parent; }
void run();
std::string name();
bool addUser(std::string userName, std::string color, std::string password, int socket);
bool addUser(std::shared_ptr<ChatUser> user, std::string password);
bool userNameExists(std::string userName);
void removeUser(std::string _token, bool silent = false);
void removeUser(std::shared_ptr<ChatUser> user, bool silent = false);
void setStop();
void addMessage(ChatUser::MsgType type, const char *messageText, std::string userName = "", std::string color = "");
void addMessage(ChatUser::MsgType type, std::string messageText, std::string userName = "", std::string color = "");
void addMessage(ChatUser::MsgType type, Json::Value messageText, std::string userName = "", std::string color = "");
RoomType type();
bool isType(RoomType type);
bool canDice();
unsigned int addDice(std::shared_ptr<ChatUser> user, int diceValue);
bool accessAllowed(std::string userName, std::string password);
bool userIsInRoom(std::string userName);
void addUserWhenQueueEmpty(std::shared_ptr<ChatUser> user);
bool userToNewRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password);
unsigned int flags();
Json::Value userList();
private:
struct Message
{
ChatUser::MsgType type;
std::string messageTr;
std::string userName;
std::string color;
};
std::shared_ptr<Server> _parent;
std::string _name;
std::string _password;
std::vector<std::string> _allowedUsers;
RoomType _type;
std::vector<std::shared_ptr<ChatUser>> _users;
bool _blocked;
bool _stop;
std::queue<Message> _msgQueue;
std::unique_ptr<std::thread> thread;
bool _roundRunning;
time_t _roundStart;
time_t _lastRoundEnd;
int _roundLength;
std::vector<std::pair<std::shared_ptr<ChatUser>, int>> _diceValues;
Yc::Object::Room _room;
void _handleDice();
void _startDiceRound();
void _endDiceRound();
void _initRound();
void _showDiceRoundResults();
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_CHAT_ROOM_H

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

71
src/core/chat_user.h Normal file
View File

@@ -0,0 +1,71 @@
#ifndef YC_LIB_CHAT_USER_H
#define YC_LIB_CHAT_USER_H
#include <memory>
#include <string>
#include <set>
#include <json/json.h>
#include <thread>
#include "lib/base.h"
#include "object/user.h"
namespace Yc
{
namespace Lib
{
class ChatRoom;
class ChatUser : public Base, public std::enable_shared_from_this<ChatUser>
{
public:
enum MsgType
{
error = -1,
token = 1,
userListe = 2,
roomList = 3,
message = 4,
system = 5,
scream = 6,
dosomething = 7,
dice = 8,
result = 9
};
ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket);
~ChatUser();
std::string name() const;
bool validateToken(std::string token);
bool isUser(std::shared_ptr<ChatUser> toValidate);
void sendMsg(MsgType type, std::string message, std::string userName, std::string color);
void sendMsg(MsgType type, const char *message, std::string userName, std::string color);
void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color);
void checkerTask();
void stop();
std::string color() const;
void setParent(std::shared_ptr<ChatRoom> parent);
private:
std::shared_ptr<ChatRoom> _parent;
Yc::Object::User _user;
std::string _name;
std::string _color;
int _socket;
std::string _token;
bool _stop;
std::unique_ptr<std::thread> thread;
void send(std::string out);
void send(Json::Value out);
void handleMessage(std::string message);
void doDice();
void changeRoom(std::string newRoom, std::string password);
void checkString(std::string message);
void sendUserList();
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_CHAT_USER_H

31
src/core/config.cpp Executable file
View File

@@ -0,0 +1,31 @@
#include "config.h"
#include <json/json.h>
#include <fstream>
#include <iostream>
namespace Yc {
namespace Lib {
Config::Config() {
loadConfig();
}
void Config::loadConfig() {
std::ifstream configStream("/etc/yourpart/chatconfig.json", std::ifstream::binary);
configStream >> jsonConfig;
}
Json::Value Config::value(std::string groupName, std::string field) {
if (jsonConfig[groupName].isNull()) {
return "";
}
return jsonConfig[groupName][field];
}
Json::Value Config::group(std::string groupName) {
return jsonConfig[groupName];
}
} // namespace Lib
} // namespace Yc

24
src/core/config.h Executable file
View File

@@ -0,0 +1,24 @@
#ifndef YC_LIB_CONFIG_H
#define YC_LIB_CONFIG_H
#include <string>
#include <json/value.h>
namespace Yc {
namespace Lib {
class Config
{
public:
Config();
void loadConfig();
Json::Value value(std::string group, std::string field);
Json::Value group(std::string groupName);
private:
Json::Value jsonConfig;
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_CONFIG_H

201
src/core/server.cpp Executable file
View File

@@ -0,0 +1,201 @@
#include "server.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <thread>
#include <future>
#include <functional>
#include <unistd.h>
#include <json/json.h>
#include <iostream>
#include <netinet/tcp.h>
#include <sstream>
namespace Yc {
namespace Lib {
Server::Server(std::shared_ptr<Yc::Lib::Config> config, std::shared_ptr<Yc::Lib::Database> database) :
_config(std::move(config)),
_database(std::move(database)),
_stop(false) {
struct sockaddr_in serverAddr;
int opt = true;
_socket = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
int flags = 1;
setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(1235);
if (bind(_socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
std::cout << "bind not possible" << std::endl;
exit(-1);
}
}
void Server::run() {
if (listen(_socket, 5) < 0) {
std::cout << "listen not possible" << std::endl;
exit(-1);
}
timeval origTv;
origTv.tv_sec = 5;
origTv.tv_usec = 0;
int _maxSd = _socket;
while (!_stop) {
timeval tv(origTv);
fd_set fd;
FD_ZERO(&fd);
FD_SET(_socket, &fd);
if (select(_maxSd + 1, &fd, NULL, NULL, &tv) > 0) {
std::thread(&Server::handleRequest, this).detach();
}
}
}
std::vector<std::string> Server::roomList() {
std::vector<std::string> list;
for (const auto &room: _rooms) {
list.push_back(room->name());
}
return list;
}
Json::Value Server::jsonRoomList() {
Json::Value list;
for (auto &room: _rooms) {
Json::Value roomJson = Json::objectValue;
roomJson["name"] = room->name();
roomJson["flags"] = room->flags();
list.append(roomJson);
}
return list;
}
bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){
for (auto &room: _rooms) {
if (room->name() == roomName && room->accessAllowed(userName, password)) {
return true;
}
}
return false;
}
bool Server::changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password) {
if (!roomAllowed(newRoom, user->name(), password)) {
return false;
}
Json::Value userMsg = Json::objectValue;
userMsg["tr"] = "room_change_user";
userMsg["to"] = newRoom;
for (auto &room: _rooms) {
if (room->userIsInRoom(user->name())) {
room->removeUser(user);
Json::Value msg = Json::objectValue;
msg["tr"] = "room_change_to";
msg["to"] = newRoom;
userMsg["from"] = room->name();
room->addMessage(ChatUser::system, msg, user->name(), user->color());
}
}
user->sendMsg(ChatUser::system, userMsg, "", "");
for (auto &room: _rooms) {
if (room->name() == newRoom) {
Json::Value msg = Json::objectValue;
msg["tr"] = "room_change_to";
msg["from"] = userMsg["from"];
room->addMessage(ChatUser::system, msg, user->name(), user->color());
room->addUserWhenQueueEmpty(user);
}
}
return true;
}
void Server::createRooms(Json::Value roomList) {
// Ignoriere roomList, lade stattdessen aus der Datenbank
auto self = shared_from_this();
std::string query = R"(
SELECT r.id, r.title, r.password_hash, r.room_type_id, r.is_public, r.owner_id, r.min_age, r.max_age, r.created_at, r.updated_at, rt.tr as room_type
FROM chat.room r
LEFT JOIN chat.room_type rt ON r.room_type_id = rt.id
)";
auto result = _database->exec(query);
for (const auto& row : result) {
Json::Value room;
room["id"] = row["id"].as<int>();
room["name"] = row["title"].c_str();
room["password"] = row["password_hash"].is_null() ? "" : row["password_hash"].c_str();
room["type"] = row["room_type_id"].is_null() ? 0 : row["room_type_id"].as<int>();
room["is_public"] = row["is_public"].as<bool>();
room["owner_id"] = row["owner_id"].is_null() ? 0 : row["owner_id"].as<int>();
room["min_age"] = row["min_age"].is_null() ? 0 : row["min_age"].as<int>();
room["max_age"] = row["max_age"].is_null() ? 0 : row["max_age"].as<int>();
room["created_at"] = row["created_at"].c_str();
room["updated_at"] = row["updated_at"].c_str();
room["room_type"] = row["room_type"].is_null() ? "" : row["room_type"].c_str();
// Platzhalter für Felder, die im Konstruktor benötigt werden
room["allowed"] = Json::arrayValue; // ggf. später befüllen
room["roundlength"] = 60; // Default-Wert
auto newRoom = std::make_shared<ChatRoom>(self, room);
_rooms.push_back(newRoom);
}
}
void Server::handleRequest() {
struct sockaddr_in sockAddr;
socklen_t sockAddrLen = sizeof(sockAddr);
int userSock = accept(_socket, (struct sockaddr *)&sockAddr, &sockAddrLen);
if (userSock < 0) {
return;
}
int flags = 1;
setsockopt(userSock, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
std::string msg = readSocket(userSock);
if (msg == "") {
return;
}
inputSwitcher(userSock, msg);
}
void Server::inputSwitcher(int userSocket, std::string input) {
Json::Value inputTree = getJsonTree(input);
if (inputTree["type"] == "init") {
initUser(userSocket, inputTree);
}
}
bool Server::userExists(std::string userName) {
for (const auto &room: _rooms) {
if (room->userNameExists(userName)) {
return true;
}
}
return false;
}
void Server::initUser(int userSocket, Json::Value data) {
if (userExists(data["name"].asString())) {
Json::Value errorJson;
errorJson["type"] = ChatUser::error;
errorJson["message"] = "loggedin";
send(userSocket, errorJson);
close(userSocket);
return;
}
std::string roomName = data["room"].asString();
bool added(false);
for (auto &room: _rooms) {
if (room->name() == roomName) {
if (room->addUser(data["name"].asString(), data["color"].asString(), data["password"].asString(), userSocket)) {
added = true;
break;
}
}
}
if (!added) {
close(userSocket);
}
}
} // namespace Lib
} // namespace Yp

39
src/core/server.h Executable file
View File

@@ -0,0 +1,39 @@
#ifndef YP_LIB_SERVER_H
#define YP_LIB_SERVER_H
#include "config.h"
#include "lib/database.h"
#include <vector>
#include "chat_room.h"
#include "lib/base.h"
#include <json/value.h>
namespace Yc {
namespace Lib {
class Server: public Base, public std::enable_shared_from_this<Server>
{
public:
Server(std::shared_ptr<Yc::Lib::Config> config, std::shared_ptr<Yc::Lib::Database> database);
void run();
std::vector<std::string> roomList();
Json::Value jsonRoomList();
bool roomAllowed(std::string roomName, std::string userName, std::string password);
bool changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password);
int _socket;
std::shared_ptr<Yc::Lib::Config> _config;
std::shared_ptr<Yc::Lib::Database> _database;
bool _stop;
std::vector<std::shared_ptr<Yc::Lib::ChatRoom>> _rooms;
void createRooms(Json::Value roomList);
private:
void handleRequest();
void inputSwitcher(int userSocket, std::string input);
bool userExists(std::string userName);
void initUser(int userSocket, Json::Value data);
};
} // namespace Lib
} // namespace Yp
#endif // YP_LIB_SERVER_H

53
src/lib/base.cpp Executable file
View File

@@ -0,0 +1,53 @@
#include "base.h"
#include <sstream>
#include <strings.h>
#include <unistd.h>
#include <sys/socket.h>
#include <memory>
#include <iostream>
namespace Yc {
namespace Lib {
std::string Base::getJsonString(Json::Value json) {
std::string outString;
std::stringstream outStream;
outStream << json;
return outStream.str();
}
void Base::send(int socket, std::string out) {
write(socket, out.c_str(), out.length());
}
void Base::send(int socket, Json::Value out) {
std::string outString = getJsonString(out);
send(socket, outString);
}
std::string Base::readSocket(int socket)
{
std::string msg("");
char buffer[256];
bzero(buffer, 256);
while (int received = recv(socket, buffer, 255, 0) > 0) {
msg += std::string(buffer);
if (received < 255) {
break;
}
}
return msg;
}
Json::Value Base::getJsonTree(std::string msg) {
Json::Value inputTree;
Json::CharReaderBuilder rbuilder;
std::unique_ptr<Json::CharReader> const reader(rbuilder.newCharReader());
JSONCPP_STRING inputJsonString(msg);
reader->parse(inputJsonString.data(), inputJsonString.data() + inputJsonString.size(), &inputTree, NULL);
return inputTree;
}
} // namespace Lib
} // namespace Yc

21
src/lib/base.h Executable file
View File

@@ -0,0 +1,21 @@
#ifndef YC_LIB_BASE_H
#define YC_LIB_BASE_H
#include <json/json.h>
namespace Yc {
namespace Lib {
class Base {
protected:
std::string getJsonString(Json::Value json);
void send(int socket, std::string out);
void send(int socket, Json::Value out);
std::string readSocket(int socket);
Json::Value getJsonTree(std::string msg);
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_BASE_H

42
src/lib/database.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "database.h"
#include <pqxx/pqxx>
#include <stdexcept>
namespace Yc {
namespace Lib {
Database::Database(std::shared_ptr<Config> config)
{
// Hole Verbindungsdaten aus der Config
std::string dbname = config->value("database", "database").asString();
std::string user = config->value("database", "user").asString();
std::string password = config->value("database", "password").asString();
std::string host = config->value("database", "host").asString();
std::string conninfo =
"dbname=" + dbname +
" user=" + user +
" password=" + password +
" host=" + host;
try {
_connection = std::make_unique<pqxx::connection>(conninfo);
if (!_connection->is_open()) {
throw std::runtime_error("Failed to open database connection");
}
} catch (const std::exception& e) {
throw std::runtime_error(std::string("Database connection error: ") + e.what());
}
}
// Beispielmethode für eine Abfrage
pqxx::result Database::exec(const std::string& query)
{
pqxx::work txn(*_connection);
pqxx::result r = txn.exec(query);
txn.commit();
return r;
}
} // namespace Lib
} // namespace Yc

23
src/lib/database.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef YC_LIB_DATABASE_H
#define YC_LIB_DATABASE_H
#include <memory>
#include <pqxx/pqxx>
#include "../core/config.h"
namespace Yc {
namespace Lib {
class Database {
public:
Database(std::shared_ptr<Config> config);
~Database() = default;
pqxx::result exec(const std::string& query);
private:
std::unique_ptr<pqxx::connection> _connection;
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_DATABASE_H

33
src/lib/tools.cpp Executable file
View File

@@ -0,0 +1,33 @@
#include "tools.h"
#include <random>
#include <string>
namespace Yc {
namespace Lib {
Tools::Tools() {
}
std::string Tools::generateRandomString(size_t length) {
std::string choices(
"0123456789"
"`~!@#$%^&*()-_=+[{]}|\\:;<,>./?"
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
);
std::random_device random;
std::mt19937 generator(random());
std::uniform_int_distribution<size_t> distribution(0, choices.size());
std::string result(length, '0');
for (size_t i = 0; i < length; ++i) {
result[i] = choices[distribution(generator)];
}
return result;
}
} // namespace Lib
} // namespace Yp

19
src/lib/tools.h Executable file
View File

@@ -0,0 +1,19 @@
#ifndef YP_LIB_TOOLS_H
#define YP_LIB_TOOLS_H
#include <string>
namespace Yc {
namespace Lib {
class Tools
{
public:
Tools();
static std::string generateRandomString(size_t length = 16);
};
} // namespace Lib
} // namespace Yp
#endif // YP_LIB_TOOLS_H

14
src/main.cpp Executable file
View File

@@ -0,0 +1,14 @@
#include "core/config.h"
#include "core/server.h"
#include "lib/database.h"
// main function
int main(int, char **) {
auto config = std::make_shared<Yc::Lib::Config>();
auto database = std::make_shared<Yc::Lib::Database>(config);
auto server = std::make_shared<Yc::Lib::Server>(config, database);
server->createRooms(config->group("rooms"));
server->run();
return 0;
}

42
src/object/room.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "room.h"
namespace Yc
{
namespace Object
{
Room::Room(const Json::Value &roomJson)
{
_id = roomJson.isMember("id") && !roomJson["id"].isNull() ? roomJson["id"].asInt() : 0;
_title = roomJson.isMember("name") && !roomJson["name"].isNull() ? roomJson["name"].asString() : "";
_owner_id = roomJson.isMember("owner_id") && !roomJson["owner_id"].isNull() ? roomJson["owner_id"].asInt() : 0;
_is_public = roomJson.isMember("is_public") && !roomJson["is_public"].isNull() ? roomJson["is_public"].asBool() : true;
_gender_restriction_id = roomJson.isMember("gender_restriction_id") && !roomJson["gender_restriction_id"].isNull() ? roomJson["gender_restriction_id"].asInt() : 0;
_min_age = roomJson.isMember("min_age") && !roomJson["min_age"].isNull() ? roomJson["min_age"].asInt() : 0;
_max_age = roomJson.isMember("max_age") && !roomJson["max_age"].isNull() ? roomJson["max_age"].asInt() : 0;
_password_hash = roomJson.isMember("password") && !roomJson["password"].isNull() ? roomJson["password"].asString() : "";
_friends_of_owner_only = roomJson.isMember("friends_of_owner_only") && !roomJson["friends_of_owner_only"].isNull() ? roomJson["friends_of_owner_only"].asBool() : false;
_required_user_right_id = roomJson.isMember("required_user_right_id") && !roomJson["required_user_right_id"].isNull() ? roomJson["required_user_right_id"].asInt() : 0;
_created_at = roomJson.isMember("created_at") && !roomJson["created_at"].isNull() ? roomJson["created_at"].asString() : "";
_updated_at = roomJson.isMember("updated_at") && !roomJson["updated_at"].isNull() ? roomJson["updated_at"].asString() : "";
_room_type_id = roomJson.isMember("type") && !roomJson["type"].isNull() ? roomJson["type"].asInt() : 0;
_room_type_tr = roomJson.isMember("room_type") && !roomJson["room_type"].isNull() ? roomJson["room_type"].asString() : "";
}
int Room::id() const { return _id; }
const std::string &Room::title() const { return _title; }
int Room::owner_id() const { return _owner_id; }
bool Room::is_public() const { return _is_public; }
int Room::gender_restriction_id() const { return _gender_restriction_id; }
int Room::min_age() const { return _min_age; }
int Room::max_age() const { return _max_age; }
const std::string &Room::password_hash() const { return _password_hash; }
bool Room::friends_of_owner_only() const { return _friends_of_owner_only; }
int Room::required_user_right_id() const { return _required_user_right_id; }
const std::string &Room::created_at() const { return _created_at; }
const std::string &Room::updated_at() const { return _updated_at; }
int Room::room_type_id() const { return _room_type_id; }
const std::string &Room::room_type_tr() const { return _room_type_tr; }
} // namespace Object
} // namespace Yc

48
src/object/room.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <json/value.h>
namespace Yc
{
namespace Object
{
class Room
{
public:
Room(const Json::Value &roomJson);
int id() const;
const std::string &title() const;
int owner_id() const;
bool is_public() const;
int gender_restriction_id() const;
int min_age() const;
int max_age() const;
const std::string &password_hash() const;
bool friends_of_owner_only() const;
int required_user_right_id() const;
const std::string &created_at() const;
const std::string &updated_at() const;
int room_type_id() const;
const std::string &room_type_tr() const;
private:
int _id = 0;
std::string _title;
int _owner_id = 0;
bool _is_public = true;
int _gender_restriction_id = 0;
int _min_age = 0;
int _max_age = 0;
std::string _password_hash;
bool _friends_of_owner_only = false;
int _required_user_right_id = 0;
std::string _created_at;
std::string _updated_at;
int _room_type_id = 0;
std::string _room_type_tr;
};
} // namespace Object
} // namespace Yc

37
src/object/user.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "user.h"
namespace Yc
{
namespace Object
{
User::User() = default;
User::User(const Json::Value& userJson) {
_id = userJson.isMember("id") && !userJson["id"].isNull() ? userJson["id"].asInt() : 0;
_falukant_user_id = userJson.isMember("falukant_user_id") && !userJson["falukant_user_id"].isNull() ? userJson["falukant_user_id"].asInt() : 0;
_display_name = userJson.isMember("display_name") && !userJson["display_name"].isNull() ? userJson["display_name"].asString() : "";
_color = userJson.isMember("color") && !userJson["color"].isNull() ? userJson["color"].asString() : "#000000";
_show_gender = userJson.isMember("show_gender") && !userJson["show_gender"].isNull() ? userJson["show_gender"].asBool() : true;
_show_age = userJson.isMember("show_age") && !userJson["show_age"].isNull() ? userJson["show_age"].asBool() : true;
_created_at = userJson.isMember("created_at") && !userJson["created_at"].isNull() ? userJson["created_at"].asString() : "";
_updated_at = userJson.isMember("updated_at") && !userJson["updated_at"].isNull() ? userJson["updated_at"].asString() : "";
if (userJson.isMember("rights") && userJson["rights"].isArray()) {
for (const auto& r : userJson["rights"]) {
_rights.push_back(r.asString());
}
}
}
int User::id() const { return _id; }
int User::falukant_user_id() const { return _falukant_user_id; }
const std::string& User::display_name() const { return _display_name; }
const std::string& User::color() const { return _color; }
bool User::show_gender() const { return _show_gender; }
bool User::show_age() const { return _show_age; }
const std::string& User::created_at() const { return _created_at; }
const std::string& User::updated_at() const { return _updated_at; }
const std::vector<std::string>& User::rights() const { return _rights; }
} // namespace Object
} // namespace Yc

39
src/object/user.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <vector>
#include <json/value.h>
namespace Yc
{
namespace Object
{
class User {
public:
User();
User(const Json::Value& userJson);
int id() const;
int falukant_user_id() const;
const std::string& display_name() const;
const std::string& color() const;
bool show_gender() const;
bool show_age() const;
const std::string& created_at() const;
const std::string& updated_at() const;
const std::vector<std::string>& rights() const;
private:
int _id = 0;
int _falukant_user_id = 0;
std::string _display_name;
std::string _color = "#000000";
bool _show_gender = true;
bool _show_age = true;
std::string _created_at;
std::string _updated_at;
std::vector<std::string> _rights;
};
} // namespace Object
} // namespace Yc

0
src/server.h Normal file
View File