SHA256
Сервер: дочистить legacy-слои и доки
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ import java.sql.SQLException;
|
||||
* отдельным recovery/resync-слоем после успешного commit.
|
||||
*
|
||||
* Важный смысл текущей реализации:
|
||||
* - мы НЕ трогаем identity-слой (`solana_users`) и НЕ трогаем DM-таблицы;
|
||||
* - мы НЕ трогаем current users слой (`solana_user_pda_current`) и НЕ трогаем DM-таблицы;
|
||||
* - мы очищаем только блокчейн пользователя и derived-state, который строится из неё;
|
||||
* - висячие cross-chain ссылки в чужих blocks допускаются как нормальное поведение системы.
|
||||
*/
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class BlockchainStateDAO {
|
||||
* Строгая вставка state только если записи ещё нет.
|
||||
*
|
||||
* Нужна для recovery / resync:
|
||||
* - identity пользователя уже может существовать в solana_users;
|
||||
* - runtime-проекция пользователя уже может существовать в current users слое;
|
||||
* - в таком случае нам надо восстановить только blockchain_state;
|
||||
* - если запись уже есть, метод просто ничего не меняет.
|
||||
*/
|
||||
|
||||
+27
-27
@@ -1,7 +1,7 @@
|
||||
package shine.db.dao;
|
||||
|
||||
import shine.db.DbController;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
@@ -11,7 +11,7 @@ import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SignedMessagesV2DAO {
|
||||
public final class SignedMessagesDAO {
|
||||
|
||||
public enum ApplyStatus {
|
||||
APPLIED,
|
||||
@@ -24,21 +24,21 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile SignedMessagesV2DAO instance;
|
||||
private static volatile SignedMessagesDAO instance;
|
||||
private final DbController db = DbController.getInstance();
|
||||
|
||||
private SignedMessagesV2DAO() {}
|
||||
private SignedMessagesDAO() {}
|
||||
|
||||
public static SignedMessagesV2DAO getInstance() {
|
||||
public static SignedMessagesDAO getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (SignedMessagesV2DAO.class) {
|
||||
if (instance == null) instance = new SignedMessagesV2DAO();
|
||||
synchronized (SignedMessagesDAO.class) {
|
||||
if (instance == null) instance = new SignedMessagesDAO();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ApplyStatus insertIfAbsent(SignedMessageV2Entry e) throws Exception {
|
||||
public ApplyStatus insertIfAbsent(SignedMessageEntry e) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
if (isBlockedByConversationDelete(c, e.getFromLogin(), e.getToLogin(), e.getTimeMs())) {
|
||||
@@ -65,7 +65,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public boolean insertPairBothOrNothing(SignedMessageV2Entry first, SignedMessageV2Entry second) throws Exception {
|
||||
public boolean insertPairBothOrNothing(SignedMessageEntry first, SignedMessageEntry second) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
@@ -94,7 +94,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public ApplyStatus upsertContentPair(SignedMessageV2Entry incoming, SignedMessageV2Entry outgoing) throws Exception {
|
||||
public ApplyStatus upsertContentPair(SignedMessageEntry incoming, SignedMessageEntry outgoing) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
@@ -135,7 +135,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public ApplyStatus upsertIncomingCopy(SignedMessageV2Entry incoming) throws Exception {
|
||||
public ApplyStatus upsertIncomingCopy(SignedMessageEntry incoming) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
@@ -172,7 +172,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public ApplyStatus applyDeleteMessage(SignedMessageV2Entry tombstone) throws Exception {
|
||||
public ApplyStatus applyDeleteMessage(SignedMessageEntry tombstone) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
@@ -203,7 +203,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public ApplyStatus applyDeleteConversation(SignedMessageV2Entry tombstone) throws Exception {
|
||||
public ApplyStatus applyDeleteConversation(SignedMessageEntry tombstone) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
@@ -231,7 +231,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public SignedMessageV2Entry getByMessageKey(String messageKey) throws Exception {
|
||||
public SignedMessageEntry getByMessageKey(String messageKey) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
String sql = """
|
||||
SELECT
|
||||
@@ -252,7 +252,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public SignedMessageV2Entry getLatestConversationDelete(String fromLogin, String toLogin) throws Exception {
|
||||
public SignedMessageEntry getLatestConversationDelete(String fromLogin, String toLogin) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
return getLatestConversationDelete(c, fromLogin, toLogin);
|
||||
}
|
||||
@@ -314,7 +314,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public List<SignedMessageV2Entry> listPendingForSession(String login, String sessionId) throws Exception {
|
||||
public List<SignedMessageEntry> listPendingForSession(String login, String sessionId) throws Exception {
|
||||
return withBusyRetry(() -> {
|
||||
try (Connection c = db.getConnection()) {
|
||||
String fillSql = """
|
||||
@@ -354,7 +354,7 @@ public final class SignedMessagesV2DAO {
|
||||
WHERE d.session_id = ? AND d.delivered = 0
|
||||
ORDER BY m.time_ms ASC, m.revision_time_ms ASC, m.reencrypted_at_ms ASC, m.created_at_ms ASC
|
||||
""".formatted(messagesTable());
|
||||
List<SignedMessageV2Entry> out = new ArrayList<>();
|
||||
List<SignedMessageEntry> out = new ArrayList<>();
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, sessionId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
@@ -366,7 +366,7 @@ public final class SignedMessagesV2DAO {
|
||||
});
|
||||
}
|
||||
|
||||
public List<SignedMessageV2Entry> listConversationPage(
|
||||
public List<SignedMessageEntry> listConversationPage(
|
||||
String login,
|
||||
String peerLogin,
|
||||
long beforeTimeMs,
|
||||
@@ -395,7 +395,7 @@ public final class SignedMessagesV2DAO {
|
||||
ORDER BY time_ms DESC, message_key DESC
|
||||
LIMIT ?
|
||||
""".formatted(messagesTable());
|
||||
List<SignedMessageV2Entry> out = new ArrayList<>();
|
||||
List<SignedMessageEntry> out = new ArrayList<>();
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, login);
|
||||
ps.setString(2, login);
|
||||
@@ -416,7 +416,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertMessage(Connection c, SignedMessageV2Entry e) throws SQLException {
|
||||
private void upsertMessage(Connection c, SignedMessageEntry e) throws SQLException {
|
||||
String sql = """
|
||||
INSERT INTO %s (
|
||||
message_key, base_key, target_login, from_login, to_login,
|
||||
@@ -448,7 +448,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private void markMessageReadByReceipt(Connection c, SignedMessageV2Entry entry) throws SQLException {
|
||||
private void markMessageReadByReceipt(Connection c, SignedMessageEntry entry) throws SQLException {
|
||||
if (entry == null) return;
|
||||
int messageType = entry.getMessageType();
|
||||
if (messageType != 3 && messageType != 4) return;
|
||||
@@ -552,7 +552,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private SignedMessageV2Entry getLatestConversationDelete(Connection c, String fromLogin, String toLogin) throws Exception {
|
||||
private SignedMessageEntry getLatestConversationDelete(Connection c, String fromLogin, String toLogin) throws Exception {
|
||||
String sql = """
|
||||
SELECT
|
||||
message_key, base_key, target_login, from_login, to_login,
|
||||
@@ -646,7 +646,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private int insertStrict(Connection c, SignedMessageV2Entry e) throws SQLException {
|
||||
private int insertStrict(Connection c, SignedMessageEntry e) throws SQLException {
|
||||
String sql = """
|
||||
INSERT INTO %s (
|
||||
message_key, base_key, target_login, from_login, to_login,
|
||||
@@ -661,7 +661,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSignedMessage(PreparedStatement ps, SignedMessageV2Entry e) throws SQLException {
|
||||
private void bindSignedMessage(PreparedStatement ps, SignedMessageEntry e) throws SQLException {
|
||||
ps.setString(1, e.getMessageKey());
|
||||
ps.setString(2, e.getBaseKey());
|
||||
ps.setString(3, e.getTargetLogin());
|
||||
@@ -718,8 +718,8 @@ public final class SignedMessagesV2DAO {
|
||||
return "signed_messages";
|
||||
}
|
||||
|
||||
private SignedMessageV2Entry mapRow(ResultSet rs) throws Exception {
|
||||
SignedMessageV2Entry e = new SignedMessageV2Entry();
|
||||
private SignedMessageEntry mapRow(ResultSet rs) throws Exception {
|
||||
SignedMessageEntry e = new SignedMessageEntry();
|
||||
e.setMessageKey(rs.getString("message_key"));
|
||||
e.setBaseKey(rs.getString("base_key"));
|
||||
e.setTargetLogin(rs.getString("target_login"));
|
||||
@@ -743,7 +743,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
|
||||
private record RevisionMarker(long revisionTimeMs, long reencryptedAtMs) {
|
||||
private static RevisionMarker of(SignedMessageV2Entry entry) {
|
||||
private static RevisionMarker of(SignedMessageEntry entry) {
|
||||
return new RevisionMarker(entry.getRevisionTimeMs(), entry.getReencryptedAtMs());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package shine.db.entities;
|
||||
|
||||
public class SignedMessageV2Entry {
|
||||
public class SignedMessageEntry {
|
||||
private String messageKey;
|
||||
private String baseKey;
|
||||
private String targetLogin;
|
||||
+9
-9
@@ -25,8 +25,8 @@ public class ConnectionContext {
|
||||
public static final int AUTH_STATUS_AUTH_IN_PROGRESS = 1; // выполнен challenge (AuthChallenge или SessionChallenge)
|
||||
public static final int AUTH_STATUS_USER = 2; // авторизованный пользователь
|
||||
|
||||
// Полный пользователь из БД (solana_users)
|
||||
private CurrentUserEntry solanaUserEntry;
|
||||
// Полный пользователь из runtime БД (current users / solana_user_pda_current)
|
||||
private CurrentUserEntry currentUserEntry;
|
||||
|
||||
// Активная сессия из БД (active_sessions)
|
||||
private ActiveSessionEntry activeSessionEntry;
|
||||
@@ -89,12 +89,12 @@ public class ConnectionContext {
|
||||
|
||||
// --- SolanaUser / ActiveSession ---
|
||||
|
||||
public CurrentUserEntry getSolanaUser() {
|
||||
return solanaUserEntry;
|
||||
public CurrentUserEntry getCurrentUser() {
|
||||
return currentUserEntry;
|
||||
}
|
||||
|
||||
public void setSolanaUser(CurrentUserEntry solanaUserEntry) {
|
||||
this.solanaUserEntry = solanaUserEntry;
|
||||
public void setCurrentUser(CurrentUserEntry currentUserEntry) {
|
||||
this.currentUserEntry = currentUserEntry;
|
||||
}
|
||||
|
||||
public ActiveSessionEntry getActiveSession() {
|
||||
@@ -108,7 +108,7 @@ public class ConnectionContext {
|
||||
// --- Удобный геттер для логина ---
|
||||
|
||||
public String getLogin() {
|
||||
return solanaUserEntry != null ? solanaUserEntry.getLogin() : null;
|
||||
return currentUserEntry != null ? currentUserEntry.getLogin() : null;
|
||||
}
|
||||
|
||||
// --- sessionId ---
|
||||
@@ -176,7 +176,7 @@ public class ConnectionContext {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
solanaUserEntry = null;
|
||||
currentUserEntry = null;
|
||||
activeSessionEntry = null;
|
||||
|
||||
sessionId = null;
|
||||
@@ -198,4 +198,4 @@ public class ConnectionContext {
|
||||
", authenticationStatus=" + authenticationStatus +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
ctx.setSolanaUser(solanaUserEntry);
|
||||
ctx.setCurrentUser(solanaUserEntry);
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS);
|
||||
|
||||
byte[] buf = new byte[32];
|
||||
|
||||
+3
-3
@@ -42,7 +42,7 @@ public class Net_CloseActiveSession_Handler implements JsonMessageHandler {
|
||||
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||
Net_CloseActiveSession_Request req = (Net_CloseActiveSession_Request) baseReq;
|
||||
|
||||
if (ctx == null || ctx.getSolanaUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
|
||||
if (ctx == null || ctx.getCurrentUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
@@ -51,7 +51,7 @@ public class Net_CloseActiveSession_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
CurrentUserEntry user = ctx.getSolanaUser();
|
||||
CurrentUserEntry user = ctx.getCurrentUser();
|
||||
String currentLogin = user.getLogin();
|
||||
|
||||
String targetSessionId = req.getSessionId();
|
||||
@@ -152,4 +152,4 @@ public class Net_CloseActiveSession_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
Net_CreateAuthSession_Request req = (Net_CreateAuthSession_Request) baseReq;
|
||||
|
||||
if (ctx == null
|
||||
|| ctx.getSolanaUser() == null
|
||||
|| ctx.getCurrentUser() == null
|
||||
|| ctx.getAuthNonce() == null
|
||||
|| ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS) {
|
||||
|
||||
@@ -72,7 +72,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
|
||||
return err;
|
||||
}
|
||||
|
||||
CurrentUserEntry userFromContext = ctx.getSolanaUser();
|
||||
CurrentUserEntry userFromContext = ctx.getCurrentUser();
|
||||
String loginFromContext = userFromContext.getLogin();
|
||||
String loginFromReq = req.getLogin();
|
||||
if (loginFromReq == null || loginFromReq.isBlank()) {
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
|
||||
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||
Net_ListSessions_Request req = (Net_ListSessions_Request) baseReq;
|
||||
|
||||
if (ctx == null || ctx.getSolanaUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
|
||||
if (ctx == null || ctx.getCurrentUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
@@ -45,7 +45,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
CurrentUserEntry user = ctx.getSolanaUser();
|
||||
CurrentUserEntry user = ctx.getCurrentUser();
|
||||
String currentLogin = user.getLogin();
|
||||
|
||||
List<ActiveSessionEntry> sessions;
|
||||
|
||||
+1
-1
@@ -294,7 +294,7 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
|
||||
|
||||
// ctx
|
||||
ctx.setActiveSession(session);
|
||||
ctx.setSolanaUser(user);
|
||||
ctx.setCurrentUser(user);
|
||||
ctx.setSessionId(sessionId);
|
||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ public class Net_GetFriendsLists_Handler implements JsonMessageHandler {
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
|
||||
// 1) Канонизируем login через solana_users (NOCASE)
|
||||
// 1) Канонизируем login через current users слой (NOCASE)
|
||||
String canonicalLogin = findCanonicalLogin(c, loginAnyCase);
|
||||
if (canonicalLogin == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ import shine.db.entities.CurrentUserEntry;
|
||||
|
||||
/**
|
||||
* GetSyncUserProfile — server-to-server профиль пользователя для межсерверной синхронизации.
|
||||
* Нужен, чтобы принимающий сервер мог создать локальные solana_users + blockchain_state
|
||||
* без прямого запроса в Solana RPC.
|
||||
* Нужен, чтобы принимающий сервер мог создать локальную runtime-проекцию пользователя
|
||||
* и blockchain_state без прямого запроса в Solana RPC.
|
||||
*/
|
||||
public final class Net_GetSyncUserProfile_Handler implements JsonMessageHandler {
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Re
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
|
||||
public class Net_AckSessionDelivery_Handler implements JsonMessageHandler {
|
||||
@Override
|
||||
@@ -22,7 +22,7 @@ public class Net_AckSessionDelivery_Handler implements JsonMessageHandler {
|
||||
}
|
||||
|
||||
String messageKey = req.getMessageKey().trim();
|
||||
SignedMessagesV2DAO.getInstance().markDelivered(messageKey, ctx.getSessionId(), System.currentTimeMillis());
|
||||
SignedMessagesDAO.getInstance().markDelivered(messageKey, ctx.getSessionId(), System.currentTimeMillis());
|
||||
|
||||
Net_AckSessionDelivery_Response resp = new Net_AckSessionDelivery_Response();
|
||||
resp.setOp(req.getOp());
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_DeleteConversation_Re
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import server.sync.DmFederationService;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
public class Net_DeleteConversation_Handler implements JsonMessageHandler {
|
||||
@Override
|
||||
@@ -38,8 +38,8 @@ public class Net_DeleteConversation_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
|
||||
}
|
||||
|
||||
SignedMessageV2Entry entry = SignedMessagesCore.toEntry(block, "DeleteConversation", null);
|
||||
SignedMessagesV2DAO.ApplyStatus status = SignedMessagesV2DAO.getInstance().applyDeleteConversation(entry);
|
||||
SignedMessageEntry entry = SignedMessagesCore.toEntry(block, "DeleteConversation", null);
|
||||
SignedMessagesDAO.ApplyStatus status = SignedMessagesDAO.getInstance().applyDeleteConversation(entry);
|
||||
|
||||
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
|
||||
if (status.applied()) {
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_DeleteMessage_Respons
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import server.sync.DmFederationService;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
public class Net_DeleteMessage_Handler implements JsonMessageHandler {
|
||||
@Override
|
||||
@@ -38,8 +38,8 @@ public class Net_DeleteMessage_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
|
||||
}
|
||||
|
||||
SignedMessageV2Entry entry = SignedMessagesCore.toEntry(block, "DeleteMessage", null);
|
||||
SignedMessagesV2DAO.ApplyStatus status = SignedMessagesV2DAO.getInstance().applyDeleteMessage(entry);
|
||||
SignedMessageEntry entry = SignedMessagesCore.toEntry(block, "DeleteMessage", null);
|
||||
SignedMessagesDAO.ApplyStatus status = SignedMessagesDAO.getInstance().applyDeleteMessage(entry);
|
||||
|
||||
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
|
||||
if (status.applied()) {
|
||||
|
||||
+4
-4
@@ -10,8 +10,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_GetDirectMessages_Req
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_GetDirectMessages_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -43,7 +43,7 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler {
|
||||
String beforeMessageKey = req.getBeforeMessageKey() == null ? "" : req.getBeforeMessageKey().trim();
|
||||
|
||||
try {
|
||||
List<SignedMessageV2Entry> page = SignedMessagesV2DAO.getInstance().listConversationPage(
|
||||
List<SignedMessageEntry> page = SignedMessagesDAO.getInstance().listConversationPage(
|
||||
login,
|
||||
peerLogin,
|
||||
beforeTimeMs,
|
||||
@@ -66,7 +66,7 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler {
|
||||
resp.setHasMore(hasMore);
|
||||
|
||||
List<Net_GetDirectMessages_Response.MessageItem> items = new ArrayList<>();
|
||||
for (SignedMessageV2Entry entry : page) {
|
||||
for (SignedMessageEntry entry : page) {
|
||||
Net_GetDirectMessages_Response.MessageItem item = new Net_GetDirectMessages_Response.MessageItem();
|
||||
item.setMessageKey(entry.getMessageKey());
|
||||
item.setBaseKey(entry.getBaseKey());
|
||||
|
||||
+8
-8
@@ -8,8 +8,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessag
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessage_Response;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
@@ -40,21 +40,21 @@ public class Net_ReceiveIncomingMessage_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
|
||||
}
|
||||
|
||||
final SignedMessageV2Entry entry;
|
||||
final SignedMessageEntry entry;
|
||||
try {
|
||||
entry = SignedMessagesCore.toEntry(incoming, "ReceiveIncomingMessage", null);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения");
|
||||
}
|
||||
|
||||
SignedMessagesV2DAO.ApplyStatus status = incoming.isContentType()
|
||||
? SignedMessagesV2DAO.getInstance().upsertIncomingCopy(entry)
|
||||
: SignedMessagesV2DAO.getInstance().insertIfAbsent(entry);
|
||||
SignedMessagesDAO.ApplyStatus status = incoming.isContentType()
|
||||
? SignedMessagesDAO.getInstance().upsertIncomingCopy(entry)
|
||||
: SignedMessagesDAO.getInstance().insertIfAbsent(entry);
|
||||
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
|
||||
if (status.applied()) {
|
||||
counters = SignedMessagesRealtime.deliverToRelevantSessions(entry, incoming);
|
||||
}
|
||||
if (status == SignedMessagesV2DAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
|
||||
if (status == SignedMessagesDAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
|
||||
bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class Net_ReceiveIncomingMessage_Handler implements JsonMessageHandler {
|
||||
}
|
||||
|
||||
private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception {
|
||||
SignedMessageV2Entry tombstone = SignedMessagesV2DAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
|
||||
SignedMessageEntry tombstone = SignedMessagesDAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
|
||||
if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return;
|
||||
server.sync.DmFederationService.fanOutDeleteConversation(
|
||||
tombstone.getFromLogin(),
|
||||
|
||||
+11
-11
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_SendMessagePair_Respo
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import server.sync.DmFederationService;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
@@ -41,8 +41,8 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
|
||||
}
|
||||
|
||||
SignedMessageV2Entry incomingEntry;
|
||||
SignedMessageV2Entry outgoingEntry;
|
||||
SignedMessageEntry incomingEntry;
|
||||
SignedMessageEntry outgoingEntry;
|
||||
try {
|
||||
String sourceApi = "SendMessagePair";
|
||||
String originSessionId = (ctx != null && !isBlank(ctx.getSessionId())) ? ctx.getSessionId() : null;
|
||||
@@ -52,15 +52,15 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения");
|
||||
}
|
||||
|
||||
SignedMessagesV2DAO.ApplyStatus pairStatus;
|
||||
SignedMessagesDAO.ApplyStatus pairStatus;
|
||||
if (incoming.isContentType()) {
|
||||
pairStatus = SignedMessagesV2DAO.getInstance().upsertContentPair(
|
||||
pairStatus = SignedMessagesDAO.getInstance().upsertContentPair(
|
||||
incomingEntry, outgoingEntry
|
||||
);
|
||||
} else {
|
||||
pairStatus = SignedMessagesV2DAO.getInstance().insertPairBothOrNothing(incomingEntry, outgoingEntry)
|
||||
? SignedMessagesV2DAO.ApplyStatus.APPLIED
|
||||
: SignedMessagesV2DAO.ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
pairStatus = SignedMessagesDAO.getInstance().insertPairBothOrNothing(incomingEntry, outgoingEntry)
|
||||
? SignedMessagesDAO.ApplyStatus.APPLIED
|
||||
: SignedMessagesDAO.ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
|
||||
SignedMessagesRealtime.DeliveryCounters inCounters = new SignedMessagesRealtime.DeliveryCounters();
|
||||
@@ -77,7 +77,7 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
|
||||
outCounters = SignedMessagesRealtime.deliverToRelevantSessions(outgoingEntry, outgoing, excludeSessionId);
|
||||
}
|
||||
|
||||
if (pairStatus == SignedMessagesV2DAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
|
||||
if (pairStatus == SignedMessagesDAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
|
||||
bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
|
||||
}
|
||||
|
||||
private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception {
|
||||
SignedMessageV2Entry tombstone = SignedMessagesV2DAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
|
||||
SignedMessageEntry tombstone = SignedMessagesDAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
|
||||
if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return;
|
||||
DmFederationService.fanOutDeleteConversation(
|
||||
tombstone.getFromLogin(),
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class Net_SendSignal_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "TIME_SKEW", "Время клиента отличается от сервера более чем на 30 секунд");
|
||||
}
|
||||
|
||||
CurrentUserEntry senderUser = ctx.getSolanaUser();
|
||||
CurrentUserEntry senderUser = ctx.getCurrentUser();
|
||||
if (senderUser == null || senderUser.getClientKey() == null || senderUser.getClientKey().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.SERVER_DATA_ERROR, "NO_CLIENT_KEY", "Для пользователя не найден client key");
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package server.logic.ws_protocol.JSON.messages;
|
||||
|
||||
import shine.db.dao.CurrentUsersDAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
import shine.db.entities.CurrentUserEntry;
|
||||
import utils.crypto.Ed25519Util;
|
||||
|
||||
@@ -96,11 +96,11 @@ final class SignedMessagesCore {
|
||||
}
|
||||
}
|
||||
|
||||
static SignedMessageV2Entry toEntry(SignedMessageBlock block, String sourceApi, String originSessionId) {
|
||||
static SignedMessageEntry toEntry(SignedMessageBlock block, String sourceApi, String originSessionId) {
|
||||
String baseKey = SignedMessageKeys.baseKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce);
|
||||
String messageKey = SignedMessageKeys.messageKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce, block.messageType);
|
||||
|
||||
SignedMessageV2Entry entry = new SignedMessageV2Entry();
|
||||
SignedMessageEntry entry = new SignedMessageEntry();
|
||||
entry.setMessageKey(messageKey);
|
||||
entry.setBaseKey(baseKey);
|
||||
entry.setTargetLogin(primaryTargetLogin(block));
|
||||
|
||||
+11
-11
@@ -9,9 +9,9 @@ import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.push.WebPushSender;
|
||||
import server.logic.ws_protocol.JSON.push.WsEventSender;
|
||||
import shine.db.dao.ActiveSessionsDAO;
|
||||
import shine.db.dao.SignedMessagesV2DAO;
|
||||
import shine.db.dao.SignedMessagesDAO;
|
||||
import shine.db.entities.ActiveSessionEntry;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.entities.SignedMessageEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -37,12 +37,12 @@ public final class SignedMessagesRealtime {
|
||||
|
||||
private SignedMessagesRealtime() {}
|
||||
|
||||
static DeliveryCounters deliverToRelevantSessions(SignedMessageV2Entry message, SignedMessageBlock block) throws Exception {
|
||||
static DeliveryCounters deliverToRelevantSessions(SignedMessageEntry message, SignedMessageBlock block) throws Exception {
|
||||
return deliverToRelevantSessions(message, block, null);
|
||||
}
|
||||
|
||||
static DeliveryCounters deliverToRelevantSessions(
|
||||
SignedMessageV2Entry message,
|
||||
SignedMessageEntry message,
|
||||
SignedMessageBlock block,
|
||||
String excludeSessionId
|
||||
) throws Exception {
|
||||
@@ -58,7 +58,7 @@ public final class SignedMessagesRealtime {
|
||||
}
|
||||
sessionIdsToTrack.add(sessionId);
|
||||
}
|
||||
SignedMessagesV2DAO.getInstance().ensureDeliveryRows(message.getMessageKey(), sessionIdsToTrack, now);
|
||||
SignedMessagesDAO.getInstance().ensureDeliveryRows(message.getMessageKey(), sessionIdsToTrack, now);
|
||||
for (ActiveSessionEntry s : sessions) {
|
||||
String sessionId = s.getSessionId();
|
||||
if (excludeSessionId != null && excludeSessionId.equals(sessionId)) {
|
||||
@@ -97,9 +97,9 @@ public final class SignedMessagesRealtime {
|
||||
|
||||
private static void dispatchPendingForSession(String login, String sessionId) {
|
||||
try {
|
||||
List<SignedMessageV2Entry> pending = SignedMessagesV2DAO.getInstance()
|
||||
List<SignedMessageEntry> pending = SignedMessagesDAO.getInstance()
|
||||
.listPendingForSession(login, sessionId);
|
||||
for (SignedMessageV2Entry e : pending) {
|
||||
for (SignedMessageEntry e : pending) {
|
||||
sendEventToSessionIfOnline(sessionId, login, e, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -110,7 +110,7 @@ public final class SignedMessagesRealtime {
|
||||
private static boolean sendEventToSessionIfOnline(
|
||||
String sessionId,
|
||||
String actualTargetLogin,
|
||||
SignedMessageV2Entry message,
|
||||
SignedMessageEntry message,
|
||||
boolean backlog
|
||||
) {
|
||||
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId);
|
||||
@@ -134,14 +134,14 @@ public final class SignedMessagesRealtime {
|
||||
return WsEventSender.sendEvent(targetCtx, "SignedMessageArrived", message.getMessageKey(), payload);
|
||||
}
|
||||
|
||||
private static boolean shouldPushNewIncomingMessage(String targetLogin, SignedMessageV2Entry message, SignedMessageBlock block) {
|
||||
private static boolean shouldPushNewIncomingMessage(String targetLogin, SignedMessageEntry message, SignedMessageBlock block) {
|
||||
if (block == null) return false;
|
||||
if (message.getMessageType() != SignedMessageBlock.TYPE_INCOMING_TEXT) return false;
|
||||
if (!targetLogin.equalsIgnoreCase(message.getToLogin())) return false;
|
||||
return block.revisionTimeMs == 0;
|
||||
}
|
||||
|
||||
private static boolean pushNewMessageNotification(ActiveSessionEntry session, SignedMessageV2Entry message) {
|
||||
private static boolean pushNewMessageNotification(ActiveSessionEntry session, SignedMessageEntry message) {
|
||||
try {
|
||||
if (session == null) return false;
|
||||
if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) {
|
||||
@@ -160,7 +160,7 @@ public final class SignedMessagesRealtime {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> targetLoginsForMessage(SignedMessageV2Entry message) {
|
||||
private static List<String> targetLoginsForMessage(SignedMessageEntry message) {
|
||||
Set<String> out = new LinkedHashSet<>();
|
||||
int type = message.getMessageType();
|
||||
if (type == SignedMessageBlock.TYPE_INCOMING_TEXT || type == SignedMessageBlock.TYPE_READ_INCOMING) {
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ public final class WsConnectionUtils {
|
||||
final String sessionId = safeString(ctx.getSessionId());
|
||||
final int authStatus = safeAuthStatus(ctx);
|
||||
|
||||
final CurrentUserEntry user = ctx.getSolanaUser();
|
||||
final CurrentUserEntry user = ctx.getCurrentUser();
|
||||
final String login = (user != null ? safeString(user.getLogin()) : "");
|
||||
|
||||
final String activeSessionId =
|
||||
@@ -152,4 +152,4 @@ public final class WsConnectionUtils {
|
||||
|
||||
return "remote=" + remote + ", local=" + local;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ solana.users.sync.pollIntervalSeconds=300
|
||||
# false - брать профиль пользователя напрямую из Solana PDA (обычный режим).
|
||||
# true - не ходить в Solana RPC, а запрашивать у сервера-партнёра специальный
|
||||
# sync-профиль пользователя и по нему локально создавать
|
||||
# solana_users + blockchain_state.
|
||||
# current user runtime-проекцию + blockchain_state.
|
||||
# Эта настройка нужна как временный обход лимитов Solana RPC (например 429),
|
||||
# чтобы чистый сервер мог восстановить цепочки от партнёра без зависимости
|
||||
# от внешнего Solana endpoint.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
client.version=1.2.354
|
||||
server.version=1.2.335
|
||||
server.version=1.2.336
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
Этот файл описывает раздел API, связанный с проверкой наличия пользователя на сервере и dev/test операциями.
|
||||
|
||||
Сейчас здесь три метода:
|
||||
Сейчас здесь два метода:
|
||||
|
||||
- `AddUser` — операция отключена (регистрация только через Solana);
|
||||
- `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных;
|
||||
- `SearchUsers` — dev/test поиск логинов по префиксу.
|
||||
|
||||
Регистрация выполняется через Solana (`shine_users`). Сервер при входе может лениво импортировать пользователя из Solana PDA в локальную БД, если записи ещё нет.
|
||||
Регистрация выполняется только через Solana. Старый серверный `AddUser` оставлен лишь как legacy-ответ `410 / ADD_USER_DISABLED` для старых клиентов и больше не считается частью актуального flow.
|
||||
|
||||
## Статус документа
|
||||
|
||||
@@ -18,11 +17,11 @@
|
||||
|
||||
---
|
||||
|
||||
## 1. Операция `AddUser`
|
||||
## Legacy-операция `AddUser`
|
||||
|
||||
### Назначение
|
||||
|
||||
Операция отключена. Используется только как явный ответ клиентам старых версий.
|
||||
Операция удалена из текущего клиентского flow. Сервер сохраняет только совместимый ответ для клиентов старых версий.
|
||||
|
||||
### Запрос
|
||||
|
||||
@@ -62,7 +61,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 2. Операция `GetUser`
|
||||
## 1. Операция `GetUser`
|
||||
|
||||
### Назначение
|
||||
|
||||
@@ -153,7 +152,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 3. Операция `SearchUsers`
|
||||
## 2. Операция `SearchUsers`
|
||||
|
||||
### Назначение
|
||||
|
||||
@@ -195,7 +194,6 @@
|
||||
|
||||
## 4. Короткое резюме
|
||||
|
||||
- `AddUser` — отключен (`410 / ADD_USER_DISABLED`).
|
||||
- `GetUser` — проверка существования пользователя на сервере.
|
||||
- `SearchUsers` — временный поиск пользователей по префиксу.
|
||||
- Регистрация выполняется только через Solana.
|
||||
|
||||
@@ -138,7 +138,7 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
||||
|
||||
Перед проверкой подписи сервер должен:
|
||||
|
||||
1. взять актуальный `solana_users.client_key`;
|
||||
1. взять актуальный `solana_user_pda_current.client_key`;
|
||||
2. сравнить его с `payload.clientKey`;
|
||||
3. только потом проверять подпись.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
|
||||
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
|
||||
- `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону.
|
||||
- `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальные `solana_users + blockchain_state` без прямого обращения в Solana. Это используется как временный обход ограничений внешнего Solana RPC.
|
||||
- `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальную runtime-проекцию пользователя и `blockchain_state` без прямого обращения в Solana.
|
||||
- `SendSignal` нужен для доверенных межсессионных команд одного пользователя. Первое практическое применение — `remote AddBlock via homeserver session`, но формат задуман как общий transport на вырост.
|
||||
|
||||
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
|
||||
@@ -216,7 +216,7 @@
|
||||
- `clientKey`
|
||||
- `blockchainSizeLimitBytes`
|
||||
|
||||
После этого принимающий сервер может локально создать записи в `solana_users` и `blockchain_state`, а затем уже докачивать блоки через `GetBlockchainBlock`.
|
||||
После этого принимающий сервер может локально создать runtime-проекцию пользователя и запись в `blockchain_state`, а затем уже докачивать блоки через `GetBlockchainBlock`.
|
||||
|
||||
Этот запрос доступен без авторизации и предназначен именно для server-to-server sync.
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
| Операция | Раздел документации | Кратко |
|
||||
| --- | --- | --- |
|
||||
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
||||
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
||||
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
||||
| `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара |
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
- `ListBlockchainHeads` — список heads всех локальных цепочек партнёра;
|
||||
- `GetBlockchainBlock` — чтение одного конкретного блока партнёра;
|
||||
- `GetSyncUserProfile` — минимальный профиль пользователя для локального создания `solana_users + blockchain_state` без обращения в Solana RPC.
|
||||
- `GetSyncUserProfile` — минимальный профиль пользователя для локального создания runtime-проекции пользователя и `blockchain_state` без обращения в Solana RPC.
|
||||
|
||||
### 4.3 Как сейчас работает periodic sync
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
- локальное состояние;
|
||||
3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`;
|
||||
4. каждый скачанный блок локально применяется через существующий `AddBlock`;
|
||||
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальный `solana_users + blockchain_state`.
|
||||
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальная runtime-проекция пользователя и `blockchain_state`.
|
||||
6. если во время replay обнаруживается рассинхрон или на одинаковой высоте удалённая цепочка сильнее, запускается полный resync:
|
||||
- цепочка помечается in-memory как `resync in progress`;
|
||||
- создаётся marker-file в `data/`;
|
||||
@@ -110,7 +110,7 @@ Full resync запускается только тогда, когда:
|
||||
Важно:
|
||||
|
||||
- full resync не делает умный rollback по одному блоку;
|
||||
- full resync не трогает DM-таблицы и `solana_users`;
|
||||
- full resync не трогает DM-таблицы и current users слой;
|
||||
- висячие cross-chain ссылки считаются допустимым поведением системы.
|
||||
|
||||
### 4.5 Как работает обычный `AddBlock` и его recovery
|
||||
@@ -150,7 +150,7 @@ Full resync запускается только тогда, когда:
|
||||
|
||||
- из `blockchainName` извлекался `login`;
|
||||
- сервер вызывал import пользователя из Solana PDA;
|
||||
- по данным PDA локально создавались `solana_users + blockchain_state`.
|
||||
- по данным PDA локально создавались runtime-проекция пользователя и `blockchain_state`.
|
||||
|
||||
На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`.
|
||||
|
||||
@@ -159,7 +159,7 @@ Full resync запускается только тогда, когда:
|
||||
- настройка `sync.importUserProfileFromPartner.enabled=true`
|
||||
- в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync;
|
||||
- вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра.
|
||||
- если локальный `solana_users` уже существует, sync восстанавливает только `blockchain_state` и не трогает identity-слой.
|
||||
- если локальная runtime-проекция пользователя уже существует, sync восстанавливает только `blockchain_state` и не трогает user-layer.
|
||||
|
||||
Это временная практическая заплатка, чтобы clean-start sync не зависел от rate limit внешнего Solana endpoint.
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
Для DM-проверки сервер использует:
|
||||
|
||||
- локальную `solana_users` как кэш;
|
||||
- локальную runtime-проекцию пользователей как кэш;
|
||||
- Solana PDA как источник истины по `clientKey` и `access_servers`.
|
||||
|
||||
Если нужного пользователя нет локально, сервер обязан попытаться lazy-import из Solana PDA:
|
||||
|
||||
@@ -9,8 +9,4 @@ shine.db.DatabaseInitializer — проверяет наличие `db_schema_ve
|
||||
|
||||
|
||||
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getClientKeyByte()).
|
||||
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, CurrentUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс “сервисные” DAO:
|
||||
|
||||
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
|
||||
// Временное runtime-решение, позволяющее регистрировать новых пользователей
|
||||
// атомарно и добавляющее запись в runtime-таблицы сервера.
|
||||
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, CurrentUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO, SignedMessagesDAO; плюс сервисные DAO под recovery/resync.
|
||||
|
||||
@@ -42,11 +42,11 @@ auth/ — авторизация и сессии
|
||||
|
||||
blockchain/ — AddBlock
|
||||
|
||||
tempToTest/ — AddUser (временный, потом уйдёт в блокчейн-логику)
|
||||
tempToTest/ — временные dev/test операции (`GetUser`, `SearchUsers`)
|
||||
|
||||
ConnectionContext
|
||||
Состояние одного WebSocket-подключения (login, session, authStatus).
|
||||
|
||||
ActiveConnectionsRegistry
|
||||
Глобальный реестр активных авторизованных соединений
|
||||
(нужно для закрытия других сессий).
|
||||
(нужно для закрытия других сессий).
|
||||
|
||||
@@ -1037,46 +1037,15 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async registerUser(login, password) {
|
||||
const cleanLogin = (login || '').trim();
|
||||
if (!cleanLogin) throw new Error('Введите логин');
|
||||
|
||||
const isFree = await this.ensureLoginFree(cleanLogin);
|
||||
if (!isFree) throw new Error('Этот логин уже занят');
|
||||
|
||||
const keyBundle = await this.derivePasswordKeyBundle(cleanLogin, password);
|
||||
const clientPair = keyBundle?.clientPair;
|
||||
|
||||
const addResp = await this.ws.request('AddUser', {
|
||||
login: cleanLogin,
|
||||
blockchainName: `${cleanLogin}-${BCH_SUFFIX}`,
|
||||
solanaKey: clientPair.publicKeyB64,
|
||||
blockchainKey: keyBundle.blockchainPair.publicKeyB64,
|
||||
clientKey: clientPair.publicKeyB64,
|
||||
bchLimit: 1000000,
|
||||
});
|
||||
if (addResp.status !== 200) throw opError('AddUser', addResp);
|
||||
|
||||
const session = await this.createAuthSession(cleanLogin, keyBundle);
|
||||
return { ...session, keyBundle };
|
||||
void login;
|
||||
void password;
|
||||
throw new Error('Серверная регистрация через AddUser удалена. Используйте регистрацию через Solana.');
|
||||
}
|
||||
|
||||
async registerUserWithKeyBundle(login, keyBundle) {
|
||||
const cleanLogin = (login || '').trim();
|
||||
if (!cleanLogin) throw new Error('Введите логин');
|
||||
const clientPair = keyBundle?.clientPair;
|
||||
|
||||
const addResp = await this.ws.request('AddUser', {
|
||||
login: cleanLogin,
|
||||
blockchainName: `${cleanLogin}-${BCH_SUFFIX}`,
|
||||
solanaKey: clientPair.publicKeyB64,
|
||||
blockchainKey: keyBundle.blockchainPair.publicKeyB64,
|
||||
clientKey: clientPair.publicKeyB64,
|
||||
bchLimit: 1000000,
|
||||
});
|
||||
if (addResp.status !== 200) throw opError('AddUser', addResp);
|
||||
|
||||
const session = await this.createAuthSession(cleanLogin, keyBundle);
|
||||
return { ...session, keyBundle };
|
||||
void login;
|
||||
void keyBundle;
|
||||
throw new Error('Серверная регистрация через AddUser удалена. Используйте регистрацию через Solana.');
|
||||
}
|
||||
|
||||
async createSessionForExistingUser(login, password) {
|
||||
|
||||
Reference in New Issue
Block a user