feat(webhooks): вебхуки комнат — бэкенд, клиент и настройки комнаты
- миграция 00012: таблица webhooks, снимок имени и аватара в messages (AGENT.md 7.11)
- API: список/создание/правка/удаление и пересоздание токена (MANAGE_WEBHOOKS,
step-up при создании, аудит), загрузка аватара отдельной multipart-ручкой
- исполнение POST /webhooks/{id}/{token} без сессии: content, username,
avatar_url, файлы создателя вебхука, лимит 30/мин на вебхук
- клиент: пункт настроек «Комната» со списком вебхуков, копированием ссылки,
пересозданием токена и удалением
- сообщения вебхуков: имя, аватар и значок в ленте вместо «неизвестного автора»
- fix: неполный ответ REST больше не затирает данные READY-снапшота
This commit is contained in:
@@ -1,10 +1,25 @@
|
||||
import { avatarUrl, hueFromId, initials } from '@/lib/identity';
|
||||
|
||||
/**
|
||||
* Безопасная ссылка на картинку аватара: файл инстанса или внешний http(s).
|
||||
* Всё остальное (в том числе `javascript:`) игнорируется.
|
||||
*/
|
||||
function safeImageUrl(url: string | undefined): string | null {
|
||||
if (url === undefined || url === '') {
|
||||
return null;
|
||||
}
|
||||
return url.startsWith('/files/') || url.startsWith('https://') || url.startsWith('http://')
|
||||
? url
|
||||
: null;
|
||||
}
|
||||
|
||||
interface AvatarProps {
|
||||
name: string;
|
||||
/** Стабильный id для оттенка заглушки. */
|
||||
seed: string;
|
||||
fileId?: string | undefined;
|
||||
/** Готовая ссылка (аватар вебхука) вместо `fileId`. */
|
||||
url?: string | undefined;
|
||||
/** Локальный предпросмотр (свежевыбранный файл) вместо `fileId`. */
|
||||
preview?: string | undefined;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
@@ -28,11 +43,12 @@ export function Avatar({
|
||||
name,
|
||||
seed,
|
||||
fileId,
|
||||
url,
|
||||
preview,
|
||||
size = 'md',
|
||||
shape = 'circle',
|
||||
}: AvatarProps) {
|
||||
const src = preview ?? avatarUrl(fileId);
|
||||
const src = preview ?? safeImageUrl(url) ?? avatarUrl(fileId);
|
||||
const hue = hueFromId(seed);
|
||||
const radius = shape === 'circle' ? 'rounded-full' : 'rounded-[var(--radius-lg)]';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user