Add close-friend flow on network tab with server API
This commit is contained in:
parent
91ed444c90
commit
09566fdfde
@ -10,6 +10,65 @@ function makeNode(name, cls = '') {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showAddCloseFriendModal({ onAdded }) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="close-friend-modal">
|
||||||
|
<div class="modal-card stack">
|
||||||
|
<h3 style="font-size:18px;">Добавить близкого друга</h3>
|
||||||
|
<input class="input" id="close-friend-query" placeholder="Логин или начало логина" maxlength="80" />
|
||||||
|
<div class="row" style="gap:8px;">
|
||||||
|
<button class="primary-btn" id="close-friend-search">Поиск</button>
|
||||||
|
<button class="ghost-btn" id="close-friend-back">Назад</button>
|
||||||
|
</div>
|
||||||
|
<div class="stack" id="close-friend-results"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const close = () => { root.innerHTML = ''; };
|
||||||
|
root.querySelector('#close-friend-back').addEventListener('click', close);
|
||||||
|
|
||||||
|
root.querySelector('#close-friend-search').addEventListener('click', async () => {
|
||||||
|
const query = root.querySelector('#close-friend-query').value.trim();
|
||||||
|
const holder = root.querySelector('#close-friend-results');
|
||||||
|
holder.innerHTML = '<p class="meta-muted">Поиск...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const logins = await authService.searchUsers(query);
|
||||||
|
holder.innerHTML = '';
|
||||||
|
if (!logins.length) {
|
||||||
|
holder.innerHTML = '<p class="meta-muted">Пользователи не найдены.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logins.forEach((login) => {
|
||||||
|
const row = document.createElement('article');
|
||||||
|
row.className = 'list-item';
|
||||||
|
row.innerHTML = `
|
||||||
|
<div class="avatar">${(login[0] || '?').toUpperCase()}</div>
|
||||||
|
<div><strong>${login}</strong><p class="meta-muted" style="margin-top:4px;">Пользователь</p></div>
|
||||||
|
<div class="meta-muted">Добавить</div>
|
||||||
|
`;
|
||||||
|
row.addEventListener('click', async () => {
|
||||||
|
const yes = window.confirm(`Добавить ${login} в близкие друзья?`);
|
||||||
|
if (!yes) return;
|
||||||
|
try {
|
||||||
|
await authService.addCloseFriend(login);
|
||||||
|
close();
|
||||||
|
if (typeof onAdded === 'function') await onAdded();
|
||||||
|
} catch (e) {
|
||||||
|
window.alert(`Ошибка добавления: ${e.message || 'unknown'}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.append(row);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
holder.innerHTML = `<p class="meta-muted">Ошибка поиска: ${e.message || 'unknown'}</p>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function render() {
|
export function render() {
|
||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'stack';
|
screen.className = 'stack';
|
||||||
@ -62,14 +121,20 @@ export function render() {
|
|||||||
right.forEach((name, i) => svg.append(mk(name, 'right', i, right.length)));
|
right.forEach((name, i) => svg.append(mk(name, 'right', i, right.length)));
|
||||||
board.prepend(svg);
|
board.prepend(svg);
|
||||||
|
|
||||||
note.textContent = 'Нажмите на любой узел, чтобы построить связи выбранного пользователя.';
|
note.textContent = 'Нажмите на узел, чтобы перестроить связи вокруг выбранного пользователя.';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
note.textContent = `Ошибка загрузки связей: ${e.message || 'unknown'}`;
|
note.textContent = `Ошибка загрузки связей: ${e.message || 'unknown'}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addBtn = document.createElement('button');
|
||||||
|
addBtn.className = 'primary-btn';
|
||||||
|
addBtn.type = 'button';
|
||||||
|
addBtn.textContent = 'Добавить близкого друга';
|
||||||
|
addBtn.addEventListener('click', () => showAddCloseFriendModal({ onAdded: () => load() }));
|
||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
||||||
screen.append(renderHeader({ title: 'Связи' }), board, note);
|
screen.append(renderHeader({ title: 'Связи' }), addBtn, board, note);
|
||||||
return screen;
|
return screen;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -264,6 +264,13 @@ export class AuthService {
|
|||||||
return response.payload || {};
|
return response.payload || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async addCloseFriend(toLogin) {
|
||||||
|
const response = await this.ws.request('AddCloseFriend', { toLogin });
|
||||||
|
if (response.status !== 200) throw opError('AddCloseFriend', response);
|
||||||
|
return response.payload || {};
|
||||||
|
}
|
||||||
|
|
||||||
async getUserConnectionsGraph(login) {
|
async getUserConnectionsGraph(login) {
|
||||||
const response = await this.ws.request('GetUserConnectionsGraph', { login });
|
const response = await this.ws.request('GetUserConnectionsGraph', { login });
|
||||||
if (response.status !== 200) throw opError('GetUserConnectionsGraph', response);
|
if (response.status !== 200) throw opError('GetUserConnectionsGraph', response);
|
||||||
|
|||||||
@ -125,4 +125,32 @@ public final class ConnectionsStateDAO {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void upsertRelation(Connection c,
|
||||||
|
String login,
|
||||||
|
int relType,
|
||||||
|
String toLogin,
|
||||||
|
String toBchName,
|
||||||
|
Integer toBlockNumber,
|
||||||
|
byte[] toBlockHash) throws SQLException {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO connections_state (login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(login, rel_type, to_login) DO UPDATE SET
|
||||||
|
to_bch_name=excluded.to_bch_name,
|
||||||
|
to_block_number=excluded.to_block_number,
|
||||||
|
to_block_hash=excluded.to_block_hash
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
|
ps.setString(1, login);
|
||||||
|
ps.setInt(2, relType);
|
||||||
|
ps.setString(3, toLogin);
|
||||||
|
ps.setString(4, toBchName);
|
||||||
|
if (toBlockNumber == null) ps.setNull(5, java.sql.Types.INTEGER); else ps.setInt(5, toBlockNumber);
|
||||||
|
ps.setBytes(6, toBlockHash);
|
||||||
|
ps.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -52,8 +52,10 @@ import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetChannelMe
|
|||||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Request;
|
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_GetMessageThread_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Request;
|
import server.logic.ws_protocol.JSON.handlers.channels.entyties.Net_ListSubscriptionsFeed_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.connections.Net_GetUserConnectionsGraph_Handler;
|
import server.logic.ws_protocol.JSON.handlers.connections.Net_GetUserConnectionsGraph_Handler;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.connections.Net_AddCloseFriend_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.connections.Net_ListContacts_Handler;
|
import server.logic.ws_protocol.JSON.handlers.connections.Net_ListContacts_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetUserConnectionsGraph_Request;
|
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetUserConnectionsGraph_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_AddCloseFriend_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_ListContacts_Request;
|
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_ListContacts_Request;
|
||||||
import server.logic.ws_protocol.JSON.messages.Net_AckIncomingMessage_Handler;
|
import server.logic.ws_protocol.JSON.messages.Net_AckIncomingMessage_Handler;
|
||||||
import server.logic.ws_protocol.JSON.messages.Net_SendDirectMessage_Handler;
|
import server.logic.ws_protocol.JSON.messages.Net_SendDirectMessage_Handler;
|
||||||
@ -108,6 +110,7 @@ public final class JsonHandlerRegistry {
|
|||||||
Map.entry("GetMessageThread", new Net_GetMessageThread_Handler()),
|
Map.entry("GetMessageThread", new Net_GetMessageThread_Handler()),
|
||||||
Map.entry("ListContacts", new Net_ListContacts_Handler()),
|
Map.entry("ListContacts", new Net_ListContacts_Handler()),
|
||||||
Map.entry("GetUserConnectionsGraph", new Net_GetUserConnectionsGraph_Handler()),
|
Map.entry("GetUserConnectionsGraph", new Net_GetUserConnectionsGraph_Handler()),
|
||||||
|
Map.entry("AddCloseFriend", new Net_AddCloseFriend_Handler()),
|
||||||
|
|
||||||
// --- direct messages / push ---
|
// --- direct messages / push ---
|
||||||
Map.entry("UpsertPushToken", new Net_UpsertPushToken_Handler()),
|
Map.entry("UpsertPushToken", new Net_UpsertPushToken_Handler()),
|
||||||
@ -153,6 +156,7 @@ public final class JsonHandlerRegistry {
|
|||||||
Map.entry("GetMessageThread", Net_GetMessageThread_Request.class),
|
Map.entry("GetMessageThread", Net_GetMessageThread_Request.class),
|
||||||
Map.entry("ListContacts", Net_ListContacts_Request.class),
|
Map.entry("ListContacts", Net_ListContacts_Request.class),
|
||||||
Map.entry("GetUserConnectionsGraph", Net_GetUserConnectionsGraph_Request.class),
|
Map.entry("GetUserConnectionsGraph", Net_GetUserConnectionsGraph_Request.class),
|
||||||
|
Map.entry("AddCloseFriend", Net_AddCloseFriend_Request.class),
|
||||||
|
|
||||||
// --- direct messages / push ---
|
// --- direct messages / push ---
|
||||||
Map.entry("UpsertPushToken", Net_UpsertPushToken_Request.class),
|
Map.entry("UpsertPushToken", Net_UpsertPushToken_Request.class),
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.connections;
|
||||||
|
|
||||||
|
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_AddCloseFriend_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_AddCloseFriend_Response;
|
||||||
|
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||||
|
import server.logic.ws_protocol.WireCodes;
|
||||||
|
import shine.db.MsgSubType;
|
||||||
|
import shine.db.dao.ConnectionsStateDAO;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
|
||||||
|
public class Net_AddCloseFriend_Handler implements JsonMessageHandler {
|
||||||
|
@Override
|
||||||
|
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
|
||||||
|
Net_AddCloseFriend_Request req = (Net_AddCloseFriend_Request) baseRequest;
|
||||||
|
if (ctx == null || !ctx.isAuthenticatedUser()) {
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.UNVERIFIED, "NOT_AUTHENTICATED", "Требуется авторизация");
|
||||||
|
}
|
||||||
|
if (req.getToLogin() == null || req.getToLogin().isBlank()) {
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "toLogin обязателен");
|
||||||
|
}
|
||||||
|
|
||||||
|
String from = ctx.getLogin();
|
||||||
|
String toLogin = req.getToLogin().trim();
|
||||||
|
if (from.equalsIgnoreCase(toLogin)) {
|
||||||
|
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "Нельзя добавить себя");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection c = shine.db.SqliteDbController.getInstance().getConnection()) {
|
||||||
|
String canonicalTo = findCanonicalLogin(c, toLogin);
|
||||||
|
if (canonicalTo == null) {
|
||||||
|
return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
|
||||||
|
}
|
||||||
|
String targetBch = findPrimaryBlockchain(c, canonicalTo);
|
||||||
|
if (targetBch == null) {
|
||||||
|
return NetExceptionResponseFactory.error(req, 404, "BLOCKCHAIN_NOT_FOUND", "У пользователя нет blockchain");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionsStateDAO.getInstance().upsertRelation(
|
||||||
|
c,
|
||||||
|
from,
|
||||||
|
MsgSubType.CONNECTION_FRIEND,
|
||||||
|
canonicalTo,
|
||||||
|
targetBch,
|
||||||
|
0,
|
||||||
|
new byte[32]
|
||||||
|
);
|
||||||
|
|
||||||
|
Net_AddCloseFriend_Response resp = new Net_AddCloseFriend_Response();
|
||||||
|
resp.setOp(req.getOp());
|
||||||
|
resp.setRequestId(req.getRequestId());
|
||||||
|
resp.setStatus(WireCodes.Status.OK);
|
||||||
|
resp.setLogin(from);
|
||||||
|
resp.setToLogin(canonicalTo);
|
||||||
|
resp.setRelation("close_friend");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findCanonicalLogin(Connection c, String login) throws Exception {
|
||||||
|
String sql = "SELECT login FROM solana_users WHERE login = ? COLLATE NOCASE LIMIT 1";
|
||||||
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
|
ps.setString(1, login);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getString("login") : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findPrimaryBlockchain(Connection c, String login) throws Exception {
|
||||||
|
String sql = "SELECT blockchain_name FROM blockchain_state WHERE login=? ORDER BY blockchain_name LIMIT 1";
|
||||||
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
|
ps.setString(1, login);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getString("blockchain_name") : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.connections.entyties;
|
||||||
|
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||||
|
|
||||||
|
public class Net_AddCloseFriend_Request extends Net_Request {
|
||||||
|
private String toLogin;
|
||||||
|
|
||||||
|
public String getToLogin() { return toLogin; }
|
||||||
|
public void setToLogin(String toLogin) { this.toLogin = toLogin; }
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.connections.entyties;
|
||||||
|
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||||
|
|
||||||
|
public class Net_AddCloseFriend_Response extends Net_Response {
|
||||||
|
private String login;
|
||||||
|
private String toLogin;
|
||||||
|
private String relation;
|
||||||
|
|
||||||
|
public String getLogin() { return login; }
|
||||||
|
public void setLogin(String login) { this.login = login; }
|
||||||
|
public String getToLogin() { return toLogin; }
|
||||||
|
public void setToLogin(String toLogin) { this.toLogin = toLogin; }
|
||||||
|
public String getRelation() { return relation; }
|
||||||
|
public void setRelation(String relation) { this.relation = relation; }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user