Добавлены родственные связи, расширен граф связей и улучшен локальный запуск

Что добавлено:\n- Новые типы CONNECTION для родственников: parent/child/sibling (50/51, 52/53, 54/55) в blockchain/db слоях.\n- Обновлены проверки ConnectionBody и DB-триггер connections_state для корректной записи/удаления новых связей.\n- В профиле добавлен блок "Близкие родственники" с модальным выбором типа связи и логина; добавление через AddBlock для parent/child/sibling.\n- Расширен API GetUserConnectionsGraph: out/in списки для родителей/детей/сиблингов, агрегированные списки родственников с полом, список allUsers с метками официальный/сияющий.\n- Полностью обновлен UI страницы "Связи": новое позиционирование родственников вокруг центра, отдельный цвет родственных связей, линия для взаимных связей и стрелка для односторонних, корректная геометрия линий при ресайзе.\n- Добавлена Gradle-задача startLocalWithBuild для запуска локального стека после build; сохранена отдельная startLocal без полного build.
This commit is contained in:
AidarKC
2026-04-17 21:01:53 +03:00
parent 9b188d56e9
commit 4a92a7fa22
13 changed files with 1061 additions and 73 deletions
@@ -14,7 +14,13 @@ import shine.db.dao.ConnectionsStateDAO;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
@Override
@@ -37,6 +43,22 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
List<String> inContacts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT);
List<String> outFollows = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW);
List<String> inFollows = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW);
List<String> outParents = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT);
List<String> inParents = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT);
List<String> outChildren = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD);
List<String> inChildren = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD);
List<String> outSiblings = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
List<String> inSiblings = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
LinkedHashSet<String> allLogins = new LinkedHashSet<>();
allLogins.add(canonicalLogin);
addAllLogins(allLogins, outFriends, inFriends, outContacts, inContacts, outFollows, inFollows,
outParents, inParents, outChildren, inChildren, outSiblings, inSiblings);
Map<String, UserMeta> metaByLogin = loadUserMeta(c, allLogins);
List<String> parentLogins = mergeUnique(outParents, inChildren);
List<String> childLogins = mergeUnique(outChildren, inParents);
List<String> siblingLogins = mergeUnique(outSiblings, inSiblings);
Net_GetUserConnectionsGraph_Response resp = new Net_GetUserConnectionsGraph_Response();
resp.setOp(req.getOp());
@@ -49,6 +71,16 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
resp.setInContacts(inContacts);
resp.setOutFollows(outFollows);
resp.setInFollows(inFollows);
resp.setOutParents(outParents);
resp.setInParents(inParents);
resp.setOutChildren(outChildren);
resp.setInChildren(inChildren);
resp.setOutSiblings(outSiblings);
resp.setInSiblings(inSiblings);
resp.setParents(toRelativeItems(parentLogins, metaByLogin));
resp.setChildren(toRelativeItems(childLogins, metaByLogin));
resp.setSiblings(toRelativeItems(siblingLogins, metaByLogin));
resp.setAllUsers(toUserMarkItems(allLogins, metaByLogin));
return resp;
}
}
@@ -62,4 +94,150 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
}
}
}
@SafeVarargs
private final void addAllLogins(Set<String> target, List<String>... lists) {
if (target == null || lists == null) return;
for (List<String> list : lists) {
if (list == null) continue;
for (String login : list) {
String clean = (login == null) ? "" : login.trim();
if (!clean.isEmpty()) target.add(clean);
}
}
}
private List<String> mergeUnique(List<String> first, List<String> second) {
LinkedHashMap<String, String> byKey = new LinkedHashMap<>();
addToMergeMap(byKey, first);
addToMergeMap(byKey, second);
return new ArrayList<>(byKey.values());
}
private void addToMergeMap(Map<String, String> target, List<String> source) {
if (target == null || source == null) return;
for (String login : source) {
String clean = (login == null) ? "" : login.trim();
if (clean.isEmpty()) continue;
String key = normKey(clean);
if (!target.containsKey(key)) target.put(key, clean);
}
}
private String normKey(String login) {
return String.valueOf(login == null ? "" : login).trim().toLowerCase();
}
private Map<String, UserMeta> loadUserMeta(Connection c, Set<String> logins) throws Exception {
Map<String, UserMeta> out = new HashMap<>();
if (logins == null || logins.isEmpty()) return out;
String[] placeholders = new String[logins.size()];
for (int i = 0; i < placeholders.length; i += 1) placeholders[i] = "?";
String sql = """
SELECT
su.login AS login,
MAX(CASE WHEN up.param = 'gender' THEN up.value END) AS gender_value,
MAX(CASE WHEN up.param = 'official' THEN up.value END) AS official_value,
MAX(CASE WHEN up.param = 'shine' THEN up.value END) AS shine_value
FROM solana_users su
LEFT JOIN users_params up
ON up.login = su.login COLLATE NOCASE
AND up.param IN ('gender', 'official', 'shine')
WHERE su.login COLLATE NOCASE IN (%s)
GROUP BY su.login
ORDER BY su.login
""".formatted(String.join(", ", placeholders));
try (PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
for (String login : logins) {
ps.setString(i, login);
i += 1;
}
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String login = rs.getString("login");
if (login == null || login.isBlank()) continue;
UserMeta meta = new UserMeta();
meta.gender = normalizeGender(rs.getString("gender_value"));
meta.official = parseToggle(rs.getString("official_value"));
meta.shine = parseToggle(rs.getString("shine_value"));
out.put(normKey(login), meta);
}
}
}
return out;
}
private boolean parseToggle(String rawValue) {
String v = String.valueOf(rawValue == null ? "" : rawValue).trim().toLowerCase();
return "1".equals(v) || "yes".equals(v) || "true".equals(v) || "on".equals(v);
}
private String normalizeGender(String rawValue) {
String v = String.valueOf(rawValue == null ? "" : rawValue).trim().toLowerCase();
if ("male".equals(v)) return "male";
if ("female".equals(v)) return "female";
return "unknown";
}
private String genderLabel(String gender) {
if ("male".equals(gender)) return "мужчина";
if ("female".equals(gender)) return "женщина";
return "не указан";
}
private List<Net_GetUserConnectionsGraph_Response.RelativeItem> toRelativeItems(
List<String> logins,
Map<String, UserMeta> metaByLogin
) {
List<Net_GetUserConnectionsGraph_Response.RelativeItem> items = new ArrayList<>();
if (logins == null) return items;
for (String login : logins) {
String clean = (login == null) ? "" : login.trim();
if (clean.isEmpty()) continue;
UserMeta meta = metaByLogin.get(normKey(clean));
String gender = (meta == null) ? "unknown" : meta.gender;
Net_GetUserConnectionsGraph_Response.RelativeItem it = new Net_GetUserConnectionsGraph_Response.RelativeItem();
it.setLogin(clean);
it.setGender(gender);
it.setGenderLabel(genderLabel(gender));
items.add(it);
}
return items;
}
private List<Net_GetUserConnectionsGraph_Response.UserMarkItem> toUserMarkItems(
Set<String> logins,
Map<String, UserMeta> metaByLogin
) {
List<Net_GetUserConnectionsGraph_Response.UserMarkItem> items = new ArrayList<>();
if (logins == null) return items;
for (String login : logins) {
String clean = (login == null) ? "" : login.trim();
if (clean.isEmpty()) continue;
UserMeta meta = metaByLogin.get(normKey(clean));
boolean official = meta != null && meta.official;
boolean shine = meta != null && meta.shine;
Net_GetUserConnectionsGraph_Response.UserMarkItem it = new Net_GetUserConnectionsGraph_Response.UserMarkItem();
it.setLogin(clean);
it.setOfficial(official);
it.setShine(shine);
it.setOfficialLabel(official ? "официальный" : "неофициальный");
it.setShineLabel(shine ? "сияющий" : "несияющий");
items.add(it);
}
return items;
}
private static final class UserMeta {
private String gender = "unknown";
private boolean official = false;
private boolean shine = false;
}
}
@@ -13,6 +13,48 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
private List<String> inContacts = new ArrayList<>();
private List<String> outFollows = new ArrayList<>();
private List<String> inFollows = new ArrayList<>();
private List<String> outParents = new ArrayList<>();
private List<String> inParents = new ArrayList<>();
private List<String> outChildren = new ArrayList<>();
private List<String> inChildren = new ArrayList<>();
private List<String> outSiblings = new ArrayList<>();
private List<String> inSiblings = new ArrayList<>();
private List<RelativeItem> parents = new ArrayList<>();
private List<RelativeItem> children = new ArrayList<>();
private List<RelativeItem> siblings = new ArrayList<>();
private List<UserMarkItem> allUsers = new ArrayList<>();
public static class RelativeItem {
private String login;
private String gender;
private String genderLabel;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getGenderLabel() { return genderLabel; }
public void setGenderLabel(String genderLabel) { this.genderLabel = genderLabel; }
}
public static class UserMarkItem {
private String login;
private boolean official;
private boolean shine;
private String officialLabel;
private String shineLabel;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public boolean isOfficial() { return official; }
public void setOfficial(boolean official) { this.official = official; }
public boolean isShine() { return shine; }
public void setShine(boolean shine) { this.shine = shine; }
public String getOfficialLabel() { return officialLabel; }
public void setOfficialLabel(String officialLabel) { this.officialLabel = officialLabel; }
public String getShineLabel() { return shineLabel; }
public void setShineLabel(String shineLabel) { this.shineLabel = shineLabel; }
}
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
@@ -28,4 +70,24 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
public void setOutFollows(List<String> outFollows) { this.outFollows = outFollows; }
public List<String> getInFollows() { return inFollows; }
public void setInFollows(List<String> inFollows) { this.inFollows = inFollows; }
public List<String> getOutParents() { return outParents; }
public void setOutParents(List<String> outParents) { this.outParents = outParents; }
public List<String> getInParents() { return inParents; }
public void setInParents(List<String> inParents) { this.inParents = inParents; }
public List<String> getOutChildren() { return outChildren; }
public void setOutChildren(List<String> outChildren) { this.outChildren = outChildren; }
public List<String> getInChildren() { return inChildren; }
public void setInChildren(List<String> inChildren) { this.inChildren = inChildren; }
public List<String> getOutSiblings() { return outSiblings; }
public void setOutSiblings(List<String> outSiblings) { this.outSiblings = outSiblings; }
public List<String> getInSiblings() { return inSiblings; }
public void setInSiblings(List<String> inSiblings) { this.inSiblings = inSiblings; }
public List<RelativeItem> getParents() { return parents; }
public void setParents(List<RelativeItem> parents) { this.parents = parents; }
public List<RelativeItem> getChildren() { return children; }
public void setChildren(List<RelativeItem> children) { this.children = children; }
public List<RelativeItem> getSiblings() { return siblings; }
public void setSiblings(List<RelativeItem> siblings) { this.siblings = siblings; }
public List<UserMarkItem> getAllUsers() { return allUsers; }
public void setAllUsers(List<UserMarkItem> allUsers) { this.allUsers = allUsers; }
}