diff --git a/VERSION.properties b/VERSION.properties index 337496b..e927a5c 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.11 -server.version=1.2.11 +client.version=1.2.12 +server.version=1.2.12 diff --git a/shine-UI/js/pages/network-view.js b/shine-UI/js/pages/network-view.js index bfee6af..f598645 100644 --- a/shine-UI/js/pages/network-view.js +++ b/shine-UI/js/pages/network-view.js @@ -1,6 +1,7 @@ import { renderHeader } from '../components/header.js'; import { authService, state } from '../state.js'; import { renderUserAvatar } from '../components/avatar-image.js'; +import { loadUserProfileCard } from '../services/user-connections.js'; export const pageMeta = { id: 'network-view', title: 'Связи' }; @@ -87,9 +88,94 @@ function getRelativeGenderMap(graph) { applyRelativeGender(map, graph?.parents); applyRelativeGender(map, graph?.children); applyRelativeGender(map, graph?.siblings); + applyRelativeGender(map, graph?.spouses); return map; } +function relativeRoleLabel(role, gender) { + const cleanGender = normalizeGender(gender); + if (role === 'parent') { + if (cleanGender === GENDER_MALE) return 'отец'; + if (cleanGender === GENDER_FEMALE) return 'мать'; + return 'родитель'; + } + if (role === 'child') { + if (cleanGender === GENDER_MALE) return 'сын'; + if (cleanGender === GENDER_FEMALE) return 'дочь'; + return 'потомок'; + } + if (role === 'sibling') { + if (cleanGender === GENDER_MALE) return 'брат'; + if (cleanGender === GENDER_FEMALE) return 'сестра'; + return 'брат/сестра'; + } + if (role === 'spouse') { + if (cleanGender === GENDER_MALE) return 'муж'; + if (cleanGender === GENDER_FEMALE) return 'жена'; + return 'жена/муж'; + } + return ''; +} + +function buildNameLines(firstName, lastName) { + const first = String(firstName || '').trim(); + const last = String(lastName || '').trim(); + if (first && last) { + const full = `${first} ${last}`.trim(); + if (full.length <= 20) return [full]; + return [first, last]; + } + if (first) return [first]; + if (last) return [last]; + return []; +} + +function applyNodeText(node, { + login, + firstName = '', + lastName = '', + role = 'friend', + gender = GENDER_UNKNOWN, + mark = null, +} = {}) { + const loginText = normalizeLogin(login); + const labelsWrap = node.querySelector('.node-label'); + const nameEl = node.querySelector('.node-name'); + const loginEl = node.querySelector('.node-login'); + const relationEl = node.querySelector('.node-relation'); + if (!(labelsWrap instanceof HTMLElement) || !(nameEl instanceof HTMLElement) || !(loginEl instanceof HTMLElement)) { + return; + } + + const nameLines = buildNameLines(firstName, lastName); + nameEl.innerHTML = ''; + if (nameLines.length) { + nameLines.forEach((line) => { + const lineEl = document.createElement('span'); + lineEl.className = 'node-name-line'; + lineEl.textContent = line; + nameEl.append(lineEl); + }); + labelsWrap.classList.remove('is-login-only'); + } else { + labelsWrap.classList.add('is-login-only'); + } + loginEl.textContent = loginText; + + const relLabel = relativeRoleLabel(role, gender); + if (relationEl instanceof HTMLElement) { + relationEl.textContent = relLabel; + relationEl.hidden = !relLabel; + } + + const metaParts = []; + if (mark?.officialLabel) metaParts.push(mark.officialLabel); + if (mark?.shineLabel) metaParts.push(mark.shineLabel); + if (relLabel) metaParts.push(`роль: ${relLabel}`); + const titleMain = nameLines.length ? `${nameLines.join(' ')} (${loginText})` : loginText; + node.title = metaParts.length ? `${titleMain}\n${metaParts.join(', ')}` : titleMain; +} + function spread(count, start, end) { if (count <= 0) return []; if (count === 1) return [(start + end) / 2]; @@ -99,7 +185,16 @@ function spread(count, start, end) { return out; } -function buildNodeElement({ login, kind = 'friend', isCenter = false, mark = null }) { +function buildNodeElement({ + login, + kind = 'friend', + isCenter = false, + mark = null, + role = 'friend', + gender = GENDER_UNKNOWN, + firstName = '', + lastName = '', +}) { const node = document.createElement('button'); node.type = 'button'; const classes = [ @@ -112,12 +207,6 @@ function buildNodeElement({ login, kind = 'friend', isCenter = false, mark = nul node.className = classes.join(' '); node.dataset.nodeLogin = login; - const metaParts = []; - if (mark?.officialLabel) metaParts.push(mark.officialLabel); - if (mark?.shineLabel) metaParts.push(mark.shineLabel); - const metaText = metaParts.join(', '); - node.title = metaText ? `${login}\n${metaText}` : login; - if (mark?.official) { const officialBadge = document.createElement('span'); officialBadge.className = 'node-badge-official'; @@ -131,10 +220,16 @@ function buildNodeElement({ login, kind = 'friend', isCenter = false, mark = nul size: 'node', title: login, })); + const label = document.createElement('span'); label.className = 'node-label'; - label.textContent = login; + label.innerHTML = ` + + + + `; node.append(label); + applyNodeText(node, { login, firstName, lastName, role, gender, mark }); return node; } @@ -148,6 +243,8 @@ function buildGraphModel(graph, centerLogin) { const inChildren = toSet(graph?.inChildren); const outSiblings = toSet(graph?.outSiblings); const inSiblings = toSet(graph?.inSiblings); + const outSpouses = toSet(graph?.outSpouses); + const inSpouses = toSet(graph?.inSpouses); const relativesGender = getRelativeGenderMap(graph); const allMarks = getMarkByLogin(graph?.allUsers); @@ -161,6 +258,8 @@ function buildGraphModel(graph, centerLogin) { ...(graph?.inChildren || []), ...(graph?.outSiblings || []), ...(graph?.inSiblings || []), + ...(graph?.outSpouses || []), + ...(graph?.inSpouses || []), ]).filter((entry) => normKey(entry) !== normKey(login)); const relations = allLogins.map((targetLogin) => { @@ -170,12 +269,15 @@ function buildGraphModel(graph, centerLogin) { const childIn = hasLogin(inParents, targetLogin); const siblingOut = hasLogin(outSiblings, targetLogin); const siblingIn = hasLogin(inSiblings, targetLogin); + const spouseOut = hasLogin(outSpouses, targetLogin); + const spouseIn = hasLogin(inSpouses, targetLogin); const friendOut = hasLogin(outFriends, targetLogin); const friendIn = hasLogin(inFriends, targetLogin); let role = 'friend'; if (parentOut || parentIn) role = 'parent'; else if (childOut || childIn) role = 'child'; + else if (spouseOut || spouseIn) role = 'spouse'; else if (siblingOut || siblingIn) role = 'sibling'; let forward = friendOut; @@ -186,6 +288,9 @@ function buildGraphModel(graph, centerLogin) { } else if (role === 'child') { forward = childOut; backward = childIn; + } else if (role === 'spouse') { + forward = spouseOut; + backward = spouseIn; } else if (role === 'sibling') { forward = siblingOut; backward = siblingIn; @@ -240,16 +345,19 @@ function layoutNodes(model) { isCenter: true, kind: 'center', relation: null, + gender: GENDER_UNKNOWN, mark: model.centerMark, }; const parents = sortByLogin(model.relations.filter((item) => item.role === 'parent')); const children = sortByLogin(model.relations.filter((item) => item.role === 'child')); + const spouses = sortByLogin(model.relations.filter((item) => item.role === 'spouse')); const siblings = sortByLogin(model.relations.filter((item) => item.role === 'sibling')); const friends = sortByLogin(model.relations.filter((item) => item.role === 'friend')); const parentSplit = splitByGender(parents); const childSplit = splitByGender(children); + const spouseSplit = splitByGender(spouses); const siblingSplit = splitByGender(siblings); const friendLeft = []; @@ -260,17 +368,20 @@ function layoutNodes(model) { }); const positioned = [ - ...positionRows(parentSplit.left, 28, 14, 30), - ...positionRows(parentSplit.right, 72, 14, 30), + ...positionRows(parentSplit.left, 28, 16, 28), + ...positionRows(parentSplit.right, 72, 16, 28), ...positionRows(parentSplit.center, 50, 10, 22), - ...positionRows(friendLeft, 12, 28, 72), - ...positionRows(friendRight, 88, 28, 72), - ...positionRows(siblingSplit.left, 30, 48, 70), - ...positionRows(siblingSplit.right, 70, 48, 70), - ...positionRows(siblingSplit.center, 50, 58, 74), - ...positionRows(childSplit.left, 28, 70, 90), - ...positionRows(childSplit.right, 72, 70, 90), - ...positionRows(childSplit.center, 50, 82, 94), + ...positionRows(friendLeft, 12, 30, 70), + ...positionRows(friendRight, 88, 30, 70), + ...positionRows(spouseSplit.left, 36, 38, 58), + ...positionRows(spouseSplit.right, 64, 38, 58), + ...positionRows(spouseSplit.center, 50, 40, 56), + ...positionRows(siblingSplit.left, 30, 46, 66), + ...positionRows(siblingSplit.right, 70, 46, 66), + ...positionRows(siblingSplit.center, 50, 54, 70), + ...positionRows(childSplit.left, 28, 68, 84), + ...positionRows(childSplit.right, 72, 68, 84), + ...positionRows(childSplit.center, 50, 78, 88), ]; const nodes = [centerNode]; @@ -286,6 +397,7 @@ function layoutNodes(model) { isCenter: false, kind: item.isRelative ? 'relative' : 'friend', relation: item.role, + gender: item.gender, mark: item.mark, }); edges.push({ @@ -401,90 +513,74 @@ export function render({ navigate }) { Односторонняя связь `; - let activeMenu = null; + const profileCardCache = new Map(); let centerLogin = state.session.login || ''; let redrawEdges = () => {}; let loadSeq = 0; - function closeNodeMenu() { - if (!activeMenu) return; - activeMenu.remove(); - activeMenu = null; + function profileInfoRoute(login) { + const cleanLogin = normalizeLogin(login); + if (!cleanLogin) return ''; + if (normKey(cleanLogin) === normKey(state.session.login)) return 'profile-view'; + return `user-profile-view/${encodeURIComponent(cleanLogin)}/network-view`; } - function openNodeMenu(node, login) { - closeNodeMenu(); - const menu = document.createElement('div'); - menu.className = 'node-menu card'; - menu.innerHTML = ` -
- - -
- `; - - const rect = node.getBoundingClientRect(); - const boardRect = board.getBoundingClientRect(); - const x = rect.left + rect.width / 2 - boardRect.left; - const y = rect.bottom - boardRect.top + 8; - - menu.style.left = `${Math.max(8, Math.min(x - 120, boardRect.width - 248))}px`; - menu.style.top = `${Math.max(8, Math.min(y, boardRect.height - 58))}px`; - - const infoBtn = menu.querySelector('[data-menu-action="show-info"]'); - const graphBtn = menu.querySelector('[data-menu-action="show-graph"]'); - infoBtn?.addEventListener('click', () => { - navigate(`user-profile-view/${encodeURIComponent(login)}/network-view`); - closeNodeMenu(); - }); - graphBtn?.addEventListener('click', async () => { - closeNodeMenu(); - await load(login); - }); - - board.append(menu); - activeMenu = menu; + function getProfileCardCached(login) { + const cleanLogin = normalizeLogin(login); + const key = normKey(cleanLogin); + if (!cleanLogin) return Promise.resolve(null); + if (!profileCardCache.has(key)) { + profileCardCache.set(key, loadUserProfileCard(cleanLogin).catch(() => null)); + } + return profileCardCache.get(key); } - function bindNodeInteraction(node, login, onLongPress) { - let timerId = 0; - let startX = 0; - let startY = 0; - let longPressTriggered = false; - - const clearTimer = () => { - if (!timerId) return; - window.clearTimeout(timerId); - timerId = 0; - }; - - node.addEventListener('pointerdown', (event) => { - if (event.button !== 0) return; - startX = event.clientX; - startY = event.clientY; - longPressTriggered = false; - clearTimer(); - timerId = window.setTimeout(async () => { - longPressTriggered = true; - closeNodeMenu(); - await onLongPress(login); - }, 500); + async function hydrateNodeProfiles(layout, nodeElements, requestId) { + const uniqueNodes = []; + const seen = new Set(); + layout.nodes.forEach((nodeModel) => { + const key = normKey(nodeModel.login); + if (!key || seen.has(key)) return; + seen.add(key); + uniqueNodes.push(nodeModel); }); - node.addEventListener('pointermove', (event) => { - if (!timerId) return; - const dx = Math.abs(event.clientX - startX); - const dy = Math.abs(event.clientY - startY); - if (dx > 8 || dy > 8) clearTimer(); + const cards = await Promise.all(uniqueNodes.map((nodeModel) => getProfileCardCached(nodeModel.login))); + if (requestId !== loadSeq) return; + + const cardByKey = new Map(); + cards.forEach((card) => { + const login = normalizeLogin(card?.login); + if (!login) return; + cardByKey.set(normKey(login), card); }); - node.addEventListener('pointerleave', clearTimer); - node.addEventListener('pointercancel', clearTimer); - node.addEventListener('pointerup', (event) => { - if (event.button !== 0) return; - clearTimer(); - if (longPressTriggered) return; - openNodeMenu(node, login); + layout.nodes.forEach((nodeModel) => { + const node = nodeElements.get(nodeModel.id); + if (!(node instanceof HTMLElement)) return; + const card = cardByKey.get(normKey(nodeModel.login)); + const cardGender = normalizeGender(card?.gender); + applyNodeText(node, { + login: nodeModel.login, + firstName: card?.firstName || '', + lastName: card?.lastName || '', + role: nodeModel.relation || 'friend', + gender: nodeModel.gender === GENDER_UNKNOWN ? cardGender : nodeModel.gender, + mark: nodeModel.mark || null, + }); + }); + + requestAnimationFrame(() => redrawEdges()); + } + + function bindNodeInteraction(node, nodeModel) { + node.addEventListener('click', () => { + if (nodeModel.isCenter) { + const routeTo = profileInfoRoute(nodeModel.login); + if (routeTo) navigate(routeTo); + return; + } + void load(nodeModel.login); }); } @@ -492,7 +588,6 @@ export function render({ navigate }) { const requestId = ++loadSeq; const targetCenter = normalizeLogin(nextCenterLogin || centerLogin || state.session.login); centerLogin = targetCenter; - closeNodeMenu(); note.textContent = 'Загрузка связей...'; try { @@ -513,33 +608,28 @@ export function render({ navigate }) { login: nodeModel.login, kind: nodeModel.kind, isCenter: nodeModel.isCenter, + role: nodeModel.relation || 'friend', + gender: nodeModel.gender, mark: nodeModel.mark, }); node.style.left = `${nodeModel.x}%`; node.style.top = `${nodeModel.y}%`; board.append(node); nodeElements.set(nodeModel.id, node); - if (!nodeModel.isCenter) bindNodeInteraction(node, nodeModel.login, load); + bindNodeInteraction(node, nodeModel); }); redrawEdges = () => renderEdges(svg, board, nodeElements, layout.edges); requestAnimationFrame(() => redrawEdges()); + void hydrateNodeProfiles(layout, nodeElements, requestId); - note.textContent = 'Тап по узлу: меню «Показать информацию» или «Показать связи». Долгое нажатие: сделать узел центром.'; + note.textContent = 'Тап по узлу: нецентральный узел станет центром. Тап по центральному узлу: открыть профиль.'; } catch (error) { if (requestId !== loadSeq) return; note.textContent = `Ошибка загрузки связей: ${error?.message || 'unknown'}`; } } - const outsideTapHandler = (event) => { - if (!activeMenu) return; - if (!(event.target instanceof Node)) return; - if (activeMenu.contains(event.target)) return; - closeNodeMenu(); - }; - document.addEventListener('pointerdown', outsideTapHandler, true); - const onResize = () => redrawEdges(); window.addEventListener('resize', onResize); @@ -550,19 +640,10 @@ export function render({ navigate }) { } screen.cleanup = () => { - document.removeEventListener('pointerdown', outsideTapHandler, true); window.removeEventListener('resize', onResize); if (observer) observer.disconnect(); }; - board.addEventListener('pointerdown', (event) => { - const target = event.target; - if (!(target instanceof Element)) return; - if (target.closest('.node')) return; - if (target.closest('.node-menu')) return; - closeNodeMenu(); - }); - load(); screen.append(renderHeader({ title: 'Связи' }), legend, board, note); return screen; diff --git a/shine-UI/js/pages/profile-view.js b/shine-UI/js/pages/profile-view.js index 32c6d2b..cf7e1fc 100644 --- a/shine-UI/js/pages/profile-view.js +++ b/shine-UI/js/pages/profile-view.js @@ -41,6 +41,7 @@ const GENDER_OPTIONS = Object.freeze([ const RELATIVE_RELATION_OPTIONS = Object.freeze([ { value: 'parent', label: 'Родитель (мать/отец по полу)' }, { value: 'child', label: 'Ребёнок (сын/дочь по полу)' }, + { value: 'spouse', label: 'Жена / Муж (по полу)' }, { value: 'sibling', label: 'Брат или сестра (по полу)' }, { value: 'close_friend', label: 'Близкий друг' }, ]); @@ -69,6 +70,11 @@ function relationAccusativeLabel(type, targetGender) { if (gender === PROFILE_GENDER_FEMALE) return 'сестру'; return 'брата/сестру'; } + if (type === 'spouse') { + if (gender === PROFILE_GENDER_MALE) return 'мужа'; + if (gender === PROFILE_GENDER_FEMALE) return 'жену'; + return 'жену/мужа'; + } return 'близкого друга'; } @@ -134,7 +140,7 @@ export function render({ navigate }) { relativesCard.innerHTML = `
Близкие родственники
- Добавьте связь: родитель, ребёнок, брат/сестра или близкий друг. + Добавьте связь: родитель, ребёнок, жена/муж, брат/сестра или близкий друг. Формулировка (мать/отец, брат/сестра) определяется по полу выбранного пользователя.
diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index b92e8a2..6c9917c 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -50,6 +50,7 @@ const CONNECTION_SUBTYPES = Object.freeze({ friend: { on: 10, off: 11 }, contact: { on: 20, off: 21 }, follow: { on: 30, off: 31 }, + spouse: { on: 40, off: 41 }, parent: { on: 50, off: 51 }, child: { on: 52, off: 53 }, sibling: { on: 54, off: 55 }, diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 9fe84af..ba4649c 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -1388,7 +1388,7 @@ textarea.input { .node { position: absolute; transform: translate(-50%, -50%); - width: 90px; + width: 126px; border: 0; background: transparent; padding: 0; @@ -1464,9 +1464,49 @@ textarea.input { } .node-label { - font-size: 11px; + display: grid; + gap: 1px; + margin-top: 1px; + font-size: 10px; color: #d6e2ff; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.28); + line-height: 1.12; +} + +.node-label.is-login-only .node-name { + display: none; +} + +.node-name { + display: grid; + gap: 1px; + color: #f2f6ff; +} + +.node-name-line { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.node-login { + display: block; + color: #c8dafd; + font-size: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.node-relation { + display: block; + color: #ffd5b3; + font-size: 10px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .node:focus-visible .node-dot, diff --git a/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java b/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java index cad0147..7f80491 100644 --- a/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java +++ b/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java @@ -93,6 +93,11 @@ public final class MsgSubType { /** Отписаться (unfollow). */ public static final short CONNECTION_UNFOLLOW = 31; + /** Добавить связь "жена/муж". */ + public static final short CONNECTION_SPOUSE = 40; + /** Удалить связь "жена/муж". */ + public static final short CONNECTION_UNSPOUSE = 41; + /** Добавить связь "родитель". */ public static final short CONNECTION_PARENT = 50; /** Удалить связь "родитель". */ diff --git a/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java b/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java index cbac35a..5b037d8 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java @@ -185,6 +185,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL || v == (MsgSubType.CONNECTION_UNCONTACT & 0xFFFF) || v == (MsgSubType.CONNECTION_FOLLOW & 0xFFFF) || v == (MsgSubType.CONNECTION_UNFOLLOW & 0xFFFF) + || v == (MsgSubType.CONNECTION_SPOUSE & 0xFFFF) + || v == (MsgSubType.CONNECTION_UNSPOUSE & 0xFFFF) || v == (MsgSubType.CONNECTION_PARENT & 0xFFFF) || v == (MsgSubType.CONNECTION_UNPARENT & 0xFFFF) || v == (MsgSubType.CONNECTION_CHILD & 0xFFFF) 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 f93736a..3ca5e5b 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -54,6 +54,9 @@ public final class DatabaseInitializer { public static final short CONNECTION_FOLLOW = 30; public static final short CONNECTION_UNFOLLOW = 31; + public static final short CONNECTION_SPOUSE = 40; + public static final short CONNECTION_UNSPOUSE = 41; + public static final short CONNECTION_PARENT = 50; public static final short CONNECTION_UNPARENT = 51; diff --git a/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java b/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java index b5b9312..f4ff8a4 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java @@ -194,6 +194,7 @@ public final class DatabaseTriggersInstaller { int FRIEND = (int) DatabaseInitializer.CONNECTION_FRIEND; int CONTACT = (int) DatabaseInitializer.CONNECTION_CONTACT; int FOLLOW = (int) DatabaseInitializer.CONNECTION_FOLLOW; + int SPOUSE = (int) DatabaseInitializer.CONNECTION_SPOUSE; int PARENT = (int) DatabaseInitializer.CONNECTION_PARENT; int CHILD = (int) DatabaseInitializer.CONNECTION_CHILD; int SIBLING = (int) DatabaseInitializer.CONNECTION_SIBLING; @@ -201,6 +202,7 @@ public final class DatabaseTriggersInstaller { int UNFRIEND = (int) DatabaseInitializer.CONNECTION_UNFRIEND; int UNCONTACT = (int) DatabaseInitializer.CONNECTION_UNCONTACT; int UNFOLLOW = (int) DatabaseInitializer.CONNECTION_UNFOLLOW; + int UNSPOUSE = (int) DatabaseInitializer.CONNECTION_UNSPOUSE; int UNPARENT = (int) DatabaseInitializer.CONNECTION_UNPARENT; int UNCHILD = (int) DatabaseInitializer.CONNECTION_UNCHILD; int UNSIBLING = (int) DatabaseInitializer.CONNECTION_UNSIBLING; @@ -210,7 +212,7 @@ public final class DatabaseTriggersInstaller { AFTER INSERT ON blocks WHEN NEW.msg_type = 3 BEGIN - -- FRIEND/CONTACT/FOLLOW/PARENT/CHILD/SIBLING: + -- FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING: -- 1) если записи нет — создаём INSERT OR IGNORE INTO connections_state ( login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash @@ -231,7 +233,7 @@ public final class DatabaseTriggersInstaller { NEW.to_bch_name, NEW.to_block_number, NEW.to_block_hash - WHERE NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d) + WHERE NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d) AND COALESCE( NEW.to_login, CASE @@ -262,7 +264,7 @@ public final class DatabaseTriggersInstaller { ELSE NULL END ) - AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d) + AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d) AND COALESCE( NEW.to_login, CASE @@ -275,7 +277,7 @@ public final class DatabaseTriggersInstaller { ) IS NOT NULL AND NEW.to_bch_name IS NOT NULL; - -- UNFRIEND/UNCONTACT/UNFOLLOW/UNPARENT/UNCHILD/UNSIBLING: + -- UNFRIEND/UNCONTACT/UNFOLLOW/UNSPOUSE/UNPARENT/UNCHILD/UNSIBLING: -- удаляем соответствующее "позитивное" состояние DELETE FROM connections_state WHERE login = NEW.login @@ -296,6 +298,7 @@ public final class DatabaseTriggersInstaller { WHEN %d THEN %d WHEN %d THEN %d WHEN %d THEN %d + WHEN %d THEN %d ELSE rel_type END AND COALESCE( @@ -308,20 +311,21 @@ public final class DatabaseTriggersInstaller { ELSE NULL END ) IS NOT NULL - AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d); + AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d); END; """.formatted( - FRIEND, CONTACT, FOLLOW, PARENT, CHILD, SIBLING, - FRIEND, CONTACT, PARENT, CHILD, SIBLING, + FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, + FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, UNFRIEND, FRIEND, UNCONTACT, CONTACT, UNFOLLOW, FOLLOW, + UNSPOUSE, SPOUSE, UNPARENT, PARENT, UNCHILD, CHILD, UNSIBLING, SIBLING, - UNFRIEND, UNCONTACT, UNFOLLOW, UNPARENT, UNCHILD, UNSIBLING + UNFRIEND, UNCONTACT, UNFOLLOW, UNSPOUSE, UNPARENT, UNCHILD, UNSIBLING )); } diff --git a/shine-server-db/src/main/java/shine/db/MsgSubType.java b/shine-server-db/src/main/java/shine/db/MsgSubType.java index 5c92f91..62f9093 100644 --- a/shine-server-db/src/main/java/shine/db/MsgSubType.java +++ b/shine-server-db/src/main/java/shine/db/MsgSubType.java @@ -39,9 +39,9 @@ public final class MsgSubType { /* ===================== CONNECTION (msg_type=3) ===================== */ /** - * Совпадает с ConnectionBody: - * SET: CLOSE_FRIEND(=FRIEND)=10, CONTACT=20, FOLLOW=30, PARENT=50, CHILD=52, SIBLING=54 - * UNSET: UNCLOSE_FRIEND(=UNFRIEND)=11, UNCONTACT=21, UNFOLLOW=31, UNPARENT=51, UNCHILD=53, UNSIBLING=55 + * Совпадает с ConnectionBody: + * SET: CLOSE_FRIEND(=FRIEND)=10, CONTACT=20, FOLLOW=30, SPOUSE=40, PARENT=50, CHILD=52, SIBLING=54 + * UNSET: UNCLOSE_FRIEND(=UNFRIEND)=11, UNCONTACT=21, UNFOLLOW=31, UNSPOUSE=41, UNPARENT=51, UNCHILD=53, UNSIBLING=55 */ /** Добавить в близкие друзья (close friend). */ @@ -68,6 +68,12 @@ public final class MsgSubType { /** Отписаться (unfollow). */ public static final short CONNECTION_UNFOLLOW = 31; + /** Добавить связь "жена/муж". */ + public static final short CONNECTION_SPOUSE = 40; + + /** Удалить связь "жена/муж". */ + public static final short CONNECTION_UNSPOUSE = 41; + /** Добавить связь "родитель". */ public static final short CONNECTION_PARENT = 50; @@ -92,8 +98,6 @@ public final class MsgSubType { public static final short USER_PARAM_TEXT_TEXT = 1; /* ===================== РЕЗЕРВ НА БУДУЩЕЕ ===================== */ - // Если позже захочешь BLOCK/UNBLOCK — лучше добавить НОВЫЕ значения, - // не трогая 10/20/30 и 11/21/31 (например, 40/41). - // public static final short CONNECTION_BLOCK = 40; - // public static final short CONNECTION_UNBLOCK = 41; + // Если позже захочешь BLOCK/UNBLOCK — лучше добавить новые значения, + // не трогая уже занятые коды. } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/Net_GetUserConnectionsGraph_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/Net_GetUserConnectionsGraph_Handler.java index 15a496e..81830a8 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/Net_GetUserConnectionsGraph_Handler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/Net_GetUserConnectionsGraph_Handler.java @@ -46,6 +46,8 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { List inContacts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT); List outFollows = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW); List inFollows = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW); + List outSpouses = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SPOUSE); + List inSpouses = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SPOUSE); List outParents = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT); List inParents = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT); List outChildren = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD); @@ -56,9 +58,10 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { LinkedHashSet allLogins = new LinkedHashSet<>(); allLogins.add(canonicalLogin); addAllLogins(allLogins, outFriends, inFriends, outContacts, inContacts, outFollows, inFollows, - outParents, inParents, outChildren, inChildren, outSiblings, inSiblings); + outSpouses, inSpouses, outParents, inParents, outChildren, inChildren, outSiblings, inSiblings); Map metaByLogin = loadUserMeta(c, allLogins); + List spouseLogins = mergeUnique(outSpouses, inSpouses); List parentLogins = mergeUnique(outParents, inChildren); List childLogins = mergeUnique(outChildren, inParents); List siblingLogins = mergeUnique(outSiblings, inSiblings); @@ -74,6 +77,8 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { resp.setInContacts(inContacts); resp.setOutFollows(outFollows); resp.setInFollows(inFollows); + resp.setOutSpouses(outSpouses); + resp.setInSpouses(inSpouses); resp.setOutParents(outParents); resp.setInParents(inParents); resp.setOutChildren(outChildren); @@ -83,6 +88,7 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { resp.setParents(toRelativeItems(parentLogins, metaByLogin)); resp.setChildren(toRelativeItems(childLogins, metaByLogin)); resp.setSiblings(toRelativeItems(siblingLogins, metaByLogin)); + resp.setSpouses(toRelativeItems(spouseLogins, metaByLogin)); resp.setAllUsers(toUserMarkItems(allLogins, metaByLogin)); return resp; } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/entyties/Net_GetUserConnectionsGraph_Response.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/entyties/Net_GetUserConnectionsGraph_Response.java index 6a19fae..33b0be4 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/entyties/Net_GetUserConnectionsGraph_Response.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/connections/entyties/Net_GetUserConnectionsGraph_Response.java @@ -13,6 +13,8 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { private List inContacts = new ArrayList<>(); private List outFollows = new ArrayList<>(); private List inFollows = new ArrayList<>(); + private List outSpouses = new ArrayList<>(); + private List inSpouses = new ArrayList<>(); private List outParents = new ArrayList<>(); private List inParents = new ArrayList<>(); private List outChildren = new ArrayList<>(); @@ -22,6 +24,7 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { private List parents = new ArrayList<>(); private List children = new ArrayList<>(); private List siblings = new ArrayList<>(); + private List spouses = new ArrayList<>(); private List allUsers = new ArrayList<>(); public static class AvatarItem { @@ -83,6 +86,10 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { public void setOutFollows(List outFollows) { this.outFollows = outFollows; } public List getInFollows() { return inFollows; } public void setInFollows(List inFollows) { this.inFollows = inFollows; } + public List getOutSpouses() { return outSpouses; } + public void setOutSpouses(List outSpouses) { this.outSpouses = outSpouses; } + public List getInSpouses() { return inSpouses; } + public void setInSpouses(List inSpouses) { this.inSpouses = inSpouses; } public List getOutParents() { return outParents; } public void setOutParents(List outParents) { this.outParents = outParents; } public List getInParents() { return inParents; } @@ -101,6 +108,8 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { public void setChildren(List children) { this.children = children; } public List getSiblings() { return siblings; } public void setSiblings(List siblings) { this.siblings = siblings; } + public List getSpouses() { return spouses; } + public void setSpouses(List spouses) { this.spouses = spouses; } public List getAllUsers() { return allUsers; } public void setAllUsers(List allUsers) { this.allUsers = allUsers; } }