05 01 25
поменял все названия таблиц и полей в таблицах на стиль только маленькие буквы и разделение через "_" . Все тесты проходят норм
This commit is contained in:
parent
93c007b2b9
commit
8fd7f4676b
@ -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) — быстрые выборки “по ссылке”.
|
||||
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 — проекция активности
|
||||
всё вычисляется детерминированно через триггеры
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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<ActiveSessionEntry> 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,
|
||||
|
||||
@ -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"));
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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 НЕ закрывают соединение
|
||||
|
||||
@ -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<SolanaUserEntry> 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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<UserParamEntry> getByLogin(Connection c, String login) throws SQLException {
|
||||
String sql = """
|
||||
SELECT
|
||||
@ -148,7 +134,6 @@ public final class UserParamsDAO {
|
||||
return list;
|
||||
}
|
||||
|
||||
/** Получить все параметры пользователя без внешнего соединения. */
|
||||
public List<UserParamEntry> getByLogin(String login) throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
return getByLogin(c, login);
|
||||
|
||||
@ -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() {
|
||||
}
|
||||
|
||||
@ -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() {}
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -2,11 +2,6 @@ package shine.db.entities;
|
||||
|
||||
/**
|
||||
* Запись в таблице ip_geo_cache.
|
||||
*
|
||||
* Храним:
|
||||
* - ip — строка IP-адреса (PRIMARY KEY)
|
||||
* - geo — строка "Country, City" или любое текстовое описание
|
||||
* - updatedAtMs — время последнего обновления (Unix time в мс)
|
||||
*/
|
||||
public class IpGeoCacheEntry {
|
||||
|
||||
|
||||
@ -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++) {
|
||||
|
||||
@ -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() {}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user