743 lines
28 KiB
C
743 lines
28 KiB
C
/*
|
|
* Ginxsom Admin Commands Implementation
|
|
*/
|
|
|
|
#include "admin_commands.h"
|
|
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
|
#include <sqlite3.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
// Forward declare app_log
|
|
typedef enum {
|
|
LOG_DEBUG = 0,
|
|
LOG_INFO = 1,
|
|
LOG_WARN = 2,
|
|
LOG_ERROR = 3
|
|
} log_level_t;
|
|
|
|
void app_log(log_level_t level, const char* format, ...);
|
|
|
|
// Global state
|
|
static struct {
|
|
int initialized;
|
|
char db_path[512];
|
|
} g_admin_state = {0};
|
|
|
|
// Initialize admin command system
|
|
int admin_commands_init(const char *db_path) {
|
|
if (g_admin_state.initialized) {
|
|
return 0;
|
|
}
|
|
|
|
strncpy(g_admin_state.db_path, db_path, sizeof(g_admin_state.db_path) - 1);
|
|
g_admin_state.initialized = 1;
|
|
|
|
app_log(LOG_INFO, "Admin command system initialized");
|
|
return 0;
|
|
}
|
|
|
|
// NIP-44 encryption helper
|
|
int admin_encrypt_response(
|
|
const unsigned char* server_privkey,
|
|
const unsigned char* admin_pubkey,
|
|
const char* plaintext_json,
|
|
char* output,
|
|
size_t output_size
|
|
) {
|
|
int result = nostr_nip44_encrypt(
|
|
server_privkey,
|
|
admin_pubkey,
|
|
plaintext_json,
|
|
output,
|
|
output_size
|
|
);
|
|
|
|
if (result != 0) {
|
|
app_log(LOG_ERROR, "Failed to encrypt admin response: %d", result);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// NIP-44 decryption helper
|
|
int admin_decrypt_command(
|
|
const unsigned char* server_privkey,
|
|
const unsigned char* admin_pubkey,
|
|
const char* encrypted_data,
|
|
char* output,
|
|
size_t output_size
|
|
) {
|
|
int result = nostr_nip44_decrypt(
|
|
server_privkey,
|
|
admin_pubkey,
|
|
encrypted_data,
|
|
output,
|
|
output_size
|
|
);
|
|
|
|
if (result != 0) {
|
|
app_log(LOG_ERROR, "Failed to decrypt admin command: %d", result);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Create error response
|
|
static cJSON* create_error_response(const char* query_type, const char* error_msg) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", query_type);
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", error_msg);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Process admin command array and generate response
|
|
cJSON* admin_commands_process(cJSON* command_array, const char* request_event_id) {
|
|
(void)request_event_id; // Reserved for future use (e.g., logging, tracking)
|
|
|
|
if (!cJSON_IsArray(command_array) || cJSON_GetArraySize(command_array) < 1) {
|
|
return create_error_response("unknown", "Invalid command format");
|
|
}
|
|
|
|
cJSON* cmd_type = cJSON_GetArrayItem(command_array, 0);
|
|
if (!cJSON_IsString(cmd_type)) {
|
|
return create_error_response("unknown", "Command type must be string");
|
|
}
|
|
|
|
const char* command = cmd_type->valuestring;
|
|
app_log(LOG_INFO, "Processing admin command: %s", command);
|
|
|
|
// Route to appropriate handler
|
|
if (strcmp(command, "config_query") == 0) {
|
|
return admin_cmd_config_query(command_array);
|
|
}
|
|
else if (strcmp(command, "config_update") == 0) {
|
|
return admin_cmd_config_update(command_array);
|
|
}
|
|
else if (strcmp(command, "stats_query") == 0) {
|
|
return admin_cmd_stats_query(command_array);
|
|
}
|
|
else if (strcmp(command, "system_command") == 0) {
|
|
// Check second parameter for system_status
|
|
if (cJSON_GetArraySize(command_array) >= 2) {
|
|
cJSON* subcmd = cJSON_GetArrayItem(command_array, 1);
|
|
if (cJSON_IsString(subcmd) && strcmp(subcmd->valuestring, "system_status") == 0) {
|
|
return admin_cmd_system_status(command_array);
|
|
}
|
|
}
|
|
return create_error_response("system_command", "Unknown system command");
|
|
}
|
|
else if (strcmp(command, "blob_list") == 0) {
|
|
return admin_cmd_blob_list(command_array);
|
|
}
|
|
else if (strcmp(command, "storage_stats") == 0) {
|
|
return admin_cmd_storage_stats(command_array);
|
|
}
|
|
else if (strcmp(command, "sql_query") == 0) {
|
|
return admin_cmd_sql_query(command_array);
|
|
}
|
|
else {
|
|
char error_msg[256];
|
|
snprintf(error_msg, sizeof(error_msg), "Unknown command: %s", command);
|
|
return create_error_response("unknown", error_msg);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// COMMAND HANDLERS (Stub implementations - to be completed)
|
|
// ============================================================================
|
|
|
|
cJSON* admin_cmd_config_query(cJSON* args) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "config_query");
|
|
|
|
// Open database
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Check if specific keys were requested (args[1] should be array of keys or null for all)
|
|
cJSON* keys_array = NULL;
|
|
if (cJSON_GetArraySize(args) >= 2) {
|
|
keys_array = cJSON_GetArrayItem(args, 1);
|
|
if (!cJSON_IsArray(keys_array) && !cJSON_IsNull(keys_array)) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Keys parameter must be array or null");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
}
|
|
|
|
sqlite3_stmt* stmt;
|
|
const char* sql;
|
|
|
|
if (keys_array && cJSON_IsArray(keys_array) && cJSON_GetArraySize(keys_array) > 0) {
|
|
// Query specific keys
|
|
int key_count = cJSON_GetArraySize(keys_array);
|
|
|
|
// Build SQL with placeholders
|
|
char sql_buffer[1024] = "SELECT key, value, description FROM config WHERE key IN (?";
|
|
for (int i = 1; i < key_count && i < 50; i++) { // Limit to 50 keys
|
|
strncat(sql_buffer, ",?", sizeof(sql_buffer) - strlen(sql_buffer) - 1);
|
|
}
|
|
strncat(sql_buffer, ")", sizeof(sql_buffer) - strlen(sql_buffer) - 1);
|
|
|
|
rc = sqlite3_prepare_v2(db, sql_buffer, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to prepare query");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
|
|
// Bind keys
|
|
for (int i = 0; i < key_count && i < 50; i++) {
|
|
cJSON* key_item = cJSON_GetArrayItem(keys_array, i);
|
|
if (cJSON_IsString(key_item)) {
|
|
sqlite3_bind_text(stmt, i + 1, key_item->valuestring, -1, SQLITE_STATIC);
|
|
}
|
|
}
|
|
} else {
|
|
// Query all config values
|
|
sql = "SELECT key, value, description FROM config ORDER BY key";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to prepare query");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
}
|
|
|
|
// Execute query and build result
|
|
cJSON* config_obj = cJSON_CreateObject();
|
|
int count = 0;
|
|
|
|
while ((rc = 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* description = (const char*)sqlite3_column_text(stmt, 2);
|
|
|
|
cJSON* entry = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(entry, "value", value ? value : "");
|
|
if (description && strlen(description) > 0) {
|
|
cJSON_AddStringToObject(entry, "description", description);
|
|
}
|
|
|
|
cJSON_AddItemToObject(config_obj, key, entry);
|
|
count++;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddNumberToObject(response, "count", count);
|
|
cJSON_AddItemToObject(response, "config", config_obj);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
app_log(LOG_INFO, "Config query returned %d entries", count);
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_config_update(cJSON* args) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "config_update");
|
|
|
|
// Expected format: ["config_update", {"key1": "value1", "key2": "value2"}]
|
|
if (cJSON_GetArraySize(args) < 2) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Missing config updates object");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
cJSON* updates = cJSON_GetArrayItem(args, 1);
|
|
if (!cJSON_IsObject(updates)) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Updates must be an object");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Open database for writing
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READWRITE, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Prepare update statement
|
|
const char* sql = "UPDATE config SET value = ?, updated_at = strftime('%s', 'now') WHERE key = ?";
|
|
sqlite3_stmt* stmt;
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to prepare update statement");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
|
|
// Process each update
|
|
cJSON* updated_keys = cJSON_CreateArray();
|
|
cJSON* failed_keys = cJSON_CreateArray();
|
|
int success_count = 0;
|
|
int fail_count = 0;
|
|
|
|
cJSON* item = NULL;
|
|
cJSON_ArrayForEach(item, updates) {
|
|
const char* key = item->string;
|
|
const char* value = cJSON_GetStringValue(item);
|
|
|
|
if (!value) {
|
|
cJSON_AddItemToArray(failed_keys, cJSON_CreateString(key));
|
|
fail_count++;
|
|
continue;
|
|
}
|
|
|
|
sqlite3_reset(stmt);
|
|
sqlite3_bind_text(stmt, 1, value, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_text(stmt, 2, key, -1, SQLITE_TRANSIENT);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_DONE && sqlite3_changes(db) > 0) {
|
|
cJSON_AddItemToArray(updated_keys, cJSON_CreateString(key));
|
|
success_count++;
|
|
app_log(LOG_INFO, "Updated config key: %s", key);
|
|
} else {
|
|
cJSON_AddItemToArray(failed_keys, cJSON_CreateString(key));
|
|
fail_count++;
|
|
}
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddNumberToObject(response, "updated_count", success_count);
|
|
cJSON_AddNumberToObject(response, "failed_count", fail_count);
|
|
cJSON_AddItemToObject(response, "updated_keys", updated_keys);
|
|
if (fail_count > 0) {
|
|
cJSON_AddItemToObject(response, "failed_keys", failed_keys);
|
|
} else {
|
|
cJSON_Delete(failed_keys);
|
|
}
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_stats_query(cJSON* args) {
|
|
(void)args;
|
|
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "stats_query");
|
|
|
|
// Open database
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Query storage stats view
|
|
const char* sql = "SELECT * FROM storage_stats";
|
|
sqlite3_stmt* stmt;
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to query stats");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
|
|
cJSON* stats = cJSON_CreateObject();
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON_AddNumberToObject(stats, "total_blobs", sqlite3_column_int64(stmt, 0));
|
|
cJSON_AddNumberToObject(stats, "total_bytes", sqlite3_column_int64(stmt, 1));
|
|
cJSON_AddNumberToObject(stats, "avg_blob_size", sqlite3_column_double(stmt, 2));
|
|
cJSON_AddNumberToObject(stats, "first_upload", sqlite3_column_int64(stmt, 3));
|
|
cJSON_AddNumberToObject(stats, "last_upload", sqlite3_column_int64(stmt, 4));
|
|
cJSON_AddNumberToObject(stats, "unique_uploaders", sqlite3_column_int64(stmt, 5));
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
// Get auth rules count
|
|
sql = "SELECT COUNT(*) FROM auth_rules WHERE enabled = 1";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON_AddNumberToObject(stats, "active_auth_rules", sqlite3_column_int(stmt, 0));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddItemToObject(response, "stats", stats);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_system_status(cJSON* args) {
|
|
(void)args;
|
|
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "system_status");
|
|
|
|
cJSON* status = cJSON_CreateObject();
|
|
|
|
// Server uptime (would need to track start time - placeholder for now)
|
|
cJSON_AddStringToObject(status, "server_status", "running");
|
|
cJSON_AddNumberToObject(status, "current_time", (double)time(NULL));
|
|
|
|
// Database status
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc == SQLITE_OK) {
|
|
cJSON_AddStringToObject(status, "database_status", "connected");
|
|
|
|
// Get database size
|
|
sqlite3_stmt* stmt;
|
|
const char* sql = "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()";
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON_AddNumberToObject(status, "database_size_bytes", sqlite3_column_int64(stmt, 0));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
sqlite3_close(db);
|
|
} else {
|
|
cJSON_AddStringToObject(status, "database_status", "error");
|
|
}
|
|
|
|
// Memory info (basic - would need more system calls for detailed info)
|
|
cJSON_AddStringToObject(status, "memory_status", "ok");
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddItemToObject(response, "system", status);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_blob_list(cJSON* args) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "blob_list");
|
|
|
|
// Parse optional parameters: limit, offset, uploader_pubkey
|
|
int limit = 100; // Default limit
|
|
int offset = 0;
|
|
const char* uploader_filter = NULL;
|
|
|
|
if (cJSON_GetArraySize(args) >= 2) {
|
|
cJSON* params = cJSON_GetArrayItem(args, 1);
|
|
if (cJSON_IsObject(params)) {
|
|
cJSON* limit_item = cJSON_GetObjectItem(params, "limit");
|
|
if (cJSON_IsNumber(limit_item)) {
|
|
limit = limit_item->valueint;
|
|
if (limit > 1000) limit = 1000; // Max 1000
|
|
if (limit < 1) limit = 1;
|
|
}
|
|
|
|
cJSON* offset_item = cJSON_GetObjectItem(params, "offset");
|
|
if (cJSON_IsNumber(offset_item)) {
|
|
offset = offset_item->valueint;
|
|
if (offset < 0) offset = 0;
|
|
}
|
|
|
|
cJSON* uploader_item = cJSON_GetObjectItem(params, "uploader");
|
|
if (cJSON_IsString(uploader_item)) {
|
|
uploader_filter = uploader_item->valuestring;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Open database
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Build query
|
|
char sql[512];
|
|
if (uploader_filter) {
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT sha256, size, type, uploaded_at, uploader_pubkey, filename "
|
|
"FROM blobs WHERE uploader_pubkey = ? "
|
|
"ORDER BY uploaded_at DESC LIMIT ? OFFSET ?");
|
|
} else {
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT sha256, size, type, uploaded_at, uploader_pubkey, filename "
|
|
"FROM blobs ORDER BY uploaded_at DESC LIMIT ? OFFSET ?");
|
|
}
|
|
|
|
sqlite3_stmt* stmt;
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to prepare query");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
|
|
// Bind parameters
|
|
int param_idx = 1;
|
|
if (uploader_filter) {
|
|
sqlite3_bind_text(stmt, param_idx++, uploader_filter, -1, SQLITE_STATIC);
|
|
}
|
|
sqlite3_bind_int(stmt, param_idx++, limit);
|
|
sqlite3_bind_int(stmt, param_idx++, offset);
|
|
|
|
// Execute and build results
|
|
cJSON* blobs = cJSON_CreateArray();
|
|
int count = 0;
|
|
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON* blob = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(blob, "sha256", (const char*)sqlite3_column_text(stmt, 0));
|
|
cJSON_AddNumberToObject(blob, "size", sqlite3_column_int64(stmt, 1));
|
|
cJSON_AddStringToObject(blob, "type", (const char*)sqlite3_column_text(stmt, 2));
|
|
cJSON_AddNumberToObject(blob, "uploaded_at", sqlite3_column_int64(stmt, 3));
|
|
|
|
const char* uploader = (const char*)sqlite3_column_text(stmt, 4);
|
|
if (uploader) {
|
|
cJSON_AddStringToObject(blob, "uploader_pubkey", uploader);
|
|
}
|
|
|
|
const char* filename = (const char*)sqlite3_column_text(stmt, 5);
|
|
if (filename) {
|
|
cJSON_AddStringToObject(blob, "filename", filename);
|
|
}
|
|
|
|
cJSON_AddItemToArray(blobs, blob);
|
|
count++;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddNumberToObject(response, "count", count);
|
|
cJSON_AddNumberToObject(response, "limit", limit);
|
|
cJSON_AddNumberToObject(response, "offset", offset);
|
|
cJSON_AddItemToObject(response, "blobs", blobs);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_storage_stats(cJSON* args) {
|
|
(void)args;
|
|
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "storage_stats");
|
|
|
|
// Open database
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
cJSON* storage = cJSON_CreateObject();
|
|
|
|
// Get overall stats from view
|
|
const char* sql = "SELECT * FROM storage_stats";
|
|
sqlite3_stmt* stmt;
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON_AddNumberToObject(storage, "total_blobs", sqlite3_column_int64(stmt, 0));
|
|
cJSON_AddNumberToObject(storage, "total_bytes", sqlite3_column_int64(stmt, 1));
|
|
cJSON_AddNumberToObject(storage, "avg_blob_size", sqlite3_column_double(stmt, 2));
|
|
cJSON_AddNumberToObject(storage, "first_upload", sqlite3_column_int64(stmt, 3));
|
|
cJSON_AddNumberToObject(storage, "last_upload", sqlite3_column_int64(stmt, 4));
|
|
cJSON_AddNumberToObject(storage, "unique_uploaders", sqlite3_column_int64(stmt, 5));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
|
|
// Get stats by MIME type
|
|
sql = "SELECT type, COUNT(*) as count, SUM(size) as total_size "
|
|
"FROM blobs GROUP BY type ORDER BY count DESC LIMIT 10";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) {
|
|
cJSON* by_type = cJSON_CreateArray();
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON* type_stat = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(type_stat, "mime_type", (const char*)sqlite3_column_text(stmt, 0));
|
|
cJSON_AddNumberToObject(type_stat, "count", sqlite3_column_int64(stmt, 1));
|
|
cJSON_AddNumberToObject(type_stat, "total_bytes", sqlite3_column_int64(stmt, 2));
|
|
cJSON_AddItemToArray(by_type, type_stat);
|
|
}
|
|
cJSON_AddItemToObject(storage, "by_mime_type", by_type);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
// Get top uploaders
|
|
sql = "SELECT uploader_pubkey, COUNT(*) as count, SUM(size) as total_size "
|
|
"FROM blobs WHERE uploader_pubkey IS NOT NULL "
|
|
"GROUP BY uploader_pubkey ORDER BY count DESC LIMIT 10";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) {
|
|
cJSON* top_uploaders = cJSON_CreateArray();
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON* uploader_stat = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(uploader_stat, "pubkey", (const char*)sqlite3_column_text(stmt, 0));
|
|
cJSON_AddNumberToObject(uploader_stat, "blob_count", sqlite3_column_int64(stmt, 1));
|
|
cJSON_AddNumberToObject(uploader_stat, "total_bytes", sqlite3_column_int64(stmt, 2));
|
|
cJSON_AddItemToArray(top_uploaders, uploader_stat);
|
|
}
|
|
cJSON_AddItemToObject(storage, "top_uploaders", top_uploaders);
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddItemToObject(response, "storage", storage);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
return response;
|
|
}
|
|
|
|
cJSON* admin_cmd_sql_query(cJSON* args) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response, "query_type", "sql_query");
|
|
|
|
// Expected format: ["sql_query", "SELECT ..."]
|
|
if (cJSON_GetArraySize(args) < 2) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Missing SQL query");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
cJSON* query_item = cJSON_GetArrayItem(args, 1);
|
|
if (!cJSON_IsString(query_item)) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Query must be a string");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
const char* sql = query_item->valuestring;
|
|
|
|
// Security: Only allow SELECT queries
|
|
const char* sql_upper = sql;
|
|
while (*sql_upper == ' ' || *sql_upper == '\t' || *sql_upper == '\n') sql_upper++;
|
|
if (strncasecmp(sql_upper, "SELECT", 6) != 0) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Only SELECT queries are allowed");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Open database (read-only for safety)
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_admin_state.db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
cJSON_AddStringToObject(response, "error", "Failed to open database");
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
return response;
|
|
}
|
|
|
|
// Prepare and execute query
|
|
sqlite3_stmt* stmt;
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response, "status", "error");
|
|
char error_msg[256];
|
|
snprintf(error_msg, sizeof(error_msg), "SQL error: %s", sqlite3_errmsg(db));
|
|
cJSON_AddStringToObject(response, "error", error_msg);
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
sqlite3_close(db);
|
|
return response;
|
|
}
|
|
|
|
// Get column names
|
|
int col_count = sqlite3_column_count(stmt);
|
|
cJSON* columns = cJSON_CreateArray();
|
|
for (int i = 0; i < col_count; i++) {
|
|
cJSON_AddItemToArray(columns, cJSON_CreateString(sqlite3_column_name(stmt, i)));
|
|
}
|
|
|
|
// Execute and collect rows (limit to 1000 rows for safety)
|
|
cJSON* rows = cJSON_CreateArray();
|
|
int row_count = 0;
|
|
const int MAX_ROWS = 1000;
|
|
|
|
while (row_count < MAX_ROWS && (rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
|
cJSON* row = cJSON_CreateArray();
|
|
for (int i = 0; i < col_count; i++) {
|
|
int col_type = sqlite3_column_type(stmt, i);
|
|
switch (col_type) {
|
|
case SQLITE_INTEGER:
|
|
cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_int64(stmt, i)));
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_double(stmt, i)));
|
|
break;
|
|
case SQLITE_TEXT:
|
|
cJSON_AddItemToArray(row, cJSON_CreateString((const char*)sqlite3_column_text(stmt, i)));
|
|
break;
|
|
case SQLITE_NULL:
|
|
cJSON_AddItemToArray(row, cJSON_CreateNull());
|
|
break;
|
|
default:
|
|
cJSON_AddItemToArray(row, cJSON_CreateString(""));
|
|
}
|
|
}
|
|
cJSON_AddItemToArray(rows, row);
|
|
row_count++;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(db);
|
|
|
|
cJSON_AddStringToObject(response, "status", "success");
|
|
cJSON_AddItemToObject(response, "columns", columns);
|
|
cJSON_AddItemToObject(response, "rows", rows);
|
|
cJSON_AddNumberToObject(response, "row_count", row_count);
|
|
if (row_count >= MAX_ROWS) {
|
|
cJSON_AddBoolToObject(response, "truncated", 1);
|
|
}
|
|
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
|
|
|
app_log(LOG_INFO, "SQL query executed: %d rows returned", row_count);
|
|
|
|
return response;
|
|
} |