SHA256
228 lines
9.1 KiB
JavaScript
228 lines
9.1 KiB
JavaScript
import { authService, state } from '../state.js';
|
||
import {
|
||
buildArweaveAvatarValue,
|
||
parseArweaveAvatarValue,
|
||
validateArweaveTxId,
|
||
validateSha256Hex,
|
||
} from './arweave-file-service.js';
|
||
|
||
export const profileFieldDefs = [
|
||
{ key: 'first_name', readKeys: ['first_name'], label: 'Имя', placeholder: 'Введите имя' },
|
||
{ key: 'last_name', readKeys: ['last_name'], label: 'Фамилия', placeholder: 'Введите фамилию' },
|
||
{ key: 'address', readKeys: ['address'], label: 'Адрес', placeholder: 'Город, улица, дом' },
|
||
{ key: 'web', readKeys: ['web'], label: 'Links', placeholder: 'Сайт, профиль или другая ссылка' },
|
||
{ key: 'phone', readKeys: ['phone'], label: 'Телефон', placeholder: '+7 ...' },
|
||
{ key: 'about', readKeys: ['about'], label: 'О себе', placeholder: 'Коротко расскажите о себе', maxLength: 160, multiline: true },
|
||
{ key: 'spiritual_path', readKeys: ['spiritual_path'], label: 'Духовный путь', placeholder: 'Расскажите о своём духовном пути, опыте, практиках и взглядах', maxLength: 5000, multiline: true },
|
||
];
|
||
|
||
export const profileToggleDefs = []; // legacy boolean toggles are no longer used by the new UI.
|
||
|
||
export const PROFILE_ACCOUNT_ROLE_PRIMARY = 'primary';
|
||
export const PROFILE_ACCOUNT_ROLE_NON_VOTING = 'non_voting';
|
||
export const PROFILE_SHINE_SHINING = 'shining';
|
||
export const PROFILE_SHINE_UNKNOWN = 'unknown';
|
||
export const PROFILE_SHINE_NOT_INTERESTED = 'not_interested';
|
||
|
||
export const PROFILE_GENDER_MALE = 'male';
|
||
export const PROFILE_GENDER_FEMALE = 'female';
|
||
export const PROFILE_GENDER_UNKNOWN = 'unknown';
|
||
export const PROFILE_GENDER_VALUES = Object.freeze([
|
||
PROFILE_GENDER_MALE,
|
||
PROFILE_GENDER_FEMALE,
|
||
PROFILE_GENDER_UNKNOWN,
|
||
]);
|
||
|
||
function normalizeItem(param, payload) {
|
||
if (!param) return null;
|
||
|
||
if (payload && typeof payload === 'object') {
|
||
const value = String(payload?.value || payload?.param_value || '');
|
||
const timeMs = Number(payload?.time_ms || payload?.timeMs || 0);
|
||
if (!value && !timeMs) return null;
|
||
return { param, value, timeMs };
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
function parseToggleValue(value) {
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
return normalized === 'true' || normalized === 'yes' || normalized === '1';
|
||
}
|
||
|
||
function normalizeGenderValue(value) {
|
||
const normalized = String(value || '').trim().toLowerCase();
|
||
if (normalized === PROFILE_GENDER_MALE) return PROFILE_GENDER_MALE;
|
||
if (normalized === PROFILE_GENDER_FEMALE) return PROFILE_GENDER_FEMALE;
|
||
return PROFILE_GENDER_UNKNOWN;
|
||
}
|
||
|
||
async function getStoragePwd() {
|
||
const storagePwd = state.session.storagePwdInMemory;
|
||
if (!storagePwd) {
|
||
throw new Error('Нет storagePwd в памяти сессии. Выполните вход заново.');
|
||
}
|
||
return storagePwd;
|
||
}
|
||
|
||
function normalizeListItems(payload) {
|
||
const rows = Array.isArray(payload?.params) ? payload.params : [];
|
||
const normalized = [];
|
||
for (let i = 0; i < rows.length; i += 1) {
|
||
const row = rows[i];
|
||
if (!row || typeof row !== 'object') continue;
|
||
const param = String(row.param || '').trim();
|
||
if (!param) continue;
|
||
const item = normalizeItem(param, row);
|
||
if (item) normalized.push(item);
|
||
}
|
||
return normalized;
|
||
}
|
||
|
||
function loadLatestByAliasesFromItems(items, aliases) {
|
||
if (!Array.isArray(items) || !items.length || !Array.isArray(aliases) || !aliases.length) return null;
|
||
const aliasSet = new Set(aliases.map((alias) => String(alias || '').trim().toLowerCase()).filter(Boolean));
|
||
if (!aliasSet.size) return null;
|
||
|
||
let latest = null;
|
||
for (let i = 0; i < items.length; i += 1) {
|
||
const item = items[i];
|
||
const itemParam = String(item?.param || '').trim().toLowerCase();
|
||
if (!itemParam || !aliasSet.has(itemParam)) continue;
|
||
if (!latest || Number(item.timeMs || 0) > Number(latest.timeMs || 0)) {
|
||
latest = item;
|
||
}
|
||
}
|
||
return latest;
|
||
}
|
||
|
||
export async function loadProfileSnapshot(login) {
|
||
const payload = await authService.listUserParams(login);
|
||
const items = normalizeListItems(payload);
|
||
|
||
const fields = [];
|
||
for (let i = 0; i < profileFieldDefs.length; i += 1) {
|
||
const field = profileFieldDefs[i];
|
||
const latest = loadLatestByAliasesFromItems(items, field.readKeys);
|
||
fields.push({
|
||
key: field.key,
|
||
label: field.label,
|
||
placeholder: field.placeholder,
|
||
maxLength: Number(field.maxLength || 300),
|
||
multiline: Boolean(field.multiline),
|
||
value: latest?.value || '',
|
||
timeMs: latest?.timeMs || 0,
|
||
});
|
||
}
|
||
|
||
const firstName = String(fields.find((field) => field.key === 'first_name')?.value || '').trim();
|
||
const lastName = String(fields.find((field) => field.key === 'last_name')?.value || '').trim();
|
||
|
||
const latestAccountRole = loadLatestByAliasesFromItems(items, ['account_role']);
|
||
const rawAccountRole = String(latestAccountRole?.value || '').trim().toLowerCase();
|
||
const accountRole = rawAccountRole === PROFILE_ACCOUNT_ROLE_PRIMARY || rawAccountRole === PROFILE_ACCOUNT_ROLE_NON_VOTING
|
||
? rawAccountRole
|
||
: '';
|
||
|
||
const latestShine = loadLatestByAliasesFromItems(items, ['shine']);
|
||
const rawShine = String(latestShine?.value || '').trim().toLowerCase();
|
||
let shineStatus = '';
|
||
if (rawShine === PROFILE_SHINE_SHINING || rawShine === 'yes') shineStatus = PROFILE_SHINE_SHINING;
|
||
else if (rawShine === PROFILE_SHINE_UNKNOWN) shineStatus = PROFILE_SHINE_UNKNOWN;
|
||
else if (rawShine === PROFILE_SHINE_NOT_INTERESTED) shineStatus = PROFILE_SHINE_NOT_INTERESTED;
|
||
// Legacy shine=no is intentionally ignored. Legacy official=yes/no is not read at all.
|
||
|
||
const toggles = [
|
||
{ key: 'account_role', label: 'Роль аккаунта', enabled: accountRole === PROFILE_ACCOUNT_ROLE_PRIMARY, rawValue: accountRole, timeMs: latestAccountRole?.timeMs || 0 },
|
||
{ key: 'shine', label: 'Сияние', enabled: shineStatus === PROFILE_SHINE_SHINING, rawValue: shineStatus, timeMs: latestShine?.timeMs || 0 },
|
||
];
|
||
|
||
const latestGender = loadLatestByAliasesFromItems(items, ['gender']);
|
||
const gender = normalizeGenderValue(latestGender?.value || PROFILE_GENDER_UNKNOWN);
|
||
const latestAvatar = loadLatestByAliasesFromItems(items, ['ava']);
|
||
const parsedAvatar = parseArweaveAvatarValue(latestAvatar?.value || '');
|
||
const avatar = parsedAvatar.ok
|
||
? {
|
||
value: buildArweaveAvatarValue(parsedAvatar.txId, parsedAvatar.sha256Hex),
|
||
source: 'arweave',
|
||
txId: parsedAvatar.txId,
|
||
sha256Hex: parsedAvatar.sha256Hex || '',
|
||
timeMs: latestAvatar?.timeMs || 0,
|
||
}
|
||
: {
|
||
value: '',
|
||
source: '',
|
||
txId: '',
|
||
sha256Hex: '',
|
||
timeMs: latestAvatar?.timeMs || 0,
|
||
};
|
||
|
||
return {
|
||
fields,
|
||
firstName,
|
||
lastName,
|
||
toggles,
|
||
accountRole,
|
||
accountRoleTimeMs: latestAccountRole?.timeMs || 0,
|
||
shineStatus,
|
||
shineTimeMs: latestShine?.timeMs || 0,
|
||
gender,
|
||
genderTimeMs: latestGender?.timeMs || 0,
|
||
avatar,
|
||
};
|
||
}
|
||
|
||
export async function saveProfileParamBlock(login, key, value) {
|
||
const storagePwd = await getStoragePwd();
|
||
await authService.addBlockUserParam({
|
||
login,
|
||
param: key,
|
||
value: String(value ?? '').trim(),
|
||
storagePwd,
|
||
});
|
||
}
|
||
|
||
export async function saveProfileStatus(login, key, value) {
|
||
const cleanKey = String(key || '').trim();
|
||
const cleanValue = String(value || '').trim().toLowerCase();
|
||
if (cleanKey === 'account_role' && ![PROFILE_ACCOUNT_ROLE_PRIMARY, PROFILE_ACCOUNT_ROLE_NON_VOTING].includes(cleanValue)) {
|
||
throw new Error('Некорректное значение account_role');
|
||
}
|
||
if (cleanKey === 'shine' && ![PROFILE_SHINE_SHINING, PROFILE_SHINE_UNKNOWN, PROFILE_SHINE_NOT_INTERESTED].includes(cleanValue)) {
|
||
throw new Error('Некорректное значение shine');
|
||
}
|
||
const storagePwd = await getStoragePwd();
|
||
await authService.addBlockUserParam({ login, param: cleanKey, value: cleanValue, storagePwd });
|
||
}
|
||
|
||
// Kept only for old callers outside the current UI. New code must use saveProfileStatus().
|
||
export async function saveProfileToggle(login, key, enabled) {
|
||
if (key === 'shine') return saveProfileStatus(login, 'shine', enabled ? PROFILE_SHINE_SHINING : PROFILE_SHINE_UNKNOWN);
|
||
if (key === 'official') throw new Error('Параметр official устарел. Используйте account_role.');
|
||
throw new Error(`Неизвестный legacy toggle: ${key}`);
|
||
}
|
||
|
||
export async function saveProfileGender(login, gender) {
|
||
const normalized = normalizeGenderValue(gender);
|
||
const storagePwd = await getStoragePwd();
|
||
await authService.addBlockUserParam({
|
||
login,
|
||
param: 'gender',
|
||
value: normalized,
|
||
storagePwd,
|
||
});
|
||
}
|
||
|
||
export async function saveProfileAvatarArweave(login, txId, sha256Hex) {
|
||
const cleanTxId = String(txId || '').trim();
|
||
const cleanSha = String(sha256Hex || '').trim().toLowerCase();
|
||
if (!validateArweaveTxId(cleanTxId)) {
|
||
throw new Error('Некорректный Transaction ID Arweave');
|
||
}
|
||
if (!validateSha256Hex(cleanSha)) {
|
||
throw new Error('Некорректный SHA256 хэш аватара');
|
||
}
|
||
await saveProfileParamBlock(login, 'ava', buildArweaveAvatarValue(cleanTxId, cleanSha));
|
||
}
|