Доделать связи и профиль

This commit is contained in:
AidarKC
2026-09-01 14:59:42 +04:00
parent a66d7a64e6
commit 390a2b988e
16 changed files with 599 additions and 304 deletions
@@ -102,16 +102,23 @@ public final class MsgSubType {
/** Удалить из близких друзей (close friend). */ /** Удалить из близких друзей (close friend). */
public static final short CONNECTION_UNCLOSE_FRIEND = 11; public static final short CONNECTION_UNCLOSE_FRIEND = 11;
/** Добавить в друзья. */
public static final short CONNECTION_FRIEND = 14;
/** Удалить из друзей. */
public static final short CONNECTION_UNFRIEND = 15;
/** Добавить в контакты. */ /** Добавить в контакты. */
public static final short CONNECTION_CONTACT = 20; public static final short CONNECTION_CONTACT = 20;
/** Удалить из контактов. */ /** Удалить из контактов. */
public static final short CONNECTION_UNCONTACT = 21; public static final short CONNECTION_UNCONTACT = 21;
// Пока не используется в UI. Сохраняем для совместимости протокола/блокчейна. DO NOT REMOVE.
/** Подписаться (follow). */ /** Подписаться (follow). */
public static final short CONNECTION_FOLLOW = 30; public static final short CONNECTION_FOLLOW = 30;
/** Отписаться (unfollow). */ /** Отписаться (unfollow). */
public static final short CONNECTION_UNFOLLOW = 31; public static final short CONNECTION_UNFOLLOW = 31;
// Родственные связи пока не используются в UI. Сохраняем коды. DO NOT REMOVE.
/** Добавить связь "жена/муж". */ /** Добавить связь "жена/муж". */
public static final short CONNECTION_SPOUSE = 40; public static final short CONNECTION_SPOUSE = 40;
/** Удалить связь "жена/муж". */ /** Удалить связь "жена/муж". */
@@ -132,9 +139,8 @@ public final class MsgSubType {
/** Удалить связь "брат/сестра". */ /** Удалить связь "брат/сестра". */
public static final short CONNECTION_UNSIBLING = 55; public static final short CONNECTION_UNSIBLING = 55;
/** Просто знаю этого человека. */ // Legacy. Допускаем старые блоки 60/61, но новый UI их не создаёт и не использует. DO NOT REMOVE.
public static final short CONNECTION_KNOWN_PERSON = 60; public static final short CONNECTION_KNOWN_PERSON = 60;
/** Не знаю этого человека. */
public static final short CONNECTION_UNKNOWN_PERSON = 61; public static final short CONNECTION_UNKNOWN_PERSON = 61;
/** Точно уверен, что сияющий. */ /** Точно уверен, что сияющий. */
@@ -142,11 +148,17 @@ public final class MsgSubType {
/** Не подтверждаю, что сияющий. */ /** Не подтверждаю, что сияющий. */
public static final short CONNECTION_SHINE_UNCONFIRMED = 71; public static final short CONNECTION_SHINE_UNCONFIRMED = 71;
// Пока не используется в UI. Сохраняем для совместимости протокола/блокчейна. DO NOT REMOVE.
/** Мало знаком, но видел сияющим. */ /** Мало знаком, но видел сияющим. */
public static final short CONNECTION_SHINE_SEEN = 74; public static final short CONNECTION_SHINE_SEEN = 74;
/** Не отмечаю, что видел сияющим. */ /** Не отмечаю, что видел сияющим. */
public static final short CONNECTION_SHINE_UNSEEN = 75; public static final short CONNECTION_SHINE_UNSEEN = 75;
/** Подтверждаю, что это действительно официальный аккаунт этого человека. */
public static final short CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED = 80;
/** Снимаю подтверждение официального аккаунта этого человека. */
public static final short CONNECTION_OFFICIAL_ACCOUNT_UNCONFIRMED = 81;
/* ===================== USER_PARAM (msg_type=4) ===================== */ /* ===================== USER_PARAM (msg_type=4) ===================== */
/** Параметр профиля key/value (обе строки). */ /** Параметр профиля key/value (обе строки). */
@@ -20,9 +20,11 @@ import java.util.Objects;
* PARENT=50, UNPARENT=51 * PARENT=50, UNPARENT=51
* CHILD=52, UNCHILD=53 * CHILD=52, UNCHILD=53
* SIBLING=54, UNSIBLING=55 * SIBLING=54, UNSIBLING=55
* KNOWN_PERSON=60, UNKNOWN_PERSON=61 * FRIEND=14, UNFRIEND=15
* KNOWN_PERSON=60, UNKNOWN_PERSON=61 (legacy; accepted but not used by current UI)
* SHINE_CONFIRMED=70, SHINE_UNCONFIRMED=71 * SHINE_CONFIRMED=70, SHINE_UNCONFIRMED=71
* SHINE_SEEN=74, SHINE_UNSEEN=75 * SHINE_SEEN=74, SHINE_UNSEEN=75 (currently not used by UI)
* OFFICIAL_ACCOUNT_CONFIRMED=80, OFFICIAL_ACCOUNT_UNCONFIRMED=81
* *
* bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ): * bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ):
* [4] lineCode * [4] lineCode
@@ -185,6 +187,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
int v = st & 0xFFFF; int v = st & 0xFFFF;
return v == (MsgSubType.CONNECTION_CLOSE_FRIEND & 0xFFFF) return v == (MsgSubType.CONNECTION_CLOSE_FRIEND & 0xFFFF)
|| v == (MsgSubType.CONNECTION_UNCLOSE_FRIEND & 0xFFFF) || v == (MsgSubType.CONNECTION_UNCLOSE_FRIEND & 0xFFFF)
|| v == (MsgSubType.CONNECTION_FRIEND & 0xFFFF)
|| v == (MsgSubType.CONNECTION_UNFRIEND & 0xFFFF)
|| v == (MsgSubType.CONNECTION_CONTACT & 0xFFFF) || v == (MsgSubType.CONNECTION_CONTACT & 0xFFFF)
|| v == (MsgSubType.CONNECTION_UNCONTACT & 0xFFFF) || v == (MsgSubType.CONNECTION_UNCONTACT & 0xFFFF)
|| v == (MsgSubType.CONNECTION_FOLLOW & 0xFFFF) || v == (MsgSubType.CONNECTION_FOLLOW & 0xFFFF)
@@ -202,7 +206,9 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|| v == (MsgSubType.CONNECTION_SHINE_CONFIRMED & 0xFFFF) || v == (MsgSubType.CONNECTION_SHINE_CONFIRMED & 0xFFFF)
|| v == (MsgSubType.CONNECTION_SHINE_UNCONFIRMED & 0xFFFF) || v == (MsgSubType.CONNECTION_SHINE_UNCONFIRMED & 0xFFFF)
|| v == (MsgSubType.CONNECTION_SHINE_SEEN & 0xFFFF) || v == (MsgSubType.CONNECTION_SHINE_SEEN & 0xFFFF)
|| v == (MsgSubType.CONNECTION_SHINE_UNSEEN & 0xFFFF); || v == (MsgSubType.CONNECTION_SHINE_UNSEEN & 0xFFFF)
|| v == (MsgSubType.CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED & 0xFFFF)
|| v == (MsgSubType.CONNECTION_OFFICIAL_ACCOUNT_UNCONFIRMED & 0xFFFF);
} }
@Override @Override
@@ -33,6 +33,7 @@ public final class DatabaseInitializer {
public static final int SCHEMA_VERSION_14 = 14; public static final int SCHEMA_VERSION_14 = 14;
public static final int SCHEMA_VERSION_15 = 15; public static final int SCHEMA_VERSION_15 = 15;
public static final int SCHEMA_VERSION_16 = 16; public static final int SCHEMA_VERSION_16 = 16;
public static final int SCHEMA_VERSION_17 = 17;
public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql"; public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql";
public static final String POSTGRES_MIGRATION_V2_RESOURCE = "postgres/migration_v2.sql"; public static final String POSTGRES_MIGRATION_V2_RESOURCE = "postgres/migration_v2.sql";
public static final String POSTGRES_MIGRATION_V3_RESOURCE = "postgres/migration_v3.sql"; public static final String POSTGRES_MIGRATION_V3_RESOURCE = "postgres/migration_v3.sql";
@@ -49,6 +50,7 @@ public final class DatabaseInitializer {
public static final String POSTGRES_MIGRATION_V14_RESOURCE = "postgres/migration_v14.sql"; public static final String POSTGRES_MIGRATION_V14_RESOURCE = "postgres/migration_v14.sql";
public static final String POSTGRES_MIGRATION_V15_RESOURCE = "postgres/migration_v15.sql"; public static final String POSTGRES_MIGRATION_V15_RESOURCE = "postgres/migration_v15.sql";
public static final String POSTGRES_MIGRATION_V16_RESOURCE = "postgres/migration_v16.sql"; public static final String POSTGRES_MIGRATION_V16_RESOURCE = "postgres/migration_v16.sql";
public static final String POSTGRES_MIGRATION_V17_RESOURCE = "postgres/migration_v17.sql";
private DatabaseInitializer() {} private DatabaseInitializer() {}
@@ -70,12 +72,17 @@ public final class DatabaseInitializer {
public static final short CONNECTION_CLOSE_FRIEND = 10; public static final short CONNECTION_CLOSE_FRIEND = 10;
public static final short CONNECTION_UNCLOSE_FRIEND = 11; public static final short CONNECTION_UNCLOSE_FRIEND = 11;
public static final short CONNECTION_FRIEND = 14;
public static final short CONNECTION_UNFRIEND = 15;
public static final short CONNECTION_CONTACT = 20; public static final short CONNECTION_CONTACT = 20;
public static final short CONNECTION_UNCONTACT = 21; public static final short CONNECTION_UNCONTACT = 21;
// Пока не используется в UI. DO NOT REMOVE.
public static final short CONNECTION_FOLLOW = 30; public static final short CONNECTION_FOLLOW = 30;
public static final short CONNECTION_UNFOLLOW = 31; public static final short CONNECTION_UNFOLLOW = 31;
// Родственные связи пока не используются в UI. DO NOT REMOVE.
public static final short CONNECTION_SPOUSE = 40; public static final short CONNECTION_SPOUSE = 40;
public static final short CONNECTION_UNSPOUSE = 41; public static final short CONNECTION_UNSPOUSE = 41;
@@ -88,15 +95,20 @@ public final class DatabaseInitializer {
public static final short CONNECTION_SIBLING = 54; public static final short CONNECTION_SIBLING = 54;
public static final short CONNECTION_UNSIBLING = 55; public static final short CONNECTION_UNSIBLING = 55;
// Legacy 60/61: новый UI не использует. DO NOT REMOVE.
public static final short CONNECTION_KNOWN_PERSON = 60; public static final short CONNECTION_KNOWN_PERSON = 60;
public static final short CONNECTION_UNKNOWN_PERSON = 61; public static final short CONNECTION_UNKNOWN_PERSON = 61;
public static final short CONNECTION_SHINE_CONFIRMED = 70; public static final short CONNECTION_SHINE_CONFIRMED = 70;
public static final short CONNECTION_SHINE_UNCONFIRMED = 71; public static final short CONNECTION_SHINE_UNCONFIRMED = 71;
// Пока не используется в UI. DO NOT REMOVE.
public static final short CONNECTION_SHINE_SEEN = 74; public static final short CONNECTION_SHINE_SEEN = 74;
public static final short CONNECTION_SHINE_UNSEEN = 75; public static final short CONNECTION_SHINE_UNSEEN = 75;
public static final short CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED = 80;
public static final short CONNECTION_OFFICIAL_ACCOUNT_UNCONFIRMED = 81;
public static void ensurePostgresSchemaInitialized(String jdbcUrl, public static void ensurePostgresSchemaInitialized(String jdbcUrl,
String user, String user,
String password) throws SQLException { String password) throws SQLException {
@@ -172,6 +184,10 @@ public final class DatabaseInitializer {
runSqlScript(conn, POSTGRES_MIGRATION_V16_RESOURCE); runSqlScript(conn, POSTGRES_MIGRATION_V16_RESOURCE);
currentVersion = SCHEMA_VERSION_16; currentVersion = SCHEMA_VERSION_16;
} }
if (currentVersion < SCHEMA_VERSION_17) {
runSqlScript(conn, POSTGRES_MIGRATION_V17_RESOURCE);
currentVersion = SCHEMA_VERSION_17;
}
} }
} }
@@ -58,10 +58,10 @@ public final class MsgSubType {
/* ===================== CONNECTION (msg_type=3) ===================== */ /* ===================== CONNECTION (msg_type=3) ===================== */
/** /**
* Совпадает с ConnectionBody: * Совпадает с ConnectionBody:
* SET: CLOSE_FRIEND=10, CONTACT=20, FOLLOW=30, SPOUSE=40, PARENT=50, CHILD=52, SIBLING=54, * SET: CLOSE_FRIEND=10, FRIEND=14, CONTACT=20, FOLLOW=30, SPOUSE=40, PARENT=50, CHILD=52, SIBLING=54,
* KNOWN_PERSON=60, SHINE_CONFIRMED=70, SHINE_SEEN=74 * KNOWN_PERSON(legacy)=60, SHINE_CONFIRMED=70, SHINE_SEEN=74, OFFICIAL_ACCOUNT_CONFIRMED=80
* UNSET: UNCLOSE_FRIEND=11, UNCONTACT=21, UNFOLLOW=31, UNSPOUSE=41, UNPARENT=51, UNCHILD=53, UNSIBLING=55, * UNSET: UNCLOSE_FRIEND=11, UNFRIEND=15, UNCONTACT=21, UNFOLLOW=31, UNSPOUSE=41, UNPARENT=51, UNCHILD=53, UNSIBLING=55,
* UNKNOWN_PERSON=61, SHINE_UNCONFIRMED=71, SHINE_UNSEEN=75 * UNKNOWN_PERSON(legacy)=61, SHINE_UNCONFIRMED=71, SHINE_UNSEEN=75, OFFICIAL_ACCOUNT_UNCONFIRMED=81
*/ */
/** Добавить в близкие друзья (close friend). */ /** Добавить в близкие друзья (close friend). */
@@ -70,18 +70,26 @@ public final class MsgSubType {
/** Удалить из близких друзей (close friend). */ /** Удалить из близких друзей (close friend). */
public static final short CONNECTION_UNCLOSE_FRIEND = 11; public static final short CONNECTION_UNCLOSE_FRIEND = 11;
/** Добавить в друзья. */
public static final short CONNECTION_FRIEND = 14;
/** Удалить из друзей. */
public static final short CONNECTION_UNFRIEND = 15;
/** Добавить в контакты. */ /** Добавить в контакты. */
public static final short CONNECTION_CONTACT = 20; public static final short CONNECTION_CONTACT = 20;
/** Удалить из контактов. */ /** Удалить из контактов. */
public static final short CONNECTION_UNCONTACT = 21; public static final short CONNECTION_UNCONTACT = 21;
// Пока не используется в UI. Сохраняем для совместимости. DO NOT REMOVE.
/** Подписаться (follow). */ /** Подписаться (follow). */
public static final short CONNECTION_FOLLOW = 30; public static final short CONNECTION_FOLLOW = 30;
/** Отписаться (unfollow). */ /** Отписаться (unfollow). */
public static final short CONNECTION_UNFOLLOW = 31; public static final short CONNECTION_UNFOLLOW = 31;
// Родственные связи пока не используются в UI. Сохраняем коды. DO NOT REMOVE.
/** Добавить связь "жена/муж". */ /** Добавить связь "жена/муж". */
public static final short CONNECTION_SPOUSE = 40; public static final short CONNECTION_SPOUSE = 40;
@@ -106,10 +114,8 @@ public final class MsgSubType {
/** Удалить связь "брат/сестра". */ /** Удалить связь "брат/сестра". */
public static final short CONNECTION_UNSIBLING = 55; public static final short CONNECTION_UNSIBLING = 55;
/** Просто знаю этого человека. */ // Legacy 60/61: принимаем старые данные, но новый UI их не использует. DO NOT REMOVE.
public static final short CONNECTION_KNOWN_PERSON = 60; public static final short CONNECTION_KNOWN_PERSON = 60;
/** Не знаю этого человека. */
public static final short CONNECTION_UNKNOWN_PERSON = 61; public static final short CONNECTION_UNKNOWN_PERSON = 61;
/** Точно уверен, что сияющий. */ /** Точно уверен, что сияющий. */
@@ -118,12 +124,19 @@ public final class MsgSubType {
/** Не подтверждаю, что сияющий. */ /** Не подтверждаю, что сияющий. */
public static final short CONNECTION_SHINE_UNCONFIRMED = 71; public static final short CONNECTION_SHINE_UNCONFIRMED = 71;
// Пока не используется в UI. Сохраняем для совместимости. DO NOT REMOVE.
/** Мало знаком, но видел сияющим. */ /** Мало знаком, но видел сияющим. */
public static final short CONNECTION_SHINE_SEEN = 74; public static final short CONNECTION_SHINE_SEEN = 74;
/** Не отмечаю, что видел сияющим. */ /** Не отмечаю, что видел сияющим. */
public static final short CONNECTION_SHINE_UNSEEN = 75; public static final short CONNECTION_SHINE_UNSEEN = 75;
/** Подтверждение официального аккаунта человека. */
public static final short CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED = 80;
/** Снятие подтверждения официального аккаунта человека. */
public static final short CONNECTION_OFFICIAL_ACCOUNT_UNCONFIRMED = 81;
/* ===================== USER_PARAM (msg_type=4) ===================== */ /* ===================== USER_PARAM (msg_type=4) ===================== */
/** Параметр профиля key/value (обе строки). */ /** Параметр профиля key/value (обе строки). */
@@ -0,0 +1,381 @@
BEGIN;
-- Add active FRIEND 14/15 and OFFICIAL_ACCOUNT confirmation 80/81 to connection state.
-- Legacy 60/61 remain accepted at protocol level; this migration does not reinterpret existing rows.
CREATE OR REPLACE FUNCTION shine_blocks_connection_state_ai()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
resolved_login TEXT;
positive_rel_type INTEGER;
existed_before BOOLEAN;
target_channel_type INTEGER;
BEGIN
IF NEW.msg_type <> 3 THEN
RETURN NEW;
END IF;
resolved_login := shine_resolve_login(NEW.to_login, NEW.to_bch_name);
IF NEW.msg_sub_type IN (10, 14, 20, 30, 40, 50, 52, 54, 60, 70, 74, 80) THEN
IF resolved_login IS NULL OR NEW.to_bch_name IS NULL THEN
RETURN NEW;
END IF;
SELECT EXISTS (
SELECT 1
FROM connections_state
WHERE login = NEW.login
AND rel_type = NEW.msg_sub_type
AND to_login = resolved_login
AND to_bch_name = NEW.to_bch_name
AND to_block_number = COALESCE(NEW.to_block_number, 0)
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
)
INTO existed_before;
IF NOT existed_before THEN
IF NEW.msg_sub_type = 10 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
1,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
close_friends_count = user_stats_state.close_friends_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF NEW.msg_sub_type = 30 THEN
IF NEW.to_block_number IS NULL THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
1,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = user_stats_state.following_channels_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF NEW.to_block_number = 0 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
1,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_users_count = user_stats_state.following_users_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
ELSE
SELECT cn.channel_type_code
INTO target_channel_type
FROM channel_names_state cn
WHERE cn.owner_bch_name = NEW.to_bch_name
AND cn.channel_root_block_number = NEW.to_block_number
AND cn.channel_root_block_hash = NEW.to_block_hash
LIMIT 1;
IF target_channel_type = 1 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
1,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = user_stats_state.following_channels_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
INSERT INTO channel_stats_state (
owner_bch_name,
channel_root_block_number,
channel_root_block_hash,
owner_login,
channel_type_code,
subscribers_count,
updated_at_ms
) VALUES (
NEW.to_bch_name,
NEW.to_block_number,
NEW.to_block_hash,
resolved_login,
target_channel_type,
1,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
owner_login = EXCLUDED.owner_login,
channel_type_code = EXCLUDED.channel_type_code,
subscribers_count = channel_stats_state.subscribers_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF target_channel_type IS NULL THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
1,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = user_stats_state.following_channels_count + 1,
updated_at_ms = EXCLUDED.updated_at_ms;
END IF;
END IF;
END IF;
END IF;
DELETE FROM connections_state
WHERE login = NEW.login
AND rel_type = NEW.msg_sub_type
AND to_login = resolved_login
AND to_bch_name = NEW.to_bch_name
AND to_block_number = COALESCE(NEW.to_block_number, 0)
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
INSERT INTO connections_state (
login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash
) VALUES (
NEW.login,
NEW.msg_sub_type,
resolved_login,
NEW.to_bch_name,
COALESCE(NEW.to_block_number, 0),
COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
);
RETURN NEW;
END IF;
positive_rel_type := CASE NEW.msg_sub_type
WHEN 11 THEN 10
WHEN 15 THEN 14
WHEN 21 THEN 20
WHEN 31 THEN 30
WHEN 41 THEN 40
WHEN 51 THEN 50
WHEN 53 THEN 52
WHEN 55 THEN 54
WHEN 61 THEN 60
WHEN 71 THEN 70
WHEN 75 THEN 74
WHEN 81 THEN 80
ELSE NULL
END;
IF positive_rel_type IS NULL OR resolved_login IS NULL THEN
RETURN NEW;
END IF;
SELECT EXISTS (
SELECT 1
FROM connections_state
WHERE login = NEW.login
AND rel_type = positive_rel_type
AND to_login = resolved_login
AND to_bch_name = NEW.to_bch_name
AND to_block_number = COALESCE(NEW.to_block_number, 0)
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
)
INTO existed_before;
IF NOT existed_before THEN
RETURN NEW;
END IF;
IF positive_rel_type = 10 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
close_friends_count = GREATEST(0, user_stats_state.close_friends_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF positive_rel_type = 30 THEN
IF NEW.to_block_number IS NULL THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF NEW.to_block_number = 0 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_users_count = GREATEST(0, user_stats_state.following_users_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
ELSE
SELECT cn.channel_type_code
INTO target_channel_type
FROM channel_names_state cn
WHERE cn.owner_bch_name = NEW.to_bch_name
AND cn.channel_root_block_number = NEW.to_block_number
AND cn.channel_root_block_hash = NEW.to_block_hash
LIMIT 1;
IF target_channel_type = 1 THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
INSERT INTO channel_stats_state (
owner_bch_name,
channel_root_block_number,
channel_root_block_hash,
owner_login,
channel_type_code,
subscribers_count,
updated_at_ms
) VALUES (
NEW.to_bch_name,
NEW.to_block_number,
NEW.to_block_hash,
resolved_login,
target_channel_type,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
owner_login = EXCLUDED.owner_login,
channel_type_code = EXCLUDED.channel_type_code,
subscribers_count = GREATEST(0, channel_stats_state.subscribers_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
ELSIF target_channel_type IS NULL THEN
INSERT INTO user_stats_state (
login,
owned_public_channels_count,
following_users_count,
following_channels_count,
close_friends_count,
updated_at_ms
) VALUES (
NEW.login,
0,
0,
0,
0,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
)
ON CONFLICT (login) DO UPDATE SET
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
updated_at_ms = EXCLUDED.updated_at_ms;
END IF;
END IF;
END IF;
DELETE FROM connections_state
WHERE login = NEW.login
AND rel_type = positive_rel_type
AND to_login = resolved_login
AND to_bch_name = NEW.to_bch_name
AND to_block_number = COALESCE(NEW.to_block_number, 0)
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
RETURN NEW;
END;
$$;
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
VALUES (1, 17, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
ON CONFLICT (id) DO UPDATE SET
schema_version = EXCLUDED.schema_version,
updated_at_ms = EXCLUDED.updated_at_ms;
COMMIT;
@@ -1113,7 +1113,7 @@ BEGIN
resolved_login := shine_resolve_login(NEW.to_login, NEW.to_bch_name); resolved_login := shine_resolve_login(NEW.to_login, NEW.to_bch_name);
IF NEW.msg_sub_type IN (10, 20, 30, 40, 50, 52, 54, 60, 70, 74) THEN IF NEW.msg_sub_type IN (10, 14, 20, 30, 40, 50, 52, 54, 60, 70, 74, 80) THEN
IF resolved_login IS NULL OR NEW.to_bch_name IS NULL THEN IF resolved_login IS NULL OR NEW.to_bch_name IS NULL THEN
RETURN NEW; RETURN NEW;
END IF; END IF;
@@ -1288,6 +1288,7 @@ BEGIN
positive_rel_type := CASE NEW.msg_sub_type positive_rel_type := CASE NEW.msg_sub_type
WHEN 11 THEN 10 WHEN 11 THEN 10
WHEN 15 THEN 14
WHEN 21 THEN 20 WHEN 21 THEN 20
WHEN 31 THEN 30 WHEN 31 THEN 30
WHEN 41 THEN 40 WHEN 41 THEN 40
@@ -1297,6 +1298,7 @@ BEGIN
WHEN 61 THEN 60 WHEN 61 THEN 60
WHEN 71 THEN 70 WHEN 71 THEN 70
WHEN 75 THEN 74 WHEN 75 THEN 74
WHEN 81 THEN 80
ELSE NULL ELSE NULL
END; END;
@@ -885,10 +885,10 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
} }
// Connection notifications are intentionally modeled as a generic kind. // Connection notifications are intentionally modeled as a generic kind.
// Today only CONNECTION_CLOSE_FRIEND is indexed; future incoming connection types // Current UI surfaces FRIEND and CLOSE_FRIEND here; other reserved relation types stay silent.
// can reuse the same notification kind and expose their code via sourceMsgSubType.
if (msgType == 3 if (msgType == 3
&& msgSubType == (MsgSubType.CONNECTION_CLOSE_FRIEND & 0xFFFF) && (msgSubType == (MsgSubType.CONNECTION_CLOSE_FRIEND & 0xFFFF)
|| msgSubType == (MsgSubType.CONNECTION_FRIEND & 0xFFFF))
&& block.body instanceof ConnectionBody) { && block.body instanceof ConnectionBody) {
UserNotificationEntry entry = baseNotificationEntry(storedEntry, ownerLogin, createdAtMs, msgSubType); UserNotificationEntry entry = baseNotificationEntry(storedEntry, ownerLogin, createdAtMs, msgSubType);
entry.setNotificationKind("connection"); entry.setNotificationKind("connection");
@@ -46,8 +46,10 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден"); return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
} }
List<String> outFriends = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND); List<String> outFriends = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FRIEND);
List<String> inFriends = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND); List<String> inFriends = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FRIEND);
List<String> outCloseFriends = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND);
List<String> inCloseFriends = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND);
List<String> outContacts = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT); List<String> outContacts = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT);
List<String> inContacts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT); List<String> inContacts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT);
List<String> outFollows = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW); List<String> outFollows = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW);
@@ -60,8 +62,11 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
List<String> inChildren = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(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> outSiblings = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
List<String> inSiblings = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING); List<String> inSiblings = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
List<String> outKnownPersons = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON); // Legacy 60/61 are accepted by the protocol but intentionally not surfaced by current UI.
List<String> inKnownPersons = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON); List<String> outKnownPersons = List.of();
List<String> inKnownPersons = List.of();
List<String> outOfficialAccounts = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED);
List<String> inOfficialAccounts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_OFFICIAL_ACCOUNT_CONFIRMED);
List<String> outShineConfirmed = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED); List<String> outShineConfirmed = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED);
List<String> inShineConfirmed = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED); List<String> inShineConfirmed = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED);
List<String> outShineSeen = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN); List<String> outShineSeen = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN);
@@ -69,9 +74,9 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
LinkedHashSet<String> allLogins = new LinkedHashSet<>(); LinkedHashSet<String> allLogins = new LinkedHashSet<>();
allLogins.add(canonicalLogin); allLogins.add(canonicalLogin);
addAllLogins(allLogins, outFriends, inFriends, outContacts, inContacts, outFollows, inFollows, addAllLogins(allLogins, outFriends, inFriends, outCloseFriends, inCloseFriends, 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); outKnownPersons, inKnownPersons, outOfficialAccounts, inOfficialAccounts, outShineConfirmed, inShineConfirmed, outShineSeen, inShineSeen);
Map<String, UserMeta> metaByLogin = loadUserMeta(c, allLogins); Map<String, UserMeta> metaByLogin = loadUserMeta(c, allLogins);
List<String> spouseLogins = mergeUnique(outSpouses, inSpouses); List<String> spouseLogins = mergeUnique(outSpouses, inSpouses);
@@ -86,6 +91,8 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
resp.setLogin(canonicalLogin); resp.setLogin(canonicalLogin);
resp.setOutFriends(outFriends); resp.setOutFriends(outFriends);
resp.setInFriends(inFriends); resp.setInFriends(inFriends);
resp.setOutCloseFriends(outCloseFriends);
resp.setInCloseFriends(inCloseFriends);
resp.setOutContacts(outContacts); resp.setOutContacts(outContacts);
resp.setInContacts(inContacts); resp.setInContacts(inContacts);
resp.setOutFollows(outFollows); resp.setOutFollows(outFollows);
@@ -100,6 +107,8 @@ public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
resp.setInSiblings(inSiblings); resp.setInSiblings(inSiblings);
resp.setOutKnownPersons(outKnownPersons); resp.setOutKnownPersons(outKnownPersons);
resp.setInKnownPersons(inKnownPersons); resp.setInKnownPersons(inKnownPersons);
resp.setOutOfficialAccounts(outOfficialAccounts);
resp.setInOfficialAccounts(inOfficialAccounts);
resp.setOutShineConfirmed(outShineConfirmed); resp.setOutShineConfirmed(outShineConfirmed);
resp.setInShineConfirmed(inShineConfirmed); resp.setInShineConfirmed(inShineConfirmed);
resp.setOutShineSeen(outShineSeen); resp.setOutShineSeen(outShineSeen);
@@ -9,6 +9,8 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
private String login; private String login;
private List<String> outFriends = new ArrayList<>(); private List<String> outFriends = new ArrayList<>();
private List<String> inFriends = new ArrayList<>(); private List<String> inFriends = new ArrayList<>();
private List<String> outCloseFriends = new ArrayList<>();
private List<String> inCloseFriends = new ArrayList<>();
private List<String> outContacts = new ArrayList<>(); private List<String> outContacts = new ArrayList<>();
private List<String> inContacts = new ArrayList<>(); private List<String> inContacts = new ArrayList<>();
private List<String> outFollows = new ArrayList<>(); private List<String> outFollows = new ArrayList<>();
@@ -23,6 +25,8 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
private List<String> inSiblings = new ArrayList<>(); private List<String> inSiblings = new ArrayList<>();
private List<String> outKnownPersons = new ArrayList<>(); private List<String> outKnownPersons = new ArrayList<>();
private List<String> inKnownPersons = new ArrayList<>(); private List<String> inKnownPersons = new ArrayList<>();
private List<String> outOfficialAccounts = new ArrayList<>();
private List<String> inOfficialAccounts = new ArrayList<>();
private List<String> outShineConfirmed = new ArrayList<>(); private List<String> outShineConfirmed = new ArrayList<>();
private List<String> inShineConfirmed = new ArrayList<>(); private List<String> inShineConfirmed = new ArrayList<>();
private List<String> outShineSeen = new ArrayList<>(); private List<String> outShineSeen = new ArrayList<>();
@@ -84,6 +88,10 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
public void setOutFriends(List<String> outFriends) { this.outFriends = outFriends; } public void setOutFriends(List<String> outFriends) { this.outFriends = outFriends; }
public List<String> getInFriends() { return inFriends; } public List<String> getInFriends() { return inFriends; }
public void setInFriends(List<String> inFriends) { this.inFriends = inFriends; } public void setInFriends(List<String> inFriends) { this.inFriends = inFriends; }
public List<String> getOutCloseFriends() { return outCloseFriends; }
public void setOutCloseFriends(List<String> outCloseFriends) { this.outCloseFriends = outCloseFriends; }
public List<String> getInCloseFriends() { return inCloseFriends; }
public void setInCloseFriends(List<String> inCloseFriends) { this.inCloseFriends = inCloseFriends; }
public List<String> getOutContacts() { return outContacts; } public List<String> getOutContacts() { return outContacts; }
public void setOutContacts(List<String> outContacts) { this.outContacts = outContacts; } public void setOutContacts(List<String> outContacts) { this.outContacts = outContacts; }
public List<String> getInContacts() { return inContacts; } public List<String> getInContacts() { return inContacts; }
@@ -112,6 +120,10 @@ public class Net_GetUserConnectionsGraph_Response extends Net_Response {
public void setOutKnownPersons(List<String> outKnownPersons) { this.outKnownPersons = outKnownPersons; } public void setOutKnownPersons(List<String> outKnownPersons) { this.outKnownPersons = outKnownPersons; }
public List<String> getInKnownPersons() { return inKnownPersons; } public List<String> getInKnownPersons() { return inKnownPersons; }
public void setInKnownPersons(List<String> inKnownPersons) { this.inKnownPersons = inKnownPersons; } public void setInKnownPersons(List<String> inKnownPersons) { this.inKnownPersons = inKnownPersons; }
public List<String> getOutOfficialAccounts() { return outOfficialAccounts; }
public void setOutOfficialAccounts(List<String> outOfficialAccounts) { this.outOfficialAccounts = outOfficialAccounts; }
public List<String> getInOfficialAccounts() { return inOfficialAccounts; }
public void setInOfficialAccounts(List<String> inOfficialAccounts) { this.inOfficialAccounts = inOfficialAccounts; }
public List<String> getOutShineConfirmed() { return outShineConfirmed; } public List<String> getOutShineConfirmed() { return outShineConfirmed; }
public void setOutShineConfirmed(List<String> outShineConfirmed) { this.outShineConfirmed = outShineConfirmed; } public void setOutShineConfirmed(List<String> outShineConfirmed) { this.outShineConfirmed = outShineConfirmed; }
public List<String> getInShineConfirmed() { return inShineConfirmed; } public List<String> getInShineConfirmed() { return inShineConfirmed; }
+2 -2
View File
@@ -1,2 +1,2 @@
client.version=1.10.0 client.version=1.10.1
server.version=1.8.0 server.version=1.8.1
+5
View File
@@ -132,6 +132,8 @@
"login": "Alice", "login": "Alice",
"outFriends": ["Bob"], "outFriends": ["Bob"],
"inFriends": ["Kate"], "inFriends": ["Kate"],
"outCloseFriends": [],
"inCloseFriends": [],
"outContacts": [], "outContacts": [],
"inContacts": [], "inContacts": [],
"outFollows": [], "outFollows": [],
@@ -146,6 +148,8 @@
"inSiblings": [], "inSiblings": [],
"outKnownPersons": [], "outKnownPersons": [],
"inKnownPersons": [], "inKnownPersons": [],
"outOfficialAccounts": [],
"inOfficialAccounts": [],
"outShineConfirmed": [], "outShineConfirmed": [],
"inShineConfirmed": [], "inShineConfirmed": [],
"outShineSeen": [], "outShineSeen": [],
@@ -171,6 +175,7 @@
### Примечание ### Примечание
Поля `known_person`, `shine_confirmed`, `shine_seen` в UI считаются недопроверенной зоной проекта; при изменениях этой логики нужна ручная end-to-end проверка. Поля `known_person`, `shine_confirmed`, `shine_seen` в UI считаются недопроверенной зоной проекта; при изменениях этой логики нужна ручная end-to-end проверка.
`outCloseFriends` / `inCloseFriends` возвращают близких друзей, а `outOfficialAccounts` / `inOfficialAccounts` - подтверждённые официальные аккаунты.
--- ---
+19 -52
View File
@@ -104,10 +104,8 @@ function applyRelativeGender(map, rows) {
function getRelativeGenderMap(graph) { function getRelativeGenderMap(graph) {
const map = new Map(); const map = new Map();
applyRelativeGender(map, graph?.parents); // Родственные связи пока скрыты из UI, хотя сервер продолжает хранить их коды.
applyRelativeGender(map, graph?.children); void graph;
applyRelativeGender(map, graph?.siblings);
applyRelativeGender(map, graph?.spouses);
return map; return map;
} }
@@ -115,6 +113,8 @@ function buildGraphModel(graph, centerLogin) {
const login = normalizeLogin(graph?.login || centerLogin || state.session.login); const login = normalizeLogin(graph?.login || centerLogin || state.session.login);
const outFriends = toSet(graph?.outFriends); const outFriends = toSet(graph?.outFriends);
const inFriends = toSet(graph?.inFriends); const inFriends = toSet(graph?.inFriends);
const outCloseFriends = toSet(graph?.outCloseFriends);
const inCloseFriends = toSet(graph?.inCloseFriends);
const outParents = toSet(graph?.outParents); const outParents = toSet(graph?.outParents);
const inParents = toSet(graph?.inParents); const inParents = toSet(graph?.inParents);
const outChildren = toSet(graph?.outChildren); const outChildren = toSet(graph?.outChildren);
@@ -128,8 +128,8 @@ function buildGraphModel(graph, centerLogin) {
const inContacts = toSet(graph?.inContacts); const inContacts = toSet(graph?.inContacts);
const outFollows = toSet(graph?.outFollows); const outFollows = toSet(graph?.outFollows);
const inFollows = toSet(graph?.inFollows); const inFollows = toSet(graph?.inFollows);
const outKnown = toSet(graph?.outKnownPersons); const outOfficial = toSet(graph?.outOfficialAccounts);
const inKnown = toSet(graph?.inKnownPersons); const inOfficial = toSet(graph?.inOfficialAccounts);
const relativesGender = getRelativeGenderMap(graph); const relativesGender = getRelativeGenderMap(graph);
const allMarks = getMarkByLogin(graph?.allUsers); const allMarks = getMarkByLogin(graph?.allUsers);
@@ -137,67 +137,35 @@ function buildGraphModel(graph, centerLogin) {
const allLogins = uniqueLogins([ const allLogins = uniqueLogins([
...(graph?.outFriends || []), ...(graph?.outFriends || []),
...(graph?.inFriends || []), ...(graph?.inFriends || []),
...(graph?.outParents || []), ...(graph?.outCloseFriends || []),
...(graph?.inParents || []), ...(graph?.inCloseFriends || []),
...(graph?.outChildren || []),
...(graph?.inChildren || []),
...(graph?.outSiblings || []),
...(graph?.inSiblings || []),
...(graph?.outSpouses || []),
...(graph?.inSpouses || []),
...(graph?.outContacts || []), ...(graph?.outContacts || []),
...(graph?.inContacts || []), ...(graph?.inContacts || []),
...(graph?.outFollows || []), ...(graph?.outFollows || []),
...(graph?.inFollows || []), ...(graph?.inFollows || []),
...(graph?.outKnownPersons || []), ...(graph?.outOfficialAccounts || []),
...(graph?.inKnownPersons || []), ...(graph?.inOfficialAccounts || []),
]).filter((entry) => normKey(entry) !== normKey(login)); ]).filter((entry) => normKey(entry) !== normKey(login));
const relations = allLogins.map((targetLogin) => { const relations = allLogins.map((targetLogin) => {
const parentOut = hasLogin(outParents, targetLogin);
const parentIn = hasLogin(inChildren, targetLogin);
const childOut = hasLogin(outChildren, targetLogin);
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 friendOut = hasLogin(outFriends, targetLogin);
const friendIn = hasLogin(inFriends, targetLogin); const friendIn = hasLogin(inFriends, targetLogin);
const contactOut = hasLogin(outContacts, targetLogin) || hasLogin(outFollows, targetLogin) || hasLogin(outKnown, targetLogin); const closeFriendOut = hasLogin(outCloseFriends, targetLogin);
const contactIn = hasLogin(inContacts, targetLogin) || hasLogin(inFollows, targetLogin) || hasLogin(inKnown, targetLogin); const closeFriendIn = hasLogin(inCloseFriends, targetLogin);
const contactOut = hasLogin(outContacts, targetLogin) || hasLogin(outFollows, targetLogin) || hasLogin(outOfficial, targetLogin);
const contactIn = hasLogin(inContacts, targetLogin) || hasLogin(inFollows, targetLogin) || hasLogin(inOfficial, targetLogin);
let role = 'contact'; let role = 'contact';
if (parentOut || parentIn) role = 'parent'; if (closeFriendOut || closeFriendIn || friendOut || friendIn) role = 'friend';
else if (childOut || childIn) role = 'child';
else if (spouseOut || spouseIn) role = 'spouse';
else if (siblingOut || siblingIn) role = 'sibling';
else if (friendOut || friendIn) role = 'friend';
let forward = friendOut; let forward = role === 'friend' ? (closeFriendOut || friendOut) : contactOut;
let backward = friendIn; let backward = role === 'friend' ? (closeFriendIn || friendIn) : contactIn;
if (role === 'parent') {
forward = parentOut;
backward = parentIn;
} else if (role === 'child') {
forward = childOut;
backward = childIn;
} else if (role === 'spouse') {
forward = spouseOut;
backward = spouseIn;
} else if (role === 'sibling') {
forward = siblingOut;
backward = siblingIn;
} else if (role === 'contact') {
forward = contactOut;
backward = contactIn;
}
return { return {
login: targetLogin, login: targetLogin,
key: normKey(targetLogin), key: normKey(targetLogin),
role, role,
isRelative: role === 'parent' || role === 'child' || role === 'spouse' || role === 'sibling', isRelative: false,
gender: normalizeGender(relativesGender.get(normKey(targetLogin))), gender: normalizeGender(relativesGender.get(normKey(targetLogin))),
forward: Boolean(forward), forward: Boolean(forward),
backward: Boolean(backward), backward: Boolean(backward),
@@ -242,11 +210,10 @@ export function render({ navigate, route, chrome } = {}) {
// Фильтры слоёв (Фаза 3). Фокус всегда виден; предикат применяется к периферийным узлам. // Фильтры слоёв (Фаза 3). Фокус всегда виден; предикат применяется к периферийным узлам.
const FILTERS = { const FILTERS = {
all: { label: 'Все', pred: () => true }, all: { label: 'Все', pred: () => true },
family: { label: 'Семья', pred: (n) => n.relationType === 'family' },
friends: { label: 'Друзья', pred: (n) => n.relationType === 'friend' }, friends: { label: 'Друзья', pred: (n) => n.relationType === 'friend' },
shining: { label: 'Сияющие', pred: (n) => Boolean(n.shining) }, shining: { label: 'Сияющие', pred: (n) => Boolean(n.shining) },
}; };
const FILTER_ORDER = ['all', 'family', 'friends', 'shining']; const FILTER_ORDER = ['all', 'friends', 'shining'];
let activeFilter = 'all'; let activeFilter = 'all';
const filterChips = {}; const filterChips = {};
+17 -22
View File
@@ -1,4 +1,4 @@
import { renderHeader } from '../components/header.js'; import { renderHeader } from '../components/header.js';
import { profile } from '../mock-data.js'; import { profile } from '../mock-data.js';
import { authService, state } from '../state.js'; import { authService, state } from '../state.js';
import { import {
@@ -38,11 +38,9 @@ const GENDER_OPTIONS = Object.freeze([
{ value: PROFILE_GENDER_UNKNOWN, label: 'Не указан' }, { value: PROFILE_GENDER_UNKNOWN, label: 'Не указан' },
]); ]);
// Родственные типы сохранены в протоколе, но пока намеренно скрыты из UI.
const RELATIVE_RELATION_OPTIONS = Object.freeze([ const RELATIVE_RELATION_OPTIONS = Object.freeze([
{ value: 'parent', label: 'Родитель (мать/отец по полу)' }, { value: 'friend', label: 'Друг' },
{ value: 'child', label: 'Ребёнок (сын/дочь по полу)' },
{ value: 'spouse', label: 'Жена / Муж (по полу)' },
{ value: 'sibling', label: 'Брат или сестра (по полу)' },
{ value: 'close_friend', label: 'Близкий друг' }, { value: 'close_friend', label: 'Близкий друг' },
]); ]);
@@ -75,6 +73,7 @@ function relationAccusativeLabel(type, targetGender) {
if (gender === PROFILE_GENDER_FEMALE) return 'жену'; if (gender === PROFILE_GENDER_FEMALE) return 'жену';
return 'жену/мужа'; return 'жену/мужа';
} }
if (type === 'friend') return 'друга';
return 'близкого друга'; return 'близкого друга';
} }
@@ -134,12 +133,12 @@ export function render({ navigate, chrome }) {
const relativesCard = document.createElement('div'); const relativesCard = document.createElement('div');
relativesCard.className = 'card stack'; relativesCard.className = 'card stack';
relativesCard.innerHTML = ` relativesCard.innerHTML = `
<div class="profile-param-value"><b>Близкие родственники</b></div> <div class="profile-param-value"><b>Друзья</b></div>
<div class="meta-muted"> <div class="meta-muted">
Добавьте связь: родитель, ребёнок, жена/муж, брат/сестра или близкий друг. Добавьте пользователя в друзья или в близкие друзья.
Формулировка (мать/отец, брат/сестра) определяется по полу выбранного пользователя. Родственные типы связей сохранены в протоколе, но пока скрыты из интерфейса.
</div> </div>
<button class="secondary-btn" type="button" data-add-relative="true">Добавить близких родственников</button> <button class="secondary-btn" type="button" data-add-relative="true">Добавить друга</button>
`; `;
const reloadBtn = topRow.querySelector('[data-reload="true"]'); const reloadBtn = topRow.querySelector('[data-reload="true"]');
@@ -753,20 +752,16 @@ export function render({ navigate, chrome }) {
status.textContent = 'Сохранение связи...'; status.textContent = 'Сохранение связи...';
try { try {
if (relationType === 'close_friend') { if (!state.session.storagePwdInMemory) {
await authService.addCloseFriend(targetLogin); throw new Error('Нет storagePwd в памяти сессии. Выполните вход заново.');
} else {
if (!state.session.storagePwdInMemory) {
throw new Error('Нет storagePwd в памяти сессии. Выполните вход заново.');
}
await authService.setUserRelation({
login: sessionLogin,
toLogin: targetLogin,
kind: relationType,
enabled: true,
storagePwd: state.session.storagePwdInMemory,
});
} }
await authService.setUserRelation({
login: sessionLogin,
toLogin: targetLogin,
kind: relationType,
enabled: true,
storagePwd: state.session.storagePwdInMemory,
});
status.className = 'status-line is-available'; status.className = 'status-line is-available';
status.textContent = `Связь добавлена: ${targetLogin} как ${relationLabel}.`; status.textContent = `Связь добавлена: ${targetLogin} как ${relationLabel}.`;
} catch (error) { } catch (error) {
+59 -203
View File
@@ -64,20 +64,29 @@ function genderText(value) {
function relationButtonLabel(kind, flags) { function relationButtonLabel(kind, flags) {
if (kind === 'contact') return flags.outContact ? 'Убрать из контактов' : 'Добавить в контакты'; if (kind === 'contact') return flags.outContact ? 'Убрать из контактов' : 'Добавить в контакты';
if (kind === 'friend') return flags.outFriend ? 'Убрать из близких друзей' : 'Добавить в близкие друзья'; if (kind === 'friend') return flags.outFriend ? 'Убрать из друзей' : 'Добавить в друзья';
return flags.outFollow ? 'Отписаться' : 'Подписаться'; if (kind === 'close_friend') return flags.outCloseFriend ? 'Убрать из близких друзей' : 'Добавить в близкие друзья';
if (kind === 'official_account') return flags.outOfficialAccount ? 'Снять подтверждение' : 'Подтвердить аккаунт';
if (kind === 'shine_confirmed') return flags.outShineConfirmed ? 'Снять подтверждение' : 'Подтвердить сияющего';
return '';
} }
function relationNextState(kind, flags) { function relationNextState(kind, flags) {
if (kind === 'contact') return !flags.outContact; if (kind === 'contact') return !flags.outContact;
if (kind === 'friend') return !flags.outFriend; if (kind === 'friend') return !flags.outFriend;
return !flags.outFollow; if (kind === 'close_friend') return !flags.outCloseFriend;
if (kind === 'official_account') return !flags.outOfficialAccount;
if (kind === 'shine_confirmed') return !flags.outShineConfirmed;
return false;
} }
function relationConfirmLabel(kind) { function relationConfirmLabel(kind) {
if (kind === 'contact') return 'контакт'; if (kind === 'contact') return 'контакт';
if (kind === 'friend') return 'статус близкого друга'; if (kind === 'friend') return 'статус друга';
return 'подписку'; if (kind === 'close_friend') return 'статус близкого друга';
if (kind === 'official_account') return 'подтверждение официального аккаунта';
if (kind === 'shine_confirmed') return 'подтверждение сияющего';
return 'связь';
} }
function relationStateText(kind, flags) { function relationStateText(kind, flags) {
@@ -88,55 +97,49 @@ function relationStateText(kind, flags) {
return ''; return '';
} }
if (kind === 'friend') { if (kind === 'friend') {
if (flags.outFriend && flags.inFriend) return 'Вы взаимно близкие друзья.'; if (flags.outFriend && flags.inFriend) return 'Вы взаимно считаете друг друга друзьями.';
if (flags.outFriend) return 'Вы считаете этот профиль близким другом.'; if (flags.outFriend) return 'Вы считаете этого человека другом.';
if (flags.inFriend) return 'Этот профиль считает вас близким другом.'; if (flags.inFriend) return 'Этот человек считает вас другом.';
return ''; return '';
} }
if (flags.outFollow && flags.inFollow) return 'Вы взаимно подписаны.'; if (kind === 'close_friend') {
if (flags.outFollow) return 'Вы подписаны на этот профиль.'; if (flags.outCloseFriend && flags.inCloseFriend) return 'Вы взаимно близкие друзья.';
if (flags.inFollow) return 'Этот профиль подписан на вас.'; if (flags.outCloseFriend) return 'Вы считаете этого человека близким другом.';
if (flags.inCloseFriend) return 'Этот человек считает вас близким другом.';
return '';
}
if (kind === 'official_account') {
return flags.outOfficialAccount
? 'Вы подтверждаете, что это действительно официальный аккаунт этого человека.'
: '';
}
if (kind === 'shine_confirmed') {
return flags.outShineConfirmed
? 'Вы подтверждаете, что этот человек сияющий.'
: '';
}
return ''; return '';
} }
function opinionItemsFromFlags(flags) { function renderRelations(flags) {
const items = []; const rows = [
if (flags.outShineSeen) { { kind: 'contact', text: relationStateText('contact', flags), button: relationButtonLabel('contact', flags) },
items.push({ { kind: 'friend', text: relationStateText('friend', flags), button: relationButtonLabel('friend', flags) },
kind: 'shine_seen', { kind: 'close_friend', text: relationStateText('close_friend', flags), button: relationButtonLabel('close_friend', flags) },
text: 'вы утверждаете, что очень мало знаете этого человека, но вы видели его сияющим, и всё, что вы о нём знаете, подтверждает это', { kind: 'official_account', text: relationStateText('official_account', flags), button: relationButtonLabel('official_account', flags) },
label: 'видел сияющим', { kind: 'shine_confirmed', text: relationStateText('shine_confirmed', flags), button: relationButtonLabel('shine_confirmed', flags) },
}); ];
}
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) { return `
if (flags.outShineSeen) return 'shine_seen'; <div class="card stack user-relations-list" data-profile-relations="true">
if (flags.outShineConfirmed) return 'shine_confirmed'; ${rows.map((row) => `
if (flags.outKnownPerson) return 'known_person'; <div class="user-rel-row ${row.text ? '' : 'is-empty'}">
return ''; <span class="user-rel-text">${escapeHtml(row.text)}</span>
} <button class="ghost-btn user-rel-action" type="button" data-relation-action="${row.kind}">${escapeHtml(row.button)}</button>
</div>
function opinionLabelByKind(kind) { `).join('')}
if (kind === 'shine_seen') return 'мало знаком, но видел сияющим'; </div>
if (kind === 'shine_confirmed') return 'точно уверен, что сияющий'; `;
if (kind === 'known_person') return 'просто знаю человека';
return kind;
} }
function renderIdentity(card) { function renderIdentity(card) {
@@ -182,82 +185,6 @@ function renderReadOnlyBadges(card) {
`; `;
} }
function renderRelations(flags) {
const rows = [
{ 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 `
<div class="card stack user-relations-list" data-profile-relations="true">
${rows.map((row) => `
<div class="user-rel-row ${row.text ? '' : 'is-empty'}">
<span class="user-rel-text">${escapeHtml(row.text)}</span>
<button class="ghost-btn user-rel-action" type="button" data-relation-action="${row.kind}">${escapeHtml(row.button)}</button>
</div>
`).join('')}
<div class="user-rel-opinions-wrap ${hasOpinion ? '' : 'is-empty'}">
<div class="user-rel-opinions-list">
${opinionItems.map((item) => `
<div class="user-rel-opinion-item">${escapeHtml(item.text)}</div>
`).join('')}
</div>
<div class="user-rel-opinions-hint">Добавьте одну из этих трёх формулировок.</div>
</div>
<div class="user-rel-row">
<span class="user-rel-text">${hasOpinion ? 'Мнение уже добавлено.' : 'Пока нет дополнительной связи.'}</span>
<button class="ghost-btn user-rel-action user-rel-opinion-btn" type="button" data-relation-action="opinion-menu">${hasOpinion ? 'Изменить мнение' : 'Добавить мнение'}</button>
</div>
</div>
`;
}
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) => `<button class="secondary-btn user-opinion-modal-btn is-add" type="button" data-opinion-kind="${item.kind}" data-opinion-mode="set">Высказать: ${item.title}</button>`)
.join('');
const removeHtml = activeKind
? `<button class="secondary-btn user-opinion-modal-btn is-remove" type="button" data-opinion-kind="${activeKind}" data-opinion-mode="remove">Убрать мнение</button>`
: '';
root.innerHTML = `
<div class="modal" id="user-opinion-modal">
<div class="modal-card stack">
<h3 class="modal-title">${activeKind ? 'Изменить мнение' : 'Добавить мнение'}</h3>
<div class="stack">${rowsHtml}${removeHtml}</div>
<button class="secondary-btn" type="button" id="user-opinion-modal-close">Закрыть</button>
</div>
</div>
`;
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) { function renderReadOnlyParams(card) {
const rows = [ const rows = [
{ label: 'Имя', value: card.firstName }, { label: 'Имя', value: card.firstName },
@@ -310,20 +237,15 @@ export function render({ navigate, route }) {
let isBusy = false; let isBusy = false;
function syncActionButtons() { function syncActionButtons() {
const followBtn = body.querySelector('[data-relation-action="follow"]'); const kinds = ['contact', 'friend', 'close_friend', 'official_account', 'shine_confirmed'];
const friendBtn = body.querySelector('[data-relation-action="friend"]'); if (!currentFlags) return;
const contactBtn = body.querySelector('[data-relation-action="contact"]');
const opinionBtn = body.querySelector('[data-relation-action="opinion-menu"]');
if (!followBtn || !friendBtn || !contactBtn || !opinionBtn || !currentFlags) return;
const isSelf = currentCard && currentCard.login.toLowerCase() === sessionLogin.toLowerCase(); const isSelf = currentCard && currentCard.login.toLowerCase() === sessionLogin.toLowerCase();
contactBtn.textContent = relationButtonLabel('contact', currentFlags); kinds.forEach((kind) => {
friendBtn.textContent = relationButtonLabel('friend', currentFlags); const btn = body.querySelector(`[data-relation-action="${kind}"]`);
followBtn.textContent = relationButtonLabel('follow', currentFlags); if (!btn) return;
contactBtn.disabled = Boolean(isSelf); btn.textContent = relationButtonLabel(kind, currentFlags);
friendBtn.disabled = Boolean(isSelf); btn.disabled = Boolean(isSelf);
followBtn.disabled = Boolean(isSelf); });
opinionBtn.textContent = opinionItemsFromFlags(currentFlags).length ? 'Изменить мнение' : 'Добавить мнение';
opinionBtn.disabled = Boolean(isSelf);
} }
async function refresh() { async function refresh() {
@@ -384,14 +306,6 @@ export function render({ navigate, route }) {
return; return;
} }
if (kind === 'opinion-menu') {
openOpinionMenuModal({
flags: currentFlags,
onApply: onOpinionApply,
});
return;
}
const nextEnabled = relationNextState(kind, currentFlags); const nextEnabled = relationNextState(kind, currentFlags);
const confirmed = window.confirm( const confirmed = window.confirm(
`Изменить ${relationConfirmLabel(kind)} с пользователем ${currentCard.login}?\n` + `Изменить ${relationConfirmLabel(kind)} с пользователем ${currentCard.login}?\n` +
@@ -420,64 +334,6 @@ 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();
if (mode === 'set') {
const opinionVisible = Boolean(
currentFlags?.outKnownPerson
|| currentFlags?.outShineConfirmed
|| currentFlags?.outShineSeen,
);
if (!opinionVisible) {
await new Promise((resolve) => window.setTimeout(resolve, 350));
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) => { body.addEventListener('click', (event) => {
const target = event.target; const target = event.target;
if (!(target instanceof HTMLElement)) return; if (!(target instanceof HTMLElement)) return;
+6 -3
View File
@@ -90,18 +90,21 @@ const SIGNAL_TYPE_REMOTE_ADDBLOCK_REQUEST = 'remote_addblock_request';
const SIGNAL_TYPE_REMOTE_ADDBLOCK_RESULT = 'remote_addblock_result'; const SIGNAL_TYPE_REMOTE_ADDBLOCK_RESULT = 'remote_addblock_result';
const CONNECTION_SUBTYPES = Object.freeze({ const CONNECTION_SUBTYPES = Object.freeze({
// Legacy alias: friend == close_friend. Оба ключа ведут на один и тот же код 10/11.
close_friend: { on: 10, off: 11 }, close_friend: { on: 10, off: 11 },
friend: { on: 10, off: 11 }, friend: { on: 14, off: 15 },
contact: { on: 20, off: 21 }, contact: { on: 20, off: 21 },
// Reserved: пока не используется UI.
follow: { on: 30, off: 31 }, follow: { on: 30, off: 31 },
// Reserved: семейные связи пока скрыты из UI.
spouse: { on: 40, off: 41 }, spouse: { on: 40, off: 41 },
parent: { on: 50, off: 51 }, parent: { on: 50, off: 51 },
child: { on: 52, off: 53 }, child: { on: 52, off: 53 },
sibling: { on: 54, off: 55 }, sibling: { on: 54, off: 55 },
known_person: { on: 60, off: 61 }, // Legacy 60/61 intentionally absent: старые блоки сервер принимает, новый UI их не создаёт.
shine_confirmed: { on: 70, off: 71 }, shine_confirmed: { on: 70, off: 71 },
// Reserved: пока не используется UI.
shine_seen: { on: 74, off: 75 }, shine_seen: { on: 74, off: 75 },
official_account: { on: 80, off: 81 },
}); });
function normalizeServerUrl(url) { function normalizeServerUrl(url) {
+18
View File
@@ -50,6 +50,8 @@ function readArray(payload, key) {
const aliases = { const aliases = {
outKnownPersons: ['outKnownPersons', 'outKnownPerson', 'out_known_persons'], outKnownPersons: ['outKnownPersons', 'outKnownPerson', 'out_known_persons'],
inKnownPersons: ['inKnownPersons', 'inKnownPerson', 'in_known_persons'], inKnownPersons: ['inKnownPersons', 'inKnownPerson', 'in_known_persons'],
outOfficialAccounts: ['outOfficialAccounts', 'outOfficialAccount', 'out_official_accounts'],
inOfficialAccounts: ['inOfficialAccounts', 'inOfficialAccount', 'in_official_accounts'],
outShineConfirmed: ['outShineConfirmed', 'outShineConfident', 'out_shine_confirmed'], outShineConfirmed: ['outShineConfirmed', 'outShineConfident', 'out_shine_confirmed'],
inShineConfirmed: ['inShineConfirmed', 'inShineConfident', 'in_shine_confirmed'], inShineConfirmed: ['inShineConfirmed', 'inShineConfident', 'in_shine_confirmed'],
outShineSeen: ['outShineSeen', 'out_shine_seen'], outShineSeen: ['outShineSeen', 'out_shine_seen'],
@@ -81,6 +83,8 @@ async function buildRelationsModel(login) {
return { return {
outFriends: [], outFriends: [],
inFriends: [], inFriends: [],
outCloseFriends: [],
inCloseFriends: [],
outContacts: [], outContacts: [],
inContacts: [], inContacts: [],
outFollows: [], outFollows: [],
@@ -93,6 +97,8 @@ async function buildRelationsModel(login) {
inSiblings: [], inSiblings: [],
outKnownPersons: [], outKnownPersons: [],
inKnownPersons: [], inKnownPersons: [],
outOfficialAccounts: [],
inOfficialAccounts: [],
outShineConfirmed: [], outShineConfirmed: [],
inShineConfirmed: [], inShineConfirmed: [],
outShineSeen: [], outShineSeen: [],
@@ -129,6 +135,8 @@ async function buildRelationsModel(login) {
return { return {
outFriends: readArray(graph, 'outFriends') || [], outFriends: readArray(graph, 'outFriends') || [],
inFriends: readArray(graph, 'inFriends') || [], inFriends: readArray(graph, 'inFriends') || [],
outCloseFriends: readArray(graph, 'outCloseFriends') || [],
inCloseFriends: readArray(graph, 'inCloseFriends') || [],
outContacts, outContacts,
inContacts: readArray(graph, 'inContacts') || [], inContacts: readArray(graph, 'inContacts') || [],
outFollows, outFollows,
@@ -141,6 +149,8 @@ async function buildRelationsModel(login) {
inSiblings: readArray(graph, 'inSiblings') || [], inSiblings: readArray(graph, 'inSiblings') || [],
outKnownPersons: readArray(graph, 'outKnownPersons') || [], outKnownPersons: readArray(graph, 'outKnownPersons') || [],
inKnownPersons: readArray(graph, 'inKnownPersons') || [], inKnownPersons: readArray(graph, 'inKnownPersons') || [],
outOfficialAccounts: readArray(graph, 'outOfficialAccounts') || [],
inOfficialAccounts: readArray(graph, 'inOfficialAccounts') || [],
outShineConfirmed: readArray(graph, 'outShineConfirmed') || [], outShineConfirmed: readArray(graph, 'outShineConfirmed') || [],
inShineConfirmed: readArray(graph, 'inShineConfirmed') || [], inShineConfirmed: readArray(graph, 'inShineConfirmed') || [],
outShineSeen: readArray(graph, 'outShineSeen') || [], outShineSeen: readArray(graph, 'outShineSeen') || [],
@@ -169,6 +179,8 @@ export async function loadCurrentRelations() {
return { return {
outFriends: [], outFriends: [],
inFriends: [], inFriends: [],
outCloseFriends: [],
inCloseFriends: [],
outContacts: [], outContacts: [],
inContacts: [], inContacts: [],
outFollows: [], outFollows: [],
@@ -181,6 +193,8 @@ export async function loadCurrentRelations() {
inSiblings: [], inSiblings: [],
outKnownPersons: [], outKnownPersons: [],
inKnownPersons: [], inKnownPersons: [],
outOfficialAccounts: [],
inOfficialAccounts: [],
outShineConfirmed: [], outShineConfirmed: [],
inShineConfirmed: [], inShineConfirmed: [],
outShineSeen: [], outShineSeen: [],
@@ -194,6 +208,8 @@ export function relationFlagsForTarget(relations, targetLogin) {
return { return {
outFriend: listContainsLogin(relations?.outFriends, targetLogin), outFriend: listContainsLogin(relations?.outFriends, targetLogin),
inFriend: listContainsLogin(relations?.inFriends, targetLogin), inFriend: listContainsLogin(relations?.inFriends, targetLogin),
outCloseFriend: listContainsLogin(relations?.outCloseFriends, targetLogin),
inCloseFriend: listContainsLogin(relations?.inCloseFriends, targetLogin),
outContact: listContainsLogin(relations?.outContacts, targetLogin), outContact: listContainsLogin(relations?.outContacts, targetLogin),
inContact: listContainsLogin(relations?.inContacts, targetLogin), inContact: listContainsLogin(relations?.inContacts, targetLogin),
outFollow: listContainsLogin(relations?.outFollows, targetLogin), outFollow: listContainsLogin(relations?.outFollows, targetLogin),
@@ -206,6 +222,8 @@ export function relationFlagsForTarget(relations, targetLogin) {
inSibling: listContainsLogin(relations?.inSiblings, targetLogin), inSibling: listContainsLogin(relations?.inSiblings, targetLogin),
outKnownPerson: listContainsLogin(relations?.outKnownPersons, targetLogin), outKnownPerson: listContainsLogin(relations?.outKnownPersons, targetLogin),
inKnownPerson: listContainsLogin(relations?.inKnownPersons, targetLogin), inKnownPerson: listContainsLogin(relations?.inKnownPersons, targetLogin),
outOfficialAccount: listContainsLogin(relations?.outOfficialAccounts, targetLogin),
inOfficialAccount: listContainsLogin(relations?.inOfficialAccounts, targetLogin),
outShineConfirmed: listContainsLogin(relations?.outShineConfirmed, targetLogin), outShineConfirmed: listContainsLogin(relations?.outShineConfirmed, targetLogin),
inShineConfirmed: listContainsLogin(relations?.inShineConfirmed, targetLogin), inShineConfirmed: listContainsLogin(relations?.inShineConfirmed, targetLogin),
outShineSeen: listContainsLogin(relations?.outShineSeen, targetLogin), outShineSeen: listContainsLogin(relations?.outShineSeen, targetLogin),