diff --git a/lib/ops/thread-activity-store-ops.js b/lib/ops/thread-activity-store-ops.js --- a/lib/ops/thread-activity-store-ops.js +++ b/lib/ops/thread-activity-store-ops.js @@ -130,4 +130,7 @@ }, }; -export { threadActivityStoreOpsHandlers }; +export { + threadActivityStoreOpsHandlers, + convertThreadActivityEntryToClientDBThreadActivityEntry, +}; diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -15,6 +15,7 @@ #include "entities/Report.h" #include "entities/SyncedMetadataEntry.h" #include "entities/Thread.h" +#include "entities/ThreadActivityEntry.h" #include "entities/UserInfo.h" #include @@ -99,6 +100,13 @@ removeAuxUserInfos(const std::vector &ids) const = 0; virtual void removeAllAuxUserInfos() const = 0; virtual std::vector getAllAuxUserInfos() const = 0; + virtual void replaceThreadActivityEntry( + const ThreadActivityEntry &thread_activity_entry) const = 0; + virtual void + removeThreadActivityEntries(const std::vector &ids) const = 0; + virtual void removeAllThreadActivityEntries() const = 0; + virtual std::vector + getAllThreadActivityEntries() const = 0; virtual void beginTransaction() const = 0; virtual void commitTransaction() const = 0; virtual void rollbackTransaction() const = 0; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -8,6 +8,7 @@ #include "entities/Draft.h" #include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" +#include "entities/ThreadActivityEntry.h" #include "entities/UserInfo.h" #include @@ -113,6 +114,12 @@ void removeAuxUserInfos(const std::vector &ids) const override; void removeAllAuxUserInfos() const override; virtual std::vector getAllAuxUserInfos() const override; + void replaceThreadActivityEntry( + const ThreadActivityEntry &thread_activity_entry) const override; + void removeThreadActivityEntries( + const std::vector &ids) const override; + void removeAllThreadActivityEntries() const override; + std::vector getAllThreadActivityEntries() const override; void beginTransaction() const override; void commitTransaction() const override; void rollbackTransaction() const override; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -1863,6 +1863,51 @@ SQLiteQueryExecutor::getConnection(), getAllAuxUserInfosSQL); } +void SQLiteQueryExecutor::replaceThreadActivityEntry( + const ThreadActivityEntry &thread_activity_entry) const { + static std::string replaceThreadActivityEntrySQL = + "REPLACE INTO thread_activity (id, thread_activity_store_entry) " + "VALUES (?, ?);"; + replaceEntity( + SQLiteQueryExecutor::getConnection(), + replaceThreadActivityEntrySQL, + thread_activity_entry); +} + +void SQLiteQueryExecutor::removeAllThreadActivityEntries() const { + static std::string removeAllThreadActivityEntriesSQL = + "DELETE FROM thread_activity;"; + removeAllEntities( + SQLiteQueryExecutor::getConnection(), removeAllThreadActivityEntriesSQL); +} + +void SQLiteQueryExecutor::removeThreadActivityEntries( + const std::vector &ids) const { + if (!ids.size()) { + return; + } + + std::stringstream removeThreadActivityEntriesByKeysSQLStream; + removeThreadActivityEntriesByKeysSQLStream << "DELETE FROM thread_activity " + "WHERE id IN " + << getSQLStatementArray(ids.size()) + << ";"; + + removeEntitiesByKeys( + SQLiteQueryExecutor::getConnection(), + removeThreadActivityEntriesByKeysSQLStream.str(), + ids); +} + +std::vector +SQLiteQueryExecutor::getAllThreadActivityEntries() const { + static std::string getAllThreadActivityEntriesSQL = + "SELECT * " + "FROM thread_activity;"; + return getAllEntities( + SQLiteQueryExecutor::getConnection(), getAllThreadActivityEntriesSQL); +} + void SQLiteQueryExecutor::beginTransaction() const { executeQuery(SQLiteQueryExecutor::getConnection(), "BEGIN TRANSACTION;"); } diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/ThreadActivityEntry.h b/native/cpp/CommonCpp/DatabaseManagers/entities/ThreadActivityEntry.h new file mode 100644 --- /dev/null +++ b/native/cpp/CommonCpp/DatabaseManagers/entities/ThreadActivityEntry.h @@ -0,0 +1,24 @@ +#pragma once + +#include "SQLiteDataConverters.h" +#include +#include + +namespace comm { + +struct ThreadActivityEntry { + std::string id; + std::string thread_activity_store_entry; + + static ThreadActivityEntry fromSQLResult(sqlite3_stmt *sqlRow, int idx) { + return ThreadActivityEntry{ + getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)}; + } + + int bindToSQL(sqlite3_stmt *sql, int idx) const { + bindStringToSQL(id, sql, idx); + return bindStringToSQL(thread_activity_store_entry, sql, idx + 1); + } +}; + +} // namespace comm diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -62,6 +62,9 @@ value_object("AuxUserInfo") .field("id", &AuxUserInfo::id) .field("auxUserInfo", &AuxUserInfo::aux_user_info); + value_object("ThreadActivityEntry") + .field("id", &ThreadActivityEntry::id) + .field("threadActivityStoreEntry", &ThreadActivityEntry::thread_activity_store_entry); value_object("WebThread") .field("id", &WebThread::id) @@ -213,6 +216,18 @@ .function( "removeAllAuxUserInfos", &SQLiteQueryExecutor::removeAllAuxUserInfos) .function("getAllAuxUserInfos", &SQLiteQueryExecutor::getAllAuxUserInfos) + .function( + "replaceThreadActivityEntry", + &SQLiteQueryExecutor::replaceThreadActivityEntry) + .function( + "removeThreadActivityEntries", + &SQLiteQueryExecutor::removeThreadActivityEntries) + .function( + "removeAllThreadActivityEntries", + &SQLiteQueryExecutor::removeAllThreadActivityEntries) + .function( + "getAllThreadActivityEntries", + &SQLiteQueryExecutor::getAllThreadActivityEntries) .function("beginTransaction", &SQLiteQueryExecutor::beginTransaction) .function("commitTransaction", &SQLiteQueryExecutor::commitTransaction) .function( diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ { + let queryExecutor: ?SQLiteQueryExecutor = null; + let dbModule: ?EmscriptenModule = null; + + beforeAll(async () => { + dbModule = getDatabaseModule(); + }); + + beforeEach(() => { + if (!dbModule) { + throw new Error('Database module is missing'); + } + queryExecutor = new dbModule.SQLiteQueryExecutor(FILE_PATH); + if (!queryExecutor) { + throw new Error('SQLiteQueryExecutor is missing'); + } + queryExecutor?.replaceThreadActivityEntry({ + id: 'test_id_1', + threadActivityStoreEntry: JSON.stringify({ + lastNavigatedTo: 1, + lastPruned: 2, + }), + }); + queryExecutor?.replaceThreadActivityEntry({ + id: 'test_id_2', + threadActivityStoreEntry: JSON.stringify({ + lastNavigatedTo: 3, + lastPruned: 4, + }), + }); + queryExecutor?.replaceThreadActivityEntry({ + id: 'test_id_3', + threadActivityStoreEntry: JSON.stringify({ + lastNavigatedTo: 5, + lastPruned: 6, + }), + }); + }); + + afterEach(() => { + if (!dbModule || !queryExecutor) { + return; + } + + clearSensitiveData(dbModule, FILE_PATH, queryExecutor); + }); + + it('should return all thread activity entries', () => { + const threadActivityEntries = queryExecutor?.getAllThreadActivityEntries(); + + expect(threadActivityEntries).toHaveLength(3); + }); + + it('should remove all thread activity entries', () => { + queryExecutor?.removeAllThreadActivityEntries(); + const threadActivityEntries = queryExecutor?.getAllThreadActivityEntries(); + + expect(threadActivityEntries).toHaveLength(0); + }); + + it('should update thread activity entry test_id_2', () => { + queryExecutor?.replaceThreadActivityEntry({ + id: 'test_id_2', + threadActivityStoreEntry: JSON.stringify({ + lastNavigatedTo: 7, + lastPruned: 8, + }), + }); + + const threadActivityEntries = queryExecutor?.getAllThreadActivityEntries(); + if (!threadActivityEntries) { + throw new Error('thread activity entries not defined'); + } + + expect(threadActivityEntries).toHaveLength(3); + + const threadActivityEntriesFromDB = + threadActivityStoreOpsHandlers.translateClientDBData( + threadActivityEntries, + ); + + expect(threadActivityEntriesFromDB['test_id_2']).toBeDefined(); + expect(threadActivityEntriesFromDB['test_id_2']).toStrictEqual({ + lastNavigatedTo: 7, + lastPruned: 8, + }); + }); + + it('should remove thread activity entries test_id_1 and test_id_3', () => { + queryExecutor?.removeThreadActivityEntries(['test_id_1', 'test_id_3']); + + const threadActivityEntries = queryExecutor?.getAllThreadActivityEntries(); + if (!threadActivityEntries) { + throw new Error('thread activity entries not defined'); + } + + expect(threadActivityEntries.length).toBe(1); + + const threadActivityEntriesFromDB = + threadActivityStoreOpsHandlers.translateClientDBData( + threadActivityEntries, + ); + + expect(threadActivityEntriesFromDB['test_id_2']).toBeDefined(); + }); +}); diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -6,6 +6,7 @@ import type { ClientDBKeyserverInfo } from 'lib/ops/keyserver-store-ops.js'; import type { ClientDBReport } from 'lib/ops/report-store-ops.js'; import type { ClientDBSyncedMetadataEntry } from 'lib/ops/synced-metadata-store-ops.js'; +import type { ClientDBThreadActivityEntry } from 'lib/ops/thread-activity-store-ops.js'; import type { ClientDBUserInfo } from 'lib/ops/user-store-ops.js'; import type { ClientDBDraftInfo } from 'lib/types/draft-types.js'; @@ -136,6 +137,13 @@ removeAllAuxUserInfos(): void; getAllAuxUserInfos(): ClientDBAuxUserInfo[]; + replaceThreadActivityEntry( + threadActivityEntry: ClientDBThreadActivityEntry, + ): void; + removeThreadActivityEntries(ids: $ReadOnlyArray): void; + removeAllThreadActivityEntries(): void; + getAllThreadActivityEntries(): ClientDBThreadActivityEntry[]; + beginTransaction(): void; commitTransaction(): void; rollbackTransaction(): void;