diff --git a/DOC/Описание БД.md b/DOC/Описание БД.md index bbe556f..ac06e07 100644 --- a/DOC/Описание БД.md +++ b/DOC/Описание БД.md @@ -1,132 +1,209 @@ -1) Перечень таблиц и кратко о каждой +SHiNE — структура БД (актуальная версия) -solana_users — справочник пользователей: логин + ключ устройства + (опционально) Solana-ключ. База для FK почти во всех таблицах. -active_sessions — активные сессии авторизации/работы клиента: пароли сессии/хранилища, времена, push-данные, IP/инфо клиента. -users_params — “последние значения параметров” пользователя. Для каждого (login,param) хранится актуальная версия по time_ms (старые обновления игнорируются). -ip_geo_cache — кеш геолокации по IP, чтобы не дергать внешние сервисы постоянно. -blockchain_state — агрегатное состояние блокчейна по blockchainName: лимиты/размер, последний глобальный блок и последние блоки по линиям 0..7. -blocks — локальное хранилище блоков/сообщений: привязка к пользователю и блокчейну, номера/хэши, тип/подтип, тело блока (BLOB), и опциональная “ссылка на другой блок” через поля to*. (PK сейчас намеренно убран.) - -2) Таблицы подробно: параметры по строкам +Перечень таблиц и назначение solana_users - -login — TEXT PK — уникальный логин пользователя. -deviceKey — TEXT NOT NULL — публичный ключ устройства (обычно Base64(32) или HEX(64)). -solanaKey — TEXT NULL — публичный ключ Solana-аккаунта (если используется). +Справочник пользователей: логин + ключ устройства + (опционально) Solana-ключ. +Базовая таблица, используется как FK почти везде. active_sessions - -sessionId — TEXT PK — идентификатор сессии (строка; по смыслу base64(32)). -login — TEXT NOT NULL, FK → solana_users(login) — владелец сессии. -sessionPwd — TEXT NOT NULL — пароль/секрет сессии (как хранится — строкой). -storagePwd — TEXT NOT NULL — пароль/секрет для storage (как хранится — строкой). -sessionCreatedAtMs — INTEGER NOT NULL — время создания сессии (Unix ms). -lastAuthirificatedAtMs — INTEGER NOT NULL — время последней авторизации/refresh (Unix ms). -pushEndpoint — TEXT NULL — endpoint для WebPush. -pushP256dhKey — TEXT NULL — p256dh ключ для WebPush. -pushAuthKey — TEXT NULL — auth ключ для WebPush. -clientIp — TEXT NULL — IP клиента на auth/refresh. -clientInfoFromClient — TEXT NULL — строка, присланная клиентом (PWA). -clientInfoFromRequest — TEXT NULL — строка, собранная сервером из запроса. -userLanguage — TEXT NULL — язык пользователя (например ru-RU). +Активные сессии авторизации/работы клиента: секреты, тайминги, WebPush-данные, IP и информация о клиенте. users_params - -login — TEXT NOT NULL, FK → solana_users(login) — владелец параметра. -param — TEXT NOT NULL — имя параметра (ключ). -time_ms — INTEGER NOT NULL — версия/время параметра (Unix ms), используется для “только если новее”. -value — TEXT NOT NULL — значение параметра. -device_key — TEXT NULL — каким ключом подписано (если используешь), строковый формат. -signature — TEXT NULL — подпись (если используешь), строковый формат. - -Ограничение: UNIQUE(login, param) — один актуальный параметр на пару. +Хранилище актуальных параметров пользователя. +Для каждой пары (login, param) хранится только самая новая версия по time_ms. ip_geo_cache - -ip — TEXT PK — IP-адрес (строкой). -geo — TEXT NULL — гео-строка (Country/City или как договоритесь). -updated_at_ms — INTEGER NOT NULL — когда кеш обновлялся (Unix ms). +Кеш геолокации по IP для снижения нагрузки на внешние сервисы. blockchain_state - -blockchainName — TEXT PK — имя/ID блокчейна (уникальное). -login — TEXT NOT NULL, FK → solana_users(login) — владелец блокчейна. -blockchainKey — TEXT NOT NULL — публичный ключ блокчейна (по смыслу Base64(32)). -size_limit — INTEGER NOT NULL — лимит размера (по смыслу bytes; в Java это long). -file_size_bytes — INTEGER NOT NULL — текущий размер файла блокчейна в байтах. -last_global_number — INTEGER NOT NULL — последний глобальный номер блока (genesis = -1). -last_global_hash — TEXT NOT NULL — хэш последнего глобального блока (или пустая строка). -updated_at_ms — INTEGER NOT NULL — время обновления состояния (Unix ms). - -Линии 0..7 (для каждой линии две колонки): -line0_last_number — INTEGER NOT NULL — последний номер в линии 0. -line0_last_hash — TEXT NOT NULL — последний хэш в линии 0. -line1_last_number — INTEGER NOT NULL — последний номер в линии 1. -line1_last_hash — TEXT NOT NULL — последний хэш в линии 1. -line2_last_number — INTEGER NOT NULL — последний номер в линии 2. -line2_last_hash — TEXT NOT NULL — последний хэш в линии 2. -line3_last_number — INTEGER NOT NULL — последний номер в линии 3. -line3_last_hash — TEXT NOT NULL — последний хэш в линии 3. -line4_last_number — INTEGER NOT NULL — последний номер в линии 4. -line4_last_hash — TEXT NOT NULL — последний хэш в линии 4. -line5_last_number — INTEGER NOT NULL — последний номер в линии 5. -line5_last_hash — TEXT NOT NULL — последний хэш в линии 5. -line6_last_number — INTEGER NOT NULL — последний номер в линии 6. -line6_last_hash — TEXT NOT NULL — последний хэш в линии 6. -line7_last_number — INTEGER NOT NULL — последний номер в линии 7. -line7_last_hash — TEXT NOT NULL — последний хэш в линии 7. +Агрегированное состояние блокчейна по blockchain_name: +лимиты, текущий размер, последний глобальный блок и состояние линий 0..7. blocks - -login — TEXT NOT NULL, FK → solana_users(login) — чей блок (логин). -bchName — TEXT NOT NULL, FK → blockchain_state(blockchainName) — к какому блокчейну относится блок. -blockGlobalNumber — INTEGER NOT NULL — глобальный номер блока. -blockGlobalPreHashe — TEXT NOT NULL — хэш предыдущего глобального блока. -blockLineIndex — INTEGER NOT NULL — индекс линии (0..7), по смыслу int16. -blockLineNumber — INTEGER NOT NULL — номер блока в линии. -blockLinePreHashe — TEXT NOT NULL — хэш предыдущего блока в линии. -msgType — INTEGER NOT NULL — общий тип сообщения/блока (по смыслу uint16). -msgSubType — INTEGER NOT NULL — подтип внутри msgType (по смыслу uint16). -blockByte — BLOB NULL — сырой байтовый блок/тело сообщения. - -Ссылка на другой блок (nullable, для ответов/репостов/связей и т.п.): -to_login — TEXT NULL — логин “на кого/кому/с кем” (смысловая цель). -toBchName — TEXT NULL — целевой блокчейн. -toBlockGlobalNumber — INTEGER NULL — целевой глобальный номер. -toBlockHashe — TEXT NULL — хэш целевого блока. +Журнал всех блоков и сообщений. +Содержит историю событий: тексты, реакции, ответы, связи. +PRIMARY KEY намеренно отсутствует. connections_state ⭐ -Текущее состояние связей пользователя. -login — TEXT NOT NULL — владелец связи. -relType — INTEGER NOT NULL — тип связи: -10 = FRIEND, 20 = CONTACT, 30 = FOLLOW. -to_login — TEXT NOT NULL — с кем связь. -toBchName — TEXT NOT NULL — блокчейн цели. -toBlockGlobalNumber — INTEGER NULL — последний известный номер блока цели. -toBlockHashe — TEXT NULL — последний известный хэш блока цели. +Актуальное состояние связей между пользователями +(друг / контакт / подписка). +Обновляется автоматически на основе событий из blocks. + +message_stats ⭐ +Агрегированные счётчики лайков и ответов на конкретные сообщения. +Поддерживается триггерами из blocks. + +Таблицы подробно + +solana_users +login — TEXT PK — уникальный логин пользователя +device_key — TEXT NOT NULL — публичный ключ устройства (Base64(32) / HEX(64)) +solana_key — TEXT NULL — публичный ключ Solana-аккаунта + +active_sessions +session_id — TEXT PK — идентификатор сессии +login — TEXT NOT NULL, FK → solana_users(login) +session_pwd — TEXT NOT NULL — секрет сессии +storage_pwd — TEXT NOT NULL — секрет storage +session_created_at_ms — INTEGER NOT NULL +last_authirificated_at_ms — INTEGER NOT NULL +push_endpoint — TEXT NULL +push_p256dh_key — TEXT NULL +push_auth_key — TEXT NULL +client_ip — TEXT NULL +client_info_from_client — TEXT NULL +client_info_from_request — TEXT NULL +user_language — TEXT NULL + +users_params +login — TEXT NOT NULL, FK → solana_users(login) +param — TEXT NOT NULL +time_ms — INTEGER NOT NULL +value — TEXT NOT NULL +device_key — TEXT NULL +signature — TEXT NULL + Ограничение: -UNIQUE(login, relType, to_login) — одно актуальное состояние связи. +UNIQUE(login, param) -3) Триггер логики связей +Логика: +обновление принимается только если excluded.time_ms > users_params.time_ms -Триггер trg_blocks_connection_state_ai срабатывает AFTER INSERT ON blocks: -если msgType = 3 (ConnectionBody) -msgSubType IN (10,20,30) → -добавить или обновить запись в connections_state -msgSubType IN (11,21,31) → -удалить соответствующую связь (10/20/30) -повторные добавления и удаления не вызывают ошибок -Таким образом: +ip_geo_cache +ip — TEXT PK +geo — TEXT NULL +updated_at_ms — INTEGER NOT NULL + +blockchain_state +blockchain_name — TEXT PK +login — TEXT NOT NULL, FK → solana_users(login) +blockchain_key — TEXT NOT NULL +size_limit — INTEGER NOT NULL +file_size_bytes — INTEGER NOT NULL +last_global_number — INTEGER NOT NULL (-1 = genesis) +last_global_hash — TEXT NOT NULL +updated_at_ms — INTEGER NOT NULL + +Линии 0..7: +для каждой линии: +lineX_last_number +lineX_last_hash + +blocks +login — TEXT NOT NULL +bch_name — TEXT NOT NULL +block_global_number — INTEGER NOT NULL +block_global_pre_hash — TEXT NOT NULL +block_line_index — INTEGER NOT NULL +block_line_number — INTEGER NOT NULL +block_line_pre_hash — TEXT NOT NULL +msg_type — INTEGER NOT NULL +msg_sub_type — INTEGER NOT NULL +block_bytes — BLOB NULL + +Ссылка на другой блок (nullable): +to_login +to_bch_name +to_block_global_number +to_block_hash + +connections_state ⭐ +Текущее агрегированное состояние связей. + +login — TEXT NOT NULL +rel_type — INTEGER NOT NULL +10 = FRIEND +20 = CONTACT +30 = FOLLOW +to_login — TEXT NOT NULL +to_bch_name — TEXT NOT NULL +to_block_global_number — INTEGER NULL +to_block_hash — TEXT NULL + +Ограничение: +UNIQUE(login, rel_type, to_login) + +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) + +Триггеры БД (полная логика) + +3.1 Связи пользователей +trg_blocks_connection_state_ai +AFTER INSERT ON blocks + +Условие: +msg_type = 3 (connection) + +Добавление / обновление связи +msg_sub_type IN (10,20,30) +выполняется UPSERT в connections_state + +Удаление связи +msg_sub_type IN (11,21,31) +удаляется соответствующая связь: +11 → 10 +21 → 20 +31 → 30 + +Итог: blocks — журнал событий connections_state — всегда актуальное состояние -Индексы (кратко, чтобы было понятно зачем) -idx_solana_users_login(login) — быстрый поиск по логину. -idx_active_sessions_login(login) — быстро получить сессии пользователя. -idx_users_params_login(login) — быстро получить параметры пользователя. -idx_ip_geo_cache_updated_at(updated_at_ms) — чистка старых записей. -idx_blockchain_state_login(login) — блокчейны пользователя. -idx_blockchain_state_updated_at(updated_at_ms) — выборки/обслуживание по “свежести”. -idx_blocks_chain_global(login,bchName,blockGlobalNumber) — выборки блоков по цепочке. -idx_blocks_to_target(to_login,toBchName,toBlockGlobalNumber) — быстрые выборки “по ссылке”. \ No newline at end of file +3.2 Подсчёт лайков ⭐ +trg_blocks_message_stats_like_ai +AFTER INSERT ON blocks + +Условие: +msg_type = 2 (reaction) +msg_sub_type = 1 (like) + +Действие: +определяется цель по to_bch_name, to_block_global_number, to_block_hash +to_login вычисляется как +substr(to_bch_name, 1, length(to_bch_name) - 3) +выполняется UPSERT в message_stats +likes_count += 1 + +3.3 Подсчёт ответов ⭐ +trg_blocks_message_stats_reply_ai +AFTER INSERT ON blocks + +Условие: +msg_type = 1 (text) +msg_sub_type = 2 (reply) + +Действие: +цель определяется аналогично лайкам +выполняется UPSERT в message_stats +replies_count += 1 + +Индексы (смысл) + +idx_solana_users_login — поиск пользователя +idx_active_sessions_login — сессии пользователя +idx_users_params_login — параметры пользователя +idx_ip_geo_cache_updated_at — чистка кеша +idx_blockchain_state_login — блокчейны пользователя +idx_blockchain_state_updated_at — обслуживание +idx_blocks_chain_global — чтение цепочки +idx_blocks_to_target — реакции / ответы +idx_message_stats_target — быстрый доступ к счётчикам + +Итоговая модель мышления + +blocks — неизменяемый журнал событий +connections_state — проекция связей +message_stats — проекция активности +всё вычисляется детерминированно через триггеры \ No newline at end of file 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 0983a60..2cdace0 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -21,8 +21,8 @@ import java.sql.Statement; * - ip_geo_cache * - blockchain_state * - blocks - * - connections_state (текущее состояние связей) - * - message_stats (счётчики лайков/ответов на сообщения) + * - connections_state + * - message_stats */ public class DatabaseInitializer { @@ -83,8 +83,8 @@ public class DatabaseInitializer { st.executeUpdate(""" CREATE TABLE IF NOT EXISTS solana_users ( login TEXT NOT NULL PRIMARY KEY, - deviceKey TEXT NOT NULL, - solanaKey TEXT + device_key TEXT NOT NULL, + solana_key TEXT ); """); @@ -96,19 +96,19 @@ public class DatabaseInitializer { // 2. active_sessions st.executeUpdate(""" CREATE TABLE IF NOT EXISTS active_sessions ( - sessionId TEXT NOT NULL PRIMARY KEY, - login TEXT NOT NULL, - sessionPwd TEXT NOT NULL, - storagePwd TEXT NOT NULL, - sessionCreatedAtMs INTEGER NOT NULL, - lastAuthirificatedAtMs INTEGER NOT NULL, - pushEndpoint TEXT, - pushP256dhKey TEXT, - pushAuthKey TEXT, - clientIp TEXT, - clientInfoFromClient TEXT, - clientInfoFromRequest TEXT, - userLanguage TEXT, + session_id TEXT NOT NULL PRIMARY KEY, + login TEXT NOT NULL, + session_pwd TEXT NOT NULL, + storage_pwd TEXT NOT NULL, + session_created_at_ms INTEGER NOT NULL, + last_authirificated_at_ms INTEGER NOT NULL, + push_endpoint TEXT, + push_p256dh_key TEXT, + push_auth_key TEXT, + client_ip TEXT, + client_info_from_client TEXT, + client_info_from_request TEXT, + user_language TEXT, FOREIGN KEY (login) REFERENCES solana_users(login) ); """); @@ -154,33 +154,33 @@ public class DatabaseInitializer { // 5. blockchain_state st.executeUpdate(""" CREATE TABLE IF NOT EXISTS blockchain_state ( - blockchainName TEXT NOT NULL PRIMARY KEY, - login TEXT NOT NULL, - blockchainKey TEXT NOT NULL, + blockchain_name TEXT NOT NULL PRIMARY KEY, + login TEXT NOT NULL, + blockchain_key TEXT NOT NULL, - size_limit INTEGER NOT NULL, - file_size_bytes INTEGER NOT NULL, + size_limit INTEGER NOT NULL, + file_size_bytes INTEGER NOT NULL, - last_global_number INTEGER NOT NULL, - last_global_hash TEXT NOT NULL, - updated_at_ms INTEGER NOT NULL, + last_global_number INTEGER NOT NULL, + last_global_hash TEXT NOT NULL, + updated_at_ms INTEGER NOT NULL, - line0_last_number INTEGER NOT NULL, - line0_last_hash TEXT NOT NULL, - line1_last_number INTEGER NOT NULL, - line1_last_hash TEXT NOT NULL, - line2_last_number INTEGER NOT NULL, - line2_last_hash TEXT NOT NULL, - line3_last_number INTEGER NOT NULL, - line3_last_hash TEXT NOT NULL, - line4_last_number INTEGER NOT NULL, - line4_last_hash TEXT NOT NULL, - line5_last_number INTEGER NOT NULL, - line5_last_hash TEXT NOT NULL, - line6_last_number INTEGER NOT NULL, - line6_last_hash TEXT NOT NULL, - line7_last_number INTEGER NOT NULL, - line7_last_hash TEXT NOT NULL, + line0_last_number INTEGER NOT NULL, + line0_last_hash TEXT NOT NULL, + line1_last_number INTEGER NOT NULL, + line1_last_hash TEXT NOT NULL, + line2_last_number INTEGER NOT NULL, + line2_last_hash TEXT NOT NULL, + line3_last_number INTEGER NOT NULL, + line3_last_hash TEXT NOT NULL, + line4_last_number INTEGER NOT NULL, + line4_last_hash TEXT NOT NULL, + line5_last_number INTEGER NOT NULL, + line5_last_hash TEXT NOT NULL, + line6_last_number INTEGER NOT NULL, + line6_last_hash TEXT NOT NULL, + line7_last_number INTEGER NOT NULL, + line7_last_hash TEXT NOT NULL, FOREIGN KEY (login) REFERENCES solana_users(login) ); @@ -199,55 +199,53 @@ public class DatabaseInitializer { // 6. blocks st.executeUpdate(""" CREATE TABLE IF NOT EXISTS blocks ( - login TEXT NOT NULL, - bchName TEXT NOT NULL, - blockGlobalNumber INTEGER NOT NULL, - blockGlobalPreHashe TEXT NOT NULL, + login TEXT NOT NULL, + bch_name TEXT NOT NULL, + block_global_number INTEGER NOT NULL, + block_global_pre_hashe TEXT NOT NULL, - blockLineIndex INTEGER NOT NULL, - blockLineNumber INTEGER NOT NULL, - blockLinePreHashe TEXT NOT NULL, + block_line_index INTEGER NOT NULL, + block_line_number INTEGER NOT NULL, + block_line_pre_hashe TEXT NOT NULL, - msgType INTEGER NOT NULL, - msgSubType INTEGER NOT NULL, + msg_type INTEGER NOT NULL, + msg_sub_type INTEGER NOT NULL, - blockByte BLOB, + block_byte BLOB, - to_login TEXT, - toBchName TEXT, - toBlockGlobalNumber INTEGER, - toBlockHashe TEXT, + to_login TEXT, + to_bch_name TEXT, + to_block_global_number INTEGER, + to_block_hashe TEXT, FOREIGN KEY (login) REFERENCES solana_users(login), - FOREIGN KEY (bchName) REFERENCES blockchain_state(blockchainName) + FOREIGN KEY (bch_name) REFERENCES blockchain_state(blockchain_name) ); """); st.executeUpdate(""" CREATE INDEX IF NOT EXISTS idx_blocks_chain_global - ON blocks (login, bchName, blockGlobalNumber); + ON blocks (login, bch_name, block_global_number); """); st.executeUpdate(""" CREATE INDEX IF NOT EXISTS idx_blocks_to_target - ON blocks (to_login, toBchName, toBlockGlobalNumber); + ON blocks (to_login, to_bch_name, to_block_global_number); """); - // ===================================================================== - // 7) connections_state — текущее состояние "кто с кем и какая связь" - // ===================================================================== + // 7) connections_state st.executeUpdate(""" CREATE TABLE IF NOT EXISTS connections_state ( - login TEXT NOT NULL, - relType INTEGER NOT NULL, -- 10/20/30 (FRIEND/CONTACT/FOLLOW) - to_login TEXT NOT NULL, - toBchName TEXT NOT NULL, - toBlockGlobalNumber INTEGER, - toBlockHashe TEXT, + login TEXT NOT NULL, + rel_type INTEGER NOT NULL, + to_login TEXT NOT NULL, + to_bch_name TEXT NOT NULL, + to_block_global_number INTEGER, + to_block_hashe TEXT, FOREIGN KEY (login) REFERENCES solana_users(login), - UNIQUE (login, relType, to_login) + UNIQUE (login, rel_type, to_login) ); """); @@ -266,54 +264,47 @@ public class DatabaseInitializer { ON connections_state (login, to_login); """); - // ===================================================================== - // 8) Trigger: при вставке connection-блоков в blocks — обновлять connections_state - // ===================================================================== + // 8) Trigger: connection state st.executeUpdate(""" CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai AFTER INSERT ON blocks - WHEN NEW.msgType = 3 + WHEN NEW.msg_type = 3 BEGIN INSERT INTO connections_state ( - login, relType, to_login, toBchName, toBlockGlobalNumber, toBlockHashe + login, rel_type, to_login, to_bch_name, to_block_global_number, to_block_hashe ) SELECT NEW.login, - NEW.msgSubType, + NEW.msg_sub_type, NEW.to_login, - NEW.toBchName, - NEW.toBlockGlobalNumber, - NEW.toBlockHashe - WHERE NEW.msgSubType IN (10, 20, 30) + NEW.to_bch_name, + NEW.to_block_global_number, + NEW.to_block_hashe + WHERE NEW.msg_sub_type IN (10, 20, 30) AND NEW.to_login IS NOT NULL - AND NEW.toBchName IS NOT NULL - ON CONFLICT(login, relType, to_login) + AND NEW.to_bch_name IS NOT NULL + ON CONFLICT(login, rel_type, to_login) DO UPDATE SET - toBchName = excluded.toBchName, - toBlockGlobalNumber = excluded.toBlockGlobalNumber, - toBlockHashe = excluded.toBlockHashe; + to_bch_name = excluded.to_bch_name, + to_block_global_number = excluded.to_block_global_number, + to_block_hashe = excluded.to_block_hashe; DELETE FROM connections_state WHERE login = NEW.login AND to_login = NEW.to_login - AND relType = CASE NEW.msgSubType + AND rel_type = CASE NEW.msg_sub_type WHEN 11 THEN 10 WHEN 21 THEN 20 WHEN 31 THEN 30 - ELSE relType + ELSE rel_type END - AND NEW.msgSubType IN (11, 21, 31); + AND NEW.msg_sub_type IN (11, 21, 31); END; """); - // ===================================================================== - // 9) message_stats — счётчики лайков/ответов на конкретный блок-цель - // - // Правило системы: - // - to_login берём из toBchName: отрезаем последние 3 символа - // ===================================================================== + // 9) message_stats st.executeUpdate(""" CREATE TABLE IF NOT EXISTS message_stats ( to_login TEXT NOT NULL, @@ -343,15 +334,11 @@ public class DatabaseInitializer { ON message_stats (to_login); """); - // ===================================================================== - // 10) Trigger: LIKE (Reaction) - // - msgType=2 (REACTION) - // - msgSubType=1 (LIKE) - // ===================================================================== + // 10) Trigger: 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 + WHEN NEW.msg_type = 2 AND NEW.msg_sub_type = 1 BEGIN INSERT INTO message_stats ( to_login, @@ -362,31 +349,27 @@ public class DatabaseInitializer { replies_count ) SELECT - substr(NEW.toBchName, 1, length(NEW.toBchName) - 3), - NEW.toBchName, - NEW.toBlockGlobalNumber, - NEW.toBlockHashe, + substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 3), + NEW.to_bch_name, + NEW.to_block_global_number, + NEW.to_block_hashe, 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 + WHERE NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 3 + AND NEW.to_block_global_number IS NOT NULL + AND NEW.to_block_hashe 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) - // ===================================================================== + // 11) Trigger: 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 + WHEN NEW.msg_type = 1 AND NEW.msg_sub_type = 2 BEGIN INSERT INTO message_stats ( to_login, @@ -397,16 +380,16 @@ public class DatabaseInitializer { replies_count ) SELECT - substr(NEW.toBchName, 1, length(NEW.toBchName) - 3), - NEW.toBchName, - NEW.toBlockGlobalNumber, - NEW.toBlockHashe, + substr(NEW.to_bch_name, 1, length(NEW.to_bch_name) - 3), + NEW.to_bch_name, + NEW.to_block_global_number, + NEW.to_block_hashe, 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 + WHERE NEW.to_bch_name IS NOT NULL + AND length(NEW.to_bch_name) > 3 + AND NEW.to_block_global_number IS NOT NULL + AND NEW.to_block_hashe 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; diff --git a/shine-server-db/src/main/java/shine/db/SqliteDbController.java b/shine-server-db/src/main/java/shine/db/SqliteDbController.java index 8eadda8..58dc75b 100644 --- a/shine-server-db/src/main/java/shine/db/SqliteDbController.java +++ b/shine-server-db/src/main/java/shine/db/SqliteDbController.java @@ -50,10 +50,6 @@ public final class SqliteDbController { return instance; } - /** - * Каждый вызов возвращает НОВОЕ соединение. - * Закрывать обязан вызывающий код (try-with-resources). - */ public Connection getConnection() throws SQLException { Connection conn = DriverManager.getConnection(jdbcUrl); conn.setAutoCommit(true); @@ -68,7 +64,6 @@ public final class SqliteDbController { return conn; } - /** Теперь close() не нужен. */ public void close() { // no-op } diff --git a/shine-server-db/src/main/java/shine/db/dao/ActiveSessionsDAO.java b/shine-server-db/src/main/java/shine/db/dao/ActiveSessionsDAO.java index be65930..46c8029 100644 --- a/shine-server-db/src/main/java/shine/db/dao/ActiveSessionsDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/ActiveSessionsDAO.java @@ -36,19 +36,19 @@ public final class ActiveSessionsDAO { public void insert(Connection c, ActiveSessionEntry session) throws SQLException { String sql = """ INSERT INTO active_sessions ( - sessionId, + session_id, login, - sessionPwd, - storagePwd, - sessionCreatedAtMs, - lastAuthirificatedAtMs, - pushEndpoint, - pushP256dhKey, - pushAuthKey, - clientIp, - clientInfoFromClient, - clientInfoFromRequest, - userLanguage + session_pwd, + storage_pwd, + session_created_at_ms, + last_authirificated_at_ms, + push_endpoint, + push_p256dh_key, + push_auth_key, + client_ip, + client_info_from_client, + client_info_from_request, + user_language ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """; @@ -83,21 +83,21 @@ public final class ActiveSessionsDAO { public ActiveSessionEntry getBySessionId(Connection c, String sessionId) throws SQLException { String sql = """ SELECT - sessionId, + session_id, login, - sessionPwd, - storagePwd, - sessionCreatedAtMs, - lastAuthirificatedAtMs, - pushEndpoint, - pushP256dhKey, - pushAuthKey, - clientIp, - clientInfoFromClient, - clientInfoFromRequest, - userLanguage + session_pwd, + storage_pwd, + session_created_at_ms, + last_authirificated_at_ms, + push_endpoint, + push_p256dh_key, + push_auth_key, + client_ip, + client_info_from_client, + client_info_from_request, + user_language FROM active_sessions - WHERE sessionId = ? + WHERE session_id = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -120,19 +120,19 @@ public final class ActiveSessionsDAO { public List getByLogin(Connection c, String login) throws SQLException { String sql = """ SELECT - sessionId, + session_id, login, - sessionPwd, - storagePwd, - sessionCreatedAtMs, - lastAuthirificatedAtMs, - pushEndpoint, - pushP256dhKey, - pushAuthKey, - clientIp, - clientInfoFromClient, - clientInfoFromRequest, - userLanguage + session_pwd, + storage_pwd, + session_created_at_ms, + last_authirificated_at_ms, + push_endpoint, + push_p256dh_key, + push_auth_key, + client_ip, + client_info_from_client, + client_info_from_request, + user_language FROM active_sessions WHERE login = ? """; @@ -162,8 +162,8 @@ public final class ActiveSessionsDAO { public void updateLastAuthirificatedAtMs(Connection c, String sessionId, long lastAuthMs) throws SQLException { String sql = """ UPDATE active_sessions - SET lastAuthirificatedAtMs = ? - WHERE sessionId = ? + SET last_authirificated_at_ms = ? + WHERE session_id = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -194,12 +194,12 @@ public final class ActiveSessionsDAO { String sql = """ UPDATE active_sessions SET - lastAuthirificatedAtMs = ?, - clientIp = ?, - clientInfoFromClient = ?, - clientInfoFromRequest = ?, - userLanguage = ? - WHERE sessionId = ? + last_authirificated_at_ms = ?, + client_ip = ?, + client_info_from_client = ?, + client_info_from_request = ?, + user_language = ? + WHERE session_id = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -231,7 +231,7 @@ public final class ActiveSessionsDAO { /** Удалить по sessionId с внешним соединением. Соединение НЕ закрывает. */ public void deleteBySessionId(Connection c, String sessionId) throws SQLException { - String sql = "DELETE FROM active_sessions WHERE sessionId = ?"; + String sql = "DELETE FROM active_sessions WHERE session_id = ?"; try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, sessionId); @@ -249,19 +249,19 @@ public final class ActiveSessionsDAO { // -------------------- MAPPER -------------------- private ActiveSessionEntry mapRow(ResultSet rs) throws SQLException { - String sessionId = rs.getString("sessionId"); + String sessionId = rs.getString("session_id"); String login = rs.getString("login"); - String sessionPwd = rs.getString("sessionPwd"); - String storagePwd = rs.getString("storagePwd"); - long sessionCreatedAtMs = rs.getLong("sessionCreatedAtMs"); - long lastAuthirificatedAtMs = rs.getLong("lastAuthirificatedAtMs"); - String pushEndpoint = rs.getString("pushEndpoint"); - String pushP256dhKey = rs.getString("pushP256dhKey"); - String pushAuthKey = rs.getString("pushAuthKey"); - String clientIp = rs.getString("clientIp"); - String clientInfoFromClient = rs.getString("clientInfoFromClient"); - String clientInfoFromRequest = rs.getString("clientInfoFromRequest"); - String userLanguage = rs.getString("userLanguage"); + String sessionPwd = rs.getString("session_pwd"); + String storagePwd = rs.getString("storage_pwd"); + long sessionCreatedAtMs = rs.getLong("session_created_at_ms"); + long lastAuthirificatedAtMs = rs.getLong("last_authirificated_at_ms"); + String pushEndpoint = rs.getString("push_endpoint"); + String pushP256dhKey = rs.getString("push_p256dh_key"); + String pushAuthKey = rs.getString("push_auth_key"); + String clientIp = rs.getString("client_ip"); + String clientInfoFromClient = rs.getString("client_info_from_client"); + String clientInfoFromRequest = rs.getString("client_info_from_request"); + String userLanguage = rs.getString("user_language"); return new ActiveSessionEntry( sessionId, diff --git a/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java b/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java index 36448f5..a8962ae 100644 --- a/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java @@ -32,9 +32,9 @@ public final class BlockchainStateDAO { public BlockchainStateEntry getByBlockchainName(Connection c, String blockchainName) throws SQLException { String sql = """ SELECT - blockchainName, + blockchain_name, login, - blockchainKey, + blockchain_key, size_limit, file_size_bytes, last_global_number, @@ -49,7 +49,7 @@ public final class BlockchainStateDAO { line6_last_number, line6_last_hash, line7_last_number, line7_last_hash FROM blockchain_state - WHERE blockchainName = ? + WHERE blockchain_name = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -79,9 +79,9 @@ public final class BlockchainStateDAO { String sql = """ INSERT INTO blockchain_state ( - blockchainName, + blockchain_name, login, - blockchainKey, + blockchain_key, size_limit, file_size_bytes, last_global_number, @@ -106,10 +106,10 @@ public final class BlockchainStateDAO { ?,?, ?,? ) - ON CONFLICT(blockchainName) + ON CONFLICT(blockchain_name) DO UPDATE SET login = excluded.login, - blockchainKey = excluded.blockchainKey, + blockchain_key = excluded.blockchain_key, size_limit = excluded.size_limit, file_size_bytes = excluded.file_size_bytes, last_global_number = excluded.last_global_number, @@ -158,12 +158,6 @@ public final class BlockchainStateDAO { /** * Атомарно увеличить file_size_bytes на deltaBytes, но только если НЕ превысим size_limit. - * - * Возвращает: - * - true если обновили (лимит не превышен) - * - false если лимит превышается или blockchainName не найден - * - * ВАЖНО: это именно тот механизм, который надо дергать при добавлении блока. */ public boolean tryIncreaseFileSizeWithinLimit(Connection c, String blockchainName, long deltaBytes, long nowMs) throws SQLException { String sql = """ @@ -172,7 +166,7 @@ public final class BlockchainStateDAO { file_size_bytes = file_size_bytes + ?, updated_at_ms = ? WHERE - blockchainName = ? + blockchain_name = ? AND (file_size_bytes + ?) <= size_limit """; @@ -202,11 +196,10 @@ public final class BlockchainStateDAO { private BlockchainStateEntry mapRow(ResultSet rs) throws SQLException { BlockchainStateEntry e = new BlockchainStateEntry(); - e.setBlockchainName(rs.getString("blockchainName")); + e.setBlockchainName(rs.getString("blockchain_name")); e.setLogin(rs.getString("login")); - e.setBlockchainKey(rs.getString("blockchainKey")); + e.setBlockchainKey(rs.getString("blockchain_key")); - // size_limit теперь long e.setSizeLimit(rs.getLong("size_limit")); e.setFileSizeBytes(rs.getLong("file_size_bytes")); diff --git a/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java b/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java index b9a7c7f..137f949 100644 --- a/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/BlocksDAO.java @@ -38,19 +38,19 @@ public final class BlocksDAO { String sql = """ INSERT INTO blocks ( login, - bchName, - blockGlobalNumber, - blockGlobalPreHashe, - blockLineIndex, - blockLineNumber, - blockLinePreHashe, - msgType, - msgSubType, - blockByte, + bch_name, + block_global_number, + block_global_pre_hashe, + block_line_index, + block_line_number, + block_line_pre_hashe, + msg_type, + msg_sub_type, + block_byte, to_login, - toBchName, - toBlockGlobalNumber, - toBlockHashe + to_bch_name, + to_block_global_number, + to_block_hashe ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """; @@ -69,16 +69,11 @@ public final class BlocksDAO { // -------------------- UPSERT (UPDATE -> INSERT) -------------------- - /** - * Сохранить (условный upsert) с внешним соединением. Соединение НЕ закрывает. - * Без PK/UNIQUE делаем: UPDATE по "ключевым" полям -> если 0 строк, то INSERT. - */ public void upsert(Connection c, BlockEntry e) throws SQLException { int updated = update(c, e); if (updated == 0) insert(c, e); } - /** Сохранить (upsert) без внешнего соединения. Сам открывает/закрывает. */ public void upsert(BlockEntry e) throws SQLException { try (Connection c = db.getConnection()) { upsert(c, e); @@ -87,7 +82,6 @@ public final class BlocksDAO { // -------------------- SELECT -------------------- - /** Получить блок по "PK-подобному" набору полей с внешним соединением. Соединение НЕ закрывает. */ public BlockEntry getByPk(Connection c, String login, String bchName, @@ -98,26 +92,26 @@ public final class BlocksDAO { String sql = """ SELECT login, - bchName, - blockGlobalNumber, - blockGlobalPreHashe, - blockLineIndex, - blockLineNumber, - blockLinePreHashe, - msgType, - msgSubType, - blockByte, + bch_name, + block_global_number, + block_global_pre_hashe, + block_line_index, + block_line_number, + block_line_pre_hashe, + msg_type, + msg_sub_type, + block_byte, to_login, - toBchName, - toBlockGlobalNumber, - toBlockHashe + to_bch_name, + to_block_global_number, + to_block_hashe FROM blocks WHERE login = ? - AND bchName = ? - AND blockGlobalNumber = ? - AND blockLineIndex = ? - AND blockLineNumber = ? + AND bch_name = ? + AND block_global_number = ? + AND block_line_index = ? + AND block_line_number = ? LIMIT 1 """; @@ -135,7 +129,6 @@ public final class BlocksDAO { } } - /** Получить блок по "PK-подобному" набору полей без внешнего соединения. Сам открывает/закрывает. */ public BlockEntry getByPk(String login, String bchName, int blockGlobalNumber, @@ -148,29 +141,25 @@ public final class BlocksDAO { // -------------------- UPDATE -------------------- - /** - * Обновить (строго UPDATE) по "PK-подобному" набору полей с внешним соединением. Соединение НЕ закрывает. - * Может обновить >1 строк, если в таблице появились дубликаты. - */ public int update(Connection c, BlockEntry e) throws SQLException { String sql = """ UPDATE blocks SET - blockGlobalPreHashe = ?, - blockLinePreHashe = ?, - msgType = ?, - msgSubType = ?, - blockByte = ?, - to_login = ?, - toBchName = ?, - toBlockGlobalNumber = ?, - toBlockHashe = ? + block_global_pre_hashe = ?, + block_line_pre_hashe = ?, + msg_type = ?, + msg_sub_type = ?, + block_byte = ?, + to_login = ?, + to_bch_name = ?, + to_block_global_number = ?, + to_block_hashe = ? WHERE login = ? - AND bchName = ? - AND blockGlobalNumber = ? - AND blockLineIndex = ? - AND blockLineNumber = ? + AND bch_name = ? + AND block_global_number = ? + AND block_line_index = ? + AND block_line_number = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -207,7 +196,6 @@ public final class BlocksDAO { } } - /** Обновить без внешнего соединения. Сам открывает/закрывает. */ public int update(BlockEntry e) throws SQLException { try (Connection c = db.getConnection()) { return update(c, e); @@ -216,10 +204,6 @@ public final class BlocksDAO { // -------------------- DELETE -------------------- - /** - * Удалить по "PK-подобному" набору полей с внешним соединением. Соединение НЕ закрывает. - * Может удалить >1 строк, если есть дубликаты. - */ public int deleteByPk(Connection c, String login, String bchName, @@ -231,10 +215,10 @@ public final class BlocksDAO { DELETE FROM blocks WHERE login = ? - AND bchName = ? - AND blockGlobalNumber = ? - AND blockLineIndex = ? - AND blockLineNumber = ? + AND bch_name = ? + AND block_global_number = ? + AND block_line_index = ? + AND block_line_number = ? """; try (PreparedStatement ps = c.prepareStatement(sql)) { @@ -247,7 +231,6 @@ public final class BlocksDAO { } } - /** Удалить по "PK-подобному" набору полей без внешнего соединения. Сам открывает/закрывает. */ public int deleteByPk(String login, String bchName, int blockGlobalNumber, @@ -260,7 +243,6 @@ public final class BlocksDAO { // -------------------- INTERNAL -------------------- - /** Единая привязка параметров под INSERT — чтобы не разъезжалось. */ private static void bindAll(PreparedStatement ps, BlockEntry e) throws SQLException { int i = 1; @@ -297,29 +279,29 @@ public final class BlocksDAO { BlockEntry e = new BlockEntry(); e.setLogin(rs.getString("login")); - e.setBchName(rs.getString("bchName")); - e.setBlockGlobalNumber(rs.getInt("blockGlobalNumber")); - e.setBlockGlobalPreHashe(rs.getString("blockGlobalPreHashe")); + e.setBchName(rs.getString("bch_name")); + e.setBlockGlobalNumber(rs.getInt("block_global_number")); + e.setBlockGlobalPreHashe(rs.getString("block_global_pre_hashe")); - e.setBlockLineIndex(rs.getInt("blockLineIndex")); - e.setBlockLineNumber(rs.getInt("blockLineNumber")); - e.setBlockLinePreHashe(rs.getString("blockLinePreHashe")); + e.setBlockLineIndex(rs.getInt("block_line_index")); + e.setBlockLineNumber(rs.getInt("block_line_number")); + e.setBlockLinePreHashe(rs.getString("block_line_pre_hashe")); - e.setMsgType(rs.getInt("msgType")); - e.setMsgSubType(rs.getInt("msgSubType")); + e.setMsgType(rs.getInt("msg_type")); + e.setMsgSubType(rs.getInt("msg_sub_type")); - e.setBlockByte(rs.getBytes("blockByte")); + e.setBlockByte(rs.getBytes("block_byte")); e.setToLogin(rs.getString("to_login")); - String toBchName = rs.getString("toBchName"); + String toBchName = rs.getString("to_bch_name"); if (rs.wasNull()) toBchName = null; e.setToBchName(toBchName); - Integer toBlockGlobalNumber = (Integer) rs.getObject("toBlockGlobalNumber"); + Integer toBlockGlobalNumber = (Integer) rs.getObject("to_block_global_number"); e.setToBlockGlobalNumber(toBlockGlobalNumber); - String toBlockHashe = rs.getString("toBlockHashe"); + String toBlockHashe = rs.getString("to_block_hashe"); if (rs.wasNull()) toBlockHashe = null; e.setToBlockHashe(toBlockHashe); diff --git a/shine-server-db/src/main/java/shine/db/dao/IpGeoCacheDAO.java b/shine-server-db/src/main/java/shine/db/dao/IpGeoCacheDAO.java index a4f7532..db9d11d 100644 --- a/shine-server-db/src/main/java/shine/db/dao/IpGeoCacheDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/IpGeoCacheDAO.java @@ -8,10 +8,10 @@ import java.sql.*; /** * DAO для таблицы ip_geo_cache. * - * * Таблица: - * * - ip TEXT PRIMARY KEY - * * - geo TEXT - * * - updated_at_ms INTEGER NOT NULL + * Таблица: + * - ip TEXT PRIMARY KEY + * - geo TEXT + * - updated_at_ms INTEGER NOT NULL * * Правило: * - методы с Connection НЕ закрывают соединение diff --git a/shine-server-db/src/main/java/shine/db/dao/SolanaUsersDAO.java b/shine-server-db/src/main/java/shine/db/dao/SolanaUsersDAO.java index aa3ec10..e1cd45a 100644 --- a/shine-server-db/src/main/java/shine/db/dao/SolanaUsersDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/SolanaUsersDAO.java @@ -14,18 +14,8 @@ import java.util.List; * * Колонки: * - login TEXT PRIMARY KEY - * Уникальный логин пользователя (case-insensitive используется на уровне запросов). - * - * - deviceKey TEXT NOT NULL - * Публичный ключ устройства пользователя. - * Хранится в Base64(32 bytes) или HEX(64 chars). - * - * - solanaKey TEXT NULLABLE - * Публичный ключ Solana-аккаунта пользователя (если есть). - * - * Назначение таблицы: - * - локальное сопоставление login → deviceKey / solanaKey - * - используется для аутентификации, валидации подписей и связки с блокчейном + * - device_key TEXT NOT NULL + * - solana_key TEXT NULLABLE * * Правило работы с соединениями: * - методы с Connection НЕ закрывают соединение @@ -52,7 +42,7 @@ public final class SolanaUsersDAO { /** Вставка с внешним соединением. Соединение НЕ закрывает. */ public void insert(Connection c, SolanaUserEntry user) throws SQLException { String sql = """ - INSERT INTO solana_users (login, deviceKey, solanaKey) + INSERT INTO solana_users (login, device_key, solana_key) VALUES (?, ?, ?) """; @@ -102,7 +92,7 @@ public final class SolanaUsersDAO { /** Получить по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */ public SolanaUserEntry getByLogin(Connection c, String login) throws SQLException { String sql = """ - SELECT login, deviceKey, solanaKey + SELECT login, device_key, solana_key FROM solana_users WHERE LOWER(login) = LOWER(?) """; @@ -126,7 +116,7 @@ public final class SolanaUsersDAO { /** Поиск по префиксу с внешним соединением. Соединение НЕ закрывает. */ public List searchByLoginPrefix(Connection c, String prefix) throws SQLException { String sql = """ - SELECT login, deviceKey, solanaKey + SELECT login, device_key, solana_key FROM solana_users WHERE LOWER(login) LIKE ? ORDER BY login @@ -157,10 +147,10 @@ public final class SolanaUsersDAO { private SolanaUserEntry mapRow(ResultSet rs) throws SQLException { SolanaUserEntry e = new SolanaUserEntry( rs.getString("login"), - rs.getString("deviceKey") + rs.getString("device_key") ); - String solanaKey = rs.getString("solanaKey"); + String solanaKey = rs.getString("solana_key"); if (rs.wasNull()) solanaKey = null; e.setSolanaKey(solanaKey); diff --git a/shine-server-db/src/main/java/shine/db/dao/UserCreateDAO.java b/shine-server-db/src/main/java/shine/db/dao/UserCreateDAO.java index 41083e8..68c6839 100644 --- a/shine-server-db/src/main/java/shine/db/dao/UserCreateDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/UserCreateDAO.java @@ -8,8 +8,8 @@ import java.sql.*; /** * UserCreateDAO — атомарное добавление пользователя: - * - solana_users (login, deviceKey) - * - blockchain_state (blockchainName, login, blockchainKey, size_limit, ... last_global_number=-1 ...) + * - solana_users (login, device_key) + * - blockchain_state (blockchain_name, login, blockchain_key, size_limit, ... last_global_number=-1 ...) * * ВАЖНО: * - только INSERT @@ -86,8 +86,6 @@ public final class UserCreateDAO { } catch (SQLException e) { c.rollback(); - // SQLITE_CONSTRAINT -> "уже существует" - // Мы не делаем UPDATE, только insert. String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase(); if (msg.contains("constraint")) { return false; diff --git a/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java b/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java index 87c6689..3611e8c 100644 --- a/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java @@ -17,11 +17,6 @@ import java.util.List; * ЛОГИКА time_ms: * - БД принимает запись только если она "новее" (time_ms строго больше текущего). * - Реализовано атомарно одним SQL: UPSERT + WHERE users_params.time_ms < excluded.time_ms - * - * Возврат результата: - * - upsertIfNewer(...) возвращает количество изменённых строк: - * 1 = вставили/обновили - * 0 = проигнорировали (запись уже новее или равная) */ public final class UserParamsDAO { @@ -41,11 +36,6 @@ public final class UserParamsDAO { // -------------------- UPSERT (IF NEWER) -------------------- - /** - * Атомарный UPSERT "только если новее". - * - * @return 1 если вставили/обновили; 0 если запись не тронули (existing.time_ms >= incoming.time_ms). - */ public int upsertIfNewer(Connection c, UserParamEntry e) throws SQLException { String sql = """ INSERT INTO users_params ( @@ -77,11 +67,10 @@ public final class UserParamsDAO { if (e.getSignature() != null) ps.setString(6, e.getSignature()); else ps.setNull(6, Types.VARCHAR); - return ps.executeUpdate(); // 1 или 0 + return ps.executeUpdate(); } } - /** То же самое, но сам открывает/закрывает соединение. */ public int upsertIfNewer(UserParamEntry e) throws SQLException { try (Connection c = db.getConnection()) { return upsertIfNewer(c, e); @@ -90,7 +79,6 @@ public final class UserParamsDAO { // -------------------- SELECT -------------------- - /** Получить параметр по (login,param) с внешним соединением. Соединение НЕ закрывает. */ public UserParamEntry getByLoginAndParam(Connection c, String login, String param) throws SQLException { String sql = """ SELECT @@ -116,14 +104,12 @@ public final class UserParamsDAO { } } - /** Получить параметр по (login,param) без внешнего соединения. Сам открывает/закрывает. */ public UserParamEntry getByLoginAndParam(String login, String param) throws SQLException { try (Connection c = db.getConnection()) { return getByLoginAndParam(c, login, param); } } - /** Получить все параметры пользователя с внешним соединением. */ public List getByLogin(Connection c, String login) throws SQLException { String sql = """ SELECT @@ -148,7 +134,6 @@ public final class UserParamsDAO { return list; } - /** Получить все параметры пользователя без внешнего соединения. */ public List getByLogin(String login) throws SQLException { try (Connection c = db.getConnection()) { return getByLogin(c, login); diff --git a/shine-server-db/src/main/java/shine/db/entities/ActiveSessionEntry.java b/shine-server-db/src/main/java/shine/db/entities/ActiveSessionEntry.java index 41f0f93..30714a7 100644 --- a/shine-server-db/src/main/java/shine/db/entities/ActiveSessionEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/ActiveSessionEntry.java @@ -2,27 +2,23 @@ package shine.db.entities; /** * Модель активной сессии (таблица active_sessions). - * - * Теперь вместо loginId: - * - login TEXT NOT NULL (FK -> solana_users(login)) */ public class ActiveSessionEntry { - private String sessionId; // TEXT base64(32 bytes) - private String login; // TEXT NOT NULL - private String sessionPwd; // TEXT - private String storagePwd; // TEXT - private long sessionCreatedAtMs; // INTEGER - private long lastAuthirificatedAtMs; // INTEGER - private String pushEndpoint; // TEXT (nullable) - private String pushP256dhKey; // TEXT (nullable) - private String pushAuthKey; // TEXT (nullable) + private String sessionId; + private String login; + private String sessionPwd; + private String storagePwd; + private long sessionCreatedAtMs; + private long lastAuthirificatedAtMs; + private String pushEndpoint; + private String pushP256dhKey; + private String pushAuthKey; - // Новые поля - private String clientIp; // IP клиента при auth/refresh - private String clientInfoFromClient; // строка от клиента (PWA) - private String clientInfoFromRequest; // строка, собранная на сервере - private String userLanguage; // prefer-language (например, "ru-RU") + private String clientIp; + private String clientInfoFromClient; + private String clientInfoFromRequest; + private String userLanguage; public ActiveSessionEntry() { } diff --git a/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java b/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java index e727a5f..60b8b20 100644 --- a/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/BlockEntry.java @@ -2,41 +2,28 @@ package shine.db.entities; /** * Запись блока (таблица blocks). - * - * Теперь: - * - login TEXT NOT NULL - * - bchName TEXT NOT NULL (идёт сразу после login) - * - to_login TEXT nullable - * - toBchName TEXT nullable - * - toBlockGlobalNumber INTEGER nullable - * - toBlockHashe TEXT nullable - * - * ДОБАВЛЕНО: - * - msgSubType INTEGER (uint16 по смыслу, храним как int) - * - * PRIMARY KEY пока убран вообще. */ public class BlockEntry { - private String login; // TEXT - private String bchName; // TEXT + private String login; + private String bchName; - private int blockGlobalNumber; // int32 - private String blockGlobalPreHashe; // TEXT + private int blockGlobalNumber; + private String blockGlobalPreHashe; - private int blockLineIndex; // int16 (храним как int) - private int blockLineNumber; // int32 - private String blockLinePreHashe; // TEXT + private int blockLineIndex; + private int blockLineNumber; + private String blockLinePreHashe; - private int msgType; // int16 (храним как int) - private int msgSubType; // int16 (храним как int) + private int msgType; + private int msgSubType; - private byte[] blockByte; // BLOB + private byte[] blockByte; - private String toLogin; // TEXT nullable - private String toBchName; // TEXT nullable - private Integer toBlockGlobalNumber; // INTEGER nullable - private String toBlockHashe; // TEXT nullable + private String toLogin; + private String toBchName; + private Integer toBlockGlobalNumber; + private String toBlockHashe; public BlockEntry() {} diff --git a/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java b/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java index b4a5143..bd777bd 100644 --- a/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java @@ -5,29 +5,22 @@ import java.util.Base64; /** * Агрегатная сущность текущего состояния блокчейна. - * 1 строка = 1 blockchainName, плюс состояние линий 0..7. + * 1 строка = 1 blockchain_name, плюс состояние линий 0..7. */ public final class BlockchainStateEntry { private String blockchainName; - private String login; - /** Ключ блокчейна (pubkey), которым подписываются блоки. Base64(32 bytes). */ private String blockchainKey; - /** Лимит (теперь long). */ private long sizeLimit; - - /** Размер файла блокчейна в байтах (то, что будем сверять/чинить при старте). */ private long fileSizeBytes; private int lastGlobalNumber; - private String lastGlobalHash; // HEX(64) либо пустая строка для "нулевого" + private String lastGlobalHash; - /** line 0..7 */ private final int[] lastLineNumbers = new int[8]; - /** line 0..7 */ private final String[] lastLineHashes = new String[8]; private long updatedAtMs; @@ -78,7 +71,6 @@ public final class BlockchainStateEntry { public String getBlockchainKey() { return blockchainKey; } public void setBlockchainKey(String blockchainKey) { this.blockchainKey = blockchainKey; } - /** blockchainKey в байтах (32) или null, если битый. */ public byte[] getBlockchainKeyBytes() { if (blockchainKey == null) return null; String s = blockchainKey.trim(); @@ -103,7 +95,6 @@ public final class BlockchainStateEntry { public String getLastGlobalHash() { return lastGlobalHash; } public void setLastGlobalHash(String lastGlobalHash) { this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash; } - /** line in [0..7] */ public int getLastLineNumber(int line) { checkLine(line); return lastLineNumbers[line]; @@ -113,7 +104,6 @@ public final class BlockchainStateEntry { lastLineNumbers[line] = value; } - /** line in [0..7] */ public String getLastLineHash(int line) { checkLine(line); return lastLineHashes[line]; diff --git a/shine-server-db/src/main/java/shine/db/entities/IpGeoCacheEntry.java b/shine-server-db/src/main/java/shine/db/entities/IpGeoCacheEntry.java index ef4b654..f3a7ba3 100644 --- a/shine-server-db/src/main/java/shine/db/entities/IpGeoCacheEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/IpGeoCacheEntry.java @@ -2,11 +2,6 @@ package shine.db.entities; /** * Запись в таблице ip_geo_cache. - * - * Храним: - * - ip — строка IP-адреса (PRIMARY KEY) - * - geo — строка "Country, City" или любое текстовое описание - * - updatedAtMs — время последнего обновления (Unix time в мс) */ public class IpGeoCacheEntry { diff --git a/shine-server-db/src/main/java/shine/db/entities/SolanaUserEntry.java b/shine-server-db/src/main/java/shine/db/entities/SolanaUserEntry.java index 8ffb44c..854986e 100644 --- a/shine-server-db/src/main/java/shine/db/entities/SolanaUserEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/SolanaUserEntry.java @@ -8,35 +8,15 @@ import java.util.Base64; * Таблица: solana_users * * Поля: - * - login — PRIMARY KEY (TEXT) - * Уникальный логин пользователя. - * - * - deviceKey — TEXT NOT NULL - * Публичный ключ устройства. - * Поддерживаемые форматы: - * • Base64 (32 байта) - * • HEX (64 hex-символа) - * - * - solanaKey — TEXT NULLABLE - * Публичный ключ Solana-аккаунта пользователя (если используется). - * - * Назначение: - * - хранение минимальной локальной информации о пользователе - * - используется для: - * • проверки подписи запросов - * • привязки пользователя к блокчейну - * • сопоставления login ↔ ключи - * - * ВАЖНО: - * - deviceKey обязателен всегда - * - solanaKey может отсутствовать (null) + * - login — PRIMARY KEY (TEXT) + * - device_key — TEXT NOT NULL + * - solana_key — TEXT NULLABLE */ - public class SolanaUserEntry { - private String login; // TEXT PK - private String deviceKey; // TEXT NOT NULL (Base64(32 bytes)) - private String solanaKey; // TEXT + private String login; + private String deviceKey; + private String solanaKey; public SolanaUserEntry() {} @@ -54,32 +34,22 @@ public class SolanaUserEntry { public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } - /** Публичный ключ устройства (device key). */ public String getDeviceKey() { return deviceKey; } public void setDeviceKey(String deviceKey) { this.deviceKey = deviceKey; } public String getSolanaKey() { return solanaKey; } public void setSolanaKey(String solanaKey) { this.solanaKey = solanaKey; } - /** - * Device key в байтах (32 байта) или null, если ключ битый/пустой. - * - * Поддержка форматов: - * - Base64 (предпочтительно) - * - HEX (ровно 64 hex-символа, без пробелов) - */ public byte[] getDeviceKeyByte() { if (deviceKey == null) return null; String s = deviceKey.trim(); if (s.isEmpty()) return null; - // 1) пробуем Base64 try { byte[] b = Base64.getDecoder().decode(s); if (b != null && b.length == 32) return b; } catch (IllegalArgumentException ignore) {} - // 2) пробуем HEX (64 символа) if (s.length() == 64 && s.matches("^[0-9a-fA-F]+$")) { byte[] out = new byte[32]; for (int i = 0; i < 32; i++) { diff --git a/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java b/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java index 72a3c43..656614a 100644 --- a/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java @@ -10,12 +10,6 @@ package shine.db.entities; * - value TEXT NOT NULL * - device_key TEXT NULL * - signature TEXT NULL - * - * UNIQUE(login, param) - * - * Смысл: - * - в таблице всегда хранится "последнее" значение параметра по времени. - * - time_ms монотонно растёт для каждого (login,param) — сервер не принимает более старые обновления. */ public class UserParamEntry { @@ -24,8 +18,8 @@ public class UserParamEntry { private long timeMs; private String value; - private String deviceKey; // base64(32) — можно хранить как "каким ключом подписано" - private String signature; // base64(64) + private String deviceKey; + private String signature; public UserParamEntry() {}