From bfffe44c4a00bdb6f25cb8ddc0785b3b41c0483ef2630e1f34f5e9f36ae59d7e Mon Sep 17 00:00:00 2001 From: AidarKC Date: Mon, 5 Jan 2026 16:11:54 +0300 Subject: [PATCH] =?UTF-8?q?05=2001=2025=20=D0=94=D0=B0=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BE=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F.=20(=D0=B1=D0=B4=20=D0=B8=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=20=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5),=20=D0=BD=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=8F=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/shine/db/DatabaseInitializer.java | 5 +- .../main/java/shine/db/dao/UserParamsDAO.java | 120 +++++---- .../shine/db/entities/UserParamEntry.java | 54 ++-- .../handlers/tempToTest/concat_to_file.sh | 20 ++ .../Net_UpsertUserParam_Handler.java | 253 ++++++++++++++++++ .../entyties/Net_UpsertUserParam_Request.java | 53 ++++ .../Net_UpsertUserParam_Response.java | 18 ++ 7 files changed, 444 insertions(+), 79 deletions(-) create mode 100755 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/concat_to_file.sh create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/Net_UpsertUserParam_Handler.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Request.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Response.java diff --git a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java index 2a6ee72..958aae7 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -125,10 +125,9 @@ public class DatabaseInitializer { CREATE TABLE IF NOT EXISTS users_params ( login TEXT NOT NULL, param TEXT NOT NULL, - bch_channel_id INTEGER NOT NULL DEFAULT 0, - value TEXT, time_ms INTEGER NOT NULL, - pubkey_num INTEGER NOT NULL, + value TEXT NOT NULL, + device_key TEXT, signature TEXT, FOREIGN KEY (login) REFERENCES solana_users(login), UNIQUE (login, param) diff --git a/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java b/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java index 504cc80..5465d91 100644 --- a/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/UserParamsDAO.java @@ -7,7 +7,17 @@ import java.sql.*; import java.util.ArrayList; import java.util.List; -/** Здесь храним сохранённые параметры пользователей (в основном до какого сообщения просмотрены ленты) */ +/** + * UserParamsDAO — хранение сохранённых параметров пользователя. + * + * Правило: + * - методы с Connection НЕ закрывают соединение + * - методы без Connection сами открывают и закрывают соединение + * + * ВАЖНО по логике времени: + * - сам DAO делает "технический upsert" + * - правила "не принимать более старый time_ms" должны проверяться в handler-е, в транзакции. + */ public final class UserParamsDAO { private static volatile UserParamsDAO instance; @@ -27,65 +37,68 @@ public final class UserParamsDAO { // -------------------- UPSERT -------------------- /** UPSERT с внешним соединением. Соединение НЕ закрывает. */ - public void upsert(Connection c, UserParamEntry param) throws SQLException { + public void upsert(Connection c, UserParamEntry e) throws SQLException { String sql = """ INSERT INTO users_params ( login, param, - bch_channel_id, - value, time_ms, - pubkey_num, + value, + device_key, signature - ) VALUES (?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(login, param) DO UPDATE SET - bch_channel_id = excluded.bch_channel_id, - value = excluded.value, - time_ms = excluded.time_ms, - pubkey_num = excluded.pubkey_num, - signature = excluded.signature + time_ms = excluded.time_ms, + value = excluded.value, + device_key = excluded.device_key, + signature = excluded.signature """; try (PreparedStatement ps = c.prepareStatement(sql)) { - ps.setString(1, param.getLogin()); - ps.setString(2, param.getParam()); - ps.setLong(3, param.getBchChannelId()); - ps.setString(4, param.getValue()); - ps.setLong(5, param.getTimeMs()); - ps.setInt(6, param.getPubkeyNum()); - ps.setString(7, param.getSignature()); + ps.setString(1, e.getLogin()); + ps.setString(2, e.getParam()); + ps.setLong(3, e.getTimeMs()); + ps.setString(4, e.getValue()); + + if (e.getDeviceKey() != null) ps.setString(5, e.getDeviceKey()); + else ps.setNull(5, Types.VARCHAR); + + if (e.getSignature() != null) ps.setString(6, e.getSignature()); + else ps.setNull(6, Types.VARCHAR); + ps.executeUpdate(); } } /** UPSERT без внешнего соединения. Сам открывает/закрывает. */ - public void upsert(UserParamEntry param) throws SQLException { + public void upsert(UserParamEntry e) throws SQLException { try (Connection c = db.getConnection()) { - upsert(c, param); + upsert(c, e); } } // -------------------- SELECT -------------------- - /** Получить параметр с внешним соединением. Соединение НЕ закрывает. */ - public UserParamEntry getByUserLoginAndParam(Connection c, String login, String paramName) throws SQLException { + /** Получить параметр по (login,param) с внешним соединением. Соединение НЕ закрывает. */ + public UserParamEntry getByLoginAndParam(Connection c, String login, String param) throws SQLException { String sql = """ SELECT login, param, - bch_channel_id, - value, time_ms, - pubkey_num, + value, + device_key, signature FROM users_params WHERE login = ? AND param = ? + LIMIT 1 """; try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, login); - ps.setString(2, paramName); + ps.setString(2, param); + try (ResultSet rs = ps.executeQuery()) { if (!rs.next()) return null; return mapRow(rs); @@ -93,59 +106,62 @@ public final class UserParamsDAO { } } - /** Получить параметр без внешнего соединения. Сам открывает/закрывает. */ - public UserParamEntry getByUserLoginAndParam(String login, String paramName) throws SQLException { + /** Получить параметр по (login,param) без внешнего соединения. Сам открывает/закрывает. */ + public UserParamEntry getByLoginAndParam(String login, String param) throws SQLException { try (Connection c = db.getConnection()) { - return getByUserLoginAndParam(c, login, paramName); + return getByLoginAndParam(c, login, param); } } - /** Получить все параметры пользователя с внешним соединением. Соединение НЕ закрывает. */ - public List getByUserLogin(Connection c, String login) throws SQLException { + /** Получить все параметры пользователя с внешним соединением. */ + public List getByLogin(Connection c, String login) throws SQLException { String sql = """ SELECT login, param, - bch_channel_id, - value, time_ms, - pubkey_num, + value, + device_key, signature FROM users_params WHERE login = ? ORDER BY time_ms DESC """; - List result = new ArrayList<>(); - + List list = new ArrayList<>(); try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, login); try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) result.add(mapRow(rs)); + while (rs.next()) list.add(mapRow(rs)); } } - - return result; + return list; } - /** Получить все параметры пользователя без внешнего соединения. Сам открывает/закрывает. */ - public List getByUserLogin(String login) throws SQLException { + /** Получить все параметры пользователя без внешнего соединения. */ + public List getByLogin(String login) throws SQLException { try (Connection c = db.getConnection()) { - return getByUserLogin(c, login); + return getByLogin(c, login); } } // -------------------- MAPPER -------------------- - private UserParamEntry mapRow(ResultSet rs) throws SQLException { - return new UserParamEntry( - rs.getString("login"), - rs.getString("param"), - rs.getLong("bch_channel_id"), - rs.getString("value"), - rs.getLong("time_ms"), - (short) rs.getInt("pubkey_num"), - rs.getString("signature") - ); + private static UserParamEntry mapRow(ResultSet rs) throws SQLException { + UserParamEntry e = new UserParamEntry(); + e.setLogin(rs.getString("login")); + e.setParam(rs.getString("param")); + e.setTimeMs(rs.getLong("time_ms")); + e.setValue(rs.getString("value")); + + String dk = rs.getString("device_key"); + if (rs.wasNull()) dk = null; + e.setDeviceKey(dk); + + String sig = rs.getString("signature"); + if (rs.wasNull()) sig = null; + e.setSignature(sig); + + return e; } } \ No newline at end of file diff --git a/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java b/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java index 010d27b..72a3c43 100644 --- a/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/UserParamEntry.java @@ -1,31 +1,40 @@ package shine.db.entities; +/** + * UserParamEntry — сохранённый параметр пользователя. + * + * Таблица: users_params + * - login TEXT NOT NULL + * - param TEXT NOT NULL + * - time_ms INTEGER NOT NULL + * - value TEXT NOT NULL + * - device_key TEXT NULL + * - signature TEXT NULL + * + * UNIQUE(login, param) + * + * Смысл: + * - в таблице всегда хранится "последнее" значение параметра по времени. + * - time_ms монотонно растёт для каждого (login,param) — сервер не принимает более старые обновления. + */ public class UserParamEntry { - private String login; // TEXT NOT NULL + private String login; private String param; - private long bchChannelId; // новый канал, 8 байт, может быть 0 + private long timeMs; private String value; - private long timeMs; // время в мс - private short pubkeyNum; - private String signature; - public UserParamEntry() { - } + private String deviceKey; // base64(32) — можно хранить как "каким ключом подписано" + private String signature; // base64(64) - public UserParamEntry(String login, - String param, - long bchChannelId, - String value, - long timeMs, - short pubkeyNum, - String signature) { + public UserParamEntry() {} + + public UserParamEntry(String login, String param, long timeMs, String value, String deviceKey, String signature) { this.login = login; this.param = param; - this.bchChannelId = bchChannelId; - this.value = value; this.timeMs = timeMs; - this.pubkeyNum = pubkeyNum; + this.value = value; + this.deviceKey = deviceKey; this.signature = signature; } @@ -35,17 +44,14 @@ public class UserParamEntry { public String getParam() { return param; } public void setParam(String param) { this.param = param; } - public long getBchChannelId() { return bchChannelId; } - public void setBchChannelId(long bchChannelId) { this.bchChannelId = bchChannelId; } + public long getTimeMs() { return timeMs; } + public void setTimeMs(long timeMs) { this.timeMs = timeMs; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } - public long getTimeMs() { return timeMs; } - public void setTimeMs(long timeMs) { this.timeMs = timeMs; } - - public short getPubkeyNum() { return pubkeyNum; } - public void setPubkeyNum(short pubkeyNum) { this.pubkeyNum = pubkeyNum; } + public String getDeviceKey() { return deviceKey; } + public void setDeviceKey(String deviceKey) { this.deviceKey = deviceKey; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/concat_to_file.sh b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/concat_to_file.sh new file mode 100755 index 0000000..f6db1f1 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/concat_to_file.sh @@ -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)" diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/Net_UpsertUserParam_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/Net_UpsertUserParam_Handler.java new file mode 100644 index 0000000..0233f7e --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/Net_UpsertUserParam_Handler.java @@ -0,0 +1,253 @@ +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.sql.Statement; +import java.util.Base64; + +/** + * Net_UpsertUserParam_Handler + * + * Делает: + * 1) Проверяет, что пользователь существует и что device_key действительно его. + * 2) Проверяет, что нет "более нового" значения этого param (time_ms монотонно растёт). + * 3) Проверяет подпись Ed25519 по device_key. + * 4) Пишет в БД (insert или update существующей записи), но только если time_ms новее. + * + * БОЛЬШОЙ КОММЕНТ ПРО АВТОРИЗАЦИЮ НА БУДУЩЕЕ: + * --------------------------------------------------------------------------------- + * Сейчас (MVP) этот эндпоинт намеренно не делает полноценную "сессию/авторизацию", + * потому что целостность обеспечивается криптографией: сервер проверяет подпись + * и то, что device_key принадлежит login. + * + * В будущем, если понадобится "ограничить кто может писать параметры", можно добавить: + * - проверку активной сессии (active_sessions) и соответствие login в сессии; + * - rate-limit на пользователя; + * - отдельные права на запись конкретных param. + * + * Но возможно это вообще не потребуется, если модель безопасности строится + * строго на подписи и владении device_key (как сейчас). + * --------------------------------------------------------------------------------- + */ +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; + + // ---- basic fields validation ---- + 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(); // value может быть пустой строкой — это ок + final String deviceKeyB64 = req.getDevice_key().trim(); + final String signatureB64 = req.getSignature().trim(); + + try { + // 1) parse keys + 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)" + ); + } + + // подписываемая строка + String signText = ShineSignatureConstants.USER_PARAMETER_PREFIX + + login + + param + + timeMs + + value; + + byte[] signBytes = signText.getBytes(StandardCharsets.UTF_8); + + // 3) verify signature (до БД можно, но нам всё равно нужна БД-проверка device_key->login) + boolean sigOk = Ed25519Util.verify(signBytes, sig64, pubKey32); + if (!sigOk) { + return NetExceptionResponseFactory.error( + req, + 403, + "SIGNATURE_INVALID", + "Подпись не прошла проверку" + ); + } + + // ---- DB checks + upsert in a transaction ---- + SqliteDbController db = SqliteDbController.getInstance(); + SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance(); + UserParamsDAO paramsDAO = UserParamsDAO.getInstance(); + + try (Connection c = db.getConnection()) { + boolean oldAuto = c.getAutoCommit(); + c.setAutoCommit(false); + + // BEGIN IMMEDIATE — чтобы избежать гонок (две записи одного param параллельно) + try (Statement st = c.createStatement()) { + st.execute("BEGIN IMMEDIATE"); + } + + try { + // 1) user exists + device_key is exactly his + SolanaUserEntry user = usersDAO.getByLogin(c, login); + if (user == null) { + c.rollback(); + return NetExceptionResponseFactory.error( + req, + 404, + "USER_NOT_FOUND", + "Пользователь не найден" + ); + } + + String userDeviceKey = user.getDeviceKey(); + if (userDeviceKey == null || userDeviceKey.isBlank()) { + c.rollback(); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.SERVER_DATA_ERROR, + "USER_DEVICE_KEY_EMPTY", + "У пользователя не задан deviceKey в БД" + ); + } + + // сравнение строкой: у тебя deviceKey хранится как Base64(32) (в идеале нормализовать) + if (!userDeviceKey.trim().equals(deviceKeyB64)) { + c.rollback(); + return NetExceptionResponseFactory.error( + req, + 403, + "DEVICE_KEY_MISMATCH", + "device_key не соответствует пользователю" + ); + } + + // 2) no newer time_ms already stored + UserParamEntry existing = paramsDAO.getByLoginAndParam(c, login, param); + if (existing != null) { + long existingTime = existing.getTimeMs(); + if (existingTime > timeMs) { + c.rollback(); + return NetExceptionResponseFactory.error( + req, + 409, + "PARAM_NEWER_EXISTS", + "Уже есть более новое значение этого параметра (time_ms больше)" + ); + } + if (existingTime == timeMs) { + // если пришёл тот же time_ms — можно либо принять как идемпотентно, + // либо сравнить value/signature. Для MVP примем как идемпотентно, + // но всё равно сделаем upsert (обновит value/signature тем же временем). + } + } + + // 4) upsert + UserParamEntry e = new UserParamEntry( + login, + param, + timeMs, + value, + deviceKeyB64, + signatureB64 + ); + + paramsDAO.upsert(c, e); + + c.commit(); + c.setAutoCommit(oldAuto); + + Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(WireCodes.Status.OK); + + log.info("✅ UpsertUserParam ok: login={}, param={}, time_ms={}", login, param, timeMs); + return resp; + + } catch (SQLException ex) { + c.rollback(); + throw ex; + } finally { + c.setAutoCommit(oldAuto); + } + } + + } 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", + "Внутренняя ошибка сервера" + ); + } + } +} \ No newline at end of file diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Request.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Request.java new file mode 100644 index 0000000..ed1c7ff --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Request.java @@ -0,0 +1,53 @@ +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; } +} \ No newline at end of file diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Response.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Response.java new file mode 100644 index 0000000..8fedce0 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/userParams/entyties/Net_UpsertUserParam_Response.java @@ -0,0 +1,18 @@ +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. +} \ No newline at end of file