diff --git a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java index 0a93db3..0983a60 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -22,7 +22,7 @@ import java.sql.Statement; * - blockchain_state * - blocks * - connections_state (текущее состояние связей) - * + * - message_stats (счётчики лайков/ответов на сообщения) */ public class DatabaseInitializer { @@ -247,7 +247,6 @@ public class DatabaseInitializer { FOREIGN KEY (login) REFERENCES solana_users(login), - -- состояние уникально по пользователю, типу связи и цели UNIQUE (login, relType, to_login) ); """); @@ -269,15 +268,6 @@ public class DatabaseInitializer { // ===================================================================== // 8) Trigger: при вставке connection-блоков в blocks — обновлять connections_state - // - // Правило: - // - msgType=3 (ConnectionBody) - // - subType 10/20/30 => добавить/обновить запись состояния - // - subType 11/21/31 => удалить запись состояния (без ошибок, даже если её нет) - // - // Примечание: - // - "повторное добавление" не должно падать => используем UPSERT (DO UPDATE) - // - "удаление того, чего нет" не падает => обычный DELETE // ===================================================================== st.executeUpdate(""" CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai @@ -285,7 +275,6 @@ public class DatabaseInitializer { WHEN NEW.msgType = 3 BEGIN - -- ADD / UPDATE: 10/20/30 INSERT INTO connections_state ( login, relType, to_login, toBchName, toBlockGlobalNumber, toBlockHashe ) @@ -305,7 +294,6 @@ public class DatabaseInitializer { toBlockGlobalNumber = excluded.toBlockGlobalNumber, toBlockHashe = excluded.toBlockHashe; - -- DELETE: 11/21/31 => удалить соответствующую "положительную" связь (10/20/30) DELETE FROM connections_state WHERE login = NEW.login AND to_login = NEW.to_login @@ -319,6 +307,111 @@ public class DatabaseInitializer { END; """); + + // ===================================================================== + // 9) message_stats — счётчики лайков/ответов на конкретный блок-цель + // + // Правило системы: + // - to_login берём из toBchName: отрезаем последние 3 символа + // ===================================================================== + st.executeUpdate(""" + CREATE TABLE IF NOT EXISTS message_stats ( + to_login TEXT NOT NULL, + to_bch_name TEXT NOT NULL, + to_block_global_number INTEGER NOT NULL, + to_block_hash TEXT NOT NULL, + + likes_count INTEGER NOT NULL DEFAULT 0, + replies_count INTEGER NOT NULL DEFAULT 0, + + UNIQUE ( + to_login, + to_bch_name, + to_block_global_number, + to_block_hash + ) + ); + """); + + st.executeUpdate(""" + CREATE INDEX IF NOT EXISTS idx_message_stats_target + ON message_stats (to_bch_name, to_block_global_number, to_block_hash); + """); + + st.executeUpdate(""" + CREATE INDEX IF NOT EXISTS idx_message_stats_login + ON message_stats (to_login); + """); + + // ===================================================================== + // 10) Trigger: LIKE (Reaction) + // - msgType=2 (REACTION) + // - msgSubType=1 (LIKE) + // ===================================================================== + st.executeUpdate(""" + CREATE TRIGGER IF NOT EXISTS trg_blocks_message_stats_like_ai + AFTER INSERT ON blocks + WHEN NEW.msgType = 2 AND NEW.msgSubType = 1 + BEGIN + INSERT INTO message_stats ( + to_login, + to_bch_name, + to_block_global_number, + to_block_hash, + likes_count, + replies_count + ) + SELECT + substr(NEW.toBchName, 1, length(NEW.toBchName) - 3), + NEW.toBchName, + NEW.toBlockGlobalNumber, + NEW.toBlockHashe, + 1, + 0 + WHERE NEW.toBchName IS NOT NULL + AND length(NEW.toBchName) > 3 + AND NEW.toBlockGlobalNumber IS NOT NULL + AND NEW.toBlockHashe IS NOT NULL + ON CONFLICT(to_login, to_bch_name, to_block_global_number, to_block_hash) + DO UPDATE SET + likes_count = message_stats.likes_count + 1; + END; + """); + + // ===================================================================== + // 11) Trigger: REPLY (Text) + // - msgType=1 (TEXT) + // - msgSubType=2 (REPLY) + // ===================================================================== + st.executeUpdate(""" + CREATE TRIGGER IF NOT EXISTS trg_blocks_message_stats_reply_ai + AFTER INSERT ON blocks + WHEN NEW.msgType = 1 AND NEW.msgSubType = 2 + BEGIN + INSERT INTO message_stats ( + to_login, + to_bch_name, + to_block_global_number, + to_block_hash, + likes_count, + replies_count + ) + SELECT + substr(NEW.toBchName, 1, length(NEW.toBchName) - 3), + NEW.toBchName, + NEW.toBlockGlobalNumber, + NEW.toBlockHashe, + 0, + 1 + WHERE NEW.toBchName IS NOT NULL + AND length(NEW.toBchName) > 3 + AND NEW.toBlockGlobalNumber IS NOT NULL + AND NEW.toBlockHashe IS NOT NULL + ON CONFLICT(to_login, to_bch_name, to_block_global_number, to_block_hash) + DO UPDATE SET + replies_count = message_stats.replies_count + 1; + END; + """); } } } \ No newline at end of file diff --git a/src/test/java/test/it/IT_03_AddBlock_NoAuth.java b/src/test/java/test/it/IT_03_AddBlock_NoAuth.java index c5a0ac3..351dcd0 100644 --- a/src/test/java/test/it/IT_03_AddBlock_NoAuth.java +++ b/src/test/java/test/it/IT_03_AddBlock_NoAuth.java @@ -11,7 +11,6 @@ import test.it.addBlockUtils.ChainState; import test.it.utils.*; import utils.crypto.Ed25519Util; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Base64; @@ -98,7 +97,7 @@ public class IT_03_AddBlock_NoAuth { ); // ========================================================= - // 3) USER1 блоки (как было раньше в IT_03) + // 3) USER1 блоки (под message_stats) // ========================================================= if (TestConfig.DEBUG()) { TestLog.titleBlock(""" @@ -122,21 +121,25 @@ public class IT_03_AddBlock_NoAuth { sender1.send(new HeaderBody(TestConfig.LOGIN()), t); assertTrue(st1.hasHeader()); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#1 (NEW)"); + // 3 NEW + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#1 (NEW) <- будет LIKE + REPLY"); sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #1 (NEW) from IT_03 test"), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#2 (NEW)"); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#2 (NEW) <- будет ONLY LIKE"); sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#3 (NEW)"); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#3 (NEW) <- будет ONLY REPLY"); sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t); byte[] text1Hash = st1.getGlobalHash32(1); byte[] text2Hash = st1.getGlobalHash32(2); + byte[] text3Hash = st1.getGlobalHash32(3); assertNotNull(text1Hash); assertNotNull(text2Hash); + assertNotNull(text3Hash); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#4 (REPLY -> TEXT#1)"); + // 2 REPLY + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#4 (REPLY -> TEXT#1) (делает TEXT#1: replies+1)"); sender1.send(new TextBody( TextBody.SUB_REPLY, "Reply to TEXT#1", @@ -145,19 +148,17 @@ public class IT_03_AddBlock_NoAuth { text1Hash ), t); - byte[] reply1Hash = st1.getGlobalHash32(4); - assertNotNull(reply1Hash); - - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#5 (REPLY -> TEXT#4)"); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#5 (REPLY -> TEXT#3) (делает TEXT#3: replies+1)"); sender1.send(new TextBody( TextBody.SUB_REPLY, - "Reply to REPLY (TEXT#4)", + "Reply to TEXT#3", TestConfig.BCH_NAME(), - 4, - reply1Hash + 3, + text3Hash ), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#1 (LIKE -> TEXT#1)"); + // 2 LIKE + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#1 (LIKE -> TEXT#1) (делает TEXT#1: likes+1)"); sender1.send(new ReactionBody( ReactionBody.SUB_LIKE, TestConfig.BCH_NAME(), @@ -165,12 +166,12 @@ public class IT_03_AddBlock_NoAuth { text1Hash ), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#2 (LIKE -> TEXT#4)"); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#2 (LIKE -> TEXT#2) (делает TEXT#2: likes+1)"); sender1.send(new ReactionBody( ReactionBody.SUB_LIKE, TestConfig.BCH_NAME(), - 4, - reply1Hash + 2, + text2Hash ), t); assertEquals(7, st1.globalLastNumber(), "USER1: должно быть 8 блоков: globalLastNumber=7");