From 5594bee618141457a31c8e3003e319aebefd7603 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 5 Jan 2025 17:16:05 -0500 Subject: [PATCH] ssb: Add get_identity, get_sequence, and get_profile commands. #89 --- src/main.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/ssb.db.c | 26 ++++++++ src/ssb.db.h | 8 +++ 3 files changed, 208 insertions(+) diff --git a/src/main.c b/src/main.c index 22c463ad..9792e33c 100644 --- a/src/main.c +++ b/src/main.c @@ -15,6 +15,7 @@ #include "unzip.h" #include +#include #include #include @@ -146,6 +147,9 @@ static int _tf_command_publish(const char* file, int argc, char* argv[]); static int _tf_command_run(const char* file, int argc, char* argv[]); static int _tf_command_sandbox(const char* file, int argc, char* argv[]); static int _tf_command_store_blob(const char* file, int argc, char* argv[]); +static int _tf_command_get_sequence(const char* file, int argc, char* argv[]); +static int _tf_command_get_identity(const char* file, int argc, char* argv[]); +static int _tf_command_get_profile(const char* file, int argc, char* argv[]); static int _tf_command_test(const char* file, int argc, char* argv[]); static int _tf_command_verify(const char* file, int argc, char* argv[]); static int _tf_command_usage(const char* file); @@ -163,6 +167,9 @@ const command_t k_commands[] = { { "import", _tf_command_import, "Import apps to SSB." }, { "export", _tf_command_export, "Export apps from SSB." }, { "publish", _tf_command_publish, "Append a message to a feed." }, + { "get_sequence", _tf_command_get_sequence, "Get the last sequence number for a feed." }, + { "get_identity", _tf_command_get_identity, "Get the server account identity." }, + { "get_profile", _tf_command_get_profile, "Get profile information for the given identity." }, { "store_blob", _tf_command_store_blob, "Write a file to the blob store." }, { "verify", _tf_command_verify, "Verify a feed." }, { "test", _tf_command_test, "Test SSB." }, @@ -592,6 +599,173 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[]) return EXIT_SUCCESS; } +static int _tf_command_get_sequence(const char* file, int argc, char* argv[]) +{ + const char* default_db_path = _get_db_path(); + const char* db_path = default_db_path; + const char* identity = NULL; + bool show_usage = false; + + while (!show_usage) + { + static const struct option k_options[] = { + { "db-path", required_argument, NULL, 'd' }, + { "id", required_argument, NULL, 'i' }, + { "help", no_argument, NULL, 'h' }, + { 0 }, + }; + int c = getopt_long(argc, argv, "d:i:h", k_options, NULL); + if (c == -1) + { + break; + } + + switch (c) + { + case '?': + case 'h': + default: + show_usage = true; + break; + case 'd': + db_path = optarg; + break; + case 'i': + identity = optarg; + break; + } + } + + if (show_usage || !identity) + { + tf_printf("\n%s get_sequence [options]\n\n", file); + tf_printf("options:\n"); + tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); + tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n"); + tf_printf(" -h, --help Show this usage information.\n"); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + + tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); + int64_t sequence = -1; + int result = tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, NULL, 0) ? EXIT_SUCCESS : EXIT_FAILURE; + tf_printf("%" PRId64 "\n", sequence); + tf_ssb_destroy(ssb); + tf_free((void*)default_db_path); + return result; +} + +static int _tf_command_get_identity(const char* file, int argc, char* argv[]) +{ + const char* default_db_path = _get_db_path(); + const char* db_path = default_db_path; + bool show_usage = false; + + while (!show_usage) + { + static const struct option k_options[] = { + { "db-path", required_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { 0 }, + }; + int c = getopt_long(argc, argv, "d:i:h", k_options, NULL); + if (c == -1) + { + break; + } + + switch (c) + { + case '?': + case 'h': + default: + show_usage = true; + break; + case 'd': + db_path = optarg; + break; + } + } + + if (show_usage) + { + tf_printf("\n%s get_identity [options]\n\n", file); + tf_printf("options:\n"); + tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); + tf_printf(" -h, --help Show this usage information.\n"); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + + char id[k_id_base64_len] = { 0 }; + tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); + int result = tf_ssb_whoami(ssb, id, sizeof(id)) ? EXIT_SUCCESS : EXIT_FAILURE; + tf_printf("%s\n", id); + tf_ssb_destroy(ssb); + tf_free((void*)default_db_path); + return result; +} + +static int _tf_command_get_profile(const char* file, int argc, char* argv[]) +{ + const char* default_db_path = _get_db_path(); + const char* db_path = default_db_path; + const char* identity = NULL; + bool show_usage = false; + + while (!show_usage) + { + static const struct option k_options[] = { + { "db-path", required_argument, NULL, 'd' }, + { "id", required_argument, NULL, 'i' }, + { "help", no_argument, NULL, 'h' }, + { 0 }, + }; + int c = getopt_long(argc, argv, "d:i:h", k_options, NULL); + if (c == -1) + { + break; + } + + switch (c) + { + case '?': + case 'h': + default: + show_usage = true; + break; + case 'd': + db_path = optarg; + break; + case 'i': + identity = optarg; + break; + } + } + + if (show_usage || !identity) + { + tf_printf("\n%s get_profile [options]\n\n", file); + tf_printf("options:\n"); + tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); + tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n"); + tf_printf(" -h, --help Show this usage information.\n"); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + + sqlite3* db = NULL; + sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL); + tf_ssb_db_init_reader(db); + const char* profile = tf_ssb_db_get_profile(db, identity); + tf_printf("%s\n", profile); + sqlite3_close(db); + tf_free((void*)profile); + tf_free((void*)default_db_path); + return profile != NULL; +} + static int _tf_command_verify(const char* file, int argc, char* argv[]) { const char* identity = NULL; diff --git a/src/ssb.db.c b/src/ssb.db.c index b65482fc..1ef89d78 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -2084,3 +2084,29 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou } return result; } + +const char* tf_ssb_db_get_profile(sqlite3* db, const char* id) +{ + const char* result = NULL; + sqlite3_stmt* statement; + if (sqlite3_prepare(db, + "SELECT json(json_group_object(key, value)) FROM (SELECT fields.key, RANK() OVER (PARTITION BY fields.key ORDER BY messages.sequence DESC) AS rank, fields.value FROM " + "messages, json_each(messages.content) AS fields WHERE messages.author = ? AND messages.content ->> '$.type' = 'about' AND messages.content ->> '$.about' = " + "messages.author AND NOT fields.key IN ('about', 'type')) WHERE rank = 1", + -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) == SQLITE_ROW) + { + result = tf_strdup((const char*)sqlite3_column_text(statement, 0)); + } + } + sqlite3_finalize(statement); + } + else + { + tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); + } + return result; +} diff --git a/src/ssb.db.h b/src/ssb.db.h index f47775d2..60b4fda4 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -483,6 +483,14 @@ bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t* */ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* out_value, size_t size); +/** +** Get the latest profile information for the given identity. +** @param db The database. +** @param id The identity. +** @return A JSON representation of the latest profile information set for the account. Free with tf_free(). +*/ +const char* tf_ssb_db_get_profile(sqlite3* db, const char* id); + /** ** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use. ** @param user_data User data registered with the authorizer.