v0.4.8 - Implement web server functionality for embedded admin interface - serve HTML/CSS/JS from /api/ endpoint with proper MIME types, CORS headers, and performance optimizations
This commit is contained in:
386
src/config.c
386
src/config.c
@@ -65,6 +65,9 @@ extern void log_error(const char* message);
|
||||
int populate_default_config_values(void);
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||
void invalidate_config_cache(void);
|
||||
|
||||
// Forward declaration for relay info initialization
|
||||
void init_relay_info(void);
|
||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value, const char* action);
|
||||
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
@@ -80,6 +83,7 @@ const char* get_first_tag_name(cJSON* event);
|
||||
const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);
|
||||
int parse_auth_query_parameters(cJSON* event, char** query_type, char** pattern_value);
|
||||
int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
|
||||
// Current configuration cache
|
||||
@@ -215,32 +219,44 @@ static int refresh_unified_cache_from_table(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
log_info("Refreshing unified configuration cache from database");
|
||||
|
||||
// Lock the cache for update (don't memset entire cache to avoid wiping relay_info)
|
||||
log_info("DEBUG: Acquiring cache lock for refresh");
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
log_info("DEBUG: Cache lock acquired");
|
||||
|
||||
// Load critical config values from table
|
||||
log_info("DEBUG: Loading admin_pubkey from table");
|
||||
const char* admin_pubkey = get_config_value_from_table("admin_pubkey");
|
||||
if (admin_pubkey) {
|
||||
log_info("DEBUG: Setting admin_pubkey in cache");
|
||||
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
free((char*)admin_pubkey);
|
||||
}
|
||||
|
||||
log_info("DEBUG: Loading relay_pubkey from table");
|
||||
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
log_info("DEBUG: Setting relay_pubkey in cache");
|
||||
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||
free((char*)relay_pubkey);
|
||||
}
|
||||
|
||||
// Load auth-related config
|
||||
const char* auth_required = get_config_value_from_table("auth_required");
|
||||
g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0;
|
||||
if (auth_required) free((char*)auth_required);
|
||||
|
||||
const char* admin_enabled = get_config_value_from_table("admin_enabled");
|
||||
g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0;
|
||||
if (admin_enabled) free((char*)admin_enabled);
|
||||
|
||||
const char* max_file_size = get_config_value_from_table("max_file_size");
|
||||
g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default
|
||||
if (max_file_size) free((char*)max_file_size);
|
||||
|
||||
const char* nip42_mode = get_config_value_from_table("nip42_mode");
|
||||
if (nip42_mode) {
|
||||
@@ -251,38 +267,122 @@ static int refresh_unified_cache_from_table(void) {
|
||||
} else {
|
||||
g_unified_cache.nip42_mode = 1; // Optional/enabled
|
||||
}
|
||||
free((char*)nip42_mode);
|
||||
} else {
|
||||
g_unified_cache.nip42_mode = 1; // Default to optional/enabled
|
||||
}
|
||||
|
||||
const char* challenge_timeout = get_config_value_from_table("nip42_challenge_timeout");
|
||||
g_unified_cache.nip42_challenge_timeout = challenge_timeout ? atoi(challenge_timeout) : 600;
|
||||
if (challenge_timeout) free((char*)challenge_timeout);
|
||||
|
||||
const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance");
|
||||
g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300;
|
||||
if (time_tolerance) free((char*)time_tolerance);
|
||||
|
||||
// Load NIP-70 protected events config
|
||||
const char* nip70_enabled = get_config_value_from_table("nip70_protected_events_enabled");
|
||||
g_unified_cache.nip70_protected_events_enabled = (nip70_enabled && strcmp(nip70_enabled, "true") == 0) ? 1 : 0;
|
||||
if (nip70_enabled) free((char*)nip70_enabled);
|
||||
|
||||
// Load NIP-11 relay info fields directly into cache (avoid circular dependency)
|
||||
const char* relay_name = get_config_value_from_table("relay_name");
|
||||
if (relay_name) {
|
||||
strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
g_unified_cache.relay_info.name[sizeof(g_unified_cache.relay_info.name) - 1] = '\0';
|
||||
free((char*)relay_name);
|
||||
}
|
||||
|
||||
const char* relay_description = get_config_value_from_table("relay_description");
|
||||
if (relay_description) {
|
||||
strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
g_unified_cache.relay_info.description[sizeof(g_unified_cache.relay_info.description) - 1] = '\0';
|
||||
free((char*)relay_description);
|
||||
}
|
||||
|
||||
const char* relay_contact = get_config_value_from_table("relay_contact");
|
||||
if (relay_contact) {
|
||||
strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1);
|
||||
g_unified_cache.relay_info.contact[sizeof(g_unified_cache.relay_info.contact) - 1] = '\0';
|
||||
free((char*)relay_contact);
|
||||
}
|
||||
|
||||
const char* relay_software = get_config_value_from_table("relay_software");
|
||||
if (relay_software) {
|
||||
strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
g_unified_cache.relay_info.software[sizeof(g_unified_cache.relay_info.software) - 1] = '\0';
|
||||
free((char*)relay_software);
|
||||
}
|
||||
|
||||
const char* relay_version = get_config_value_from_table("relay_version");
|
||||
if (relay_version) {
|
||||
strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
g_unified_cache.relay_info.version[sizeof(g_unified_cache.relay_info.version) - 1] = '\0';
|
||||
free((char*)relay_version);
|
||||
}
|
||||
|
||||
const char* supported_nips = get_config_value_from_table("supported_nips");
|
||||
if (supported_nips) {
|
||||
strncpy(g_unified_cache.relay_info.supported_nips_str, supported_nips, sizeof(g_unified_cache.relay_info.supported_nips_str) - 1);
|
||||
g_unified_cache.relay_info.supported_nips_str[sizeof(g_unified_cache.relay_info.supported_nips_str) - 1] = '\0';
|
||||
free((char*)supported_nips);
|
||||
}
|
||||
|
||||
const char* language_tags = get_config_value_from_table("language_tags");
|
||||
if (language_tags) {
|
||||
strncpy(g_unified_cache.relay_info.language_tags_str, language_tags, sizeof(g_unified_cache.relay_info.language_tags_str) - 1);
|
||||
g_unified_cache.relay_info.language_tags_str[sizeof(g_unified_cache.relay_info.language_tags_str) - 1] = '\0';
|
||||
free((char*)language_tags);
|
||||
}
|
||||
|
||||
const char* relay_countries = get_config_value_from_table("relay_countries");
|
||||
if (relay_countries) {
|
||||
strncpy(g_unified_cache.relay_info.relay_countries_str, relay_countries, sizeof(g_unified_cache.relay_info.relay_countries_str) - 1);
|
||||
g_unified_cache.relay_info.relay_countries_str[sizeof(g_unified_cache.relay_info.relay_countries_str) - 1] = '\0';
|
||||
free((char*)relay_countries);
|
||||
}
|
||||
|
||||
const char* posting_policy = get_config_value_from_table("posting_policy");
|
||||
if (posting_policy) {
|
||||
strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, sizeof(g_unified_cache.relay_info.posting_policy) - 1);
|
||||
g_unified_cache.relay_info.posting_policy[sizeof(g_unified_cache.relay_info.posting_policy) - 1] = '\0';
|
||||
free((char*)posting_policy);
|
||||
}
|
||||
|
||||
const char* payments_url = get_config_value_from_table("payments_url");
|
||||
if (payments_url) {
|
||||
strncpy(g_unified_cache.relay_info.payments_url, payments_url, sizeof(g_unified_cache.relay_info.payments_url) - 1);
|
||||
g_unified_cache.relay_info.payments_url[sizeof(g_unified_cache.relay_info.payments_url) - 1] = '\0';
|
||||
free((char*)payments_url);
|
||||
}
|
||||
|
||||
// Set cache expiration
|
||||
log_info("DEBUG: Setting cache expiration and validity");
|
||||
int cache_timeout = get_cache_timeout();
|
||||
g_unified_cache.cache_expires = time(NULL) + cache_timeout;
|
||||
g_unified_cache.cache_valid = 1;
|
||||
|
||||
log_info("DEBUG: Releasing cache lock");
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
log_info("Unified configuration cache refreshed from database");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get admin pubkey from cache (with automatic refresh)
|
||||
const char* get_admin_pubkey_cached(void) {
|
||||
// First check without holding lock: whether we need refresh
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Check cache validity
|
||||
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
|
||||
int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (need_refresh) {
|
||||
// Perform refresh, which locks internally
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
|
||||
// Now read under lock
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
const char* result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
return result;
|
||||
@@ -290,13 +390,18 @@ const char* get_admin_pubkey_cached(void) {
|
||||
|
||||
// Get relay pubkey from cache (with automatic refresh)
|
||||
const char* get_relay_pubkey_cached(void) {
|
||||
// First check without holding lock: whether we need refresh
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Check cache validity
|
||||
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
|
||||
int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires);
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (need_refresh) {
|
||||
// Perform refresh, which locks internally
|
||||
refresh_unified_cache_from_table();
|
||||
}
|
||||
|
||||
|
||||
// Now read under lock
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
const char* result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
return result;
|
||||
@@ -696,23 +801,22 @@ int init_configuration_system(const char* config_dir_override, const char* confi
|
||||
|
||||
// Initialize unified cache with proper structure initialization
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Clear the entire cache structure
|
||||
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||
|
||||
// Reinitialize the mutex after memset
|
||||
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// Initialize basic cache state
|
||||
|
||||
// Initialize basic cache state (do not memset entire struct to avoid corrupting mutex)
|
||||
g_unified_cache.cache_valid = 0;
|
||||
g_unified_cache.cache_expires = 0;
|
||||
|
||||
|
||||
// Clear string fields
|
||||
memset(g_unified_cache.admin_pubkey, 0, sizeof(g_unified_cache.admin_pubkey));
|
||||
memset(g_unified_cache.relay_pubkey, 0, sizeof(g_unified_cache.relay_pubkey));
|
||||
memset(&g_unified_cache.relay_info, 0, sizeof(g_unified_cache.relay_info));
|
||||
|
||||
// Initialize relay_info structure with default values
|
||||
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git",
|
||||
sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
strncpy(g_unified_cache.relay_info.version, "0.2.0",
|
||||
sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
|
||||
|
||||
// Initialize pow_config structure with defaults
|
||||
g_unified_cache.pow_config.enabled = 1;
|
||||
g_unified_cache.pow_config.min_pow_difficulty = 0;
|
||||
@@ -721,14 +825,23 @@ int init_configuration_system(const char* config_dir_override, const char* confi
|
||||
g_unified_cache.pow_config.reject_lower_targets = 0;
|
||||
g_unified_cache.pow_config.strict_format = 0;
|
||||
g_unified_cache.pow_config.anti_spam_mode = 0;
|
||||
|
||||
|
||||
// Initialize expiration_config structure with defaults
|
||||
g_unified_cache.expiration_config.enabled = 1;
|
||||
g_unified_cache.expiration_config.strict_mode = 1;
|
||||
g_unified_cache.expiration_config.filter_responses = 1;
|
||||
g_unified_cache.expiration_config.delete_expired = 0;
|
||||
g_unified_cache.expiration_config.grace_period = 1;
|
||||
|
||||
|
||||
// Initialize other scalar fields
|
||||
g_unified_cache.auth_required = 0;
|
||||
g_unified_cache.admin_enabled = 0;
|
||||
g_unified_cache.max_file_size = 0;
|
||||
g_unified_cache.nip42_mode = 0;
|
||||
g_unified_cache.nip42_challenge_timeout = 0;
|
||||
g_unified_cache.nip42_time_tolerance = 0;
|
||||
g_unified_cache.nip70_protected_events_enabled = 0;
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
log_success("Event-based configuration system initialized with unified cache structures");
|
||||
@@ -750,34 +863,53 @@ void cleanup_configuration_system(void) {
|
||||
|
||||
// Clear unified cache with proper cleanup of JSON objects
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
|
||||
// Clean up relay_info JSON objects if they exist
|
||||
if (g_unified_cache.relay_info.supported_nips) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
|
||||
g_unified_cache.relay_info.supported_nips = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.limitation) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.limitation);
|
||||
g_unified_cache.relay_info.limitation = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.retention) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.retention);
|
||||
g_unified_cache.relay_info.retention = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.relay_countries) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
|
||||
g_unified_cache.relay_info.relay_countries = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.language_tags) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.language_tags);
|
||||
g_unified_cache.relay_info.language_tags = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.tags) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.tags);
|
||||
g_unified_cache.relay_info.tags = NULL;
|
||||
}
|
||||
if (g_unified_cache.relay_info.fees) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.fees);
|
||||
g_unified_cache.relay_info.fees = NULL;
|
||||
}
|
||||
|
||||
// Clear the entire cache structure
|
||||
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
// Clear cache fields individually (do not memset entire struct to avoid corrupting mutex)
|
||||
g_unified_cache.cache_valid = 0;
|
||||
g_unified_cache.cache_expires = 0;
|
||||
memset(g_unified_cache.admin_pubkey, 0, sizeof(g_unified_cache.admin_pubkey));
|
||||
memset(g_unified_cache.relay_pubkey, 0, sizeof(g_unified_cache.relay_pubkey));
|
||||
memset(&g_unified_cache.relay_info, 0, sizeof(g_unified_cache.relay_info));
|
||||
g_unified_cache.auth_required = 0;
|
||||
g_unified_cache.admin_enabled = 0;
|
||||
g_unified_cache.max_file_size = 0;
|
||||
g_unified_cache.nip42_mode = 0;
|
||||
g_unified_cache.nip42_challenge_timeout = 0;
|
||||
g_unified_cache.nip42_time_tolerance = 0;
|
||||
g_unified_cache.nip70_protected_events_enabled = 0;
|
||||
memset(&g_unified_cache.pow_config, 0, sizeof(g_unified_cache.pow_config));
|
||||
memset(&g_unified_cache.expiration_config, 0, sizeof(g_unified_cache.expiration_config));
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
log_success("Configuration system cleaned up with proper JSON cleanup");
|
||||
}
|
||||
@@ -1938,71 +2070,8 @@ const char* get_config_value_from_table(const char* key) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* value = (char*)sqlite3_column_text(stmt, 0);
|
||||
if (value) {
|
||||
// For NIP-11 fields, store in cache buffers but return dynamically allocated strings for consistency
|
||||
if (strcmp(key, "relay_name") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.name, value, sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
g_unified_cache.relay_info.name[sizeof(g_unified_cache.relay_info.name) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "relay_description") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.description, value, sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
g_unified_cache.relay_info.description[sizeof(g_unified_cache.relay_info.description) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "relay_contact") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.contact, value, sizeof(g_unified_cache.relay_info.contact) - 1);
|
||||
g_unified_cache.relay_info.contact[sizeof(g_unified_cache.relay_info.contact) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "relay_software") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.software, value, sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
g_unified_cache.relay_info.software[sizeof(g_unified_cache.relay_info.software) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "relay_version") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.version, value, sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
g_unified_cache.relay_info.version[sizeof(g_unified_cache.relay_info.version) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "supported_nips") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.supported_nips_str, value, sizeof(g_unified_cache.relay_info.supported_nips_str) - 1);
|
||||
g_unified_cache.relay_info.supported_nips_str[sizeof(g_unified_cache.relay_info.supported_nips_str) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "language_tags") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.language_tags_str, value, sizeof(g_unified_cache.relay_info.language_tags_str) - 1);
|
||||
g_unified_cache.relay_info.language_tags_str[sizeof(g_unified_cache.relay_info.language_tags_str) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "relay_countries") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.relay_countries_str, value, sizeof(g_unified_cache.relay_info.relay_countries_str) - 1);
|
||||
g_unified_cache.relay_info.relay_countries_str[sizeof(g_unified_cache.relay_info.relay_countries_str) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "posting_policy") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.posting_policy, value, sizeof(g_unified_cache.relay_info.posting_policy) - 1);
|
||||
g_unified_cache.relay_info.posting_policy[sizeof(g_unified_cache.relay_info.posting_policy) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else if (strcmp(key, "payments_url") == 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
strncpy(g_unified_cache.relay_info.payments_url, value, sizeof(g_unified_cache.relay_info.payments_url) - 1);
|
||||
g_unified_cache.relay_info.payments_url[sizeof(g_unified_cache.relay_info.payments_url) - 1] = '\0';
|
||||
result = strdup(value); // Return dynamically allocated copy
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
} else {
|
||||
// For other keys, return a dynamically allocated string to prevent buffer reuse
|
||||
result = strdup(value);
|
||||
}
|
||||
// Return a dynamically allocated string to prevent buffer reuse
|
||||
result = strdup(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3032,6 +3101,10 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
printf(" Command: %s\n", command);
|
||||
return handle_system_command_unified(event, command, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "stats_query") == 0) {
|
||||
log_info("DEBUG: Routing to stats_query handler");
|
||||
return handle_stats_query_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "whitelist") == 0 || strcmp(action_type, "blacklist") == 0) {
|
||||
log_info("DEBUG: Routing to auth rule modification handler");
|
||||
printf(" Rule type: %s\n", action_type);
|
||||
@@ -3708,6 +3781,131 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Unified stats query handler
|
||||
int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified stats query");
|
||||
printf(" Query type: stats_query\n");
|
||||
|
||||
// Build response with database statistics
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "query_type", "stats_query");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
// Get database file size
|
||||
struct stat db_stat;
|
||||
long long db_size = 0;
|
||||
if (stat(g_database_path, &db_stat) == 0) {
|
||||
db_size = db_stat.st_size;
|
||||
}
|
||||
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
|
||||
|
||||
// Query total events count
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON_AddNumberToObject(response, "total_events", sqlite3_column_int64(stmt, 0));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Query event kinds distribution
|
||||
cJSON* event_kinds = cJSON_CreateArray();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT kind, count, percentage FROM event_kinds_view ORDER BY count DESC", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* kind_obj = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0));
|
||||
cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2));
|
||||
cJSON_AddItemToArray(event_kinds, kind_obj);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
cJSON_AddItemToObject(response, "event_kinds", event_kinds);
|
||||
|
||||
// Query time-based statistics
|
||||
cJSON* time_stats = cJSON_CreateObject();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT total_events, events_24h, events_7d, events_30d FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON_AddNumberToObject(time_stats, "total", sqlite3_column_int64(stmt, 0));
|
||||
cJSON_AddNumberToObject(time_stats, "last_24h", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(time_stats, "last_7d", sqlite3_column_int64(stmt, 2));
|
||||
cJSON_AddNumberToObject(time_stats, "last_30d", sqlite3_column_int64(stmt, 3));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
cJSON_AddItemToObject(response, "time_stats", time_stats);
|
||||
|
||||
// Query top pubkeys
|
||||
cJSON* top_pubkeys = cJSON_CreateArray();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT pubkey, event_count, percentage FROM top_pubkeys_view ORDER BY event_count DESC LIMIT 10", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* pubkey_obj = cJSON_CreateObject();
|
||||
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(pubkey_obj, "percentage", sqlite3_column_double(stmt, 2));
|
||||
cJSON_AddItemToArray(top_pubkeys, pubkey_obj);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
cJSON_AddItemToObject(response, "top_pubkeys", top_pubkeys);
|
||||
|
||||
// Get database creation timestamp (oldest event)
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT MIN(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
sqlite3_int64 oldest_timestamp = sqlite3_column_int64(stmt, 0);
|
||||
if (oldest_timestamp > 0) {
|
||||
cJSON_AddNumberToObject(response, "database_created_at", (double)oldest_timestamp);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// Get latest event timestamp
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT MAX(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
sqlite3_int64 latest_timestamp = sqlite3_column_int64(stmt, 0);
|
||||
if (latest_timestamp > 0) {
|
||||
cJSON_AddNumberToObject(response, "latest_event_at", (double)latest_timestamp);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
printf("=== Database Statistics ===\n");
|
||||
printf("Database size: %lld bytes\n", db_size);
|
||||
printf("Event kinds: %d\n", cJSON_GetArraySize(event_kinds));
|
||||
printf("Top pubkeys: %d\n", cJSON_GetArraySize(top_pubkeys));
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Stats query completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send stats query response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unified config update handler - handles multiple config objects in single atomic command
|
||||
int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
|
||||
Reference in New Issue
Block a user