Füge UndergroundWorker hinzu und implementiere Logik für unterirdische Aufgaben. Aktualisiere CMakeLists.txt, um neue Quell- und Header-Dateien einzuschließen. Verbessere die Fehlerbehandlung in der Datenbank und sende Benachrichtigungen nach bestimmten Ereignissen. Integriere Hilfsfunktionen zur sicheren Verarbeitung von Daten.
This commit is contained in:
committed by
Torsten (PC)
parent
1451225978
commit
23c07a3570
@@ -63,7 +63,9 @@ set(HEADERS
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Define executable target
|
# Define executable target
|
||||||
add_executable(yourpart-daemon ${SOURCES} ${HEADERS})
|
add_executable(yourpart-daemon ${SOURCES} ${HEADERS}
|
||||||
|
src/utils.h src/utils.cpp
|
||||||
|
src/underground_worker.h src/underground_worker.cpp)
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(yourpart-daemon PRIVATE
|
target_include_directories(yourpart-daemon PRIVATE
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE QtCreatorProject>
|
<!DOCTYPE QtCreatorProject>
|
||||||
<!-- Written by QtCreator 17.0.0, 2025-07-21T14:02:18. -->
|
<!-- Written by QtCreator 17.0.0, 2025-08-16T22:07:06. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
@@ -104,14 +104,14 @@
|
|||||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_BUILD_TYPE:STRING=Release
|
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||||
|
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
|
||||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
|
||||||
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
|
||||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||||
|
-DCMAKE_BUILD_TYPE:STRING=Release
|
||||||
|
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build/</value>
|
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build/</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||||
@@ -229,14 +229,14 @@
|
|||||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_BUILD_TYPE:STRING=Debug
|
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||||
|
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
|
||||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
|
||||||
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
|
||||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||||
|
-DCMAKE_BUILD_TYPE:STRING=Debug
|
||||||
|
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||||
<value type="QString" key="CMake.Source.Directory">/mnt/share/torsten/Programs/yourpart-daemon</value>
|
<value type="QString" key="CMake.Source.Directory">/mnt/share/torsten/Programs/yourpart-daemon</value>
|
||||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build</value>
|
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/torsten/Programs/yourpart-daemon/build</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||||
|
|||||||
203
deploy.sh
203
deploy.sh
@@ -1,36 +1,183 @@
|
|||||||
|
# YourPart Daemon Deployment Script für Ubuntu 22
|
||||||
|
# Verwendung: ./deploy.sh [server_ip] [ssh_user]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Farben für Output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
SERVER_IP="${1:-your-part.de}"
|
||||||
|
SSH_USER="${2:-root}"
|
||||||
|
DAEMON_USER="yourpart"
|
||||||
|
PROJECT_NAME="yourpart-daemon"
|
||||||
|
REMOTE_DIR="/opt/yourpart"
|
||||||
|
SERVICE_NAME="yourpart-daemon"
|
||||||
|
|
||||||
|
# Funktionen
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prüfe ob wir im richtigen Verzeichnis sind
|
||||||
|
if [ ! -f "CMakeLists.txt" ] || [ ! -f "daemon.conf" ]; then
|
||||||
|
log_error "Bitte führen Sie dieses Script aus dem Projektverzeichnis aus!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Starte Deployment für YourPart Daemon..."
|
||||||
|
log_info "Server: $SERVER_IP"
|
||||||
|
log_info "SSH User: $SSH_USER"
|
||||||
|
|
||||||
|
# 1. Lokales Build
|
||||||
|
log_info "Baue Projekt lokal..."
|
||||||
|
if [ ! -d "build" ]; then
|
||||||
|
mkdir build
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
make -j$(nproc)
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
log_success "Lokaler Build abgeschlossen"
|
||||||
|
|
||||||
|
# 2. Erstelle Deployment-Paket
|
||||||
|
log_info "Erstelle Deployment-Paket..."
|
||||||
|
DEPLOY_DIR="deploy_package"
|
||||||
|
rm -rf "$DEPLOY_DIR"
|
||||||
|
mkdir -p "$DEPLOY_DIR"
|
||||||
|
|
||||||
|
# Kopiere Binärdatei
|
||||||
|
cp build/yourpart-daemon "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# Kopiere Konfigurationsdatei
|
||||||
|
cp daemon.conf "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# Kopiere Service-Datei
|
||||||
|
cp yourpart-daemon.service "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# Erstelle Installations-Script
|
||||||
|
cat > "$DEPLOY_DIR/install.sh" << 'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
echo "=== YourPart Deployment Script ==="
|
# Farben
|
||||||
echo ""
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
# Prüfen ob wir im richtigen Verzeichnis sind
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||||
if [ ! -f "package.json" ]; then
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||||
echo "Error: Please run this script from the YourPart3 root directory"
|
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||||
exit 1
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
DAEMON_USER="yourpart"
|
||||||
|
REMOTE_DIR="/opt/yourpart"
|
||||||
|
SERVICE_NAME="yourpart-daemon"
|
||||||
|
|
||||||
|
log_info "Installiere YourPart Daemon..."
|
||||||
|
|
||||||
|
# Erstelle Benutzer falls nicht vorhanden
|
||||||
|
if ! id "$DAEMON_USER" &>/dev/null; then
|
||||||
|
log_info "Erstelle Benutzer $DAEMON_USER..."
|
||||||
|
useradd --system --shell /bin/false --home-dir "$REMOTE_DIR" --create-home "$DAEMON_USER"
|
||||||
|
log_success "Benutzer $DAEMON_USER erstellt"
|
||||||
|
else
|
||||||
|
log_info "Benutzer $DAEMON_USER existiert bereits"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Prüfen ob sudo verfügbar ist
|
# Erstelle Verzeichnisse
|
||||||
if ! command -v sudo &> /dev/null; then
|
log_info "Erstelle Verzeichnisse..."
|
||||||
echo "Error: sudo is required but not installed"
|
mkdir -p "$REMOTE_DIR"/{logs,config}
|
||||||
exit 1
|
mkdir -p /etc/yourpart
|
||||||
fi
|
mkdir -p /var/log/yourpart
|
||||||
|
|
||||||
# Backend deployen
|
# Kopiere Dateien
|
||||||
echo ""
|
log_info "Kopiere Dateien..."
|
||||||
echo "=== Deploying Backend ==="
|
cp yourpart-daemon /usr/local/bin/
|
||||||
./deploy-backend.sh
|
cp daemon.conf /etc/yourpart/
|
||||||
|
cp yourpart-daemon.service /etc/systemd/system/
|
||||||
|
|
||||||
# Frontend bauen und deployen
|
# Setze Berechtigungen
|
||||||
echo ""
|
chmod +x /usr/local/bin/yourpart-daemon
|
||||||
echo "=== Building and Deploying Frontend ==="
|
chown -R "$DAEMON_USER:$DAEMON_USER" "$REMOTE_DIR"
|
||||||
./deploy-frontend.sh
|
chown -R "$DAEMON_USER:$DAEMON_USER" /var/log/yourpart
|
||||||
|
chmod 600 /etc/yourpart/daemon.conf
|
||||||
|
|
||||||
echo ""
|
# Lade systemd neu
|
||||||
echo "=== Deployment Completed! ==="
|
log_info "Lade systemd Konfiguration neu..."
|
||||||
echo "Your application should now be available at:"
|
systemctl daemon-reload
|
||||||
echo " HTTP: http://your-part.de (redirects to HTTPS)"
|
|
||||||
echo " HTTPS: https://www.your-part.de"
|
# Aktiviere Service
|
||||||
echo ""
|
log_info "Aktiviere Service..."
|
||||||
echo "To check logs:"
|
systemctl enable "$SERVICE_NAME"
|
||||||
echo " Backend: sudo journalctl -u yourpart.service -f"
|
|
||||||
echo " Apache: sudo tail -f /var/log/apache2/yourpart.*.log"
|
log_success "Installation abgeschlossen!"
|
||||||
|
log_info "Verwenden Sie 'systemctl start $SERVICE_NAME' um den Service zu starten"
|
||||||
|
log_info "Verwenden Sie 'systemctl status $SERVICE_NAME' um den Status zu prüfen"
|
||||||
|
log_info "Verwenden Sie 'journalctl -u $SERVICE_NAME -f' um die Logs zu verfolgen"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$DEPLOY_DIR/install.sh"
|
||||||
|
|
||||||
|
# Erstelle Tarball
|
||||||
|
tar -czf "${PROJECT_NAME}_deploy.tar.gz" -C "$DEPLOY_DIR" .
|
||||||
|
|
||||||
|
log_success "Deployment-Paket erstellt: ${PROJECT_NAME}_deploy.tar.gz"
|
||||||
|
|
||||||
|
# 3. Upload zum Server
|
||||||
|
log_info "Lade Dateien zum Server hoch..."
|
||||||
|
scp "${PROJECT_NAME}_deploy.tar.gz" "$SSH_USER@$SERVER_IP:/tmp/"
|
||||||
|
|
||||||
|
# 4. Installation auf dem Server
|
||||||
|
log_info "Installiere auf dem Server..."
|
||||||
|
ssh "$SSH_USER@$SERVER_IP" << EOF
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Entpacke Deployment-Paket
|
||||||
|
cd /tmp
|
||||||
|
tar -xzf "${PROJECT_NAME}_deploy.tar.gz"
|
||||||
|
|
||||||
|
# Führe Installation aus
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# Starte Service
|
||||||
|
systemctl start $SERVICE_NAME
|
||||||
|
|
||||||
|
# Prüfe Status
|
||||||
|
systemctl status $SERVICE_NAME --no-pager
|
||||||
|
|
||||||
|
# Aufräumen
|
||||||
|
rm -f "${PROJECT_NAME}_deploy.tar.gz"
|
||||||
|
rm -rf /tmp/yourpart-daemon /tmp/daemon.conf /tmp/yourpart-daemon.service /tmp/install.sh
|
||||||
|
|
||||||
|
echo "Deployment erfolgreich abgeschlossen!"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 5. Aufräumen
|
||||||
|
log_info "Räume lokale Dateien auf..."
|
||||||
|
rm -rf "$DEPLOY_DIR"
|
||||||
|
rm -f "${PROJECT_NAME}_deploy.tar.gz"
|
||||||
|
|
||||||
|
log_success "Deployment erfolgreich abgeschlossen!"
|
||||||
|
log_info "Der YourPart Daemon läuft jetzt auf $SERVER_IP"
|
||||||
|
log_info "Verwenden Sie 'ssh $SSH_USER@$SERVER_IP systemctl status $SERVICE_NAME' um den Status zu prüfen"
|
||||||
|
|||||||
159
install-dependencies.sh
Normal file
159
install-dependencies.sh
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# YourPart Daemon Dependencies Installation Script für Ubuntu 22
|
||||||
|
# Führen Sie dieses Script auf dem Server aus, bevor Sie das Deployment durchführen
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Farben für Output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info "Installiere Dependencies für YourPart Daemon auf Ubuntu 22..."
|
||||||
|
|
||||||
|
# Update Package Lists
|
||||||
|
log_info "Aktualisiere Paketlisten..."
|
||||||
|
apt update
|
||||||
|
|
||||||
|
# Installiere Build-Tools
|
||||||
|
log_info "Installiere Build-Tools..."
|
||||||
|
apt install -y \
|
||||||
|
build-essential \
|
||||||
|
cmake \
|
||||||
|
pkg-config \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
wget
|
||||||
|
|
||||||
|
# Installiere C++ Compiler (Ubuntu 22 hat GCC 11, aber wir brauchen GCC 15)
|
||||||
|
log_info "Installiere GCC 15..."
|
||||||
|
apt install -y software-properties-common
|
||||||
|
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||||
|
apt update
|
||||||
|
apt install -y gcc-15 g++-15
|
||||||
|
|
||||||
|
# Setze GCC 15 als Standard
|
||||||
|
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 100
|
||||||
|
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 100
|
||||||
|
|
||||||
|
# Installiere PostgreSQL Development Libraries
|
||||||
|
log_info "Installiere PostgreSQL Development Libraries..."
|
||||||
|
apt install -y \
|
||||||
|
postgresql-server-dev-14 \
|
||||||
|
libpq-dev \
|
||||||
|
libpqxx-dev
|
||||||
|
|
||||||
|
# Installiere libwebsockets
|
||||||
|
log_info "Installiere libwebsockets..."
|
||||||
|
apt install -y \
|
||||||
|
libwebsockets-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libz-dev
|
||||||
|
|
||||||
|
# Installiere nlohmann-json
|
||||||
|
log_info "Installiere nlohmann-json..."
|
||||||
|
apt install -y nlohmann-json3-dev
|
||||||
|
|
||||||
|
# Installiere PostgreSQL Server (falls nicht vorhanden)
|
||||||
|
log_info "Prüfe PostgreSQL Installation..."
|
||||||
|
if ! systemctl is-active --quiet postgresql; then
|
||||||
|
log_info "Installiere PostgreSQL Server..."
|
||||||
|
apt install -y postgresql postgresql-contrib
|
||||||
|
|
||||||
|
# Starte PostgreSQL
|
||||||
|
systemctl start postgresql
|
||||||
|
systemctl enable postgresql
|
||||||
|
|
||||||
|
log_success "PostgreSQL installiert und gestartet"
|
||||||
|
else
|
||||||
|
log_success "PostgreSQL läuft bereits"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Erstelle Datenbank und Benutzer
|
||||||
|
log_info "Konfiguriere PostgreSQL..."
|
||||||
|
sudo -u postgres psql << EOF
|
||||||
|
-- Erstelle Benutzer falls nicht vorhanden
|
||||||
|
DO \$\$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'yourpart') THEN
|
||||||
|
CREATE USER yourpart WITH PASSWORD 'hitomisan';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
\$\$;
|
||||||
|
|
||||||
|
-- Erstelle Datenbank falls nicht vorhanden
|
||||||
|
SELECT 'CREATE DATABASE yp3 OWNER yourpart'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'yp3')\gexec
|
||||||
|
|
||||||
|
-- Setze Berechtigungen
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE yp3 TO yourpart;
|
||||||
|
\q
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_success "PostgreSQL konfiguriert"
|
||||||
|
|
||||||
|
# Installiere systemd (sollte bereits vorhanden sein)
|
||||||
|
log_info "Prüfe systemd..."
|
||||||
|
if ! command -v systemctl &> /dev/null; then
|
||||||
|
log_error "systemd ist nicht installiert. Bitte installieren Sie Ubuntu 22 LTS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "systemd verfügbar"
|
||||||
|
|
||||||
|
# Installiere zusätzliche Tools für Monitoring
|
||||||
|
log_info "Installiere Monitoring-Tools..."
|
||||||
|
apt install -y \
|
||||||
|
htop \
|
||||||
|
iotop \
|
||||||
|
netstat-nat \
|
||||||
|
lsof
|
||||||
|
|
||||||
|
# Konfiguriere Firewall (falls ufw installiert ist)
|
||||||
|
if command -v ufw &> /dev/null; then
|
||||||
|
log_info "Konfiguriere Firewall..."
|
||||||
|
ufw allow 4551/tcp comment "YourPart Daemon WebSocket"
|
||||||
|
ufw allow 22/tcp comment "SSH"
|
||||||
|
log_success "Firewall konfiguriert"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Erstelle Log-Verzeichnis
|
||||||
|
log_info "Erstelle Log-Verzeichnisse..."
|
||||||
|
mkdir -p /var/log/yourpart
|
||||||
|
chmod 755 /var/log/yourpart
|
||||||
|
|
||||||
|
log_success "Alle Dependencies erfolgreich installiert!"
|
||||||
|
log_info ""
|
||||||
|
log_info "Nächste Schritte:"
|
||||||
|
log_info "1. Führen Sie das deploy.sh Script von Ihrem Entwicklungsrechner aus"
|
||||||
|
log_info "2. Oder kopieren Sie die Binärdatei manuell und konfigurieren Sie den Service"
|
||||||
|
log_info ""
|
||||||
|
log_info "Verfügbare Services:"
|
||||||
|
log_info "- PostgreSQL: systemctl status postgresql"
|
||||||
|
log_info "- Firewall: ufw status"
|
||||||
|
log_info ""
|
||||||
|
log_info "Datenbankverbindung:"
|
||||||
|
log_info "- Host: localhost"
|
||||||
|
log_info "- Port: 5432"
|
||||||
|
log_info "- Database: yp3"
|
||||||
|
log_info "- User: yourpart"
|
||||||
|
log_info "- Password: hitomisan"
|
||||||
@@ -186,6 +186,14 @@ void CharacterCreationWorker::notifyUser(int userId, const std::string &eventTyp
|
|||||||
db.prepare("insert_notification", QUERY_INSERT_NOTIFICATION);
|
db.prepare("insert_notification", QUERY_INSERT_NOTIFICATION);
|
||||||
db.execute("insert_notification", { std::to_string(userId) });
|
db.execute("insert_notification", { std::to_string(userId) });
|
||||||
|
|
||||||
|
// Sende falukantUpdateStatus nach dem Einfügen der Benachrichtigung
|
||||||
|
nlohmann::json updateMessage = {
|
||||||
|
{"event", "falukantUpdateStatus"},
|
||||||
|
{"user_id", userId}
|
||||||
|
};
|
||||||
|
broker.publish(updateMessage.dump());
|
||||||
|
|
||||||
|
// Sende auch die ursprüngliche Benachrichtigung
|
||||||
nlohmann::json message = {
|
nlohmann::json message = {
|
||||||
{"event", eventType},
|
{"event", eventType},
|
||||||
{"user_id", userId}
|
{"user_id", userId}
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ Database::query(const std::string &sql)
|
|||||||
void Database::prepare(const std::string &stmtName, const std::string &sql)
|
void Database::prepare(const std::string &stmtName, const std::string &sql)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Versuche zuerst, das alte Statement zu entfernen, falls es existiert
|
||||||
|
try {
|
||||||
|
remove(stmtName);
|
||||||
|
} catch (...) {
|
||||||
|
// Ignoriere Fehler beim Entfernen - das Statement existiert möglicherweise nicht
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstelle das neue Statement
|
||||||
pqxx::work txn(*connection_);
|
pqxx::work txn(*connection_);
|
||||||
txn.conn().prepare(stmtName, sql);
|
txn.conn().prepare(stmtName, sql);
|
||||||
txn.commit();
|
txn.commit();
|
||||||
@@ -55,31 +63,38 @@ void Database::prepare(const std::string &stmtName, const std::string &sql)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::FieldList Database::execute(const std::string &stmtName, const std::vector<std::string> ¶ms) {
|
Database::FieldList Database::execute(const std::string& stmtName,
|
||||||
std::vector<std::unordered_map<std::string, std::string>> rows;
|
const std::vector<std::string>& params)
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
pqxx::work txn(*connection_);
|
pqxx::work txn(*connection_);
|
||||||
pqxx::prepare::invocation inv = txn.prepared(stmtName);
|
|
||||||
for (auto &p : params) {
|
|
||||||
inv(p);
|
|
||||||
}
|
|
||||||
pqxx::result r = inv.exec();
|
|
||||||
txn.commit();
|
|
||||||
for (const auto &row : r) {
|
|
||||||
std::unordered_map<std::string, std::string> mapRow;
|
|
||||||
for (pqxx::row::size_type i = 0; i < row.size(); ++i) {
|
|
||||||
std::string colName = r.column_name(i);
|
|
||||||
mapRow[colName] = row[i].c_str() ? row[i].c_str() : "";
|
|
||||||
}
|
|
||||||
rows.push_back(std::move(mapRow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception &ex) {
|
|
||||||
std::cerr << "[Database] execute-Fehler: " << ex.what()
|
|
||||||
<< "\nStatement: " << stmtName << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
pqxx::result res;
|
||||||
|
if (params.empty()) {
|
||||||
|
res = txn.exec_prepared(stmtName);
|
||||||
|
} else {
|
||||||
|
pqxx::params p;
|
||||||
|
for (const auto& v : params) p.append(v);
|
||||||
|
res = txn.exec_prepared(stmtName, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldList out;
|
||||||
|
out.reserve(res.size());
|
||||||
|
for (const auto& row : res) {
|
||||||
|
std::unordered_map<std::string, std::string> m;
|
||||||
|
for (const auto& f : row) {
|
||||||
|
m.emplace(f.name(), f.is_null() ? std::string{} : std::string(f.c_str()));
|
||||||
|
}
|
||||||
|
out.emplace_back(std::move(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
return out;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[Database] execute-Fehler: " << e.what()
|
||||||
|
<< "\n\nStatement: " << stmtName << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::remove(const std::string &stmtName) {
|
void Database::remove(const std::string &stmtName) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "usercharacterworker.h"
|
#include "usercharacterworker.h"
|
||||||
#include "houseworker.h"
|
#include "houseworker.h"
|
||||||
#include "politics_worker.h"
|
#include "politics_worker.h"
|
||||||
|
#include "underground_worker.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -52,6 +53,7 @@ int main() {
|
|||||||
workers.push_back(std::make_unique<UserCharacterWorker>(pool, broker));
|
workers.push_back(std::make_unique<UserCharacterWorker>(pool, broker));
|
||||||
workers.push_back(std::make_unique<HouseWorker>(pool, broker));
|
workers.push_back(std::make_unique<HouseWorker>(pool, broker));
|
||||||
workers.push_back(std::make_unique<PoliticsWorker>(pool, broker));
|
workers.push_back(std::make_unique<PoliticsWorker>(pool, broker));
|
||||||
|
workers.push_back(std::make_unique<UndergroundWorker>(pool, broker));
|
||||||
websocketServer.setWorkers(workers);
|
websocketServer.setWorkers(workers);
|
||||||
|
|
||||||
broker.start();
|
broker.start();
|
||||||
|
|||||||
@@ -180,6 +180,15 @@ void PoliticsWorker::notifyOfficeExpirations() {
|
|||||||
signalActivity();
|
signalActivity();
|
||||||
db.execute("NOTIFY_OFFICE_EXPIRATION");
|
db.execute("NOTIFY_OFFICE_EXPIRATION");
|
||||||
signalActivity();
|
signalActivity();
|
||||||
|
|
||||||
|
// Sende falukantUpdateStatus an alle betroffenen Benutzer
|
||||||
|
db.prepare("GET_USERS_WITH_EXPIRING_OFFICES", QUERY_GET_USERS_WITH_EXPIRING_OFFICES);
|
||||||
|
const auto affectedUsers = db.execute("GET_USERS_WITH_EXPIRING_OFFICES");
|
||||||
|
for (const auto &user : affectedUsers) {
|
||||||
|
int userId = std::stoi(user.at("user_id"));
|
||||||
|
nlohmann::json message = { { "event", "falukantUpdateStatus" } };
|
||||||
|
sendMessageToFalukantUsers(userId, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PoliticsWorker::notifyElectionCreated(const std::vector<std::pair<int,int>>& elections) {
|
void PoliticsWorker::notifyElectionCreated(const std::vector<std::pair<int,int>>& elections) {
|
||||||
@@ -192,6 +201,15 @@ void PoliticsWorker::notifyElectionCreated(const std::vector<std::pair<int,int>>
|
|||||||
db.execute("NOTIFY_ELECTION_CREATED", { std::to_string(pr.first) });
|
db.execute("NOTIFY_ELECTION_CREATED", { std::to_string(pr.first) });
|
||||||
signalActivity();
|
signalActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sende falukantUpdateStatus an alle betroffenen Benutzer
|
||||||
|
db.prepare("GET_USERS_IN_REGIONS_WITH_ELECTIONS", QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS);
|
||||||
|
const auto affectedUsers = db.execute("GET_USERS_IN_REGIONS_WITH_ELECTIONS");
|
||||||
|
for (const auto &user : affectedUsers) {
|
||||||
|
int userId = std::stoi(user.at("user_id"));
|
||||||
|
nlohmann::json message = { { "event", "falukantUpdateStatus" } };
|
||||||
|
sendMessageToFalukantUsers(userId, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PoliticsWorker::notifyOfficeFilled(const std::vector<std::tuple<int,int,int,int>>& newOffices) {
|
void PoliticsWorker::notifyOfficeFilled(const std::vector<std::tuple<int,int,int,int>>& newOffices) {
|
||||||
@@ -205,6 +223,15 @@ void PoliticsWorker::notifyOfficeFilled(const std::vector<std::tuple<int,int,int
|
|||||||
db.execute("NOTIFY_OFFICE_FILLED", { std::to_string(characterId) });
|
db.execute("NOTIFY_OFFICE_FILLED", { std::to_string(characterId) });
|
||||||
signalActivity();
|
signalActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sende falukantUpdateStatus an alle betroffenen Benutzer
|
||||||
|
db.prepare("GET_USERS_WITH_FILLED_OFFICES", QUERY_GET_USERS_WITH_FILLED_OFFICES);
|
||||||
|
const auto affectedUsers = db.execute("GET_USERS_WITH_FILLED_OFFICES");
|
||||||
|
for (const auto &user : affectedUsers) {
|
||||||
|
int userId = std::stoi(user.at("user_id"));
|
||||||
|
nlohmann::json message = { { "event", "falukantUpdateStatus" } };
|
||||||
|
sendMessageToFalukantUsers(userId, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::tuple<int,int,int,int>> PoliticsWorker::processElections() {
|
std::vector<std::tuple<int,int,int,int>> PoliticsWorker::processElections() {
|
||||||
|
|||||||
@@ -451,6 +451,59 @@ private:
|
|||||||
($1, 'notify_office_filled', NOW(), NOW());
|
($1, 'notify_office_filled', NOW(), NOW());
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// QUERY: Hole alle Benutzer, deren Amt in 2 Tagen abläuft
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
static constexpr const char* QUERY_GET_USERS_WITH_EXPIRING_OFFICES = R"(
|
||||||
|
SELECT DISTINCT
|
||||||
|
ch.user_id
|
||||||
|
FROM
|
||||||
|
falukant_data.political_office AS po
|
||||||
|
JOIN
|
||||||
|
falukant_type.political_office_type AS pot
|
||||||
|
ON po.office_type_id = pot.id
|
||||||
|
JOIN
|
||||||
|
falukant_data."character" AS ch
|
||||||
|
ON po.character_id = ch.id
|
||||||
|
WHERE
|
||||||
|
ch.user_id IS NOT NULL
|
||||||
|
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))
|
||||||
|
BETWEEN (NOW() + INTERVAL '2 days')
|
||||||
|
AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
|
||||||
|
)";
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// QUERY: Hole alle Benutzer in Regionen mit neuen Wahlen
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
static constexpr const char* QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS = R"(
|
||||||
|
SELECT DISTINCT
|
||||||
|
ch.user_id
|
||||||
|
FROM
|
||||||
|
falukant_data.election AS e
|
||||||
|
JOIN
|
||||||
|
falukant_data."character" AS ch
|
||||||
|
ON ch.region_id = e.region_id
|
||||||
|
WHERE
|
||||||
|
ch.user_id IS NOT NULL
|
||||||
|
AND e."date" >= NOW() - INTERVAL '1 day';
|
||||||
|
)";
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// QUERY: Hole alle Benutzer, deren Amt neu besetzt wurde
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
static constexpr const char* QUERY_GET_USERS_WITH_FILLED_OFFICES = R"(
|
||||||
|
SELECT DISTINCT
|
||||||
|
ch.user_id
|
||||||
|
FROM
|
||||||
|
falukant_data.political_office AS po
|
||||||
|
JOIN
|
||||||
|
falukant_data."character" AS ch
|
||||||
|
ON po.character_id = ch.id
|
||||||
|
WHERE
|
||||||
|
ch.user_id IS NOT NULL
|
||||||
|
AND po.created_at >= NOW() - INTERVAL '1 minute';
|
||||||
|
)";
|
||||||
|
|
||||||
static constexpr const char* QUERY_PROCESS_ELECTIONS = R"(
|
static constexpr const char* QUERY_PROCESS_ELECTIONS = R"(
|
||||||
SELECT office_id, office_type_id, character_id, region_id
|
SELECT office_id, office_type_id, character_id, region_id
|
||||||
FROM falukant_data.process_elections();
|
FROM falukant_data.process_elections();
|
||||||
|
|||||||
@@ -120,6 +120,14 @@ bool ProduceWorker::addToInventory(Database &db,
|
|||||||
{"value", remainingQuantity}
|
{"value", remainingQuantity}
|
||||||
};
|
};
|
||||||
db.execute("QUERY_ADD_OVERPRODUCTION_NOTIFICATION", {std::to_string(userId), notification.dump()});
|
db.execute("QUERY_ADD_OVERPRODUCTION_NOTIFICATION", {std::to_string(userId), notification.dump()});
|
||||||
|
|
||||||
|
// Sende falukantUpdateStatus nach dem Einfügen der Benachrichtigung
|
||||||
|
nlohmann::json updateMessage = {
|
||||||
|
{"event", "falukantUpdateStatus"},
|
||||||
|
{"user_id", userId}
|
||||||
|
};
|
||||||
|
broker.publish(updateMessage.dump());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
std::cerr << "[ProduceWorker] Fehler in addToInventory: " << e.what() << std::endl;
|
std::cerr << "[ProduceWorker] Fehler in addToInventory: " << e.what() << std::endl;
|
||||||
|
|||||||
445
src/underground_worker.cpp
Normal file
445
src/underground_worker.cpp
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
#include "underground_worker.h"
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
UndergroundWorker::~UndergroundWorker() = default;
|
||||||
|
|
||||||
|
static std::mt19937& rng() {
|
||||||
|
static thread_local std::mt19937 g{std::random_device{}()};
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UndergroundWorker::randomInt(int lo,int hi){
|
||||||
|
std::uniform_int_distribution<int> d(lo,hi);
|
||||||
|
return d(rng());
|
||||||
|
}
|
||||||
|
|
||||||
|
long long UndergroundWorker::randomLL(long long lo,long long hi){
|
||||||
|
std::uniform_int_distribution<long long> d(lo,hi);
|
||||||
|
return d(rng());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> UndergroundWorker::randomIndices(size_t n,size_t k){
|
||||||
|
std::vector<size_t> idx(n);
|
||||||
|
std::iota(idx.begin(),idx.end(),0);
|
||||||
|
std::shuffle(idx.begin(),idx.end(),rng());
|
||||||
|
if(k<idx.size()) idx.resize(k);
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndergroundWorker::run(){
|
||||||
|
using namespace std::chrono;
|
||||||
|
while(runningWorker){
|
||||||
|
setCurrentStep("Process underground jobs");
|
||||||
|
signalActivity();
|
||||||
|
tick();
|
||||||
|
setCurrentStep("Idle");
|
||||||
|
for(int i=0;i<60 && runningWorker;++i){
|
||||||
|
std::this_thread::sleep_for(seconds(1));
|
||||||
|
signalActivity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndergroundWorker::tick(){
|
||||||
|
setCurrentStep("Fetch pending underground jobs");
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_PENDING",Q_SELECT_PENDING);
|
||||||
|
db.prepare("UG_UPDATE_RESULT",Q_UPDATE_RESULT);
|
||||||
|
const auto rows=db.execute("UG_SELECT_PENDING");
|
||||||
|
for(const auto& r:rows){
|
||||||
|
try{
|
||||||
|
auto res=executeRow(r);
|
||||||
|
int id=std::stoi(r.at("id"));
|
||||||
|
updateResult(id,res);
|
||||||
|
broker.publish(json{{"event","underground_processed"},{"id",id},{"type",r.at("underground_type")}}.dump());
|
||||||
|
}catch(const std::exception& e){
|
||||||
|
try{
|
||||||
|
int id=std::stoi(r.at("id"));
|
||||||
|
updateResult(id,json{{"status","error"},{"message",e.what()}});
|
||||||
|
}catch(...){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<UndergroundWorker::Row> UndergroundWorker::fetchPending(){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_PENDING",Q_SELECT_PENDING);
|
||||||
|
return db.execute("UG_SELECT_PENDING");
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::executeRow(const Row& r){
|
||||||
|
int performerId=std::stoi(r.at("performer_id"));
|
||||||
|
int victimId=std::stoi(r.at("victim_id"));
|
||||||
|
std::string type=r.at("underground_type");
|
||||||
|
std::string params=r.at("parameters");
|
||||||
|
return handleTask(type,performerId,victimId,params);
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::handleTask(const std::string& type,int performerId,int victimId,const std::string& paramsJson){
|
||||||
|
json p; try{ p=json::parse(paramsJson);} catch(...){ p=json::object(); }
|
||||||
|
if(type=="spyin") return spyIn(performerId,victimId,p);
|
||||||
|
if(type=="assassin") return assassin(performerId,victimId,p);
|
||||||
|
if(type=="sabotage") return sabotage(performerId,victimId,p);
|
||||||
|
if(type=="corrupt_politician") return corruptPolitician(performerId,victimId,p);
|
||||||
|
if(type=="rob") return rob(performerId,victimId,p);
|
||||||
|
return {{"status","unknown_type"},{"type",type}};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::spyIn(int performerId,int victimId,const json& p){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_BY_PERFORMER",Q_SELECT_BY_PERFORMER);
|
||||||
|
const auto rows = db.execute("UG_SELECT_BY_PERFORMER",{ std::to_string(victimId) });
|
||||||
|
|
||||||
|
json activities = json::array();
|
||||||
|
for(const auto& r : rows){
|
||||||
|
json params = json::object();
|
||||||
|
try{ params = json::parse(r.at("parameters")); }catch(...){}
|
||||||
|
json result = nullptr;
|
||||||
|
auto it = r.find("result_text");
|
||||||
|
if(it != r.end()){
|
||||||
|
try{ result = json::parse(it->second); }catch(...){}
|
||||||
|
}
|
||||||
|
std::string status = "pending";
|
||||||
|
if(result.is_object()){
|
||||||
|
if(auto s = result.find("status"); s!=result.end() && s->is_string()) status = *s;
|
||||||
|
else status = "done";
|
||||||
|
}
|
||||||
|
activities.push_back({
|
||||||
|
{"id", std::stoi(r.at("id"))},
|
||||||
|
{"type", r.at("underground_type")},
|
||||||
|
{"performed_by", std::stoi(r.at("performer_id"))},
|
||||||
|
{"victim_id", std::stoi(r.at("victim_id"))},
|
||||||
|
{"created_at", r.at("created_at")},
|
||||||
|
{"parameters", params},
|
||||||
|
{"result", result},
|
||||||
|
{"status", status}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
{"status","success"},
|
||||||
|
{"action","spyin"},
|
||||||
|
{"performer_id", performerId},
|
||||||
|
{"victim_id", victimId},
|
||||||
|
{"details", p},
|
||||||
|
{"victim_illegal_activity_count", activities.size()},
|
||||||
|
{"victim_illegal_activities", activities}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::assassin(int performerId,int victimId,const json& p){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_CHAR_HEALTH",Q_SELECT_CHAR_HEALTH);
|
||||||
|
db.prepare("UG_UPDATE_CHAR_HEALTH",Q_UPDATE_CHAR_HEALTH);
|
||||||
|
const auto rows=db.execute("UG_SELECT_CHAR_HEALTH",{std::to_string(victimId)});
|
||||||
|
if(rows.empty()) return {{"status","error"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"message","victim_not_found"},{"details",p}};
|
||||||
|
int current=std::stoi(rows.front().at("health"));
|
||||||
|
std::uniform_int_distribution<int> dist(0,current);
|
||||||
|
int new_health=dist(rng());
|
||||||
|
db.execute("UG_UPDATE_CHAR_HEALTH",{std::to_string(victimId),std::to_string(new_health)});
|
||||||
|
return {{"status","success"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"previous_health",current},{"new_health",new_health},{"reduced_by",current-new_health}};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::sabotage(int performerId,int victimId,const json& p){
|
||||||
|
const auto target=p.value("target",std::string{});
|
||||||
|
if(target=="house") return sabotageHouse(performerId,victimId,p);
|
||||||
|
if(target=="storage") return sabotageStorage(performerId,victimId,p);
|
||||||
|
return {{"status","error"},{"action","sabotage"},{"message","unknown_target"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
}
|
||||||
|
|
||||||
|
int UndergroundWorker::getUserIdForCharacter(int characterId){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_CHAR_USER",Q_SELECT_CHAR_USER);
|
||||||
|
const auto r=db.execute("UG_SELECT_CHAR_USER",{std::to_string(characterId)});
|
||||||
|
if(r.empty()) return -1;
|
||||||
|
return std::stoi(r.front().at("user_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<UndergroundWorker::HouseConditions> UndergroundWorker::getHouseByUser(int userId){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_HOUSE_BY_USER",Q_SELECT_HOUSE_BY_USER);
|
||||||
|
const auto r=db.execute("UG_SELECT_HOUSE_BY_USER",{std::to_string(userId)});
|
||||||
|
if(r.empty()) return std::nullopt;
|
||||||
|
HouseConditions h{
|
||||||
|
std::stoi(r.front().at("id")),
|
||||||
|
std::stoi(r.front().at("roof_condition")),
|
||||||
|
std::stoi(r.front().at("floor_condition")),
|
||||||
|
std::stoi(r.front().at("wall_condition")),
|
||||||
|
std::stoi(r.front().at("window_condition"))
|
||||||
|
};
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndergroundWorker::updateHouse(const HouseConditions& h){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_UPDATE_HOUSE",Q_UPDATE_HOUSE);
|
||||||
|
db.execute("UG_UPDATE_HOUSE",{
|
||||||
|
std::to_string(h.id),
|
||||||
|
std::to_string(std::clamp(h.roof,0,100)),
|
||||||
|
std::to_string(std::clamp(h.floor,0,100)),
|
||||||
|
std::to_string(std::clamp(h.wall,0,100)),
|
||||||
|
std::to_string(std::clamp(h.windowc,0,100))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::sabotageHouse(int performerId,int victimId,const json& p){
|
||||||
|
int userId=getUserIdForCharacter(victimId);
|
||||||
|
if(userId<0) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
auto hopt=getHouseByUser(userId);
|
||||||
|
if(!hopt) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","house_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
auto h=*hopt;
|
||||||
|
|
||||||
|
std::vector<std::string> allow;
|
||||||
|
if(p.contains("conditions") && p["conditions"].is_array())
|
||||||
|
for(const auto& s:p["conditions"]) if(s.is_string()) allow.push_back(s.get<std::string>());
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string,int*>> fields={
|
||||||
|
{"roof_condition",&h.roof},
|
||||||
|
{"floor_condition",&h.floor},
|
||||||
|
{"wall_condition",&h.wall},
|
||||||
|
{"window_condition",&h.windowc}
|
||||||
|
};
|
||||||
|
std::vector<std::pair<std::string,int*>> pool;
|
||||||
|
for(auto& f:fields) if(allow.empty() || std::find(allow.begin(),allow.end(),f.first)!=allow.end()) pool.push_back(f);
|
||||||
|
if(pool.empty()) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","no_conditions_selected"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
|
||||||
|
size_t k=static_cast<size_t>(randomInt(1,(int)pool.size()));
|
||||||
|
std::vector<size_t> picks=randomIndices(pool.size(),k);
|
||||||
|
|
||||||
|
json changed=json::array();
|
||||||
|
for(size_t i: picks){
|
||||||
|
int& cur=*pool[i].second;
|
||||||
|
if(cur>0){
|
||||||
|
int red=randomInt(1,cur);
|
||||||
|
cur=std::clamp(cur-red,0,100);
|
||||||
|
}
|
||||||
|
changed.push_back(pool[i].first);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHouse(h);
|
||||||
|
return {
|
||||||
|
{"status","success"},
|
||||||
|
{"action","sabotage"},
|
||||||
|
{"target","house"},
|
||||||
|
{"performer_id",performerId},
|
||||||
|
{"victim_id",victimId},
|
||||||
|
{"details",p},
|
||||||
|
{"changed_conditions",changed},
|
||||||
|
{"new_conditions",{
|
||||||
|
{"roof_condition",h.roof},
|
||||||
|
{"floor_condition",h.floor},
|
||||||
|
{"wall_condition",h.wall},
|
||||||
|
{"window_condition",h.windowc}
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<UndergroundWorker::Row> UndergroundWorker::selectStockByBranch(int branchId){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_STOCK_BY_BRANCH",Q_SELECT_STOCK_BY_BRANCH);
|
||||||
|
return db.execute("UG_SELECT_STOCK_BY_BRANCH",{std::to_string(branchId)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<UndergroundWorker::Row> UndergroundWorker::filterByStockTypes(const std::vector<Row>& rows,const std::vector<int>& allowed){
|
||||||
|
if(allowed.empty()) return rows;
|
||||||
|
std::vector<Row> out;
|
||||||
|
out.reserve(rows.size());
|
||||||
|
for(const auto& r:rows){
|
||||||
|
int t=std::stoi(r.at("stock_type_id"));
|
||||||
|
if(std::find(allowed.begin(),allowed.end(),t)!=allowed.end()) out.push_back(r);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndergroundWorker::updateStockQty(int id,long long qty){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_UPDATE_STOCK_QTY",Q_UPDATE_STOCK_QTY);
|
||||||
|
db.execute("UG_UPDATE_STOCK_QTY",{std::to_string(id),std::to_string(qty)});
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::sabotageStorage(int performerId,int victimId,const json& p){
|
||||||
|
if(!p.contains("branch_id") || !p["branch_id"].is_number_integer())
|
||||||
|
return {{"status","error"},{"action","sabotage"},{"target","storage"},{"message","branch_id_required"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
int branchId=p["branch_id"].get<int>();
|
||||||
|
|
||||||
|
std::vector<int> allowed;
|
||||||
|
if(p.contains("stock_type_ids") && p["stock_type_ids"].is_array())
|
||||||
|
for(const auto& v:p["stock_type_ids"]) if(v.is_number_integer()) allowed.push_back(v.get<int>());
|
||||||
|
|
||||||
|
auto rows=filterByStockTypes(selectStockByBranch(branchId),allowed);
|
||||||
|
if(rows.empty()) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
|
||||||
|
long long total=0;
|
||||||
|
for(const auto& r:rows) total+=std::stoll(r.at("quantity"));
|
||||||
|
if(total<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
|
||||||
|
long long cap=total/4;
|
||||||
|
if(cap<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
|
||||||
|
long long to_remove=randomLL(1,cap);
|
||||||
|
std::shuffle(rows.begin(),rows.end(),rng());
|
||||||
|
|
||||||
|
json affected=json::array();
|
||||||
|
for(const auto& r:rows){
|
||||||
|
if(to_remove==0) break;
|
||||||
|
int id=std::stoi(r.at("id"));
|
||||||
|
long long q=std::stoll(r.at("quantity"));
|
||||||
|
if(q<=0) continue;
|
||||||
|
long long take=randomLL(1,std::min(q,to_remove));
|
||||||
|
long long newq=q-take;
|
||||||
|
updateStockQty(id,newq);
|
||||||
|
to_remove-=take;
|
||||||
|
affected.push_back({{"id",id},{"stock_type_id",std::stoi(r.at("stock_type_id"))},{"previous_quantity",q},{"new_quantity",newq},{"removed",take}});
|
||||||
|
}
|
||||||
|
|
||||||
|
long long removed=0;
|
||||||
|
for(const auto& a:affected) removed+=a.at("removed").get<long long>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
{"status","success"},
|
||||||
|
{"action","sabotage"},
|
||||||
|
{"target","storage"},
|
||||||
|
{"performer_id",performerId},
|
||||||
|
{"victim_id",victimId},
|
||||||
|
{"details",p},
|
||||||
|
{"removed_total",removed},
|
||||||
|
{"affected_rows",affected}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::corruptPolitician(int performerId,int victimId,const json& p){
|
||||||
|
return {{"status","success"},{"action","corrupt_politician"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json UndergroundWorker::rob(int performerId,int victimId,const json& p){
|
||||||
|
int userId=getUserIdForCharacter(victimId);
|
||||||
|
if(userId<0) return {{"status","error"},{"action","rob"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_SELECT_FALUKANT_USER",Q_SELECT_FALUKANT_USER);
|
||||||
|
const auto fu=db.execute("UG_SELECT_FALUKANT_USER",{std::to_string(userId)});
|
||||||
|
if(fu.empty()) return {{"status","error"},{"action","rob"},{"message","falukant_user_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
|
||||||
|
|
||||||
|
int falukantUserId=std::stoi(fu.front().at("id"));
|
||||||
|
double money=std::stod(fu.front().at("money"));
|
||||||
|
int defaultBranch=std::stoi(fu.front().at("main_branch_region_id"));
|
||||||
|
|
||||||
|
bool stealGoods = (randomInt(0,1)==1);
|
||||||
|
if(stealGoods){
|
||||||
|
int branchId = p.contains("branch_id") && p["branch_id"].is_number_integer()
|
||||||
|
? p["branch_id"].get<int>()
|
||||||
|
: defaultBranch;
|
||||||
|
|
||||||
|
if(branchId<=0){
|
||||||
|
return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rows = selectStockByBranch(branchId);
|
||||||
|
if(rows.empty()){
|
||||||
|
return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
long long total=0;
|
||||||
|
for(const auto& r:rows) total+=std::stoll(r.at("quantity"));
|
||||||
|
if(total<=0){
|
||||||
|
return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
long long cap = std::max<long long>(1, total/2);
|
||||||
|
long long to_remove = randomLL(1, cap);
|
||||||
|
|
||||||
|
std::shuffle(rows.begin(),rows.end(),rng());
|
||||||
|
json affected = json::array();
|
||||||
|
for(const auto& r:rows){
|
||||||
|
if(to_remove==0) break;
|
||||||
|
int id=std::stoi(r.at("id"));
|
||||||
|
long long q=std::stoll(r.at("quantity"));
|
||||||
|
if(q<=0) continue;
|
||||||
|
long long take=randomLL(1,std::min(q,to_remove));
|
||||||
|
long long newq=q-take;
|
||||||
|
updateStockQty(id,newq);
|
||||||
|
to_remove-=take;
|
||||||
|
affected.push_back({
|
||||||
|
{"id",id},
|
||||||
|
{"stock_type_id",std::stoi(r.at("stock_type_id"))},
|
||||||
|
{"previous_quantity",q},
|
||||||
|
{"new_quantity",newq},
|
||||||
|
{"removed",take}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
long long removed=0;
|
||||||
|
for(const auto& a:affected) removed+=a.at("removed").get<long long>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
{"status","success"},
|
||||||
|
{"action","rob"},
|
||||||
|
{"mode","goods"},
|
||||||
|
{"performer_id",performerId},
|
||||||
|
{"victim_id",victimId},
|
||||||
|
{"details",p},
|
||||||
|
{"removed_total",removed},
|
||||||
|
{"affected_rows",affected}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if(money<=0.0){
|
||||||
|
return {{"status","success"},{"action","rob"},{"mode","money"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"stolen",0.0},{"balance_before",0.0},{"balance_after",0.0}};
|
||||||
|
}
|
||||||
|
|
||||||
|
double rate = randomDouble(0.0,0.18);
|
||||||
|
double amount = std::floor(money * rate * 100.0 + 0.5) / 100.0;
|
||||||
|
if(amount < 0.01) amount = 0.01;
|
||||||
|
if(amount > money) amount = money;
|
||||||
|
|
||||||
|
json msg = {
|
||||||
|
{"event","money_changed"},
|
||||||
|
{"reason","robbery"},
|
||||||
|
{"delta",-amount},
|
||||||
|
{"performer_id",performerId},
|
||||||
|
{"victim_id",victimId}
|
||||||
|
};
|
||||||
|
changeFalukantUserMoney(falukantUserId, -amount, "robbery", msg);
|
||||||
|
|
||||||
|
double after = std::floor((money - amount) * 100.0 + 0.5)/100.0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
{"status","success"},
|
||||||
|
{"action","rob"},
|
||||||
|
{"mode","money"},
|
||||||
|
{"performer_id",performerId},
|
||||||
|
{"victim_id",victimId},
|
||||||
|
{"details",p},
|
||||||
|
{"stolen",amount},
|
||||||
|
{"rate",rate},
|
||||||
|
{"balance_before",money},
|
||||||
|
{"balance_after",after}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UndergroundWorker::updateResult(int id,const nlohmann::json& result){
|
||||||
|
ConnectionGuard g(pool);
|
||||||
|
auto& db=g.get();
|
||||||
|
db.prepare("UG_UPDATE_RESULT",Q_UPDATE_RESULT);
|
||||||
|
db.execute("UG_UPDATE_RESULT",{std::to_string(id),result.dump()});
|
||||||
|
}
|
||||||
|
|
||||||
|
double UndergroundWorker::randomDouble(double lo,double hi){
|
||||||
|
std::uniform_real_distribution<double> d(lo,hi);
|
||||||
|
return d(rng());
|
||||||
|
}
|
||||||
101
src/underground_worker.h
Normal file
101
src/underground_worker.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <optional>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include "worker.h"
|
||||||
|
|
||||||
|
class UndergroundWorker final: public Worker{
|
||||||
|
using Row = std::unordered_map<std::string,std::string>;
|
||||||
|
struct HouseConditions { int id; int roof; int floor; int wall; int windowc; };
|
||||||
|
public:
|
||||||
|
UndergroundWorker(ConnectionPool& pool,MessageBroker& broker):Worker(pool,broker,"UndergroundWorker"){}
|
||||||
|
~UndergroundWorker() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void tick();
|
||||||
|
std::vector<Row> fetchPending();
|
||||||
|
nlohmann::json executeRow(const Row& r);
|
||||||
|
nlohmann::json handleTask(const std::string& type,int performerId,int victimId,const std::string& paramsJson);
|
||||||
|
nlohmann::json spyIn(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
nlohmann::json assassin(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
nlohmann::json sabotage(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
nlohmann::json corruptPolitician(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
nlohmann::json rob(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
void updateResult(int id,const nlohmann::json& result);
|
||||||
|
|
||||||
|
nlohmann::json sabotageHouse(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
nlohmann::json sabotageStorage(int performerId,int victimId,const nlohmann::json& p);
|
||||||
|
|
||||||
|
int getUserIdForCharacter(int characterId);
|
||||||
|
std::optional<HouseConditions> getHouseByUser(int userId);
|
||||||
|
void updateHouse(const HouseConditions& h);
|
||||||
|
std::vector<Row> selectStockByBranch(int branchId);
|
||||||
|
std::vector<Row> filterByStockTypes(const std::vector<Row>& rows,const std::vector<int>& allowed);
|
||||||
|
void updateStockQty(int id,long long qty);
|
||||||
|
static int randomInt(int lo,int hi);
|
||||||
|
static long long randomLL(long long lo,long long hi);
|
||||||
|
static std::vector<size_t> randomIndices(size_t n,size_t k);
|
||||||
|
static double randomDouble(double lo,double hi);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const char* Q_SELECT_BY_PERFORMER=R"SQL(
|
||||||
|
SELECT u.id, t.tr AS underground_type, u.performer_id, u.victim_id,
|
||||||
|
to_char(u.created_at,'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS created_at,
|
||||||
|
COALESCE(u.parameters::text,'{}') AS parameters,
|
||||||
|
COALESCE(u.result::text,'null') AS result_text
|
||||||
|
FROM falukant_data.underground u
|
||||||
|
JOIN falukant_type.underground t ON t.tr=u.underground_type_id
|
||||||
|
WHERE u.performer_id=$1
|
||||||
|
ORDER BY u.created_at DESC
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_PENDING=R"SQL(
|
||||||
|
SELECT u.id,t.tr AS underground_type,u.performer_id,u.victim_id,COALESCE(u.parameters::text,'{}') AS parameters
|
||||||
|
FROM falukant_data.underground u
|
||||||
|
JOIN falukant_type.underground t ON t.tr=u.underground_type_id
|
||||||
|
WHERE u.result IS NULL AND u.created_at<=NOW()-INTERVAL '1 day'
|
||||||
|
ORDER BY u.created_at ASC
|
||||||
|
LIMIT 200
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_UPDATE_RESULT=R"SQL(
|
||||||
|
UPDATE falukant_data.underground SET result=$2::jsonb,updated_at=NOW() WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_CHAR_USER=R"SQL(
|
||||||
|
SELECT user_id FROM falukant_data."character" WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_HOUSE_BY_USER=R"SQL(
|
||||||
|
SELECT id, roof_condition, floor_condition, wall_condition, window_condition
|
||||||
|
FROM falukant_data.user_house
|
||||||
|
WHERE user_id=$1
|
||||||
|
LIMIT 1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_UPDATE_HOUSE=R"SQL(
|
||||||
|
UPDATE falukant_data.user_house
|
||||||
|
SET roof_condition=$2, floor_condition=$3, wall_condition=$4, window_condition=$5
|
||||||
|
WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_STOCK_BY_BRANCH=R"SQL(
|
||||||
|
SELECT id, stock_type_id, quantity
|
||||||
|
FROM falukant_data.stock
|
||||||
|
WHERE branch_id=$1
|
||||||
|
ORDER BY quantity DESC
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_UPDATE_STOCK_QTY=R"SQL(
|
||||||
|
UPDATE falukant_data.stock SET quantity=$2 WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_CHAR_HEALTH=R"SQL(
|
||||||
|
SELECT health FROM falukant_data."character" WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_UPDATE_CHAR_HEALTH=R"SQL(
|
||||||
|
UPDATE falukant_data."character" SET health=$2, updated_at=NOW() WHERE id=$1
|
||||||
|
)SQL";
|
||||||
|
static constexpr const char* Q_SELECT_FALUKANT_USER=R"SQL(
|
||||||
|
SELECT id, money, COALESCE(main_branch_region_id,0) AS main_branch_region_id
|
||||||
|
FROM falukant_data.falukant_user
|
||||||
|
WHERE user_id=$1
|
||||||
|
LIMIT 1
|
||||||
|
)SQL";
|
||||||
|
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
UserCharacterWorker::UserCharacterWorker(ConnectionPool &pool, MessageBroker &broker)
|
UserCharacterWorker::UserCharacterWorker(ConnectionPool &pool, MessageBroker &broker)
|
||||||
: Worker(pool, broker, "UserCharacterWorker"),
|
: Worker(pool, broker, "UserCharacterWorker"),
|
||||||
@@ -17,8 +18,11 @@ void UserCharacterWorker::run() {
|
|||||||
|
|
||||||
auto lastExecutionTime = steady_clock::now();
|
auto lastExecutionTime = steady_clock::now();
|
||||||
int lastPregnancyDay = -1;
|
int lastPregnancyDay = -1;
|
||||||
|
|
||||||
while (runningWorker) {
|
while (runningWorker) {
|
||||||
signalActivity();
|
signalActivity();
|
||||||
|
|
||||||
|
// 1h-Block
|
||||||
auto nowSteady = steady_clock::now();
|
auto nowSteady = steady_clock::now();
|
||||||
auto elapsed = duration_cast<seconds>(nowSteady - lastExecutionTime).count();
|
auto elapsed = duration_cast<seconds>(nowSteady - lastExecutionTime).count();
|
||||||
if (elapsed >= 3600) {
|
if (elapsed >= 3600) {
|
||||||
@@ -31,20 +35,22 @@ void UserCharacterWorker::run() {
|
|||||||
}
|
}
|
||||||
lastExecutionTime = nowSteady;
|
lastExecutionTime = nowSteady;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
auto nowSys = system_clock::now();
|
// Schwangerschaftsverarbeitung: initial oder täglich um 06:00 einmal pro Tag
|
||||||
std::time_t t = system_clock::to_time_t(nowSys);
|
auto nowSys = system_clock::now();
|
||||||
std::tm local_tm;
|
std::time_t t = system_clock::to_time_t(nowSys);
|
||||||
localtime_r(&t, &local_tm);
|
std::tm local_tm;
|
||||||
if (local_tm.tm_hour == 6 && local_tm.tm_yday != lastPregnancyDay) {
|
localtime_r(&t, &local_tm);
|
||||||
try {
|
|
||||||
processPregnancies();
|
if (lastPregnancyDay == -1 || (local_tm.tm_hour == 6 && local_tm.tm_yday != lastPregnancyDay)) {
|
||||||
} catch (const std::exception &e) {
|
try {
|
||||||
std::cerr << "[UserCharacterWorker] Fehler in processPregnancies: " << e.what() << std::endl;
|
processPregnancies();
|
||||||
}
|
} catch (const std::exception &e) {
|
||||||
lastPregnancyDay = local_tm.tm_yday;
|
std::cerr << "[UserCharacterWorker] Fehler in processPregnancies: " << e.what() << std::endl;
|
||||||
}
|
}
|
||||||
|
lastPregnancyDay = local_tm.tm_yday;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(seconds(1));
|
std::this_thread::sleep_for(seconds(1));
|
||||||
recalculateKnowledge();
|
recalculateKnowledge();
|
||||||
}
|
}
|
||||||
@@ -225,22 +231,34 @@ void UserCharacterWorker::recalculateKnowledge() {
|
|||||||
void UserCharacterWorker::processPregnancies() {
|
void UserCharacterWorker::processPregnancies() {
|
||||||
ConnectionGuard connGuard(pool);
|
ConnectionGuard connGuard(pool);
|
||||||
auto &db = connGuard.get();
|
auto &db = connGuard.get();
|
||||||
|
|
||||||
db.prepare("QUERY_AUTOBATISM", QUERY_AUTOBATISM);
|
db.prepare("QUERY_AUTOBATISM", QUERY_AUTOBATISM);
|
||||||
db.execute("QUERY_AUTOBATISM");
|
db.execute("QUERY_AUTOBATISM");
|
||||||
|
|
||||||
db.prepare("get_candidates", QUERY_GET_PREGNANCY_CANDIDATES);
|
db.prepare("get_candidates", QUERY_GET_PREGNANCY_CANDIDATES);
|
||||||
auto rows = db.execute("get_candidates");
|
auto rows = db.execute("get_candidates");
|
||||||
|
|
||||||
const nlohmann::json message = {
|
const nlohmann::json message = {
|
||||||
{"event", "children_update"},
|
{"event", "children_update"},
|
||||||
};
|
};
|
||||||
for (auto const &row : rows) {
|
|
||||||
int fatherCid = std::stoi(row.at("father_cid"));
|
for (const auto &row : rows) {
|
||||||
int motherCid = std::stoi(row.at("mother_cid"));
|
int fatherCid = Utils::optionalStoiOrDefault(row, "father_cid", -1);
|
||||||
int fatherUid = std::stoi(row.at("father_uid"));
|
int motherCid = Utils::optionalStoiOrDefault(row, "mother_cid", -1);
|
||||||
int motherUid = std::stoi(row.at("mother_uid"));
|
if (fatherCid < 0 || motherCid < 0) {
|
||||||
int titleOfNobility = std::stoi(row.at("title_of_nobility"));
|
continue; // ungültige Daten überspringen
|
||||||
int lastName = std::stoi(row.at("last_name"));
|
}
|
||||||
int regionId = std::stoi(row.at("region_id"));
|
|
||||||
|
int titleOfNobility = Utils::optionalStoiOrDefault(row, "title_of_nobility", 0);
|
||||||
|
int lastName = Utils::optionalStoiOrDefault(row, "last_name", 0);
|
||||||
|
int regionId = Utils::optionalStoiOrDefault(row, "region_id", 0);
|
||||||
|
|
||||||
|
auto fatherUidOpt = Utils::optionalUid(row.at("father_uid"));
|
||||||
|
auto motherUidOpt = Utils::optionalUid(row.at("mother_uid"));
|
||||||
|
|
||||||
|
// Geschlecht zufällig
|
||||||
std::string gender = (dist(gen) < 0.5) ? "male" : "female";
|
std::string gender = (dist(gen) < 0.5) ? "male" : "female";
|
||||||
|
|
||||||
db.prepare("insert_child", QUERY_INSERT_CHILD);
|
db.prepare("insert_child", QUERY_INSERT_CHILD);
|
||||||
auto resChild = db.execute("insert_child", {
|
auto resChild = db.execute("insert_child", {
|
||||||
std::to_string(regionId), // $1
|
std::to_string(regionId), // $1
|
||||||
@@ -248,16 +266,30 @@ void UserCharacterWorker::processPregnancies() {
|
|||||||
std::to_string(lastName), // $3
|
std::to_string(lastName), // $3
|
||||||
std::to_string(titleOfNobility) // $4
|
std::to_string(titleOfNobility) // $4
|
||||||
});
|
});
|
||||||
int childCid = std::stoi(resChild.front().at("child_cid"));
|
|
||||||
|
if (resChild.empty()) continue;
|
||||||
|
int childCid = Utils::optionalStoiOrDefault(resChild.front(), "child_cid", -1);
|
||||||
|
if (childCid < 0) continue;
|
||||||
|
|
||||||
db.prepare("insert_relation", QUERY_INSERT_CHILD_RELATION);
|
db.prepare("insert_relation", QUERY_INSERT_CHILD_RELATION);
|
||||||
auto resRel = db.execute("insert_relation", {
|
db.execute("insert_relation", {
|
||||||
std::to_string(fatherCid),
|
std::to_string(fatherCid),
|
||||||
std::to_string(motherCid),
|
std::to_string(motherCid),
|
||||||
std::to_string(childCid)
|
std::to_string(childCid)
|
||||||
});
|
});
|
||||||
const nlohmann::json message = {{"event", "children_update"}};
|
|
||||||
sendMessageToFalukantUsers(fatherUid, message);
|
if (fatherUidOpt) {
|
||||||
sendMessageToFalukantUsers(motherUid, message);
|
sendMessageToFalukantUsers(*fatherUidOpt, message);
|
||||||
|
// Sende falukantUpdateStatus nach dem Erstellen des Kindes
|
||||||
|
nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
|
||||||
|
sendMessageToFalukantUsers(*fatherUidOpt, updateMessage);
|
||||||
|
}
|
||||||
|
if (motherUidOpt) {
|
||||||
|
sendMessageToFalukantUsers(*motherUidOpt, message);
|
||||||
|
// Sende falukantUpdateStatus nach dem Erstellen des Kindes
|
||||||
|
nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
|
||||||
|
sendMessageToFalukantUsers(*motherUidOpt, updateMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ private:
|
|||||||
- 2.638267
|
- 2.638267
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
) / 2; -- Geburtenrate halbiert
|
||||||
)";
|
)";
|
||||||
|
|
||||||
static constexpr char const* QUERY_INSERT_CHILD = R"(
|
static constexpr char const* QUERY_INSERT_CHILD = R"(
|
||||||
|
|||||||
94
src/utils.cpp
Normal file
94
src/utils.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
int Utils::optionalStoiOrDefault(const std::unordered_map<std::string, std::string>& row,
|
||||||
|
const std::string& key, int def) {
|
||||||
|
auto it = row.find(key);
|
||||||
|
if (it == row.end()) return def;
|
||||||
|
const std::string& val = it->second;
|
||||||
|
if (isNullOrEmpty(val)) return def;
|
||||||
|
try {
|
||||||
|
return std::stoi(val);
|
||||||
|
} catch (...) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double Utils::optionalStodOrDefault(const std::unordered_map<std::string, std::string>& row,
|
||||||
|
const std::string& key, double def) {
|
||||||
|
auto it = row.find(key);
|
||||||
|
if (it == row.end()) return def;
|
||||||
|
const std::string& val = it->second;
|
||||||
|
if (isNullOrEmpty(val)) return def;
|
||||||
|
try {
|
||||||
|
return std::stod(val);
|
||||||
|
} catch (...) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::isNullOrEmpty(const std::string& s) {
|
||||||
|
return s.empty() || s == "NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::chrono::system_clock::time_point> Utils::parseTimestamp(const std::string& iso) {
|
||||||
|
std::istringstream ss(iso);
|
||||||
|
std::tm tm = {};
|
||||||
|
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||||
|
if (ss.fail()) {
|
||||||
|
ss.clear();
|
||||||
|
ss.str(iso);
|
||||||
|
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
|
||||||
|
if (ss.fail()) return std::nullopt;
|
||||||
|
}
|
||||||
|
std::time_t time_c = std::mktime(&tm);
|
||||||
|
if (time_c == -1) return std::nullopt;
|
||||||
|
return std::chrono::system_clock::from_time_t(time_c);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> Utils::computeAgeYears(const std::string& birthdate_iso) {
|
||||||
|
auto birth_tp = parseTimestamp(birthdate_iso);
|
||||||
|
if (!birth_tp) return std::nullopt;
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
std::time_t birth_time = std::chrono::system_clock::to_time_t(*birth_tp);
|
||||||
|
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
|
||||||
|
|
||||||
|
std::tm birth_tm;
|
||||||
|
std::tm now_tm;
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
localtime_s(&birth_tm, &birth_time);
|
||||||
|
localtime_s(&now_tm, &now_time);
|
||||||
|
#else
|
||||||
|
localtime_r(&birth_time, &birth_tm);
|
||||||
|
localtime_r(&now_time, &now_tm);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int years = now_tm.tm_year - birth_tm.tm_year;
|
||||||
|
if (now_tm.tm_mon < birth_tm.tm_mon ||
|
||||||
|
(now_tm.tm_mon == birth_tm.tm_mon && now_tm.tm_mday < birth_tm.tm_mday)) {
|
||||||
|
years--;
|
||||||
|
}
|
||||||
|
return years;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Utils::buildPgIntArrayLiteral(const std::vector<int>& elems) {
|
||||||
|
std::string res = "{";
|
||||||
|
for (size_t i = 0; i < elems.size(); ++i) {
|
||||||
|
res += std::to_string(elems[i]);
|
||||||
|
if (i + 1 < elems.size()) res += ",";
|
||||||
|
}
|
||||||
|
res += "}";
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> Utils::optionalUid(const std::string& val) {
|
||||||
|
if (isNullOrEmpty(val)) return std::nullopt;
|
||||||
|
try {
|
||||||
|
return std::stoi(val);
|
||||||
|
} catch (...) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/utils.h
Normal file
30
src/utils.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public:
|
||||||
|
// Safe conversions with fallback
|
||||||
|
static int optionalStoiOrDefault(const std::unordered_map<std::string, std::string>& row,
|
||||||
|
const std::string& key, int def = -1);
|
||||||
|
static double optionalStodOrDefault(const std::unordered_map<std::string, std::string>& row,
|
||||||
|
const std::string& key, double def = 0.0);
|
||||||
|
|
||||||
|
static bool isNullOrEmpty(const std::string& s);
|
||||||
|
|
||||||
|
// Parse timestamp from common ISO / SQL formats into time_point
|
||||||
|
static std::optional<std::chrono::system_clock::time_point> parseTimestamp(const std::string& iso);
|
||||||
|
|
||||||
|
// Compute full years age from birthdate string; returns nullopt on parse failure.
|
||||||
|
static std::optional<int> computeAgeYears(const std::string& birthdate_iso);
|
||||||
|
|
||||||
|
// Build Postgres integer array literal "{1,2,3}"
|
||||||
|
static std::string buildPgIntArrayLiteral(const std::vector<int>& elems);
|
||||||
|
|
||||||
|
// Safely parse a nullable integer-like string
|
||||||
|
static std::optional<int> optionalUid(const std::string& val);
|
||||||
|
};
|
||||||
@@ -56,6 +56,11 @@ void WebSocketServer::startServer() {
|
|||||||
memset(&info, 0, sizeof(info));
|
memset(&info, 0, sizeof(info));
|
||||||
info.port = port;
|
info.port = port;
|
||||||
info.protocols = protocols;
|
info.protocols = protocols;
|
||||||
|
|
||||||
|
// Reduziere Log-Level um weniger Debug-Ausgaben zu haben
|
||||||
|
// Setze Umgebungsvariable für Log-Level
|
||||||
|
setenv("LWS_LOG_LEVEL", "0", 1); // 0 = nur Fehler
|
||||||
|
|
||||||
context = lws_create_context(&info);
|
context = lws_create_context(&info);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw std::runtime_error("Failed to create LWS context");
|
throw std::runtime_error("Failed to create LWS context");
|
||||||
|
|||||||
39
yourpart-daemon.service
Normal file
39
yourpart-daemon.service
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=YourPart Daemon Service
|
||||||
|
Documentation=https://your-part.de
|
||||||
|
After=network.target postgresql.service
|
||||||
|
Wants=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=yourpart
|
||||||
|
Group=yourpart
|
||||||
|
WorkingDirectory=/opt/yourpart
|
||||||
|
ExecStart=/usr/local/bin/yourpart-daemon
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=yourpart-daemon
|
||||||
|
|
||||||
|
# Sicherheitseinstellungen
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/opt/yourpart/logs /var/log/yourpart
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
|
||||||
|
# Umgebungsvariablen
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Ressourcenlimits
|
||||||
|
LimitNOFILE=65536
|
||||||
|
LimitNPROC=4096
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user