v0.1.16 - Got the admin page kinda working
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include "ginxsom.h"
|
||||
|
||||
// Forward declarations for nostr_core_lib functions
|
||||
@@ -27,6 +29,7 @@ extern char g_db_path[];
|
||||
static int get_server_privkey(unsigned char* privkey_bytes);
|
||||
static int get_server_pubkey(char* pubkey_hex, size_t size);
|
||||
static int handle_config_query_command(cJSON* response_data);
|
||||
static int handle_query_view_command(cJSON* command_array, cJSON* response_data);
|
||||
static int send_admin_response_event(const char* admin_pubkey, const char* request_id,
|
||||
cJSON* response_data);
|
||||
static cJSON* parse_authorization_header(void);
|
||||
@@ -269,9 +272,13 @@ static int process_admin_event(cJSON* event) {
|
||||
return -1;
|
||||
}
|
||||
content_to_parse = decrypted_content;
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Decrypted content: %s", decrypted_content);
|
||||
} else {
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Using plaintext content (starts with '['): %s", encrypted_content);
|
||||
}
|
||||
|
||||
// Parse command array (either decrypted or plaintext)
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Parsing command array from: %s", content_to_parse);
|
||||
cJSON* command_array = cJSON_Parse(content_to_parse);
|
||||
if (!command_array || !cJSON_IsArray(command_array)) {
|
||||
printf("Status: 400 Bad Request\r\n");
|
||||
@@ -300,19 +307,30 @@ static int process_admin_event(cJSON* event) {
|
||||
// Handle command
|
||||
int result = -1;
|
||||
if (strcmp(cmd, "config_query") == 0) {
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Handling config_query command");
|
||||
result = handle_config_query_command(response_data);
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: config_query result: %d", result);
|
||||
} else if (strcmp(cmd, "query_view") == 0) {
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Handling query_view command");
|
||||
result = handle_query_view_command(command_array, response_data);
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: query_view result: %d", result);
|
||||
} else {
|
||||
app_log(LOG_WARN, "ADMIN_EVENT: Unknown command: %s", cmd);
|
||||
cJSON_AddStringToObject(response_data, "status", "error");
|
||||
cJSON_AddStringToObject(response_data, "error", "Unknown command");
|
||||
result = -1;
|
||||
}
|
||||
|
||||
cJSON_Delete(command_array);
|
||||
|
||||
if (result == 0) {
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Sending Kind 23459 response");
|
||||
// Send Kind 23459 response
|
||||
send_admin_response_event(admin_pubkey, request_id, response_data);
|
||||
return 0;
|
||||
int send_result = send_admin_response_event(admin_pubkey, request_id, response_data);
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Response sent with result: %d", send_result);
|
||||
return send_result;
|
||||
} else {
|
||||
app_log(LOG_ERROR, "ADMIN_EVENT: Command processing failed");
|
||||
cJSON_Delete(response_data);
|
||||
printf("Status: 500 Internal Server Error\r\n");
|
||||
printf("Content-Type: application/json\r\n\r\n");
|
||||
@@ -415,6 +433,125 @@ static int handle_config_query_command(cJSON* response_data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle query_view command - returns data from a specified database view
|
||||
* Command format: ["query_view", "view_name"]
|
||||
*/
|
||||
static int handle_query_view_command(cJSON* command_array, cJSON* response_data) {
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: handle_query_view_command called");
|
||||
|
||||
// Get view name from command array
|
||||
cJSON* view_name_obj = cJSON_GetArrayItem(command_array, 1);
|
||||
if (!view_name_obj || !cJSON_IsString(view_name_obj)) {
|
||||
app_log(LOG_ERROR, "ADMIN_EVENT: View name missing or not a string");
|
||||
cJSON_AddStringToObject(response_data, "status", "error");
|
||||
cJSON_AddStringToObject(response_data, "error", "View name required");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* view_name = cJSON_GetStringValue(view_name_obj);
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Querying view: %s", view_name);
|
||||
|
||||
// Validate view name (whitelist approach for security)
|
||||
const char* allowed_views[] = {
|
||||
"blob_overview",
|
||||
"blob_type_distribution",
|
||||
"blob_time_stats",
|
||||
"top_uploaders",
|
||||
NULL
|
||||
};
|
||||
|
||||
int view_allowed = 0;
|
||||
for (int i = 0; allowed_views[i] != NULL; i++) {
|
||||
if (strcmp(view_name, allowed_views[i]) == 0) {
|
||||
view_allowed = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!view_allowed) {
|
||||
cJSON_AddStringToObject(response_data, "status", "error");
|
||||
cJSON_AddStringToObject(response_data, "error", "Invalid view name");
|
||||
app_log(LOG_WARN, "ADMIN_EVENT: Attempted to query invalid view: %s", view_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: View '%s' is allowed, opening database: %s", view_name, g_db_path);
|
||||
|
||||
// Open database
|
||||
sqlite3* db;
|
||||
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
app_log(LOG_ERROR, "ADMIN_EVENT: Failed to open database: %s (error: %s)", g_db_path, sqlite3_errmsg(db));
|
||||
cJSON_AddStringToObject(response_data, "status", "error");
|
||||
cJSON_AddStringToObject(response_data, "error", "Database error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Build SQL query
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "SELECT * FROM %s", view_name);
|
||||
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Executing SQL: %s", sql);
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
app_log(LOG_ERROR, "ADMIN_EVENT: Failed to prepare query: %s (error: %s)", sql, sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
cJSON_AddStringToObject(response_data, "status", "error");
|
||||
cJSON_AddStringToObject(response_data, "error", "Failed to prepare query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get column count and names
|
||||
int col_count = sqlite3_column_count(stmt);
|
||||
|
||||
// Create results array
|
||||
cJSON* results = cJSON_CreateArray();
|
||||
|
||||
// Fetch all rows
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON* row = cJSON_CreateObject();
|
||||
|
||||
for (int i = 0; i < col_count; i++) {
|
||||
const char* col_name = sqlite3_column_name(stmt, i);
|
||||
int col_type = sqlite3_column_type(stmt, i);
|
||||
|
||||
switch (col_type) {
|
||||
case SQLITE_INTEGER:
|
||||
cJSON_AddNumberToObject(row, col_name, (double)sqlite3_column_int64(stmt, i));
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
cJSON_AddNumberToObject(row, col_name, sqlite3_column_double(stmt, i));
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
cJSON_AddStringToObject(row, col_name, (const char*)sqlite3_column_text(stmt, i));
|
||||
break;
|
||||
case SQLITE_NULL:
|
||||
cJSON_AddNullToObject(row, col_name);
|
||||
break;
|
||||
default:
|
||||
// For BLOB or unknown types, skip
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(results, row);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
|
||||
// Build response
|
||||
cJSON_AddStringToObject(response_data, "status", "success");
|
||||
cJSON_AddStringToObject(response_data, "view_name", view_name);
|
||||
cJSON_AddItemToObject(response_data, "data", results);
|
||||
|
||||
app_log(LOG_DEBUG, "ADMIN_EVENT: Query view '%s' returned %d rows", view_name, cJSON_GetArraySize(results));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Kind 23459 admin response event
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,8 +10,8 @@
|
||||
// Version information (auto-updated by build system)
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 1
|
||||
#define VERSION_PATCH 15
|
||||
#define VERSION "v0.1.15"
|
||||
#define VERSION_PATCH 16
|
||||
#define VERSION "v0.1.16"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
81
src/main.c
81
src/main.c
@@ -248,7 +248,7 @@ int initialize_database(const char *db_path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create storage_stats view
|
||||
// Create storage_stats view (legacy - kept for backward compatibility)
|
||||
const char *create_view =
|
||||
"CREATE VIEW IF NOT EXISTS storage_stats AS "
|
||||
"SELECT "
|
||||
@@ -268,6 +268,85 @@ int initialize_database(const char *db_path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create blob_overview view for admin dashboard
|
||||
const char *create_overview_view =
|
||||
"CREATE VIEW IF NOT EXISTS blob_overview AS "
|
||||
"SELECT "
|
||||
" COUNT(*) as total_blobs, "
|
||||
" COALESCE(SUM(size), 0) as total_bytes, "
|
||||
" MIN(uploaded_at) as first_upload, "
|
||||
" MAX(uploaded_at) as last_upload "
|
||||
"FROM blobs;";
|
||||
|
||||
rc = sqlite3_exec(db, create_overview_view, NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to create blob_overview view: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_close(db);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create blob_type_distribution view for MIME type statistics
|
||||
const char *create_type_view =
|
||||
"CREATE VIEW IF NOT EXISTS blob_type_distribution AS "
|
||||
"SELECT "
|
||||
" type as mime_type, "
|
||||
" COUNT(*) as blob_count, "
|
||||
" SUM(size) as total_bytes, "
|
||||
" ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM blobs), 2) as percentage "
|
||||
"FROM blobs "
|
||||
"GROUP BY type "
|
||||
"ORDER BY blob_count DESC;";
|
||||
|
||||
rc = sqlite3_exec(db, create_type_view, NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to create blob_type_distribution view: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_close(db);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create blob_time_stats view for time-based statistics
|
||||
const char *create_time_view =
|
||||
"CREATE VIEW IF NOT EXISTS blob_time_stats AS "
|
||||
"SELECT "
|
||||
" COUNT(CASE WHEN uploaded_at >= strftime('%s', 'now', '-1 day') THEN 1 END) as blobs_24h, "
|
||||
" COUNT(CASE WHEN uploaded_at >= strftime('%s', 'now', '-7 days') THEN 1 END) as blobs_7d, "
|
||||
" COUNT(CASE WHEN uploaded_at >= strftime('%s', 'now', '-30 days') THEN 1 END) as blobs_30d "
|
||||
"FROM blobs;";
|
||||
|
||||
rc = sqlite3_exec(db, create_time_view, NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to create blob_time_stats view: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_close(db);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create top_uploaders view for pubkey statistics
|
||||
const char *create_uploaders_view =
|
||||
"CREATE VIEW IF NOT EXISTS top_uploaders AS "
|
||||
"SELECT "
|
||||
" uploader_pubkey, "
|
||||
" COUNT(*) as blob_count, "
|
||||
" SUM(size) as total_bytes, "
|
||||
" ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM blobs), 2) as percentage, "
|
||||
" MIN(uploaded_at) as first_upload, "
|
||||
" MAX(uploaded_at) as last_upload "
|
||||
"FROM blobs "
|
||||
"WHERE uploader_pubkey IS NOT NULL "
|
||||
"GROUP BY uploader_pubkey "
|
||||
"ORDER BY blob_count DESC "
|
||||
"LIMIT 20;";
|
||||
|
||||
rc = sqlite3_exec(db, create_uploaders_view, NULL, NULL, &err_msg);
|
||||
if (rc != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to create top_uploaders view: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
sqlite3_close(db);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Database schema initialized successfully\n");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user