Files
glchat/web/src/lib/identity.ts
T

170 lines
6.0 KiB
TypeScript
Raw Normal View History

/** Инициалы для аватара-заглушки: до двух первых букв значимых слов. */
export function initials(name: string): string {
const parts = name
.trim()
.split(/\s+/u)
.filter((part) => part.length > 0);
if (parts.length === 0) {
return '?';
}
const first = parts[0] ?? '';
if (parts.length === 1) {
return [...first].slice(0, 2).join('').toLocaleUpperCase();
}
const second = parts[1] ?? '';
return `${[...first][0] ?? ''}${[...second][0] ?? ''}`.toLocaleUpperCase();
}
/** Стабильный оттенок для заглушки сервера/пользователя. */
export function hueFromId(id: string): number {
let hash = 0;
for (let index = 0; index < id.length; index += 1) {
hash = (hash * 31 + id.charCodeAt(index)) % 360;
}
return hash;
}
/**
* Ссылка на файл аватара. Файловый сервис отдаётся тем же origin
* (`/files/{id}`), поэтому отдельный хост не нужен.
*/
export function avatarUrl(fileId: string | undefined): string | null {
return fileId === undefined || fileId === '' ? null : `/files/${encodeURIComponent(fileId)}`;
}
/**
* Права приходят с сервера списком имён (`permissions.Names` в Go):
* например `["VIEW_GUILD", "MANAGE_CHANNELS"]`. Проверяем вхождение имени.
*/
export const PERMISSION_ADMINISTRATOR = 'ADMINISTRATOR';
export const PERMISSION_MANAGE_GUILD = 'MANAGE_GUILD';
export const PERMISSION_MANAGE_CHANNELS = 'MANAGE_CHANNELS';
export const PERMISSION_MANAGE_MESSAGES = 'MANAGE_MESSAGES';
export const PERMISSION_MANAGE_EMOJIS = 'MANAGE_EMOJIS';
export const PERMISSION_SEND_MESSAGES = 'SEND_MESSAGES';
export const PERMISSION_ATTACH_FILES = 'ATTACH_FILES';
export const PERMISSION_ADD_REACTIONS = 'ADD_REACTIONS';
/** Есть ли у пользователя право с указанным именем. */
export function hasPermission(permissions: readonly string[], name: string): boolean {
return permissions.includes(name);
}
/** Может ли пользователь управлять сервером (создавать комнаты и т.п.). */
export function canManageGuild(
permissions: readonly string[],
ownerId: string,
userId: string | undefined,
): boolean {
if (userId !== undefined && userId === ownerId) {
return true;
}
return (
hasPermission(permissions, PERMISSION_ADMINISTRATOR) ||
hasPermission(permissions, PERMISSION_MANAGE_GUILD) ||
hasPermission(permissions, PERMISSION_MANAGE_CHANNELS)
);
}
/**
* Может ли пользователь менять настройки сервера (имя, описание, удаление).
* `MANAGE_CHANNELS` сюда не входит: его достаточно для комнат, но не для
* самого сервера — сервер разрешает `PATCH /guilds/{id}` только владельцу,
* ADMINISTRATOR и MANAGE_GUILD.
*/
export function canManageGuildSettings(
permissions: readonly string[],
ownerId: string,
userId: string | undefined,
): boolean {
if (userId !== undefined && userId === ownerId) {
return true;
}
return (
hasPermission(permissions, PERMISSION_ADMINISTRATOR) ||
hasPermission(permissions, PERMISSION_MANAGE_GUILD)
);
}
/**
* Может ли пользователь открыть администрирование сервера: владелец,
* ADMINISTRATOR/MANAGE_GUILD или администратор инстанса.
*/
export function canAdministerGuild(
permissions: readonly string[],
ownerId: string,
userId: string | undefined,
isInstanceAdmin = false,
): boolean {
return isInstanceAdmin || canManageGuildSettings(permissions, ownerId, userId);
}
/**
* Может ли пользователь модерировать сообщения (удалять и закреплять чужие).
* Права приходят на уровне сервера, поэтому это приближение: сервер всё равно
* проверит права в конкретной комнате.
*/ export function canManageMessages(
permissions: readonly string[],
isInstanceAdmin = false,
): boolean {
if (isInstanceAdmin) {
return true;
}
return (
hasPermission(permissions, PERMISSION_ADMINISTRATOR) ||
hasPermission(permissions, PERMISSION_MANAGE_MESSAGES)
);
}
/**
* Может ли пользователь управлять эмодзи сервера (AGENT.md 7.12): владелец,
* ADMINISTRATOR, MANAGE_EMOJIS или администратор инстанса. `MANAGE_GUILD`
* сюда не входит: сервер проверяет именно `MANAGE_EMOJIS`.
*/
export function canManageEmojis(
permissions: readonly string[],
ownerId: string,
userId: string | undefined,
isInstanceAdmin = false,
): boolean {
if (isInstanceAdmin || (userId !== undefined && userId === ownerId)) {
return true;
}
return (
hasPermission(permissions, PERMISSION_ADMINISTRATOR) ||
hasPermission(permissions, PERMISSION_MANAGE_EMOJIS)
);
}
/** Может ли пользователь отправлять сообщения в комнату. */
export function canSendMessages(
permissions: readonly string[],
isInstanceAdmin = false,
channelCanSend?: boolean,
): boolean {
if (channelCanSend === false) {
return false;
}
if (isInstanceAdmin) {
return true;
}
return (
hasPermission(permissions, PERMISSION_ADMINISTRATOR) ||
hasPermission(permissions, PERMISSION_SEND_MESSAGES)
);
}
/** Человекочитаемая дата/время для списков (сессии, аудит). */ export function formatDateTime(
value: string,
locale: string,
): string {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return value;
}
return new Intl.DateTimeFormat(locale, {
dateStyle: 'medium',
timeStyle: 'short',
}).format(date);
}