Добавил запрос проверить есть ли в системе такой пользователь и получить его данные.

И тесты добавил.

Все тесты проходят
This commit is contained in:
AidarKC
2026-01-28 20:33:06 +03:00
parent 43b0efb4d3
commit ebf7c9f18e
13 changed files with 621 additions and 67 deletions
@@ -101,14 +101,26 @@ public final class DatabaseInitializer {
st.execute("PRAGMA foreign_keys = ON");
// 1. solana_users
// ВАЖНО:
// - Все требуемые поля теперь лежат в solana_users:
// login, blockchain_name, solana_key, blockchain_key, device_key
// - Поиск по login в DAO сделан case-insensitive.
// - Для защиты от дублей "Anya" и "anya" добавляем COLLATE NOCASE на PRIMARY KEY.
st.executeUpdate("""
CREATE TABLE IF NOT EXISTS solana_users (
login TEXT NOT NULL PRIMARY KEY,
device_key TEXT NOT NULL,
solana_key TEXT
login TEXT NOT NULL PRIMARY KEY COLLATE NOCASE,
blockchain_name TEXT NOT NULL,
solana_key TEXT NOT NULL,
blockchain_key TEXT NOT NULL,
device_key TEXT NOT NULL
);
""");
st.executeUpdate("""
CREATE UNIQUE INDEX IF NOT EXISTS uq_solana_users_blockchain_name
ON solana_users (blockchain_name);
""");
st.executeUpdate("""
CREATE INDEX IF NOT EXISTS idx_solana_users_login
ON solana_users (login);
@@ -13,9 +13,11 @@ import java.util.List;
* Таблица: solana_users
*
* Колонки:
* - login TEXT PRIMARY KEY
* - device_key TEXT NOT NULL
* - solana_key TEXT NULLABLE
* - login TEXT PRIMARY KEY (COLLATE NOCASE)
* - blockchain_name TEXT NOT NULL
* - solana_key TEXT NOT NULL
* - blockchain_key TEXT NOT NULL
* - device_key TEXT NOT NULL
*
* Правило работы с соединениями:
* - методы с Connection НЕ закрывают соединение
@@ -42,14 +44,17 @@ public final class SolanaUsersDAO {
/** Вставка с внешним соединением. Соединение НЕ закрывает. */
public void insert(Connection c, SolanaUserEntry user) throws SQLException {
String sql = """
INSERT INTO solana_users (login, device_key, solana_key)
VALUES (?, ?, ?)
INSERT INTO solana_users (
login, blockchain_name, solana_key, blockchain_key, device_key
) VALUES (?, ?, ?, ?, ?)
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, user.getLogin());
ps.setString(2, user.getDeviceKey());
ps.setString(2, user.getBlockchainName());
ps.setString(3, user.getSolanaKey());
ps.setString(4, user.getBlockchainKey());
ps.setString(5, user.getDeviceKey());
ps.executeUpdate();
}
}
@@ -87,12 +92,41 @@ public final class SolanaUsersDAO {
}
}
/** Проверка существования по blockchain_name (case-sensitive, как в БД) с внешним соединением. */
public boolean existsByBlockchainName(Connection c, String blockchainName) throws SQLException {
String sql = """
SELECT 1
FROM solana_users
WHERE blockchain_name = ?
LIMIT 1
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, blockchainName);
try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}
}
}
/** Проверка существования по blockchain_name без внешнего соединения. */
public boolean existsByBlockchainName(String blockchainName) throws SQLException {
try (Connection c = db.getConnection()) {
return existsByBlockchainName(c, blockchainName);
}
}
// -------------------- SELECT --------------------
/** Получить по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */
public SolanaUserEntry getByLogin(Connection c, String login) throws SQLException {
String sql = """
SELECT login, device_key, solana_key
SELECT
login,
blockchain_name,
solana_key,
blockchain_key,
device_key
FROM solana_users
WHERE LOWER(login) = LOWER(?)
""";
@@ -113,10 +147,44 @@ public final class SolanaUsersDAO {
}
}
/** Получить по blockchain_name (case-sensitive) с внешним соединением. Соединение НЕ закрывает. */
public SolanaUserEntry getByBlockchainName(Connection c, String blockchainName) throws SQLException {
String sql = """
SELECT
login,
blockchain_name,
solana_key,
blockchain_key,
device_key
FROM solana_users
WHERE blockchain_name = ?
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, blockchainName);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return null;
return mapRow(rs);
}
}
}
/** Получить по blockchain_name без внешнего соединения. */
public SolanaUserEntry getByBlockchainName(String blockchainName) throws SQLException {
try (Connection c = db.getConnection()) {
return getByBlockchainName(c, blockchainName);
}
}
/** Поиск по префиксу с внешним соединением. Соединение НЕ закрывает. */
public List<SolanaUserEntry> searchByLoginPrefix(Connection c, String prefix) throws SQLException {
String sql = """
SELECT login, device_key, solana_key
SELECT
login,
blockchain_name,
solana_key,
blockchain_key,
device_key
FROM solana_users
WHERE LOWER(login) LIKE ?
ORDER BY login
@@ -145,14 +213,13 @@ public final class SolanaUsersDAO {
// -------------------- MAPPER --------------------
private SolanaUserEntry mapRow(ResultSet rs) throws SQLException {
SolanaUserEntry e = new SolanaUserEntry(
rs.getString("login"),
rs.getString("device_key")
);
SolanaUserEntry e = new SolanaUserEntry();
String solanaKey = rs.getString("solana_key");
if (rs.wasNull()) solanaKey = null;
e.setSolanaKey(solanaKey);
e.setLogin(rs.getString("login"));
e.setBlockchainName(rs.getString("blockchain_name"));
e.setSolanaKey(rs.getString("solana_key"));
e.setBlockchainKey(rs.getString("blockchain_key"));
e.setDeviceKey(rs.getString("device_key"));
return e;
}
@@ -1,18 +1,17 @@
package shine.db.dao;
import shine.db.SqliteDbController;
import shine.db.entities.BlockchainStateEntry;
import shine.db.entities.SolanaUserEntry;
import java.sql.*;
/**
* UserCreateDAO — атомарное добавление пользователя:
* - solana_users (login, device_key)
* - solana_users (login, blockchain_name, solana_key, blockchain_key, device_key)
* - blockchain_state (blockchain_name, login, blockchain_key, size_limit, ... last_block_number=-1 ...)
*
* ВАЖНО:
* - только INSERT/UPSERT
* - только INSERT (без перезаписи существующих записей)
* - если login или blockchainName заняты — возвращаем false (пользователь уже есть/занято)
*/
public final class UserCreateDAO {
@@ -20,7 +19,6 @@ public final class UserCreateDAO {
private static volatile UserCreateDAO instance;
private final SqliteDbController db = SqliteDbController.getInstance();
private final SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
private final BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
private UserCreateDAO() {}
@@ -38,9 +36,10 @@ public final class UserCreateDAO {
*/
public boolean insertUserWithBlockchain(
String login,
String deviceKey,
String blockchainName,
String solanaKey,
String blockchainKey,
String deviceKey,
long sizeLimit,
long nowMs
) throws SQLException {
@@ -55,25 +54,25 @@ public final class UserCreateDAO {
}
try {
// 1) user
SolanaUserEntry u = new SolanaUserEntry(login, deviceKey, deviceKey);
usersDao.insert(c, u); // если login занят -> constraint
// 1) solana_users
SolanaUserEntry u = new SolanaUserEntry();
u.setLogin(login);
u.setBlockchainName(blockchainName);
u.setSolanaKey(solanaKey);
u.setBlockchainKey(blockchainKey);
u.setDeviceKey(deviceKey);
// 2) blockchain_state
BlockchainStateEntry st = new BlockchainStateEntry();
st.setBlockchainName(blockchainName);
st.setLogin(login);
st.setBlockchainKey(blockchainKey);
st.setSizeLimit(sizeLimit);
st.setFileSizeBytes(0L);
usersDao.insert(c, u); // если login занят (NOCASE) или blockchainName (unique) -> constraint
// старт: блоков ещё нет
st.setLastBlockNumber(-1);
st.setLastBlockHash(null);
st.setUpdatedAtMs(nowMs);
stateDao.upsert(c, st); // если blockchainName занят -> constraint (PK)
// 2) blockchain_state — строго INSERT, без UPSERT (иначе можно перезаписать существующую цепочку)
insertBlockchainStateStrict(
c,
blockchainName,
login,
blockchainKey,
sizeLimit,
nowMs
);
c.commit();
return true;
@@ -92,4 +91,43 @@ public final class UserCreateDAO {
}
}
}
private static void insertBlockchainStateStrict(
Connection c,
String blockchainName,
String login,
String blockchainKey,
long sizeLimit,
long nowMs
) throws SQLException {
String sql = """
INSERT INTO blockchain_state (
blockchain_name,
login,
blockchain_key,
size_limit,
file_size_bytes,
last_block_number,
last_block_hash,
updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
ps.setString(i++, blockchainName);
ps.setString(i++, login);
ps.setString(i++, blockchainKey);
ps.setLong(i++, sizeLimit);
ps.setLong(i++, 0L);
ps.setInt(i++, -1);
ps.setNull(i++, Types.BLOB); // старт: блоков ещё нет
ps.setLong(i++, nowMs);
ps.executeUpdate(); // если blockchainName занят -> constraint (PK)
}
}
}
@@ -8,38 +8,57 @@ import java.util.Base64;
* Таблица: solana_users
*
* Поля:
* - login — PRIMARY KEY (TEXT)
* - device_key — TEXT NOT NULL
* - solana_key — TEXT NULLABLE
* - login — PRIMARY KEY (TEXT) (case-insensitive на уровне COLLATE NOCASE)
* - blockchain_name — TEXT NOT NULL
* - solana_key — TEXT NOT NULL
* - blockchain_key — TEXT NOT NULL
* - device_key — TEXT NOT NULL
*/
public class SolanaUserEntry {
private String login;
private String deviceKey;
private String blockchainName;
/** Ключ пользователя Solana (публичный ключ логина) */
private String solanaKey;
/** Ключ блокчейна (публичный ключ блокчейна) */
private String blockchainKey;
/** Ключ устройства (публичный ключ устройства) */
private String deviceKey;
public SolanaUserEntry() {}
public SolanaUserEntry(String login, String deviceKey) {
public SolanaUserEntry(String login,
String blockchainName,
String solanaKey,
String blockchainKey,
String deviceKey) {
this.login = login;
this.deviceKey = deviceKey;
}
public SolanaUserEntry(String login, String deviceKey, String solanaKey) {
this.login = login;
this.deviceKey = deviceKey;
this.blockchainName = blockchainName;
this.solanaKey = solanaKey;
this.blockchainKey = blockchainKey;
this.deviceKey = deviceKey;
}
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getDeviceKey() { return deviceKey; }
public void setDeviceKey(String deviceKey) { this.deviceKey = deviceKey; }
public String getBlockchainName() { return blockchainName; }
public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; }
public String getSolanaKey() { return solanaKey; }
public void setSolanaKey(String solanaKey) { this.solanaKey = solanaKey; }
public String getBlockchainKey() { return blockchainKey; }
public void setBlockchainKey(String blockchainKey) { this.blockchainKey = blockchainKey; }
public String getDeviceKey() { return deviceKey; }
public void setDeviceKey(String deviceKey) { this.deviceKey = deviceKey; }
// оставляю этот метод как утилиту (иногда удобно), но он работает только для deviceKey:
public byte[] getDeviceKeyByte() {
if (deviceKey == null) return null;
String s = deviceKey.trim();