Update dependencies and enhance WebSocket server logging: Add 'chrono' and 'android_system_properties' to Cargo.lock, improve error handling and logging in websocket_server.rs, and streamline character creation notifications in worker modules for better clarity and maintainability.
This commit is contained in:
@@ -4,6 +4,7 @@ use std::collections::HashSet;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use chrono::{Local, Timelike};
|
||||
|
||||
use super::base::{BaseWorker, Worker, WorkerState};
|
||||
use crate::worker::sql::{
|
||||
@@ -22,6 +23,15 @@ use crate::worker::sql::{
|
||||
QUERY_GET_USERS_WITH_FILLED_OFFICES,
|
||||
QUERY_PROCESS_ELECTIONS,
|
||||
QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
|
||||
QUERY_FIND_AVAILABLE_CHURCH_OFFICES,
|
||||
QUERY_FIND_CHURCH_SUPERVISOR,
|
||||
QUERY_GET_CHURCH_OFFICE_REQUIREMENTS,
|
||||
QUERY_GET_PENDING_CHURCH_APPLICATIONS,
|
||||
QUERY_CHECK_CHARACTER_ELIGIBILITY,
|
||||
QUERY_APPROVE_CHURCH_APPLICATION,
|
||||
QUERY_REJECT_CHURCH_APPLICATION,
|
||||
QUERY_CREATE_CHURCH_APPLICATION_JOB,
|
||||
QUERY_GET_CHARACTERS_FOR_CHURCH_OFFICE,
|
||||
};
|
||||
|
||||
pub struct PoliticsWorker {
|
||||
@@ -57,6 +67,32 @@ struct Office {
|
||||
region_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AvailableChurchOffice {
|
||||
office_type_id: i32,
|
||||
seats_per_region: i32,
|
||||
region_id: i32,
|
||||
occupied_seats: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChurchSupervisor {
|
||||
supervisor_character_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChurchOfficeRequirement {
|
||||
prerequisite_office_type_id: Option<i32>,
|
||||
min_title_level: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChurchApplication {
|
||||
application_id: i32,
|
||||
office_type_id: i32,
|
||||
applicant_character_id: i32,
|
||||
}
|
||||
|
||||
// --- SQL-Konstanten (1:1 aus politics_worker.h übernommen) ------------------
|
||||
|
||||
|
||||
@@ -69,6 +105,7 @@ impl PoliticsWorker {
|
||||
|
||||
fn run_loop(pool: ConnectionPool, broker: MessageBroker, state: Arc<WorkerState>) {
|
||||
let mut last_execution: Option<Instant> = None;
|
||||
let mut last_church_office_run: Option<Instant> = None;
|
||||
|
||||
while state.running_worker.load(Ordering::Relaxed) {
|
||||
let now = Instant::now();
|
||||
@@ -84,6 +121,25 @@ impl PoliticsWorker {
|
||||
last_execution = Some(now);
|
||||
}
|
||||
|
||||
// Church Office Job um 13 Uhr
|
||||
if Self::is_time_13_00() {
|
||||
let should_run_church = match last_church_office_run {
|
||||
None => true,
|
||||
Some(prev) => {
|
||||
// Prüfe ob seit letztem Lauf mindestens 23 Stunden vergangen sind
|
||||
// (um sicherzustellen, dass es nur einmal pro Tag läuft)
|
||||
now.saturating_duration_since(prev) >= Duration::from_secs(23 * 3600)
|
||||
}
|
||||
};
|
||||
|
||||
if should_run_church {
|
||||
if let Err(err) = Self::perform_church_office_task(&pool, &broker) {
|
||||
eprintln!("[PoliticsWorker] Fehler bei performChurchOfficeTask: {err}");
|
||||
}
|
||||
last_church_office_run = Some(now);
|
||||
}
|
||||
}
|
||||
|
||||
// Entspricht ungefähr der 5-Sekunden-Schleife im C++-Code
|
||||
for _ in 0..5 {
|
||||
if !state.running_worker.load(Ordering::Relaxed) {
|
||||
@@ -94,6 +150,14 @@ impl PoliticsWorker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prüft ob die aktuelle Uhrzeit 13:00 ist (mit Toleranz von ±1 Minute)
|
||||
fn is_time_13_00() -> bool {
|
||||
let now = Local::now();
|
||||
let hour = now.hour();
|
||||
let minute = now.minute();
|
||||
hour == 13 && minute <= 1
|
||||
}
|
||||
|
||||
fn perform_daily_politics_task(
|
||||
pool: &ConnectionPool,
|
||||
broker: &MessageBroker,
|
||||
@@ -553,6 +617,415 @@ impl PoliticsWorker {
|
||||
.filter_map(map_row_to_office)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Verarbeitet Church Office Jobs um 13 Uhr:
|
||||
/// - Findet verfügbare Positionen
|
||||
/// - Verarbeitet bestehende Bewerbungen
|
||||
/// - Erstellt neue Bewerbungen falls nötig
|
||||
fn perform_church_office_task(
|
||||
pool: &ConnectionPool,
|
||||
broker: &MessageBroker,
|
||||
) -> Result<(), DbError> {
|
||||
eprintln!("[PoliticsWorker] Starte Church Office Task um 13 Uhr");
|
||||
|
||||
// 1) Verfügbare Church Office Positionen finden
|
||||
let available_offices = Self::find_available_church_offices(pool)?;
|
||||
eprintln!(
|
||||
"[PoliticsWorker] Gefunden: {} verfügbare Church Office Positionen",
|
||||
available_offices.len()
|
||||
);
|
||||
|
||||
// 2) Für jede verfügbare Position Bewerbungen verarbeiten
|
||||
for office in &available_offices {
|
||||
// Supervisor finden
|
||||
if let Some(supervisor) = Self::find_church_supervisor(pool, office.region_id, office.office_type_id)? {
|
||||
// Bestehende Bewerbungen für diesen Supervisor verarbeiten
|
||||
Self::process_church_applications(pool, broker, supervisor.supervisor_character_id)?;
|
||||
|
||||
// Falls noch Plätze frei sind, neue Bewerbungen erstellen
|
||||
let remaining_seats = office.seats_per_region - office.occupied_seats;
|
||||
if remaining_seats > 0 {
|
||||
Self::create_church_application_jobs(
|
||||
pool,
|
||||
office.office_type_id,
|
||||
office.region_id,
|
||||
supervisor.supervisor_character_id,
|
||||
remaining_seats,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"[PoliticsWorker] Kein Supervisor gefunden für office_type_id={}, region_id={}",
|
||||
office.office_type_id, office.region_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("[PoliticsWorker] Church Office Task abgeschlossen");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_available_church_offices(
|
||||
pool: &ConnectionPool,
|
||||
) -> Result<Vec<AvailableChurchOffice>, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare(
|
||||
"find_available_church_offices",
|
||||
QUERY_FIND_AVAILABLE_CHURCH_OFFICES,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare find_available_church_offices: {e}"
|
||||
))
|
||||
})?;
|
||||
let rows = conn
|
||||
.execute("find_available_church_offices", &[])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec find_available_church_offices: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut offices = Vec::new();
|
||||
for row in rows {
|
||||
let office_type_id = parse_i32(&row, "office_type_id", -1);
|
||||
let region_id = parse_i32(&row, "region_id", -1);
|
||||
let seats_per_region = parse_i32(&row, "seats_per_region", 0);
|
||||
let occupied_seats = parse_i32(&row, "occupied_seats", 0);
|
||||
|
||||
if office_type_id >= 0 && region_id >= 0 {
|
||||
offices.push(AvailableChurchOffice {
|
||||
office_type_id,
|
||||
seats_per_region,
|
||||
region_id,
|
||||
occupied_seats,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(offices)
|
||||
}
|
||||
|
||||
fn find_church_supervisor(
|
||||
pool: &ConnectionPool,
|
||||
region_id: i32,
|
||||
office_type_id: i32,
|
||||
) -> Result<Option<ChurchSupervisor>, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("find_church_supervisor", QUERY_FIND_CHURCH_SUPERVISOR)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare find_church_supervisor: {e}"
|
||||
))
|
||||
})?;
|
||||
let rows = conn
|
||||
.execute("find_church_supervisor", &[®ion_id, &office_type_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec find_church_supervisor: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
for row in rows {
|
||||
let supervisor_character_id = parse_i32(&row, "supervisor_character_id", -1);
|
||||
|
||||
if supervisor_character_id >= 0 {
|
||||
return Ok(Some(ChurchSupervisor {
|
||||
supervisor_character_id,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn process_church_applications(
|
||||
pool: &ConnectionPool,
|
||||
broker: &MessageBroker,
|
||||
supervisor_id: i32,
|
||||
) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
// Bewerbungen für diesen Supervisor abrufen
|
||||
conn.prepare(
|
||||
"get_pending_church_applications",
|
||||
QUERY_GET_PENDING_CHURCH_APPLICATIONS,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare get_pending_church_applications: {e}"
|
||||
))
|
||||
})?;
|
||||
let rows = conn
|
||||
.execute("get_pending_church_applications", &[&supervisor_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec get_pending_church_applications: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut applications = Vec::new();
|
||||
for row in rows {
|
||||
let application_id = parse_i32(&row, "application_id", -1);
|
||||
let office_type_id = parse_i32(&row, "office_type_id", -1);
|
||||
let applicant_character_id = parse_i32(&row, "applicant_character_id", -1);
|
||||
|
||||
if application_id >= 0 {
|
||||
applications.push(ChurchApplication {
|
||||
application_id,
|
||||
office_type_id,
|
||||
applicant_character_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Voraussetzungen prüfen und Bewerbungen verarbeiten
|
||||
conn.prepare(
|
||||
"get_church_office_requirements",
|
||||
QUERY_GET_CHURCH_OFFICE_REQUIREMENTS,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare get_church_office_requirements: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
conn.prepare(
|
||||
"check_character_eligibility",
|
||||
QUERY_CHECK_CHARACTER_ELIGIBILITY,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare check_character_eligibility: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
conn.prepare("approve_church_application", QUERY_APPROVE_CHURCH_APPLICATION)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare approve_church_application: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
conn.prepare("reject_church_application", QUERY_REJECT_CHURCH_APPLICATION)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare reject_church_application: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
for app in &applications {
|
||||
// Voraussetzungen für dieses Amt abrufen
|
||||
let req_rows = conn
|
||||
.execute("get_church_office_requirements", &[&app.office_type_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec get_church_office_requirements: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut requirements = Vec::new();
|
||||
for req_row in req_rows {
|
||||
let prerequisite_office_type_id = req_row
|
||||
.get("prerequisite_office_type_id")
|
||||
.and_then(|v| v.parse::<i32>().ok());
|
||||
let min_title_level = req_row
|
||||
.get("min_title_level")
|
||||
.and_then(|v| v.parse::<i32>().ok());
|
||||
|
||||
requirements.push(ChurchOfficeRequirement {
|
||||
prerequisite_office_type_id,
|
||||
min_title_level,
|
||||
});
|
||||
}
|
||||
|
||||
// Prüfe ob Character die Voraussetzungen erfüllt
|
||||
let mut eligible = true;
|
||||
for req in &requirements {
|
||||
let elig_rows = conn
|
||||
.execute(
|
||||
"check_character_eligibility",
|
||||
&[
|
||||
&app.applicant_character_id,
|
||||
&req.prerequisite_office_type_id,
|
||||
&req.min_title_level,
|
||||
],
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec check_character_eligibility: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
for elig_row in elig_rows {
|
||||
let has_prerequisite: bool = elig_row
|
||||
.get("has_prerequisite")
|
||||
.and_then(|v| v.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let meets_title_requirement: bool = elig_row
|
||||
.get("meets_title_requirement")
|
||||
.and_then(|v| v.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_prerequisite || !meets_title_requirement {
|
||||
eligible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bewerbung genehmigen oder ablehnen
|
||||
if eligible {
|
||||
let approve_rows = conn
|
||||
.execute("approve_church_application", &[&app.application_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec approve_church_application: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
if !approve_rows.is_empty() {
|
||||
eprintln!(
|
||||
"[PoliticsWorker] Church Application {} genehmigt (office_type_id={}, character_id={})",
|
||||
app.application_id, app.office_type_id, app.applicant_character_id
|
||||
);
|
||||
|
||||
// Benachrichtigung senden
|
||||
if let Some(user_id) = Self::get_user_id_for_character(pool, app.applicant_character_id)? {
|
||||
let msg = format!(
|
||||
r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#,
|
||||
user_id
|
||||
);
|
||||
broker.publish(msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conn.execute("reject_church_application", &[&app.application_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec reject_church_application: {e}"
|
||||
))
|
||||
})?;
|
||||
eprintln!(
|
||||
"[PoliticsWorker] Church Application {} abgelehnt (Voraussetzungen nicht erfüllt)",
|
||||
app.application_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_church_application_jobs(
|
||||
pool: &ConnectionPool,
|
||||
office_type_id: i32,
|
||||
region_id: i32,
|
||||
supervisor_id: i32,
|
||||
count: i32,
|
||||
) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
// Charaktere für diese Region finden
|
||||
conn.prepare(
|
||||
"get_characters_for_church_office",
|
||||
QUERY_GET_CHARACTERS_FOR_CHURCH_OFFICE,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare get_characters_for_church_office: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let rows = conn
|
||||
.execute("get_characters_for_church_office", &[®ion_id, &count])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec get_characters_for_church_office: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
conn.prepare(
|
||||
"create_church_application_job",
|
||||
QUERY_CREATE_CHURCH_APPLICATION_JOB,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] prepare create_church_application_job: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut created = 0;
|
||||
for row in rows {
|
||||
if let Some(character_id) = row
|
||||
.get("character_id")
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
{
|
||||
let app_rows = conn
|
||||
.execute(
|
||||
"create_church_application_job",
|
||||
&[&office_type_id, &character_id, ®ion_id, &supervisor_id],
|
||||
)
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec create_church_application_job: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
if !app_rows.is_empty() {
|
||||
created += 1;
|
||||
eprintln!(
|
||||
"[PoliticsWorker] Church Application Job erstellt: office_type_id={}, character_id={}, region_id={}",
|
||||
office_type_id, character_id, region_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[PoliticsWorker] {} Church Application Jobs erstellt für office_type_id={}, region_id={}",
|
||||
created, office_type_id, region_id
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_user_id_for_character(
|
||||
pool: &ConnectionPool,
|
||||
character_id: i32,
|
||||
) -> Result<Option<i32>, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
let query = "SELECT user_id FROM falukant_data.character WHERE id = $1";
|
||||
conn.prepare("get_user_id_for_character", query)
|
||||
.map_err(|e| DbError::new(format!("[PoliticsWorker] prepare get_user_id_for_character: {e}")))?;
|
||||
let rows = conn
|
||||
.execute("get_user_id_for_character", &[&character_id])
|
||||
.map_err(|e| {
|
||||
DbError::new(format!(
|
||||
"[PoliticsWorker] exec get_user_id_for_character: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
for row in rows {
|
||||
if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) {
|
||||
return Ok(Some(user_id));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Worker for PoliticsWorker {
|
||||
|
||||
Reference in New Issue
Block a user