v0.3.16 - Admin system getting better

This commit is contained in:
Your Name
2025-09-30 05:32:23 -04:00
parent 6fd3e531c3
commit 6dac231040
10 changed files with 2528 additions and 1053 deletions

View File

@@ -72,11 +72,14 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type
int is_config_table_ready(void);
int migrate_config_from_events_to_table(void);
int populate_config_table_from_event(const cJSON* event);
int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, char* error_message, size_t error_size, struct lws* wsi);
// Forward declarations for tag parsing utilities
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);
// Current configuration cache
@@ -380,48 +383,9 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
return NULL;
}
const char* sql;
sqlite3_stmt* stmt;
int rc;
// Configuration is now managed through config table, not events
log_info("Configuration events are no longer stored in events table");
return NULL;
cJSON* event = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
// Reconstruct the event JSON from database columns
event = cJSON_CreateObject();
if (event) {
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 1);
cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0));
cJSON_AddStringToObject(event, "pubkey", event_pubkey);
cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
// If we didn't have admin pubkey in cache, we should update the cache
// Note: This will be handled by the cache refresh mechanism automatically
// Parse tags JSON
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
if (tags_str) {
cJSON* tags = cJSON_Parse(tags_str);
if (tags) {
cJSON_AddItemToObject(event, "tags", tags);
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
}
}
sqlite3_finalize(stmt);
return event;
}
// ================================
@@ -920,7 +884,7 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
// Create and sign event using nostr_core_lib
cJSON* event = nostr_create_and_sign_event(
23455, // kind
33334, // kind
"C Nostr Relay Configuration", // content
tags, // tags
admin_privkey_bytes, // private key bytes for signing
@@ -1597,7 +1561,7 @@ int process_configuration_event(const cJSON* event) {
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!kind_obj || (cJSON_GetNumberValue(kind_obj) != 23455 && cJSON_GetNumberValue(kind_obj) != 23456)) {
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
log_error("Invalid event kind for configuration");
return -1;
}
@@ -2047,7 +2011,7 @@ int add_pubkeys_to_config_table(void) {
// Forward declaration for admin authorization function from main.c
extern int is_authorized_admin_event(cJSON* event);
// Process admin events (updated for new Kind 23455/23456)
// Process admin events (updated for Kind 23456)
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
log_info("DEBUG: Entering process_admin_event_in_config()");
@@ -2098,9 +2062,6 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
// Route to appropriate handler based on kind
log_info("DEBUG: Routing to kind-specific handler");
switch (kind) {
case 23455: // New ephemeral configuration management
log_info("DEBUG: Routing to process_admin_config_event (kind 23455)");
return process_admin_config_event(event, error_message, error_size);
case 23456: // New ephemeral auth rules management
log_info("DEBUG: Routing to process_admin_auth_event (kind 23456)");
return process_admin_auth_event(event, error_message, error_size, wsi);
@@ -2112,7 +2073,7 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
}
}
// Handle Kind 23455 configuration management events
// Handle legacy Kind 33334 configuration management events
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
@@ -2143,9 +2104,8 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
if (strcmp(tag_key, "config_query") == 0) {
printf(" Config Query: %s\n", tag_val);
// For now, config queries are not implemented in the unified handler
// They would need to be added to handle_kind_23455_unified similar to auth queries
snprintf(error_message, error_size, "config queries not yet implemented in unified handler");
// Config queries are not implemented for legacy kind 33334
snprintf(error_message, error_size, "config queries not supported for legacy kind 33334");
return -1;
}
}
@@ -2592,6 +2552,38 @@ int send_admin_response_event(const cJSON* response_data, const char* recipient_
// ================================
// Map query command types to proper response types for frontend routing
static const char* map_auth_query_type_to_response(const char* query_type) {
if (!query_type) return "auth_rules_unknown";
if (strcmp(query_type, "all") == 0) {
return "auth_rules_all";
} else if (strcmp(query_type, "whitelist") == 0) {
return "auth_rules_whitelist";
} else if (strcmp(query_type, "blacklist") == 0) {
return "auth_rules_blacklist";
} else if (strcmp(query_type, "pattern") == 0) {
return "auth_rules_pattern";
} else {
return "auth_rules_unknown";
}
}
// Map config query command types to proper response types for frontend routing
static const char* map_config_query_type_to_response(const char* query_type) {
if (!query_type) return "config_unknown";
if (strcmp(query_type, "all") == 0) {
return "config_all";
} else if (strcmp(query_type, "category") == 0) {
return "config_category";
} else if (strcmp(query_type, "key") == 0) {
return "config_key";
} else {
return "config_unknown";
}
}
// Build standardized query response
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count) {
if (!query_type || !results_array) return NULL;
@@ -2826,6 +2818,33 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
printf(" Query type: %s\n", query_type);
return handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
}
else if (strcmp(action_type, "config_query") == 0) {
log_info("DEBUG: Routing to config_query handler");
const char* query_type = get_tag_value(event, action_type, 1);
if (!query_type) {
log_error("DEBUG: Missing config_query type parameter");
snprintf(error_message, error_size, "invalid: missing config_query type");
return -1;
}
printf(" Query type: %s\n", query_type);
return handle_config_query_unified(event, query_type, error_message, error_size, wsi);
}
else if (strcmp(action_type, "config_set") == 0) {
log_info("DEBUG: Routing to config_set handler");
const char* config_key = get_tag_value(event, action_type, 1);
const char* config_value = get_tag_value(event, action_type, 2);
if (!config_key || !config_value) {
log_error("DEBUG: Missing config_set parameters");
snprintf(error_message, error_size, "invalid: missing config_set key or value");
return -1;
}
printf(" Key: %s, Value: %s\n", config_key, config_value);
return handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
}
else if (strcmp(action_type, "config_update") == 0) {
log_info("DEBUG: Routing to config_update handler");
return handle_config_update_unified(event, error_message, error_size, wsi);
}
else if (strcmp(action_type, "system_command") == 0) {
log_info("DEBUG: Routing to system_command handler");
const char* command = get_tag_value(event, action_type, 1);
@@ -2940,8 +2959,9 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
sqlite3_finalize(stmt);
// Build and send response
cJSON* response = build_query_response(query_type, results_array, rule_count);
// Build and send response with mapped query type for frontend routing
const char* mapped_query_type = map_auth_query_type_to_response(query_type);
cJSON* response = build_query_response(mapped_query_type, results_array, rule_count);
if (response) {
// Get admin pubkey from event for response
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
@@ -2958,6 +2978,7 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
printf("Total results: %d\n", rule_count);
log_success("Auth query completed successfully with signed response");
printf(" Response query_type: %s (mapped from %s)\n", mapped_query_type, query_type);
cJSON_Delete(response);
cJSON_Delete(results_array);
return 0;
@@ -2970,6 +2991,219 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
return -1;
}
// Unified config query handler
int handle_config_query_unified(cJSON* event, const char* query_type, 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 config query");
printf(" Query type: %s\n", query_type);
const char* sql = NULL;
int use_pattern_param = 0;
char* pattern_value = NULL;
// Build appropriate SQL query based on query type
if (strcmp(query_type, "all") == 0) {
sql = "SELECT key, value, data_type, category, description FROM config ORDER BY category, key";
}
else if (strcmp(query_type, "category") == 0) {
// Get category value from tags
pattern_value = (char*)get_tag_value(event, "config_query", 2);
if (!pattern_value) {
snprintf(error_message, error_size, "invalid: category query requires category value");
return -1;
}
sql = "SELECT key, value, data_type, category, description FROM config WHERE category = ? ORDER BY key";
use_pattern_param = 1;
}
else if (strcmp(query_type, "key") == 0) {
// Get key value from tags
pattern_value = (char*)get_tag_value(event, "config_query", 2);
if (!pattern_value) {
snprintf(error_message, error_size, "invalid: key query requires key value");
return -1;
}
sql = "SELECT key, value, data_type, category, description FROM config WHERE key = ? ORDER BY key";
use_pattern_param = 1;
}
else {
snprintf(error_message, error_size, "invalid: unknown config query type '%s'", query_type);
return -1;
}
// Execute query
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
snprintf(error_message, error_size, "failed to prepare config query");
return -1;
}
if (use_pattern_param && pattern_value) {
sqlite3_bind_text(stmt, 1, pattern_value, -1, SQLITE_STATIC);
}
// Build results array
cJSON* results_array = cJSON_CreateArray();
if (!results_array) {
sqlite3_finalize(stmt);
snprintf(error_message, error_size, "failed to create results array");
return -1;
}
int config_count = 0;
printf("=== Config Query Results (%s) ===\n", query_type);
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
const char* data_type = (const char*)sqlite3_column_text(stmt, 2);
const char* category = (const char*)sqlite3_column_text(stmt, 3);
const char* description = (const char*)sqlite3_column_text(stmt, 4);
printf(" %s = %s [%s] (%s)\n",
key ? key : "",
value ? value : "",
data_type ? data_type : "string",
category ? category : "general");
// Add config item to results array
cJSON* config_obj = cJSON_CreateObject();
cJSON_AddStringToObject(config_obj, "key", key ? key : "");
cJSON_AddStringToObject(config_obj, "value", value ? value : "");
cJSON_AddStringToObject(config_obj, "data_type", data_type ? data_type : "string");
cJSON_AddStringToObject(config_obj, "category", category ? category : "general");
cJSON_AddStringToObject(config_obj, "description", description ? description : "");
cJSON_AddItemToArray(results_array, config_obj);
config_count++;
}
sqlite3_finalize(stmt);
// Build and send response with mapped query type for frontend routing
const char* mapped_query_type = map_config_query_type_to_response(query_type);
cJSON* response = build_query_response(mapped_query_type, results_array, config_count);
if (response) {
// 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);
cJSON_Delete(results_array);
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) {
printf("Total results: %d\n", config_count);
log_success("Config query completed successfully with signed response");
printf(" Response query_type: %s (mapped from %s)\n", mapped_query_type, query_type);
cJSON_Delete(response);
cJSON_Delete(results_array);
return 0;
}
cJSON_Delete(response);
}
cJSON_Delete(results_array);
snprintf(error_message, error_size, "failed to send config query response");
return -1;
}
// Unified config set handler
int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, 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 config set command");
printf(" Key: %s\n", config_key);
printf(" Value: %s\n", config_value);
// Validate the configuration field before updating
char validation_error[512];
if (validate_config_field(config_key, config_value, validation_error, sizeof(validation_error)) != 0) {
log_error("Config field validation failed");
printf(" Validation error: %s\n", validation_error);
snprintf(error_message, error_size, "validation failed: %s", validation_error);
return -1;
}
// Check if the config key exists in the table
const char* check_sql = "SELECT COUNT(*) FROM config WHERE key = ?";
sqlite3_stmt* check_stmt;
int check_rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
if (check_rc != SQLITE_OK) {
snprintf(error_message, error_size, "failed to prepare config existence check");
return -1;
}
sqlite3_bind_text(check_stmt, 1, config_key, -1, SQLITE_STATIC);
int config_exists = 0;
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
config_exists = sqlite3_column_int(check_stmt, 0) > 0;
}
sqlite3_finalize(check_stmt);
if (!config_exists) {
snprintf(error_message, error_size, "error: configuration key '%s' not found", config_key);
return -1;
}
// Update the configuration value
if (update_config_in_table(config_key, config_value) != 0) {
snprintf(error_message, error_size, "failed to update configuration in database");
return -1;
}
// Invalidate cache to ensure fresh reads
invalidate_config_cache();
// Build response
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "command", "config_set");
cJSON_AddStringToObject(response, "key", config_key);
cJSON_AddStringToObject(response, "value", config_value);
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
printf("Updated config: %s = %s\n", config_key, config_value);
// 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("Config set command completed successfully with signed response");
cJSON_Delete(response);
return 0;
}
cJSON_Delete(response);
snprintf(error_message, error_size, "failed to send config set response");
return -1;
}
// Unified system command handler
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
// Suppress unused parameter warning
@@ -3298,6 +3532,340 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
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
(void)wsi;
if (!g_db) {
snprintf(error_message, error_size, "database not available");
return -1;
}
log_info("Processing unified config update command");
// Extract config objects array from synthetic tags created by NIP-44 decryption
// The decryption process creates synthetic tags like: ["config_update", [config_objects]]
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
snprintf(error_message, error_size, "invalid: config update event must have tags");
return -1;
}
// Find the config_update tag with config objects array
cJSON* config_objects_array = NULL;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags_obj) {
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
continue;
}
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name)) {
continue;
}
if (strcmp(cJSON_GetStringValue(tag_name), "config_update") == 0) {
// Found config_update tag, get the config objects array
cJSON* config_array_item = cJSON_GetArrayItem(tag, 1);
if (config_array_item) {
// The config objects should be in a JSON string format in the tag
if (cJSON_IsString(config_array_item)) {
// Parse the JSON string to get the actual array
const char* config_json = cJSON_GetStringValue(config_array_item);
config_objects_array = cJSON_Parse(config_json);
} else if (cJSON_IsArray(config_array_item)) {
// Direct array reference
config_objects_array = cJSON_Duplicate(config_array_item, 1);
}
}
break;
}
}
if (!config_objects_array || !cJSON_IsArray(config_objects_array)) {
snprintf(error_message, error_size, "invalid: config_update command requires config objects array");
return -1;
}
int config_count = cJSON_GetArraySize(config_objects_array);
log_info("Config update command contains config objects");
printf(" Config objects count: %d\n", config_count);
if (config_count == 0) {
cJSON_Delete(config_objects_array);
snprintf(error_message, error_size, "invalid: config_update command requires at least one config object");
return -1;
}
// Begin transaction for atomic config updates
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
cJSON_Delete(config_objects_array);
snprintf(error_message, error_size, "failed to begin config update transaction");
return -1;
}
int updates_applied = 0;
int validation_errors = 0;
char first_validation_error[512] = {0}; // Track first specific validation error
char first_error_field[128] = {0}; // Track which field failed first
cJSON* processed_configs = cJSON_CreateArray();
if (!processed_configs) {
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
cJSON_Delete(config_objects_array);
snprintf(error_message, error_size, "failed to create response array");
return -1;
}
// Process each config object in the array
cJSON* config_obj = NULL;
cJSON_ArrayForEach(config_obj, config_objects_array) {
if (!cJSON_IsObject(config_obj)) {
log_warning("Skipping non-object item in config objects array");
continue;
}
// Extract required fields from config object
cJSON* key_obj = cJSON_GetObjectItem(config_obj, "key");
cJSON* value_obj = cJSON_GetObjectItem(config_obj, "value");
cJSON* data_type_obj = cJSON_GetObjectItem(config_obj, "data_type");
cJSON* category_obj = cJSON_GetObjectItem(config_obj, "category");
if (!key_obj || !cJSON_IsString(key_obj) ||
!value_obj || !cJSON_IsString(value_obj)) {
log_error("Config object missing required key or value fields");
validation_errors++;
continue;
}
const char* key = cJSON_GetStringValue(key_obj);
const char* value = cJSON_GetStringValue(value_obj);
const char* data_type = data_type_obj && cJSON_IsString(data_type_obj) ?
cJSON_GetStringValue(data_type_obj) : "string";
const char* category = category_obj && cJSON_IsString(category_obj) ?
cJSON_GetStringValue(category_obj) : "general";
log_info("Processing config object");
printf(" Key: %s\n", key);
printf(" Value: %s\n", value);
printf(" Data type: %s\n", data_type);
printf(" Category: %s\n", category);
// Validate the configuration field before updating
char validation_error[512];
if (validate_config_field(key, value, validation_error, sizeof(validation_error)) != 0) {
log_error("Config field validation failed");
printf(" Validation error: %s\n", validation_error);
validation_errors++;
// Capture first validation error for enhanced error message
if (validation_errors == 1) {
strncpy(first_validation_error, validation_error, sizeof(first_validation_error) - 1);
first_validation_error[sizeof(first_validation_error) - 1] = '\0';
strncpy(first_error_field, key, sizeof(first_error_field) - 1);
first_error_field[sizeof(first_error_field) - 1] = '\0';
}
// Add failed config to response array
cJSON* failed_config = cJSON_CreateObject();
cJSON_AddStringToObject(failed_config, "key", key);
cJSON_AddStringToObject(failed_config, "value", value);
cJSON_AddStringToObject(failed_config, "data_type", data_type);
cJSON_AddStringToObject(failed_config, "category", category);
cJSON_AddStringToObject(failed_config, "status", "validation_failed");
cJSON_AddStringToObject(failed_config, "error", validation_error);
cJSON_AddItemToArray(processed_configs, failed_config);
continue;
}
// Check if the config key exists in the table
const char* check_sql = "SELECT COUNT(*) FROM config WHERE key = ?";
sqlite3_stmt* check_stmt;
int check_rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
if (check_rc != SQLITE_OK) {
log_error("Failed to prepare config existence check");
validation_errors++;
continue;
}
sqlite3_bind_text(check_stmt, 1, key, -1, SQLITE_STATIC);
int config_exists = 0;
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
config_exists = sqlite3_column_int(check_stmt, 0) > 0;
}
sqlite3_finalize(check_stmt);
if (!config_exists) {
log_error("Configuration key not found");
printf(" Key not found: %s\n", key);
validation_errors++;
// Add failed config to response array
cJSON* failed_config = cJSON_CreateObject();
cJSON_AddStringToObject(failed_config, "key", key);
cJSON_AddStringToObject(failed_config, "value", value);
cJSON_AddStringToObject(failed_config, "data_type", data_type);
cJSON_AddStringToObject(failed_config, "category", category);
cJSON_AddStringToObject(failed_config, "status", "key_not_found");
cJSON_AddStringToObject(failed_config, "error", "configuration key not found in database");
cJSON_AddItemToArray(processed_configs, failed_config);
continue;
}
// Update the configuration value in the table
if (update_config_in_table(key, value) == 0) {
updates_applied++;
// Add successful config to response array
cJSON* success_config = cJSON_CreateObject();
cJSON_AddStringToObject(success_config, "key", key);
cJSON_AddStringToObject(success_config, "value", value);
cJSON_AddStringToObject(success_config, "data_type", data_type);
cJSON_AddStringToObject(success_config, "category", category);
cJSON_AddStringToObject(success_config, "status", "updated");
cJSON_AddItemToArray(processed_configs, success_config);
log_success("Config field updated successfully");
printf(" Updated: %s = %s\n", key, value);
} else {
log_error("Failed to update config field in database");
printf(" Failed to update: %s = %s\n", key, value);
validation_errors++;
// Add failed config to response array
cJSON* failed_config = cJSON_CreateObject();
cJSON_AddStringToObject(failed_config, "key", key);
cJSON_AddStringToObject(failed_config, "value", value);
cJSON_AddStringToObject(failed_config, "data_type", data_type);
cJSON_AddStringToObject(failed_config, "category", category);
cJSON_AddStringToObject(failed_config, "status", "database_error");
cJSON_AddStringToObject(failed_config, "error", "failed to update configuration in database");
cJSON_AddItemToArray(processed_configs, failed_config);
}
}
// Clean up config objects array
cJSON_Delete(config_objects_array);
// Determine transaction outcome
if (updates_applied > 0 && validation_errors == 0) {
// All updates successful
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
invalidate_config_cache();
char success_msg[256];
snprintf(success_msg, sizeof(success_msg), "Applied %d configuration updates successfully", updates_applied);
log_success(success_msg);
} else if (updates_applied > 0 && validation_errors > 0) {
// Partial success - rollback for atomic behavior
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
char error_msg[256];
snprintf(error_msg, sizeof(error_msg), "Config update failed: %d validation errors (atomic rollback)", validation_errors);
log_error(error_msg);
// Build error response with validation details
cJSON* error_response = cJSON_CreateObject();
cJSON_AddStringToObject(error_response, "query_type", "config_update");
cJSON_AddStringToObject(error_response, "status", "error");
// Create enhanced error message with specific validation details
char enhanced_error_message[1024];
if (strlen(first_validation_error) > 0 && strlen(first_error_field) > 0) {
snprintf(enhanced_error_message, sizeof(enhanced_error_message),
"field validation failed: %s - %s",
first_error_field, first_validation_error);
} else {
snprintf(enhanced_error_message, sizeof(enhanced_error_message),
"field validation failed: atomic rollback performed");
}
cJSON_AddStringToObject(error_response, "error", enhanced_error_message);
cJSON_AddNumberToObject(error_response, "validation_errors", validation_errors);
cJSON_AddNumberToObject(error_response, "timestamp", (double)time(NULL));
cJSON_AddItemToObject(error_response, "data", processed_configs);
// Get admin pubkey from event for error response
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
if (admin_pubkey) {
// Send error response as signed kind 23457 event
if (send_admin_response_event(error_response, admin_pubkey, wsi) == 0) {
log_info("Config update validation error response sent successfully");
cJSON_Delete(error_response);
return 0; // Return success after sending error response
}
}
cJSON_Delete(error_response);
snprintf(error_message, error_size, "validation failed: %d errors, atomic rollback performed", validation_errors);
return -1;
} else {
// No updates applied
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
// Build error response for no valid updates
cJSON* error_response = cJSON_CreateObject();
cJSON_AddStringToObject(error_response, "query_type", "config_update");
cJSON_AddStringToObject(error_response, "status", "error");
cJSON_AddStringToObject(error_response, "error", "no valid configuration updates found");
cJSON_AddNumberToObject(error_response, "timestamp", (double)time(NULL));
cJSON_AddItemToObject(error_response, "data", processed_configs);
// Get admin pubkey from event for error response
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
if (admin_pubkey) {
// Send error response as signed kind 23457 event
if (send_admin_response_event(error_response, admin_pubkey, wsi) == 0) {
log_info("Config update 'no valid updates' error response sent successfully");
cJSON_Delete(error_response);
return 0; // Return success after sending error response
}
}
cJSON_Delete(error_response);
snprintf(error_message, error_size, "no valid configuration updates found");
return -1;
}
// Build response with query_type for frontend routing
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "query_type", "config_update");
cJSON_AddStringToObject(response, "command", "config_update");
cJSON_AddNumberToObject(response, "configs_processed", updates_applied);
cJSON_AddNumberToObject(response, "total_configs", config_count);
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
cJSON_AddItemToObject(response, "processed_configs", processed_configs);
printf("Config update completed: %d/%d configs updated successfully\n", updates_applied, config_count);
// 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("Config update command completed successfully with signed response");
printf(" Response query_type: config_update\n");
cJSON_Delete(response);
return 0;
}
cJSON_Delete(response);
snprintf(error_message, error_size, "failed to send config update response");
return -1;
}
@@ -3580,7 +4148,7 @@ int process_startup_config_event(const cJSON* event) {
// Validate event structure first
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 23455) {
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
log_error("Invalid event kind for startup configuration");
return -1;
}
@@ -3709,7 +4277,7 @@ cJSON* generate_config_event_from_table(void) {
cJSON_AddStringToObject(event, "id", "synthetic_config_event_id");
cJSON_AddStringToObject(event, "pubkey", relay_pubkey); // Use relay pubkey as event author
cJSON_AddNumberToObject(event, "created_at", (double)time(NULL));
cJSON_AddNumberToObject(event, "kind", 23455);
cJSON_AddNumberToObject(event, "kind", 33334);
cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration");
cJSON_AddStringToObject(event, "sig", "synthetic_signature");
@@ -3790,7 +4358,7 @@ int req_filter_requests_config_events(const cJSON* filter) {
cJSON* kind_item = NULL;
cJSON_ArrayForEach(kind_item, kinds) {
int kind_val = (int)cJSON_GetNumberValue(kind_item);
if (cJSON_IsNumber(kind_item) && (kind_val == 23455 || kind_val == 23456)) {
if (cJSON_IsNumber(kind_item) && kind_val == 33334) {
return 1;
}
}