From bc3fd57d7aed4c5f258ed904caf7f7fcd73791bd Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sat, 29 Nov 2025 11:33:16 -0500 Subject: [PATCH] ssb: The block list can be crudely managed through the admin app. --- apps/admin.json | 2 +- apps/admin/app.js | 8 ++++++ apps/admin/script.js | 31 +++++++++++++++++++++- core/core.js | 1 + src/ssb.db.c | 12 +++++++++ src/ssb.db.h | 8 ++++++ src/ssb.js.c | 63 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 2 deletions(-) diff --git a/apps/admin.json b/apps/admin.json index a468be8d..bad3f9b9 100644 --- a/apps/admin.json +++ b/apps/admin.json @@ -1,5 +1,5 @@ { "type": "tildefriends-app", "emoji": "🎛", - "previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256" + "previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256" } diff --git a/apps/admin/app.js b/apps/admin/app.js index 5916f11f..5f142dda 100644 --- a/apps/admin/app.js +++ b/apps/admin/app.js @@ -8,12 +8,20 @@ tfrpc.register(function global_settings_set(key, value) { return core.globalSettingsSet(key, value); }); +tfrpc.register(function addBlock(id) { + return ssb.addBlock(id); +}); +tfrpc.register(function removeBlock(id) { + return ssb.removeBlock(id); +}); + async function main() { try { let data = { users: {}, granted: await core.allPermissionsGranted(), settings: await core.globalSettingsDescriptions(), + blocks: await ssb.getBlocks(), }; for (let user of await core.users()) { data.users[user] = await core.permissionsForUser(user); diff --git a/apps/admin/script.js b/apps/admin/script.js index 4b97b542..485f933a 100644 --- a/apps/admin/script.js +++ b/apps/admin/script.js @@ -16,6 +16,14 @@ function delete_user(user) { } } +async function add_block() { + await tfrpc.rpc.addBlock(document.getElementById('add_block').value); +} + +async function remove_block(id) { + await tfrpc.rpc.removeBlock(id); +} + function global_settings_set(key, value) { tfrpc.rpc .global_settings_set(key, value) @@ -94,11 +102,32 @@ ${description.value} permission_template(x))} `; + const block_template = (block) => html` +
  • + + ${block.id} + ${new Date(block.timestamp)} +
  • + `; const users_template = (users) => html`

    Users

    `; + const blocks_template = (blocks) => + html`

    Blocks

    +
    + + +
    + `; const page_template = (data) => html`

    Global Settings

    @@ -109,7 +138,7 @@ ${description.value} html`${input_template(x, data.settings[x])}`)}
    - ${users_template(data.users)} + ${users_template(data.users)} ${blocks_template(data.blocks)} `; render(page_template(g_data), document.body); }); diff --git a/core/core.js b/core/core.js index da7e348c..9a73ba0b 100644 --- a/core/core.js +++ b/core/core.js @@ -515,6 +515,7 @@ async function getProcessBlob(blobId, key, options) { await imports.core.permissionTest('modify_blocks', `Unblock ${id}.`); await ssb_internal.removeBlock(id); }; + imports.ssb.getBlocks = ssb_internal.getBlocks.bind(ssb_internal); } if ( diff --git a/src/ssb.db.c b/src/ssb.db.c index d48962b2..961702f9 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -2936,3 +2936,15 @@ bool tf_ssb_db_is_blocked(sqlite3* db, const char* id) } return is_blocked; } + +void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data) +{ + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare_v2(db, "SELECT id, timestamp FROM blocks ORDER BY timestamp", -1, &statement, NULL) == SQLITE_OK) + { + while (sqlite3_step(statement) == SQLITE_ROW) + { + callback((const char*)sqlite3_column_text(statement, 0), sqlite3_column_double(statement, 1), user_data); + } + } +} diff --git a/src/ssb.db.h b/src/ssb.db.h index dfde0790..6f87c440 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -639,4 +639,12 @@ void tf_ssb_db_remove_block(sqlite3* db, const char* id); */ bool tf_ssb_db_is_blocked(sqlite3* db, const char* id); +/** +** Get block list. +** @param db The database. +** @param callback Called for each entry. +** @param user_data User data to be passed to the callback. +*/ +void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data); + /** @} */ diff --git a/src/ssb.js.c b/src/ssb.js.c index db5d466c..6cb600f8 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -2232,6 +2232,68 @@ static JSValue _tf_ssb_remove_block(JSContext* context, JSValueConst this_val, i return result; } +typedef struct _block_t +{ + char id[k_id_base64_len]; + double timestamp; +} block_t; + +typedef struct _get_blocks_t +{ + block_t* blocks; + int count; + JSValue promise[2]; +} get_blocks_t; + +static void _get_blocks_callback(const char* id, double timestamp, void* user_data) +{ + get_blocks_t* work = user_data; + work->blocks = tf_resize_vec(work->blocks, sizeof(block_t) * (work->count + 1)); + work->blocks[work->count] = (block_t) { .timestamp = timestamp }; + tf_string_set(work->blocks[work->count].id, sizeof(work->blocks[work->count].id), id); + work->count++; +} + +static void _tf_ssb_get_blocks_work(tf_ssb_t* ssb, void* user_data) +{ + sqlite3* db = tf_ssb_acquire_db_reader(ssb); + tf_ssb_db_get_blocks(db, _get_blocks_callback, user_data); + tf_ssb_release_db_reader(ssb, db); +} + +static void _tf_ssb_get_blocks_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + get_blocks_t* work = user_data; + JSContext* context = tf_ssb_get_context(ssb); + + JSValue result = JS_NewArray(context); + for (int i = 0; i < work->count; i++) + { + JSValue entry = JS_NewObject(context); + JS_SetPropertyStr(context, entry, "id", JS_NewString(context, work->blocks[i].id)); + JS_SetPropertyStr(context, entry, "timestamp", JS_NewFloat64(context, work->blocks[i].timestamp)); + JS_SetPropertyUint32(context, result, i, entry); + } + + JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result); + JS_FreeValue(context, result); + tf_util_report_error(context, error); + JS_FreeValue(context, error); + JS_FreeValue(context, work->promise[0]); + JS_FreeValue(context, work->promise[1]); + tf_free(work); +} + +static JSValue _tf_ssb_get_blocks(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + get_blocks_t* work = tf_malloc(sizeof(get_blocks_t)); + *work = (get_blocks_t) { 0 }; + JSValue result = JS_NewPromiseCapability(context, work->promise); + tf_ssb_run_work(ssb, _tf_ssb_get_blocks_work, _tf_ssb_get_blocks_after_work, work); + return result; +} + void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) { JS_NewClassID(&_tf_ssb_classId); @@ -2289,6 +2351,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2)); JS_SetPropertyStr(context, object_internal, "addBlock", JS_NewCFunction(context, _tf_ssb_add_block, "addBlock", 1)); JS_SetPropertyStr(context, object_internal, "removeBlock", JS_NewCFunction(context, _tf_ssb_remove_block, "removeBlock", 1)); + JS_SetPropertyStr(context, object_internal, "getBlocks", JS_NewCFunction(context, _tf_ssb_get_blocks, "getBlocks", 0)); JS_FreeValue(context, global); }