diff --git a/AGENTS.md b/AGENTS.md index 089bcf0..417aa37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,14 @@ - Дополнительно обязательно вести `Dev_Docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение. - Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён. - Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять). +- Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`. +- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока. + +## Документация личных сообщений (DM) +- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`. +- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`. +- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`. +- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии. ## Версионирование - Единый файл версий проекта: `VERSION.properties` (в корне репозитория). diff --git a/Dev_Docs/API/04_Add_Block_to_Blockchain_API.md b/Dev_Docs/API/04_Add_Block_to_Blockchain_API.md index 3447eeb..5cf882f 100644 --- a/Dev_Docs/API/04_Add_Block_to_Blockchain_API.md +++ b/Dev_Docs/API/04_Add_Block_to_Blockchain_API.md @@ -107,6 +107,20 @@ - `CONNECTION_UNCONTACT (21)` - `CONNECTION_FOLLOW (30)` - `CONNECTION_UNFOLLOW (31)` + - `CONNECTION_SPOUSE (40)` + - `CONNECTION_UNSPOUSE (41)` + - `CONNECTION_PARENT (50)` + - `CONNECTION_UNPARENT (51)` + - `CONNECTION_CHILD (52)` + - `CONNECTION_UNCHILD (53)` + - `CONNECTION_SIBLING (54)` + - `CONNECTION_UNSIBLING (55)` + - `CONNECTION_KNOWN_PERSON (60)` + - `CONNECTION_UNKNOWN_PERSON (61)` + - `CONNECTION_SHINE_CONFIRMED (70)` + - `CONNECTION_SHINE_UNCONFIRMED (71)` + - `CONNECTION_SHINE_SEEN (74)` + - `CONNECTION_SHINE_UNSEEN (75)` 5. **USER_PARAM (type=4)** - `USER_PARAM_TEXT_TEXT (1)` diff --git a/Dev_Docs/Blockchain/13_CONNECTION_Blocks.md b/Dev_Docs/Blockchain/13_CONNECTION_Blocks.md index bbfe25a..b2991b5 100644 --- a/Dev_Docs/Blockchain/13_CONNECTION_Blocks.md +++ b/Dev_Docs/Blockchain/13_CONNECTION_Blocks.md @@ -4,20 +4,16 @@ CONNECTION-тип описывает социальные связи и подп ## Подтипы -1. `subType=10` — `CONNECTION_FRIEND` -2. `subType=11` — `CONNECTION_UNFRIEND` -3. `subType=20` — `CONNECTION_CONTACT` -4. `subType=21` — `CONNECTION_UNCONTACT` -5. `subType=30` — `CONNECTION_FOLLOW` -6. `subType=31` — `CONNECTION_UNFOLLOW` -7. `subType=40` — `CONNECTION_SPOUSE` -8. `subType=41` — `CONNECTION_UNSPOUSE` -9. `subType=50` — `CONNECTION_PARENT` -10. `subType=51` — `CONNECTION_UNPARENT` -11. `subType=52` — `CONNECTION_CHILD` -12. `subType=53` — `CONNECTION_UNCHILD` -13. `subType=54` — `CONNECTION_SIBLING` -14. `subType=55` — `CONNECTION_UNSIBLING` +• `10/11` — `close_friend / unclose_friend` (близкий друг) +• `20/21` — `contact / uncontact` (контакт) +• `30/31` — `follow / unfollow` (подписан) +• `40/41` — `spouse / unspouse` (супруг/супруга) +• `50/51` — `parent / unparent` (родитель) +• `52/53` — `child / unchild` (ребёнок) +• `54/55` — `sibling / unsibling` (брат/сестра) +• `60/61` — `known_person / unknown_person` (знаю этого человека) +• `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий) +• `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим) ## Общий формат payload diff --git a/Dev_Docs/Blockchain/CHANGELOG.md b/Dev_Docs/Blockchain/CHANGELOG.md index c168468..ce58f2e 100644 --- a/Dev_Docs/Blockchain/CHANGELOG.md +++ b/Dev_Docs/Blockchain/CHANGELOG.md @@ -1,5 +1,13 @@ # История изменений документации блокчейна +## 2026-05-20 11:34:17 +0300 +- Базовый коммит-ориентир: `a53444b`. +- В `13_CONNECTION_Blocks.md` добавлены новые CONNECTION подтипы: + - `60/61` — `known_person / unknown_person` (знаю этого человека); + - `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий); + - `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим). +- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`. + ## 2026-05-19 20:30:21 +0300 - Базовый коммит-ориентир: `7986184`. - Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения. diff --git a/Dev_Docs/Pending_Features/2026-05-20_1134_connection-shine-known-codes.md b/Dev_Docs/Pending_Features/2026-05-20_1134_connection-shine-known-codes.md new file mode 100644 index 0000000..3124883 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-05-20_1134_connection-shine-known-codes.md @@ -0,0 +1,23 @@ +## Краткое описание +Добавлены новые типы connection-связей в блокчейне и API: +- `known_person` (`60/61`) +- `shine_confirmed` (`70/71`) +- `shine_seen` (`74/75`) + +## Что проверять +1. `AddBlock` принимает новые `msg_sub_type` для `type=3`. +2. Связи корректно попадают в `connections_state`: + - ON создаёт/обновляет запись; + - OFF удаляет запись соответствующего ON-типа. +3. `GetUserConnectionsGraph` возвращает новые поля: + - `outKnownPersons`, `inKnownPersons` + - `outShineConfirmed`, `inShineConfirmed` + - `outShineSeen`, `inShineSeen` +4. Клиент `setUserRelation` принимает `kind`: + - `known_person`, `shine_confirmed`, `shine_seen`. + +## Ожидаемый результат +Новые связи работают как обычные ON/OFF relation-типы, но не ломают текущие friend/contact/follow и остальные существующие связи. + +## Статус +`pending` diff --git a/Dev_Docs/Pending_Features/2026-05-20_1221_profile-opinion-ui-and-order.md b/Dev_Docs/Pending_Features/2026-05-20_1221_profile-opinion-ui-and-order.md new file mode 100644 index 0000000..9651779 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-05-20_1221_profile-opinion-ui-and-order.md @@ -0,0 +1,25 @@ +## Краткое описание +Перестроен блок связей в профиле чужого пользователя и добавлен UI для одностороннего "мнения" (`known_person` / `shine_confirmed` / `shine_seen`) с взаимным исключением на уровне UI. + +## Что проверять +1. Порядок базовых строк в профиле: + - Контакт + - Близкий друг + - Подписка +2. Под этими строками отображается блок мнений: + - при отсутствии мнения кнопка `Добавить связь`; + - при наличии мнения кнопка `Изменить связи`; + - показываются текстовые формулировки для активного мнения. +3. В модальном меню: + - варианты добавления (синие); + - `Убрать мнение` (красная). +4. При смене мнения отправляется последовательность: + - OFF старой связи, + - ON новой связи. +5. Для новых мнений показываются только исходящие (`out*`) оценки текущего пользователя (односторонняя логика). + +## Ожидаемый результат +Пользователь управляет одним активным мнением через UI, состояние читается корректно и не ломает существующие friend/contact/follow кнопки. + +## Статус +`pending` diff --git a/VERSION.properties b/VERSION.properties index 5e43199..e40f564 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.78 -server.version=1.2.72 +client.version=1.2.79 +server.version=1.2.73 diff --git a/shine-UI/js/pages/user-profile-view.js b/shine-UI/js/pages/user-profile-view.js index a68b1b8..818f9dc 100644 --- a/shine-UI/js/pages/user-profile-view.js +++ b/shine-UI/js/pages/user-profile-view.js @@ -28,28 +28,28 @@ function genderText(value) { } function relationButtonLabel(kind, flags) { - if (kind === 'follow') return flags.outFollow ? 'Отписаться' : 'Подписаться'; + if (kind === 'contact') return flags.outContact ? 'Убрать из контактов' : 'Добавить в контакты'; if (kind === 'friend') return flags.outFriend ? 'Убрать из близких друзей' : 'Добавить в близкие друзья'; - return flags.outContact ? 'Убрать из контактов' : 'Добавить в контакты'; + return flags.outFollow ? 'Отписаться' : 'Подписаться'; } function relationNextState(kind, flags) { - if (kind === 'follow') return !flags.outFollow; + if (kind === 'contact') return !flags.outContact; if (kind === 'friend') return !flags.outFriend; - return !flags.outContact; + return !flags.outFollow; } function relationConfirmLabel(kind) { - if (kind === 'follow') return 'подписку'; + if (kind === 'contact') return 'контакт'; if (kind === 'friend') return 'статус близкого друга'; - return 'контакт'; + return 'подписку'; } function relationStateText(kind, flags) { - if (kind === 'follow') { - if (flags.outFollow && flags.inFollow) return 'Вы взаимно подписаны.'; - if (flags.outFollow) return 'Вы подписаны на этот профиль.'; - if (flags.inFollow) return 'Этот профиль подписан на вас.'; + if (kind === 'contact') { + if (flags.outContact && flags.inContact) return 'Вы обменялись контактами.'; + if (flags.outContact) return 'Вы добавили этот профиль в контакты.'; + if (flags.inContact) return 'Этот профиль добавил вас в контакты.'; return ''; } if (kind === 'friend') { @@ -58,12 +58,52 @@ function relationStateText(kind, flags) { if (flags.inFriend) return 'Этот профиль считает вас близким другом.'; return ''; } - if (flags.outContact && flags.inContact) return 'Вы обменялись контактами.'; - if (flags.outContact) return 'Вы добавили этот профиль в контакты.'; - if (flags.inContact) return 'Этот профиль добавил вас в контакты.'; + if (flags.outFollow && flags.inFollow) return 'Вы взаимно подписаны.'; + if (flags.outFollow) return 'Вы подписаны на этот профиль.'; + if (flags.inFollow) return 'Этот профиль подписан на вас.'; return ''; } +function opinionItemsFromFlags(flags) { + const items = []; + if (flags.outShineSeen) { + items.push({ + kind: 'shine_seen', + text: 'вы утверждаете, что очень мало знаете этого человека, но вы видели его сияющим, и всё, что вы о нём знаете, подтверждает это', + label: 'видел сияющим', + }); + } + if (flags.outShineConfirmed) { + items.push({ + kind: 'shine_confirmed', + text: 'вы утверждаете, что достаточно хорошо знаете этого человека и точно уверены, что этот человек сияющий', + label: 'точно сияющий', + }); + } + if (flags.outKnownPerson) { + items.push({ + kind: 'known_person', + text: 'вы утверждаете, что просто знаете этого человека', + label: 'просто знаю', + }); + } + return items; +} + +function resolveActiveOpinionKind(flags) { + if (flags.outShineSeen) return 'shine_seen'; + if (flags.outShineConfirmed) return 'shine_confirmed'; + if (flags.outKnownPerson) return 'known_person'; + return ''; +} + +function opinionLabelByKind(kind) { + if (kind === 'shine_seen') return 'мало знаком, но видел сияющим'; + if (kind === 'shine_confirmed') return 'точно уверен, что сияющий'; + if (kind === 'known_person') return 'просто знаю человека'; + return kind; +} + function renderIdentity(card) { const lines = buildIdentityLines({ login: card.login, @@ -109,10 +149,12 @@ function renderReadOnlyBadges(card) { function renderRelations(flags) { const rows = [ - { kind: 'follow', text: relationStateText('follow', flags), button: relationButtonLabel('follow', flags) }, - { kind: 'friend', text: relationStateText('friend', flags), button: relationButtonLabel('friend', flags) }, { kind: 'contact', text: relationStateText('contact', flags), button: relationButtonLabel('contact', flags) }, + { kind: 'friend', text: relationStateText('friend', flags), button: relationButtonLabel('friend', flags) }, + { kind: 'follow', text: relationStateText('follow', flags), button: relationButtonLabel('follow', flags) }, ]; + const opinionItems = opinionItemsFromFlags(flags); + const hasOpinion = opinionItems.length > 0; return `
@@ -122,10 +164,65 @@ function renderRelations(flags) {
`).join('')} +
+
+ ${opinionItems.map((item) => ` +
${escapeHtml(item.text)}
+ `).join('')} +
+
Добавьте одну из этих трёх формулировок.
+
+
+ ${hasOpinion ? 'Мнение уже добавлено.' : 'Пока нет дополнительной связи.'} + +
`; } +function openOpinionMenuModal({ flags, onApply }) { + const root = document.getElementById('modal-root'); + if (!root) return; + const activeKind = resolveActiveOpinionKind(flags); + const items = [ + { kind: 'known_person', title: 'просто знаю человека' }, + { kind: 'shine_confirmed', title: 'точно уверен, что сияющий' }, + { kind: 'shine_seen', title: 'мало знаком, но видел сияющим' }, + ]; + const rowsHtml = items + .filter((item) => item.kind !== activeKind) + .map((item) => ``) + .join(''); + const removeHtml = activeKind + ? `` + : ''; + + root.innerHTML = ` + + `; + + const close = () => { root.innerHTML = ''; }; + root.querySelector('#user-opinion-modal-close')?.addEventListener('click', close); + root.querySelector('#user-opinion-modal')?.addEventListener('click', (event) => { + if (event.target?.id === 'user-opinion-modal') close(); + }); + root.querySelectorAll('[data-opinion-mode]').forEach((btn) => { + btn.addEventListener('click', async () => { + const nextKind = String(btn.getAttribute('data-opinion-kind') || '').trim(); + const mode = String(btn.getAttribute('data-opinion-mode') || '').trim(); + close(); + if (!nextKind) return; + await onApply({ mode, nextKind, activeKind }); + }); + }); +} + function renderReadOnlyParams(card) { const rows = [ { label: 'Имя', value: card.firstName }, @@ -179,14 +276,17 @@ export function render({ navigate, route }) { const followBtn = body.querySelector('[data-relation-action="follow"]'); const friendBtn = body.querySelector('[data-relation-action="friend"]'); const contactBtn = body.querySelector('[data-relation-action="contact"]'); - if (!followBtn || !friendBtn || !contactBtn || !currentFlags) return; + const opinionBtn = body.querySelector('[data-relation-action="opinion-menu"]'); + if (!followBtn || !friendBtn || !contactBtn || !opinionBtn || !currentFlags) return; const isSelf = currentCard && currentCard.login.toLowerCase() === sessionLogin.toLowerCase(); - followBtn.textContent = relationButtonLabel('follow', currentFlags); - friendBtn.textContent = relationButtonLabel('friend', currentFlags); contactBtn.textContent = relationButtonLabel('contact', currentFlags); - followBtn.disabled = Boolean(isSelf); - friendBtn.disabled = Boolean(isSelf); + friendBtn.textContent = relationButtonLabel('friend', currentFlags); + followBtn.textContent = relationButtonLabel('follow', currentFlags); contactBtn.disabled = Boolean(isSelf); + friendBtn.disabled = Boolean(isSelf); + followBtn.disabled = Boolean(isSelf); + opinionBtn.textContent = opinionItemsFromFlags(currentFlags).length ? 'Изменить связи' : 'Добавить связь'; + opinionBtn.disabled = Boolean(isSelf); } async function refresh() { @@ -243,6 +343,14 @@ export function render({ navigate, route }) { return; } + if (kind === 'opinion-menu') { + openOpinionMenuModal({ + flags: currentFlags, + onApply: onOpinionApply, + }); + return; + } + const nextEnabled = relationNextState(kind, currentFlags); const confirmed = window.confirm( `Изменить ${relationConfirmLabel(kind)} с пользователем ${currentCard.login}?\n` + @@ -271,13 +379,60 @@ export function render({ navigate, route }) { } } + async function onOpinionApply({ mode, nextKind, activeKind }) { + if (isBusy || !currentCard || !currentFlags) return; + if (!sessionLogin) { + window.alert('Для изменения связей нужен активный вход.'); + return; + } + if (!state.session.storagePwdInMemory) { + window.alert('Нет storagePwd в памяти сессии. Выполните вход заново.'); + return; + } + + const confirmed = window.confirm(`Изменить мнение о пользователе ${currentCard.login}?`); + if (!confirmed) return; + + isBusy = true; + status.className = 'status-line'; + status.textContent = 'Сохранение отношения в блокчейн...'; + + try { + if (activeKind) { + await authService.setUserRelation({ + login: sessionLogin, + toLogin: currentCard.login, + kind: activeKind, + enabled: false, + storagePwd: state.session.storagePwdInMemory, + }); + } + + if (mode === 'set') { + await authService.setUserRelation({ + login: sessionLogin, + toLogin: currentCard.login, + kind: nextKind, + enabled: true, + storagePwd: state.session.storagePwdInMemory, + }); + } + await refresh(); + } catch (error) { + status.className = 'status-line is-unavailable'; + status.textContent = `Ошибка изменения связи: ${error.message || 'unknown'}`; + window.alert(`Не удалось изменить связь: ${error.message || 'unknown'}`); + isBusy = false; + } + } + body.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; const actionBtn = target.closest('[data-relation-action]'); const kind = String(actionBtn?.getAttribute('data-relation-action') || ''); if (!kind) return; - onRelationAction(kind); + void onRelationAction(kind); }); refresh(); diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index ebbe710..e5544cc 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -61,6 +61,9 @@ const CONNECTION_SUBTYPES = Object.freeze({ parent: { on: 50, off: 51 }, child: { on: 52, off: 53 }, sibling: { on: 54, off: 55 }, + known_person: { on: 60, off: 61 }, + shine_confirmed: { on: 70, off: 71 }, + shine_seen: { on: 74, off: 75 }, }); function normalizeServerUrl(url) { diff --git a/shine-UI/js/services/user-connections.js b/shine-UI/js/services/user-connections.js index e4d3a79..658b76b 100644 --- a/shine-UI/js/services/user-connections.js +++ b/shine-UI/js/services/user-connections.js @@ -75,6 +75,12 @@ async function buildRelationsModel(login) { inChildren: [], outSiblings: [], inSiblings: [], + outKnownPersons: [], + inKnownPersons: [], + outShineConfirmed: [], + inShineConfirmed: [], + outShineSeen: [], + inShineSeen: [], }; } @@ -117,6 +123,12 @@ async function buildRelationsModel(login) { inChildren: readArray(graph, 'inChildren') || [], outSiblings: readArray(graph, 'outSiblings') || [], inSiblings: readArray(graph, 'inSiblings') || [], + outKnownPersons: readArray(graph, 'outKnownPersons') || [], + inKnownPersons: readArray(graph, 'inKnownPersons') || [], + outShineConfirmed: readArray(graph, 'outShineConfirmed') || [], + inShineConfirmed: readArray(graph, 'inShineConfirmed') || [], + outShineSeen: readArray(graph, 'outShineSeen') || [], + inShineSeen: readArray(graph, 'inShineSeen') || [], }; } @@ -151,6 +163,12 @@ export async function loadCurrentRelations() { inChildren: [], outSiblings: [], inSiblings: [], + outKnownPersons: [], + inKnownPersons: [], + outShineConfirmed: [], + inShineConfirmed: [], + outShineSeen: [], + inShineSeen: [], }; } return buildRelationsModel(login); @@ -170,6 +188,12 @@ export function relationFlagsForTarget(relations, targetLogin) { inChild: listContainsLogin(relations?.inChildren, targetLogin), outSibling: listContainsLogin(relations?.outSiblings, targetLogin), inSibling: listContainsLogin(relations?.inSiblings, targetLogin), + outKnownPerson: listContainsLogin(relations?.outKnownPersons, targetLogin), + inKnownPerson: listContainsLogin(relations?.inKnownPersons, targetLogin), + outShineConfirmed: listContainsLogin(relations?.outShineConfirmed, targetLogin), + inShineConfirmed: listContainsLogin(relations?.inShineConfirmed, targetLogin), + outShineSeen: listContainsLogin(relations?.outShineSeen, targetLogin), + inShineSeen: listContainsLogin(relations?.inShineSeen, targetLogin), }; } diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index f3de5dd..4d5f957 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -644,6 +644,8 @@ .avatar-image { position: relative; overflow: hidden; + display: grid; + place-items: center; } .avatar-image > .avatar-fallback, @@ -1639,6 +1641,49 @@ textarea.input { color: transparent; } +.user-rel-opinions-wrap { + display: grid; + gap: 8px; + padding: 6px 8px; + border-radius: 10px; + border: 1px dashed rgba(131, 196, 255, 0.45); + background: rgba(9, 18, 31, 0.42); +} + +.user-rel-opinions-wrap.is-empty .user-rel-opinions-list { + display: none; +} + +.user-rel-opinions-list { + display: grid; + gap: 6px; +} + +.user-rel-opinion-item { + color: #d7e6ff; + line-height: 1.35; + font-size: 13px; +} + +.user-rel-opinions-hint { + color: rgba(173, 199, 236, 0.9); + font-size: 12px; +} + +.user-opinion-modal-btn { + text-align: left; +} + +.user-opinion-modal-btn.is-add { + border-color: rgba(97, 170, 255, 0.7); + color: #9fcbff; +} + +.user-opinion-modal-btn.is-remove { + border-color: rgba(255, 120, 120, 0.72); + color: #ff9b9b; +} + .tabs { display: grid; grid-template-columns: repeat(2, 1fr); @@ -2219,6 +2264,11 @@ textarea.input { background: radial-gradient(circle at 30% 30%, #8a73ff, #4f4bda 58%, #3b2b89); } +.channel-message-avatar.avatar-image { + display: grid; + place-items: center; +} + .channel-message-author { display: grid; gap: 4px; diff --git a/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java b/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java index 7f80491..7d1f309 100644 --- a/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java +++ b/shine-server-blockchain/src/main/java/blockchain/MsgSubType.java @@ -113,6 +113,21 @@ public final class MsgSubType { /** Удалить связь "брат/сестра". */ public static final short CONNECTION_UNSIBLING = 55; + /** Просто знаю этого человека. */ + public static final short CONNECTION_KNOWN_PERSON = 60; + /** Не знаю этого человека. */ + public static final short CONNECTION_UNKNOWN_PERSON = 61; + + /** Точно уверен, что сияющий. */ + public static final short CONNECTION_SHINE_CONFIRMED = 70; + /** Не подтверждаю, что сияющий. */ + public static final short CONNECTION_SHINE_UNCONFIRMED = 71; + + /** Мало знаком, но видел сияющим. */ + public static final short CONNECTION_SHINE_SEEN = 74; + /** Не отмечаю, что видел сияющим. */ + public static final short CONNECTION_SHINE_UNSEEN = 75; + /* ===================== USER_PARAM (msg_type=4) ===================== */ /** Параметр профиля key/value (обе строки). */ 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 5b037d8..bb7ef40 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/ConnectionBody.java @@ -16,9 +16,13 @@ import java.util.Objects; * FRIEND=10, UNFRIEND=11 * CONTACT=20, UNCONTACT=21 * FOLLOW=30, UNFOLLOW=31 + * SPOUSE=40, UNSPOUSE=41 * PARENT=50, UNPARENT=51 * CHILD=52, UNCHILD=53 * SIBLING=54, UNSIBLING=55 + * KNOWN_PERSON=60, UNKNOWN_PERSON=61 + * SHINE_CONFIRMED=70, SHINE_UNCONFIRMED=71 + * SHINE_SEEN=74, SHINE_UNSEEN=75 * * bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ): * [4] lineCode @@ -192,7 +196,13 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL || v == (MsgSubType.CONNECTION_CHILD & 0xFFFF) || v == (MsgSubType.CONNECTION_UNCHILD & 0xFFFF) || v == (MsgSubType.CONNECTION_SIBLING & 0xFFFF) - || v == (MsgSubType.CONNECTION_UNSIBLING & 0xFFFF); + || v == (MsgSubType.CONNECTION_UNSIBLING & 0xFFFF) + || v == (MsgSubType.CONNECTION_KNOWN_PERSON & 0xFFFF) + || v == (MsgSubType.CONNECTION_UNKNOWN_PERSON & 0xFFFF) + || v == (MsgSubType.CONNECTION_SHINE_CONFIRMED & 0xFFFF) + || v == (MsgSubType.CONNECTION_SHINE_UNCONFIRMED & 0xFFFF) + || v == (MsgSubType.CONNECTION_SHINE_SEEN & 0xFFFF) + || v == (MsgSubType.CONNECTION_SHINE_UNSEEN & 0xFFFF); } @Override 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 3145c0e..2afb541 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -66,6 +66,15 @@ public final class DatabaseInitializer { public static final short CONNECTION_SIBLING = 54; public static final short CONNECTION_UNSIBLING = 55; + public static final short CONNECTION_KNOWN_PERSON = 60; + public static final short CONNECTION_UNKNOWN_PERSON = 61; + + public static final short CONNECTION_SHINE_CONFIRMED = 70; + public static final short CONNECTION_SHINE_UNCONFIRMED = 71; + + public static final short CONNECTION_SHINE_SEEN = 74; + public static final short CONNECTION_SHINE_UNSEEN = 75; + public static void createNewDB(String[] args) { AppConfig config = AppConfig.getInstance(); String dbPath = config.getParam("db.path"); 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 f4ff8a4..1327905 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseTriggersInstaller.java @@ -198,6 +198,9 @@ public final class DatabaseTriggersInstaller { int PARENT = (int) DatabaseInitializer.CONNECTION_PARENT; int CHILD = (int) DatabaseInitializer.CONNECTION_CHILD; int SIBLING = (int) DatabaseInitializer.CONNECTION_SIBLING; + int KNOWN = (int) DatabaseInitializer.CONNECTION_KNOWN_PERSON; + int SHINE_CONF = (int) DatabaseInitializer.CONNECTION_SHINE_CONFIRMED; + int SHINE_SEEN = (int) DatabaseInitializer.CONNECTION_SHINE_SEEN; int UNFRIEND = (int) DatabaseInitializer.CONNECTION_UNFRIEND; int UNCONTACT = (int) DatabaseInitializer.CONNECTION_UNCONTACT; @@ -206,13 +209,16 @@ public final class DatabaseTriggersInstaller { int UNPARENT = (int) DatabaseInitializer.CONNECTION_UNPARENT; int UNCHILD = (int) DatabaseInitializer.CONNECTION_UNCHILD; int UNSIBLING = (int) DatabaseInitializer.CONNECTION_UNSIBLING; + int UNKNOWN = (int) DatabaseInitializer.CONNECTION_UNKNOWN_PERSON; + int SHINE_UNCONF = (int) DatabaseInitializer.CONNECTION_SHINE_UNCONFIRMED; + int SHINE_UNSEEN = (int) DatabaseInitializer.CONNECTION_SHINE_UNSEEN; st.executeUpdate(""" CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai AFTER INSERT ON blocks WHEN NEW.msg_type = 3 BEGIN - -- FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING: + -- FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING/KNOWN_PERSON/SHINE_*: -- 1) если записи нет — создаём INSERT OR IGNORE INTO connections_state ( login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash @@ -233,7 +239,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, %d) + WHERE NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d) AND COALESCE( NEW.to_login, CASE @@ -264,7 +270,7 @@ public final class DatabaseTriggersInstaller { ELSE NULL END ) - AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d) + AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d) AND COALESCE( NEW.to_login, CASE @@ -277,7 +283,7 @@ public final class DatabaseTriggersInstaller { ) IS NOT NULL AND NEW.to_bch_name IS NOT NULL; - -- UNFRIEND/UNCONTACT/UNFOLLOW/UNSPOUSE/UNPARENT/UNCHILD/UNSIBLING: + -- UNFRIEND/UNCONTACT/UNFOLLOW/UNSPOUSE/UNPARENT/UNCHILD/UNSIBLING/UNKNOWN_PERSON/SHINE_UN*: -- удаляем соответствующее "позитивное" состояние DELETE FROM connections_state WHERE login = NEW.login @@ -299,6 +305,9 @@ public final class DatabaseTriggersInstaller { WHEN %d THEN %d WHEN %d THEN %d WHEN %d THEN %d + WHEN %d THEN %d + WHEN %d THEN %d + WHEN %d THEN %d ELSE rel_type END AND COALESCE( @@ -311,11 +320,11 @@ public final class DatabaseTriggersInstaller { ELSE NULL END ) IS NOT NULL - AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d); + AND NEW.msg_sub_type IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d); END; """.formatted( - FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, - FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, + FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, KNOWN, SHINE_CONF, SHINE_SEEN, + FRIEND, CONTACT, FOLLOW, SPOUSE, PARENT, CHILD, SIBLING, KNOWN, SHINE_CONF, SHINE_SEEN, UNFRIEND, FRIEND, UNCONTACT, CONTACT, @@ -324,8 +333,11 @@ public final class DatabaseTriggersInstaller { UNPARENT, PARENT, UNCHILD, CHILD, UNSIBLING, SIBLING, + UNKNOWN, KNOWN, + SHINE_UNCONF, SHINE_CONF, + SHINE_UNSEEN, SHINE_SEEN, - UNFRIEND, UNCONTACT, UNFOLLOW, UNSPOUSE, UNPARENT, UNCHILD, UNSIBLING + UNFRIEND, UNCONTACT, UNFOLLOW, UNSPOUSE, UNPARENT, UNCHILD, UNSIBLING, UNKNOWN, SHINE_UNCONF, SHINE_UNSEEN )); } 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 62f9093..7d3083a 100644 --- a/shine-server-db/src/main/java/shine/db/MsgSubType.java +++ b/shine-server-db/src/main/java/shine/db/MsgSubType.java @@ -40,8 +40,10 @@ public final class MsgSubType { /* ===================== CONNECTION (msg_type=3) ===================== */ /** * Совпадает с 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 + * SET: CLOSE_FRIEND(=FRIEND)=10, CONTACT=20, FOLLOW=30, SPOUSE=40, PARENT=50, CHILD=52, SIBLING=54, + * KNOWN_PERSON=60, SHINE_CONFIRMED=70, SHINE_SEEN=74 + * UNSET: UNCLOSE_FRIEND(=UNFRIEND)=11, UNCONTACT=21, UNFOLLOW=31, UNSPOUSE=41, UNPARENT=51, UNCHILD=53, UNSIBLING=55, + * UNKNOWN_PERSON=61, SHINE_UNCONFIRMED=71, SHINE_UNSEEN=75 */ /** Добавить в близкие друзья (close friend). */ @@ -92,6 +94,24 @@ public final class MsgSubType { /** Удалить связь "брат/сестра". */ public static final short CONNECTION_UNSIBLING = 55; + /** Просто знаю этого человека. */ + public static final short CONNECTION_KNOWN_PERSON = 60; + + /** Не знаю этого человека. */ + public static final short CONNECTION_UNKNOWN_PERSON = 61; + + /** Точно уверен, что сияющий. */ + public static final short CONNECTION_SHINE_CONFIRMED = 70; + + /** Не подтверждаю, что сияющий. */ + public static final short CONNECTION_SHINE_UNCONFIRMED = 71; + + /** Мало знаком, но видел сияющим. */ + public static final short CONNECTION_SHINE_SEEN = 74; + + /** Не отмечаю, что видел сияющим. */ + public static final short CONNECTION_SHINE_UNSEEN = 75; + /* ===================== USER_PARAM (msg_type=4) ===================== */ /** Параметр профиля key/value (обе строки). */ 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 5406117..397895e 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 @@ -55,11 +55,18 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { List inChildren = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD); List outSiblings = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING); List inSiblings = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING); + List outKnownPersons = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON); + List inKnownPersons = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON); + List outShineConfirmed = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED); + List inShineConfirmed = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED); + List outShineSeen = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN); + List inShineSeen = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN); LinkedHashSet allLogins = new LinkedHashSet<>(); allLogins.add(canonicalLogin); addAllLogins(allLogins, outFriends, inFriends, outContacts, inContacts, outFollows, inFollows, - outSpouses, inSpouses, outParents, inParents, outChildren, inChildren, outSiblings, inSiblings); + outSpouses, inSpouses, outParents, inParents, outChildren, inChildren, outSiblings, inSiblings, + outKnownPersons, inKnownPersons, outShineConfirmed, inShineConfirmed, outShineSeen, inShineSeen); Map metaByLogin = loadUserMeta(c, allLogins); List spouseLogins = mergeUnique(outSpouses, inSpouses); @@ -86,6 +93,12 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler { resp.setInChildren(inChildren); resp.setOutSiblings(outSiblings); resp.setInSiblings(inSiblings); + resp.setOutKnownPersons(outKnownPersons); + resp.setInKnownPersons(inKnownPersons); + resp.setOutShineConfirmed(outShineConfirmed); + resp.setInShineConfirmed(inShineConfirmed); + resp.setOutShineSeen(outShineSeen); + resp.setInShineSeen(inShineSeen); resp.setParents(toRelativeItems(parentLogins, metaByLogin)); resp.setChildren(toRelativeItems(childLogins, metaByLogin)); resp.setSiblings(toRelativeItems(siblingLogins, metaByLogin)); 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 33b0be4..feced17 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 @@ -21,6 +21,12 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { private List inChildren = new ArrayList<>(); private List outSiblings = new ArrayList<>(); private List inSiblings = new ArrayList<>(); + private List outKnownPersons = new ArrayList<>(); + private List inKnownPersons = new ArrayList<>(); + private List outShineConfirmed = new ArrayList<>(); + private List inShineConfirmed = new ArrayList<>(); + private List outShineSeen = new ArrayList<>(); + private List inShineSeen = new ArrayList<>(); private List parents = new ArrayList<>(); private List children = new ArrayList<>(); private List siblings = new ArrayList<>(); @@ -102,6 +108,18 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response { public void setOutSiblings(List outSiblings) { this.outSiblings = outSiblings; } public List getInSiblings() { return inSiblings; } public void setInSiblings(List inSiblings) { this.inSiblings = inSiblings; } + public List getOutKnownPersons() { return outKnownPersons; } + public void setOutKnownPersons(List outKnownPersons) { this.outKnownPersons = outKnownPersons; } + public List getInKnownPersons() { return inKnownPersons; } + public void setInKnownPersons(List inKnownPersons) { this.inKnownPersons = inKnownPersons; } + public List getOutShineConfirmed() { return outShineConfirmed; } + public void setOutShineConfirmed(List outShineConfirmed) { this.outShineConfirmed = outShineConfirmed; } + public List getInShineConfirmed() { return inShineConfirmed; } + public void setInShineConfirmed(List inShineConfirmed) { this.inShineConfirmed = inShineConfirmed; } + public List getOutShineSeen() { return outShineSeen; } + public void setOutShineSeen(List outShineSeen) { this.outShineSeen = outShineSeen; } + public List getInShineSeen() { return inShineSeen; } + public void setInShineSeen(List inShineSeen) { this.inShineSeen = inShineSeen; } public List getParents() { return parents; } public void setParents(List parents) { this.parents = parents; } public List getChildren() { return children; }