SHA256
Initial commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package server.logic.ws_protocol.JSON.entyties;
|
||||
|
||||
/**
|
||||
* Базовый класс для всех событий (event).
|
||||
* Общие поля: op и payload.
|
||||
*.
|
||||
* Формат JSON (event):
|
||||
* {
|
||||
* "op": "...",
|
||||
* "payload": { ... }
|
||||
* }
|
||||
*/
|
||||
public abstract class Net_Event {
|
||||
|
||||
/** Имя операции / события (op). */
|
||||
private String op;
|
||||
|
||||
/**
|
||||
* Произвольные данные.
|
||||
* В JSON это поле "payload".
|
||||
*/
|
||||
private Object payload;
|
||||
|
||||
// --- getters / setters ---
|
||||
|
||||
public String getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOp(String op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public Object getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(Object payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
package server.logic.ws_protocol.JSON.entyties;
|
||||
|
||||
/**
|
||||
* Ответ с ошибкой (любой отказ).
|
||||
*.
|
||||
* В payload будет:
|
||||
* {
|
||||
* "code": "...",
|
||||
* "message": "..."
|
||||
* }
|
||||
*/
|
||||
public class Net_Exception_Response extends Net_Response {
|
||||
|
||||
private String code;
|
||||
private String message;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
package server.logic.ws_protocol.JSON.entyties;
|
||||
|
||||
/**
|
||||
* Базовый класс для всех запросов (client → server).
|
||||
*.
|
||||
* Наследуется от NetEvent и добавляет requestId.
|
||||
*.
|
||||
* Формат JSON (request):
|
||||
* {
|
||||
* "op": "...",
|
||||
* "requestId": "...",
|
||||
* "payload": { ... }
|
||||
* }
|
||||
*/
|
||||
public abstract class Net_Request extends Net_Event {
|
||||
|
||||
/** Идентификатор запроса, чтобы связать запрос и ответ. */
|
||||
private String requestId;
|
||||
|
||||
// --- getters / setters ---
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public void setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
package server.logic.ws_protocol.JSON.entyties;
|
||||
|
||||
/**
|
||||
* Базовый класс для всех ответов (server → client).
|
||||
*.
|
||||
* Наследуется от NetRequest и добавляет status.
|
||||
*.
|
||||
* Формат JSON (response):
|
||||
* {
|
||||
* "op": "...",
|
||||
* "requestId": "...",
|
||||
* "status": 200,
|
||||
* "payload": { ... } // и для успеха, и для ошибки
|
||||
* }
|
||||
*/
|
||||
public abstract class Net_Response extends Net_Request {
|
||||
|
||||
/** Статус результата (200 — успех, любое другое значение — ошибка). */
|
||||
private int status;
|
||||
|
||||
// --- getters / setters ---
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return status == 200;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
+3475
File diff suppressed because it is too large
Load Diff
+1439
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.connections.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос GetFriendsLists — получить два списка "друзей" по connections_state.
|
||||
*
|
||||
* {
|
||||
* "op": "GetFriendsLists",
|
||||
* "requestId": "req-100",
|
||||
* "payload": {
|
||||
* "login": "anya"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Возвращает:
|
||||
* - out_friends: кому login поставил FRIEND
|
||||
* - in_friends: кто поставил FRIEND этому login
|
||||
*
|
||||
* ПРО ДОСТУП (на будущее):
|
||||
* Сейчас (MVP) без ограничений. Позже можно ограничить видимость связей.
|
||||
*/
|
||||
public class Net_GetFriendsLists_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.connections.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ответ GetFriendsLists.
|
||||
*
|
||||
* {
|
||||
* "op": "GetFriendsLists",
|
||||
* "requestId": "req-100",
|
||||
* "status": 200,
|
||||
* "payload": {
|
||||
* "login": "Anya", // канонический регистр из БД
|
||||
* "out_friends": ["Bob", "Kate"], // кому login поставил FRIEND
|
||||
* "in_friends": ["Alex", "Kate"] // кто поставил FRIEND login
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public class Net_GetFriendsLists_Response extends Net_Response {
|
||||
|
||||
private String login;
|
||||
|
||||
private List<String> out_friends = new ArrayList<>();
|
||||
private List<String> in_friends = new ArrayList<>();
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public List<String> getOut_friends() { return out_friends; }
|
||||
public void setOut_friends(List<String> out_friends) { this.out_friends = out_friends; }
|
||||
|
||||
public List<String> getIn_friends() { return in_friends; }
|
||||
public void setIn_friends(List<String> in_friends) { this.in_friends = in_friends; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.connections;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetFriendsLists_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetFriendsLists_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.MsgSubType;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.ConnectionsStateDAO;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* GetFriendsLists — получить 2 списка:
|
||||
* - out_friends: кому login поставил FRIEND
|
||||
* - in_friends: кто поставил FRIEND этому login
|
||||
*
|
||||
* ВАЖНО:
|
||||
* - login в запросе может быть любым регистром
|
||||
* - в ответе возвращаем канонический регистр (как в solana_users.login)
|
||||
*
|
||||
* ПРИМЕЧАНИЕ:
|
||||
* Таблица пользователей тут названа "solana_users". Если у тебя иначе — поменяй SQL.
|
||||
*/
|
||||
public class Net_GetFriendsLists_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_GetFriendsLists_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_GetFriendsLists_Request req = (Net_GetFriendsLists_Request) baseRequest;
|
||||
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные поля: login"
|
||||
);
|
||||
}
|
||||
|
||||
final String loginAnyCase = req.getLogin().trim();
|
||||
|
||||
try {
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
ConnectionsStateDAO dao = ConnectionsStateDAO.getInstance();
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
|
||||
// 1) Канонизируем login через solana_users (NOCASE)
|
||||
String canonicalLogin = findCanonicalLogin(c, loginAnyCase);
|
||||
if (canonicalLogin == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
404,
|
||||
"USER_NOT_FOUND",
|
||||
"Пользователь не найден"
|
||||
);
|
||||
}
|
||||
|
||||
int relType = (int) MsgSubType.CONNECTION_FRIEND;
|
||||
|
||||
// 2) Два списка (логины канонические)
|
||||
List<String> outFriends = dao.listOutgoingByRelTypeCanonical(c, canonicalLogin, relType);
|
||||
List<String> inFriends = dao.listIncomingByRelTypeCanonical(c, canonicalLogin, relType);
|
||||
|
||||
Net_GetFriendsLists_Response resp = new Net_GetFriendsLists_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
resp.setLogin(canonicalLogin);
|
||||
resp.setOut_friends(outFriends);
|
||||
resp.setIn_friends(inFriends);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Internal error GetFriendsLists", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private String findCanonicalLogin(Connection c, String loginAnyCase) throws Exception {
|
||||
String sql = """
|
||||
SELECT login
|
||||
FROM solana_users
|
||||
WHERE login = ? COLLATE NOCASE
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, loginAnyCase);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
return rs.getString("login");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос AddUser — временная/тестовая регистрация локального пользователя.
|
||||
*
|
||||
* Клиент отправляет:
|
||||
*
|
||||
* {
|
||||
* "op": "AddUser",
|
||||
* "requestId": "test-add-1",
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "blockchainName": "anya-001",
|
||||
* "solanaKey": "base64-ed25519-public-key-login",
|
||||
* "blockchainKey": "base64-ed25519-public-key-blockchain",
|
||||
* "deviceKey": "base64-ed25519-public-key-device",
|
||||
* "bchLimit": 1000000
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Все поля лежат внутри payload.
|
||||
*/
|
||||
public class Net_AddUser_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
private String blockchainName;
|
||||
|
||||
/** Ключ пользователя Solana (публичный ключ логина) */
|
||||
private String solanaKey;
|
||||
|
||||
/** Ключ блокчейна (публичный ключ блокчейна) */
|
||||
private String blockchainKey;
|
||||
|
||||
/** Ключ устройства (публичный ключ устройства) */
|
||||
private String deviceKey;
|
||||
|
||||
private Integer bchLimit;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
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; }
|
||||
|
||||
public Integer getBchLimit() { return bchLimit; }
|
||||
public void setBchLimit(Integer bchLimit) { this.bchLimit = bchLimit; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
/**
|
||||
* Успешный ответ на AddUser.
|
||||
*
|
||||
* Сейчас дополнительных полей нет — достаточно status=200.
|
||||
*
|
||||
* Пример:
|
||||
* {
|
||||
* "op": "AddUser",
|
||||
* "requestId": "test-add-1",
|
||||
* "status": 200,
|
||||
* "payload": { }
|
||||
* }
|
||||
*/
|
||||
public class Net_AddUser_Response extends Net_Response {
|
||||
// При необходимости сюда можно добавить, например, флаг created/updated и т.п.
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.BlockchainStateDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.BlockchainStateEntry;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import utils.blockchain.BlockchainNameUtil;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Net_AddUser_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_AddUser_Handler.class);
|
||||
|
||||
/** TEST ONLY */
|
||||
private static final int TEST_BCH_LIMIT = 1_000_000;
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_AddUser_Request req = (Net_AddUser_Request) baseRequest;
|
||||
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()
|
||||
|| req.getBlockchainName() == null || req.getBlockchainName().isBlank()
|
||||
|| req.getSolanaKey() == null || req.getSolanaKey().isBlank()
|
||||
|| req.getBlockchainKey() == null || req.getBlockchainKey().isBlank()
|
||||
|| req.getDeviceKey() == null || req.getDeviceKey().isBlank()) {
|
||||
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные поля: login/blockchainName/solanaKey/blockchainKey/deviceKey"
|
||||
);
|
||||
}
|
||||
|
||||
// blockchainName должен быть вида: <login>-NNN
|
||||
if (!BlockchainNameUtil.isBlockchainNameMatchesLogin(req.getBlockchainName(), req.getLogin())) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_BLOCKCHAIN_NAME",
|
||||
"blockchainName должен быть вида <login>-NNN (пример: anya-001)"
|
||||
);
|
||||
}
|
||||
|
||||
int limit = (req.getBchLimit() == null || req.getBchLimit() <= 0)
|
||||
? TEST_BCH_LIMIT
|
||||
: req.getBchLimit();
|
||||
|
||||
try {
|
||||
byte[] blockchainKey32 = Base64.getDecoder().decode(req.getBlockchainKey());
|
||||
if (blockchainKey32.length != 32) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_BLOCKCHAIN_KEY",
|
||||
"blockchainKey должен быть Base64(32 bytes)"
|
||||
);
|
||||
}
|
||||
|
||||
SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
||||
BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
||||
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
c.setAutoCommit(false);
|
||||
|
||||
// 1. Проверяем, что пользователя нет
|
||||
if (usersDAO.getByLogin(req.getLogin()) != null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
409,
|
||||
"USER_ALREADY_EXISTS",
|
||||
"Пользователь с таким login уже существует"
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Проверяем, что blockchain_state ещё нет
|
||||
if (stateDAO.getByBlockchainName(req.getBlockchainName()) != null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
409,
|
||||
"BLOCKCHAIN_ALREADY_EXISTS",
|
||||
"blockchain_state уже существует"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Создаём пользователя (solanaKey + deviceKey)
|
||||
SolanaUserEntry user = new SolanaUserEntry(
|
||||
req.getLogin(),
|
||||
req.getSolanaKey(),
|
||||
req.getDeviceKey()
|
||||
);
|
||||
|
||||
usersDAO.insert(c, user);
|
||||
|
||||
// 4. Создаём INITIAL blockchain_state (blockchainKey)
|
||||
BlockchainStateEntry st = new BlockchainStateEntry();
|
||||
st.setBlockchainName(req.getBlockchainName());
|
||||
st.setLogin(req.getLogin());
|
||||
st.setBlockchainKey(req.getBlockchainKey()); // Base64(32)
|
||||
st.setLastBlockNumber(-1);
|
||||
st.setLastBlockHash(new byte[32]);
|
||||
st.setFileSizeBytes(0);
|
||||
st.setSizeLimit(limit);
|
||||
st.setUpdatedAtMs(System.currentTimeMillis());
|
||||
|
||||
stateDAO.upsert(c, st);
|
||||
|
||||
c.commit();
|
||||
}
|
||||
|
||||
Net_AddUser_Response resp = new Net_AddUser_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
log.info("✅ AddUser ok: login={}, blockchainName={}, limit={}",
|
||||
req.getLogin(), req.getBlockchainName(), limit);
|
||||
|
||||
return resp;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_KEY_FORMAT",
|
||||
e.getMessage()
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
log.error("❌ DB error AddUser", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"DB_ERROR",
|
||||
"Ошибка БД"
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Internal error AddUser", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+640
@@ -0,0 +1,640 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос GetUserParam — получить один параметр пользователя.
|
||||
*
|
||||
* {
|
||||
* "op": "GetUserParam",
|
||||
* "requestId": "req-1",
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "param": "feed:lastSeenGlobal"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ПРО ДОСТУП (на будущее):
|
||||
* ---------------------------------------------------------------------------------
|
||||
* Сейчас (MVP) этот запрос не ограничивает просмотр параметров, т.к. проект в тестовом режиме.
|
||||
* Позже, вероятно, потребуется ограничить: кто и какие параметры может читать (сессия/права).
|
||||
* Но для MVP эти проверки не нужны.
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
public class Net_GetUserParam_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
private String param;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public String getParam() { return param; }
|
||||
public void setParam(String param) { this.param = param; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
/**
|
||||
* Ответ GetUserParam.
|
||||
*
|
||||
* Если найден:
|
||||
* {
|
||||
* "op": "GetUserParam",
|
||||
* "requestId": "req-1",
|
||||
* "status": 200,
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "param": "feed:lastSeenGlobal",
|
||||
* "time_ms": 1736000000123,
|
||||
* "value": "105",
|
||||
* "device_key": "base64-32",
|
||||
* "signature": "base64-64"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Если не найден:
|
||||
* status=404, payload пустой.
|
||||
*/
|
||||
public class Net_GetUserParam_Response extends Net_Response {
|
||||
|
||||
private String login;
|
||||
private String param;
|
||||
private Long time_ms;
|
||||
private String value;
|
||||
private String device_key;
|
||||
private String signature;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public String getParam() { return param; }
|
||||
public void setParam(String param) { this.param = param; }
|
||||
|
||||
public Long getTime_ms() { return time_ms; }
|
||||
public void setTime_ms(Long time_ms) { this.time_ms = time_ms; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
public void setValue(String value) { this.value = value; }
|
||||
|
||||
public String getDevice_key() { return device_key; }
|
||||
public void setDevice_key(String device_key) { this.device_key = device_key; }
|
||||
|
||||
public String getSignature() { return signature; }
|
||||
public void setSignature(String signature) { this.signature = signature; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос ListUserParams — получить все сохранённые параметры пользователя.
|
||||
*
|
||||
* {
|
||||
* "op": "ListUserParams",
|
||||
* "requestId": "req-2",
|
||||
* "payload": {
|
||||
* "login": "anya"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ПРО ДОСТУП (на будущее):
|
||||
* ---------------------------------------------------------------------------------
|
||||
* Сейчас (MVP) запрос не ограничивает просмотр параметров.
|
||||
* В будущем, вероятно, потребуется проверка сессии/прав: кто может читать параметры.
|
||||
* Для MVP эти проверки не нужны.
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
public class Net_ListUserParams_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ответ ListUserParams — список всех параметров пользователя.
|
||||
*
|
||||
* {
|
||||
* "op": "ListUserParams",
|
||||
* "requestId": "req-2",
|
||||
* "status": 200,
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "params": [
|
||||
* {
|
||||
* "login": "anya",
|
||||
* "param": "feed:lastSeenGlobal",
|
||||
* "time_ms": 1736000000123,
|
||||
* "value": "105",
|
||||
* "device_key": "base64-32",
|
||||
* "signature": "base64-64"
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public class Net_ListUserParams_Response extends Net_Response {
|
||||
|
||||
private String login;
|
||||
private List<Item> params = new ArrayList<>();
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public List<Item> getParams() { return params; }
|
||||
public void setParams(List<Item> params) { this.params = params; }
|
||||
|
||||
public static class Item {
|
||||
private String login;
|
||||
private String param;
|
||||
private Long time_ms;
|
||||
private String value;
|
||||
private String device_key;
|
||||
private String signature;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public String getParam() { return param; }
|
||||
public void setParam(String param) { this.param = param; }
|
||||
|
||||
public Long getTime_ms() { return time_ms; }
|
||||
public void setTime_ms(Long time_ms) { this.time_ms = time_ms; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
public void setValue(String value) { this.value = value; }
|
||||
|
||||
public String getDevice_key() { return device_key; }
|
||||
public void setDevice_key(String device_key) { this.device_key = device_key; }
|
||||
|
||||
public String getSignature() { return signature; }
|
||||
public void setSignature(String signature) { this.signature = signature; }
|
||||
}
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос UpsertUserParam — добавить/обновить сохранённый параметр пользователя.
|
||||
*
|
||||
* Клиент отправляет:
|
||||
*
|
||||
* {
|
||||
* "op": "UpsertUserParam",
|
||||
* "requestId": "req-123",
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "param": "feed:lastSeenGlobal",
|
||||
* "time_ms": 1736000000123,
|
||||
* "value": "105",
|
||||
* "device_key": "base64-ed25519-public-key-32",
|
||||
* "signature": "base64-ed25519-signature-64"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Подпись считается от UTF-8 строки:
|
||||
* USER_PARAMETER_PREFIX + login + param + time_ms + value
|
||||
*/
|
||||
public class Net_UpsertUserParam_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
private String param;
|
||||
private Long time_ms;
|
||||
private String value;
|
||||
|
||||
private String device_key;
|
||||
private String signature;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public String getParam() { return param; }
|
||||
public void setParam(String param) { this.param = param; }
|
||||
|
||||
public Long getTime_ms() { return time_ms; }
|
||||
public void setTime_ms(Long time_ms) { this.time_ms = time_ms; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
public void setValue(String value) { this.value = value; }
|
||||
|
||||
public String getDevice_key() { return device_key; }
|
||||
public void setDevice_key(String device_key) { this.device_key = device_key; }
|
||||
|
||||
public String getSignature() { return signature; }
|
||||
public void setSignature(String signature) { this.signature = signature; }
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
/**
|
||||
* Ответ на UpsertUserParam.
|
||||
*
|
||||
* Успех:
|
||||
* {
|
||||
* "op": "UpsertUserParam",
|
||||
* "requestId": "req-123",
|
||||
* "status": 200,
|
||||
* "payload": { }
|
||||
* }
|
||||
*/
|
||||
public class Net_UpsertUserParam_Response extends Net_Response {
|
||||
// MVP: без payload. При желании позже можно добавить created/updated.
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_GetUserParam_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_GetUserParam_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.UserParamsDAO;
|
||||
import shine.db.entities.UserParamEntry;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* GetUserParam — получить один параметр пользователя.
|
||||
*
|
||||
* ПРО ДОСТУП (на будущее):
|
||||
* ---------------------------------------------------------------------------------
|
||||
* Сейчас (MVP) запрос не ограничивает просмотр параметров.
|
||||
* В будущем, вероятно, потребуется проверка сессии/прав: кто может читать параметры.
|
||||
* Для MVP эти проверки не нужны.
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
public class Net_GetUserParam_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_GetUserParam_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_GetUserParam_Request req = (Net_GetUserParam_Request) baseRequest;
|
||||
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()
|
||||
|| req.getParam() == null || req.getParam().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные поля: login/param"
|
||||
);
|
||||
}
|
||||
|
||||
String login = req.getLogin().trim();
|
||||
String param = req.getParam().trim();
|
||||
|
||||
try {
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
UserParamsDAO dao = UserParamsDAO.getInstance();
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
UserParamEntry e = dao.getByLoginAndParam(c, login, param);
|
||||
|
||||
if (e == null) {
|
||||
Net_GetUserParam_Response resp = new Net_GetUserParam_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(404);
|
||||
return resp;
|
||||
}
|
||||
|
||||
Net_GetUserParam_Response resp = new Net_GetUserParam_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
resp.setLogin(e.getLogin());
|
||||
resp.setParam(e.getParam());
|
||||
resp.setTime_ms(e.getTimeMs());
|
||||
resp.setValue(e.getValue());
|
||||
resp.setDevice_key(e.getDeviceKey());
|
||||
resp.setSignature(e.getSignature());
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Internal error GetUserParam", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_ListUserParams_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_ListUserParams_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.UserParamsDAO;
|
||||
import shine.db.entities.UserParamEntry;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ListUserParams — получить все параметры пользователя.
|
||||
*
|
||||
* ПРО ДОСТУП (на будущее):
|
||||
* ---------------------------------------------------------------------------------
|
||||
* Сейчас (MVP) запрос не ограничивает просмотр параметров.
|
||||
* В будущем, вероятно, потребуется проверка сессии/прав: кто может читать параметры.
|
||||
* Для MVP эти проверки не нужны.
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
public class Net_ListUserParams_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_ListUserParams_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_ListUserParams_Request req = (Net_ListUserParams_Request) baseRequest;
|
||||
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные поля: login"
|
||||
);
|
||||
}
|
||||
|
||||
String login = req.getLogin().trim();
|
||||
|
||||
try {
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
UserParamsDAO dao = UserParamsDAO.getInstance();
|
||||
|
||||
List<UserParamEntry> entries;
|
||||
try (Connection c = db.getConnection()) {
|
||||
entries = dao.getByLogin(c, login);
|
||||
}
|
||||
|
||||
Net_ListUserParams_Response resp = new Net_ListUserParams_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
resp.setLogin(login);
|
||||
|
||||
List<Net_ListUserParams_Response.Item> items = new ArrayList<>();
|
||||
for (UserParamEntry e : entries) {
|
||||
Net_ListUserParams_Response.Item it = new Net_ListUserParams_Response.Item();
|
||||
it.setLogin(e.getLogin());
|
||||
it.setParam(e.getParam());
|
||||
it.setTime_ms(e.getTimeMs());
|
||||
it.setValue(e.getValue());
|
||||
it.setDevice_key(e.getDeviceKey());
|
||||
it.setSignature(e.getSignature());
|
||||
items.add(it);
|
||||
}
|
||||
resp.setParams(items);
|
||||
|
||||
return resp;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Internal error ListUserParams", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
package server.logic.ws_protocol.JSON.handlers.userParams;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_UpsertUserParam_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.userParams.entyties.Net_UpsertUserParam_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.dao.UserParamsDAO;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import shine.db.entities.UserParamEntry;
|
||||
import utils.config.ShineSignatureConstants;
|
||||
import utils.crypto.Ed25519Util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Net_UpsertUserParam_Handler
|
||||
*
|
||||
* Делает (MVP, без "сессий"):
|
||||
* 1) Проверка входных полей.
|
||||
* 2) Проверка подписи Ed25519 по device_key.
|
||||
* 3) Проверка, что пользователь существует и что device_key принадлежит этому login.
|
||||
* 4) Атомарная запись в БД "только если time_ms новее" (UPSERT + WHERE).
|
||||
*
|
||||
* ВАЖНО:
|
||||
* - НИКАКИХ ручных транзакций / BEGIN здесь нет.
|
||||
* - autoCommit=true, каждый statement завершённый сам по себе.
|
||||
* - Гонки не страшны: если за время проверок кто-то записал более новый time_ms,
|
||||
* наш финальный UPSERT просто вернёт 0 обновлённых строк.
|
||||
*/
|
||||
public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_UpsertUserParam_Handler.class);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_UpsertUserParam_Request req = (Net_UpsertUserParam_Request) baseRequest;
|
||||
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()
|
||||
|| req.getParam() == null || req.getParam().isBlank()
|
||||
|| req.getTime_ms() == null || req.getTime_ms() <= 0
|
||||
|| req.getValue() == null
|
||||
|| req.getDevice_key() == null || req.getDevice_key().isBlank()
|
||||
|| req.getSignature() == null || req.getSignature().isBlank()) {
|
||||
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные поля: login/param/time_ms/value/device_key/signature"
|
||||
);
|
||||
}
|
||||
|
||||
final String login = req.getLogin().trim();
|
||||
final String param = req.getParam().trim();
|
||||
final long timeMs = req.getTime_ms();
|
||||
final String value = req.getValue();
|
||||
final String deviceKeyB64 = req.getDevice_key().trim();
|
||||
final String signatureB64 = req.getSignature().trim();
|
||||
|
||||
try {
|
||||
// ---------------- Base64 decode ----------------
|
||||
byte[] pubKey32;
|
||||
byte[] sig64;
|
||||
try {
|
||||
pubKey32 = Base64.getDecoder().decode(deviceKeyB64);
|
||||
sig64 = Base64.getDecoder().decode(signatureB64);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_BASE64",
|
||||
"device_key/signature должны быть Base64"
|
||||
);
|
||||
}
|
||||
|
||||
if (pubKey32.length != 32) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_DEVICE_KEY",
|
||||
"device_key должен быть Base64(32 bytes)"
|
||||
);
|
||||
}
|
||||
if (sig64.length != 64) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_SIGNATURE",
|
||||
"signature должна быть Base64(64 bytes)"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- Signature verify ----------------
|
||||
String signText = ShineSignatureConstants.USER_PARAMETER_PREFIX
|
||||
+ login
|
||||
+ param
|
||||
+ timeMs
|
||||
+ value;
|
||||
|
||||
byte[] signBytes = signText.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
boolean sigOk = Ed25519Util.verify(signBytes, sig64, pubKey32);
|
||||
if (!sigOk) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
403,
|
||||
"SIGNATURE_INVALID",
|
||||
"Подпись не прошла проверку"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- DB checks + upsert ----------------
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
||||
UserParamsDAO paramsDAO = UserParamsDAO.getInstance();
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
// 1) user exists
|
||||
SolanaUserEntry user = usersDAO.getByLogin(c, login);
|
||||
if (user == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
404,
|
||||
"USER_NOT_FOUND",
|
||||
"Пользователь не найден"
|
||||
);
|
||||
}
|
||||
|
||||
// 2) device key must match the user's stored deviceKey
|
||||
String userDeviceKey = user.getDeviceKey();
|
||||
if (userDeviceKey == null || userDeviceKey.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"USER_DEVICE_KEY_EMPTY",
|
||||
"У пользователя не задан deviceKey в БД"
|
||||
);
|
||||
}
|
||||
|
||||
if (!userDeviceKey.trim().equals(deviceKeyB64)) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
403,
|
||||
"DEVICE_KEY_MISMATCH",
|
||||
"device_key не соответствует пользователю"
|
||||
);
|
||||
}
|
||||
|
||||
// 3) atomic upsert-if-newer
|
||||
UserParamEntry e = new UserParamEntry(
|
||||
login,
|
||||
param,
|
||||
timeMs,
|
||||
value,
|
||||
deviceKeyB64,
|
||||
signatureB64
|
||||
);
|
||||
|
||||
int changed = paramsDAO.upsertIfNewer(c, e);
|
||||
|
||||
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
if (changed == 1) {
|
||||
log.info("✅ UpsertUserParam applied: login={}, param={}, time_ms={}", login, param, timeMs);
|
||||
} else {
|
||||
// 0 строк — значит в БД уже есть time_ms >= incoming
|
||||
log.info("ℹ️ UpsertUserParam ignored (not newer): login={}, param={}, time_ms={}", login, param, timeMs);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.error("❌ DB error UpsertUserParam", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"DB_ERROR",
|
||||
"Ошибка БД"
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Internal error UpsertUserParam", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
"INTERNAL_ERROR",
|
||||
"Внутренняя ошибка сервера"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
Reference in New Issue
Block a user