v0.7.40 - Removed event_broadcasts table and related code to fix FOREIGN KEY constraint failures preventing event insertion

This commit is contained in:
Your Name
2025-10-25 15:26:31 -04:00
parent 3dc09d55fd
commit edb73d50cf
15 changed files with 1062 additions and 347 deletions

423
src/api.c
View File

@@ -13,6 +13,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <strings.h>
#include <stdbool.h>
#include "api.h"
#include "embedded_web_content.h"
#include "config.h"
@@ -222,79 +223,6 @@ cJSON* query_top_pubkeys(void) {
return top_pubkeys;
}
// Query active subscriptions summary from database
cJSON* query_active_subscriptions(void) {
extern sqlite3* g_db;
if (!g_db) {
DEBUG_ERROR("Database not available for active subscriptions query");
return NULL;
}
// Get configuration limits
int max_subs = g_subscription_manager.max_total_subscriptions;
int max_per_client = g_subscription_manager.max_subscriptions_per_client;
// Query total active subscriptions from database
sqlite3_stmt* stmt;
const char* sql =
"SELECT COUNT(*) as total_subs, "
"COUNT(DISTINCT client_ip) as client_count "
"FROM subscriptions "
"WHERE event_type = 'created' AND ended_at IS NULL";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare active subscriptions query");
return NULL;
}
int total_subs = 0;
int client_count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
total_subs = sqlite3_column_int(stmt, 0);
client_count = sqlite3_column_int(stmt, 1);
}
sqlite3_finalize(stmt);
// Query max subscriptions per client
int most_subs_per_client = 0;
const char* max_sql =
"SELECT MAX(sub_count) FROM ("
" SELECT COUNT(*) as sub_count "
" FROM subscriptions "
" WHERE event_type = 'created' AND ended_at IS NULL "
" GROUP BY client_ip"
")";
if (sqlite3_prepare_v2(g_db, max_sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
most_subs_per_client = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
}
// Calculate statistics
double utilization_percentage = max_subs > 0 ? (total_subs * 100.0 / max_subs) : 0.0;
double avg_subs_per_client = client_count > 0 ? (total_subs * 1.0 / client_count) : 0.0;
// Build JSON response matching the design spec
cJSON* subscriptions = cJSON_CreateObject();
cJSON_AddStringToObject(subscriptions, "data_type", "active_subscriptions");
cJSON_AddNumberToObject(subscriptions, "timestamp", (double)time(NULL));
cJSON* data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "total_subscriptions", total_subs);
cJSON_AddNumberToObject(data, "max_subscriptions", max_subs);
cJSON_AddNumberToObject(data, "utilization_percentage", utilization_percentage);
cJSON_AddNumberToObject(data, "subscriptions_per_client_avg", avg_subs_per_client);
cJSON_AddNumberToObject(data, "most_subscriptions_per_client", most_subs_per_client);
cJSON_AddNumberToObject(data, "max_subscriptions_per_client", max_per_client);
cJSON_AddNumberToObject(data, "active_clients", client_count);
cJSON_AddItemToObject(subscriptions, "data", data);
return subscriptions;
}
// Query detailed subscription information from database log (ADMIN ONLY)
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
@@ -305,16 +233,18 @@ cJSON* query_subscription_details(void) {
return NULL;
}
// Query active subscriptions directly from subscriptions table
// Get subscriptions that were created but not yet closed/expired/disconnected
// Query active subscriptions from the active_subscriptions_log view
// This view properly handles deduplication of closed/expired subscriptions
sqlite3_stmt* stmt;
const char* sql =
"SELECT subscription_id, client_ip, wsi_pointer, filter_json, events_sent, "
"created_at, (strftime('%s', 'now') - created_at) as duration_seconds "
"FROM subscriptions "
"WHERE event_type = 'created' AND ended_at IS NULL "
"SELECT * "
"FROM active_subscriptions_log "
"ORDER BY created_at DESC LIMIT 100";
// DEBUG: Log the query results for debugging subscription_details
DEBUG_LOG("=== SUBSCRIPTION_DETAILS QUERY DEBUG ===");
DEBUG_LOG("Query: %s", sql);
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare subscription details query");
return NULL;
@@ -329,22 +259,27 @@ cJSON* query_subscription_details(void) {
cJSON* subscriptions_array = cJSON_CreateArray();
// Iterate through query results
int row_count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
row_count++;
cJSON* sub_obj = cJSON_CreateObject();
// Extract subscription data from database
const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
const char* wsi_pointer = (const char*)sqlite3_column_text(stmt, 2);
const char* filter_json = (const char*)sqlite3_column_text(stmt, 3);
long long events_sent = sqlite3_column_int64(stmt, 4);
long long created_at = sqlite3_column_int64(stmt, 5);
long long duration_seconds = sqlite3_column_int64(stmt, 6);
const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
long long events_sent = sqlite3_column_int64(stmt, 3);
long long created_at = sqlite3_column_int64(stmt, 4);
long long duration_seconds = sqlite3_column_int64(stmt, 5);
// DEBUG: Log each subscription found
DEBUG_LOG("Row %d: sub_id=%s, client_ip=%s, events_sent=%lld, created_at=%lld",
row_count, sub_id ? sub_id : "NULL", client_ip ? client_ip : "NULL",
events_sent, created_at);
// Add basic subscription info
cJSON_AddStringToObject(sub_obj, "id", sub_id ? sub_id : "");
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip ? client_ip : "");
cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer ? wsi_pointer : "");
cJSON_AddNumberToObject(sub_obj, "created_at", (double)created_at);
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)duration_seconds);
cJSON_AddNumberToObject(sub_obj, "events_sent", events_sent);
@@ -374,6 +309,10 @@ cJSON* query_subscription_details(void) {
cJSON_AddItemToObject(subscriptions_data, "data", data);
// DEBUG: Log final summary
DEBUG_LOG("Total subscriptions found: %d", cJSON_GetArraySize(subscriptions_array));
DEBUG_LOG("=== END SUBSCRIPTION_DETAILS QUERY DEBUG ===");
return subscriptions_data;
}
@@ -397,11 +336,6 @@ int generate_event_driven_monitoring(void) {
return -1;
}
// Generate active_subscriptions monitoring event
if (generate_monitoring_event_for_type("active_subscriptions", query_active_subscriptions) != 0) {
DEBUG_ERROR("Failed to generate active_subscriptions monitoring event");
return -1;
}
// Generate CPU metrics monitoring event (also triggered by event storage)
if (generate_monitoring_event_for_type("cpu_metrics", query_cpu_metrics) != 0) {
@@ -409,17 +343,11 @@ int generate_event_driven_monitoring(void) {
return -1;
}
DEBUG_INFO("Generated and broadcast event-driven monitoring events");
return 0;
}
// Generate subscription-driven monitoring events (triggered by subscription changes)
int generate_subscription_driven_monitoring(void) {
// Generate active_subscriptions monitoring event (subscription changes affect this)
if (generate_monitoring_event_for_type("active_subscriptions", query_active_subscriptions) != 0) {
DEBUG_ERROR("Failed to generate active_subscriptions monitoring event");
return -1;
}
// Generate subscription_details monitoring event (admin-only)
if (generate_monitoring_event_for_type("subscription_details", query_subscription_details) != 0) {
@@ -433,7 +361,6 @@ int generate_subscription_driven_monitoring(void) {
return -1;
}
DEBUG_INFO("Generated and broadcast subscription-driven monitoring events");
return 0;
}
@@ -799,7 +726,7 @@ int send_admin_response(const char* sender_pubkey, const char* response_content,
}
// Encrypt response content using NIP-44
char encrypted_content[16384]; // Buffer for encrypted content (increased size)
char encrypted_content[131072]; // Buffer for encrypted content (128KB to handle large SQL responses)
int encrypt_result = nostr_nip44_encrypt(
relay_privkey, // sender private key (bytes)
sender_pubkey_bytes, // recipient public key (bytes)
@@ -2304,6 +2231,306 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
return 1; // Confirmation sent
}
// Forward declarations for relay event creation functions
cJSON* create_relay_metadata_event(cJSON* metadata);
cJSON* create_relay_dm_list_event(cJSON* dm_relays);
cJSON* create_relay_list_event(cJSON* relays);
// Handle create_relay_event admin commands
int handle_create_relay_event_command(cJSON* event, int kind, cJSON* event_data, char* error_message, size_t error_size, struct lws* wsi) {
if (!event || !event_data || !error_message) {
return -1;
}
// Get request event ID for response correlation
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
snprintf(error_message, error_size, "Missing request event ID");
return -1;
}
const char* request_id = cJSON_GetStringValue(request_id_obj);
// Get sender pubkey for response
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
snprintf(error_message, error_size, "Missing sender pubkey");
return -1;
}
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
// Create the relay event based on kind
cJSON* relay_event = NULL;
switch (kind) {
case 0: // User metadata
relay_event = create_relay_metadata_event(event_data);
break;
case 10050: // DM relay list
relay_event = create_relay_dm_list_event(event_data);
break;
case 10002: // Relay list
relay_event = create_relay_list_event(event_data);
break;
default: {
char response_content[256];
snprintf(response_content, sizeof(response_content),
"❌ Unsupported event kind: %d\n\nSupported kinds: 0 (metadata), 10050 (DM relays), 10002 (relays)",
kind);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
}
if (!relay_event) {
char response_content[128];
snprintf(response_content, sizeof(response_content),
"❌ Failed to create relay event (kind %d)\n\nCheck relay logs for details.", kind);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
// Store the event in database
int store_result = store_event(relay_event);
if (store_result != 0) {
cJSON_Delete(relay_event);
char response_content[128];
snprintf(response_content, sizeof(response_content),
"❌ Failed to store relay event (kind %d) in database", kind);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
// Broadcast the event to connected clients
broadcast_event_to_subscriptions(relay_event);
// Clean up
cJSON_Delete(relay_event);
// Send success response (plain text like other admin commands)
char response_content[256];
const char* kind_name = (kind == 0) ? "metadata" : (kind == 10050) ? "DM relay list" : "relay list";
snprintf(response_content, sizeof(response_content),
"✅ Relay event created successfully\n\nKind: %d (%s)\n\nEvent has been stored and broadcast to subscribers.",
kind, kind_name);
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}
// Create a relay metadata event (kind 0)
cJSON* create_relay_metadata_event(cJSON* metadata) {
if (!metadata || !cJSON_IsObject(metadata)) {
DEBUG_ERROR("Invalid metadata object for kind 0 event");
return NULL;
}
// Get relay keys
const char* relay_pubkey = get_config_value("relay_pubkey");
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
DEBUG_ERROR("Could not get relay keys for metadata event");
if (relay_privkey_hex) free(relay_privkey_hex);
return NULL;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(relay_privkey_hex);
DEBUG_ERROR("Failed to convert relay private key for metadata event");
return NULL;
}
free(relay_privkey_hex);
// Create metadata content
char* content = cJSON_Print(metadata);
if (!content) {
DEBUG_ERROR("Failed to serialize metadata for kind 0 event");
return NULL;
}
// Create and sign the event
cJSON* signed_event = nostr_create_and_sign_event(
0, // kind (metadata)
content, // content
NULL, // tags (none for kind 0)
relay_privkey, // private key
(time_t)time(NULL) // timestamp
);
free(content);
if (!signed_event) {
DEBUG_ERROR("Failed to create and sign metadata event");
return NULL;
}
DEBUG_LOG("Created relay metadata event (kind 0)");
return signed_event;
}
// Create a relay DM list event (kind 10050)
cJSON* create_relay_dm_list_event(cJSON* dm_relays) {
if (!dm_relays || !cJSON_IsObject(dm_relays)) {
DEBUG_ERROR("Invalid DM relays object for kind 10050 event");
return NULL;
}
// Get relay keys
const char* relay_pubkey = get_config_value("relay_pubkey");
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
DEBUG_ERROR("Could not get relay keys for DM list event");
if (relay_privkey_hex) free(relay_privkey_hex);
return NULL;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(relay_privkey_hex);
DEBUG_ERROR("Failed to convert relay private key for DM list event");
return NULL;
}
free(relay_privkey_hex);
// Create empty content for kind 10050
const char* content = "";
// Create tags from relay list
cJSON* tags = cJSON_CreateArray();
if (!tags) {
DEBUG_ERROR("Failed to create tags array for DM list event");
return NULL;
}
// Extract relays array
cJSON* relays_array = cJSON_GetObjectItem(dm_relays, "relays");
if (relays_array && cJSON_IsArray(relays_array)) {
cJSON* relay_item = NULL;
cJSON_ArrayForEach(relay_item, relays_array) {
if (cJSON_IsString(relay_item)) {
const char* relay_url = cJSON_GetStringValue(relay_item);
if (relay_url && strlen(relay_url) > 0) {
cJSON* tag = cJSON_CreateArray();
cJSON_AddItemToArray(tag, cJSON_CreateString("relay"));
cJSON_AddItemToArray(tag, cJSON_CreateString(relay_url));
cJSON_AddItemToArray(tags, tag);
}
}
}
}
// Create and sign the event
cJSON* signed_event = nostr_create_and_sign_event(
10050, // kind (DM relay list)
content, // content (empty)
tags, // tags
relay_privkey, // private key
(time_t)time(NULL) // timestamp
);
cJSON_Delete(tags);
if (!signed_event) {
DEBUG_ERROR("Failed to create and sign DM list event");
return NULL;
}
DEBUG_LOG("Created relay DM list event (kind 10050)");
return signed_event;
}
// Create a relay list event (kind 10002)
cJSON* create_relay_list_event(cJSON* relays) {
if (!relays || !cJSON_IsObject(relays)) {
DEBUG_ERROR("Invalid relays object for kind 10002 event");
return NULL;
}
// Get relay keys
const char* relay_pubkey = get_config_value("relay_pubkey");
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
DEBUG_ERROR("Could not get relay keys for relay list event");
if (relay_privkey_hex) free(relay_privkey_hex);
return NULL;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(relay_privkey_hex);
DEBUG_ERROR("Failed to convert relay private key for relay list event");
return NULL;
}
free(relay_privkey_hex);
// Create empty content for kind 10002
const char* content = "";
// Create tags from relay list
cJSON* tags = cJSON_CreateArray();
if (!tags) {
DEBUG_ERROR("Failed to create tags array for relay list event");
return NULL;
}
// Extract relays array
cJSON* relays_array = cJSON_GetObjectItem(relays, "relays");
if (relays_array && cJSON_IsArray(relays_array)) {
cJSON* relay_item = NULL;
cJSON_ArrayForEach(relay_item, relays_array) {
if (cJSON_IsObject(relay_item)) {
cJSON* url = cJSON_GetObjectItem(relay_item, "url");
cJSON* read = cJSON_GetObjectItem(relay_item, "read");
cJSON* write = cJSON_GetObjectItem(relay_item, "write");
if (url && cJSON_IsString(url)) {
const char* relay_url = cJSON_GetStringValue(url);
int read_flag = read && cJSON_IsBool(read) ? cJSON_IsTrue(read) : true;
int write_flag = write && cJSON_IsBool(write) ? cJSON_IsTrue(write) : true;
// Create marker string
const char* marker = NULL;
if (read_flag && write_flag) {
marker = ""; // No marker means both read and write
} else if (read_flag) {
marker = "read";
} else if (write_flag) {
marker = "write";
} else {
// Skip invalid entries
continue;
}
cJSON* tag = cJSON_CreateArray();
cJSON_AddItemToArray(tag, cJSON_CreateString("r"));
cJSON_AddItemToArray(tag, cJSON_CreateString(relay_url));
if (marker[0] != '\0') {
cJSON_AddItemToArray(tag, cJSON_CreateString(marker));
}
cJSON_AddItemToArray(tags, tag);
}
}
}
}
// Create and sign the event
cJSON* signed_event = nostr_create_and_sign_event(
10002, // kind (relay list)
content, // content (empty)
tags, // tags
relay_privkey, // private key
(time_t)time(NULL) // timestamp
);
cJSON_Delete(tags);
if (!signed_event) {
DEBUG_ERROR("Failed to create and sign relay list event");
return NULL;
}
DEBUG_LOG("Created relay list event (kind 10002)");
return signed_event;
}
// Handle monitoring system admin commands
int handle_monitoring_command(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
if (!event || !command || !error_message) {