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:
2026-09-21 00:26:50 +03:00
parent 4ea2965892
commit 2e038a1d3f
32 changed files with 2494 additions and 23 deletions
+10 -2
View File
@@ -89,14 +89,22 @@ function routes(guild: Record<string, unknown> = guildDetail()): FetchRoute[] {
];
}
/** Рендерит приложение с READY-снапшотом сервера. */
/**
* Рендерит приложение с READY-снапшотом сервера. Права и владелец берутся из
* того же описания сервера, что и ответ REST: снапшот — источник правды.
*/
async function bootApp(guild: Record<string, unknown> = guildDetail()) {
const permissions = Array.isArray(guild.my_permissions)
? (guild.my_permissions as string[])
: ['MANAGE_GUILD', 'CREATE_INVITES'];
const ownerId = typeof guild.owner_id === 'string' ? guild.owner_id : 'user-1';
const snapshot = makeReadySnapshot([
{
id: 'g-1',
name: 'Сервер',
is_main: true,
my_permissions: ['MANAGE_GUILD', 'CREATE_INVITES'],
owner_id: ownerId,
my_permissions: permissions,
channels,
voice_states: [makeVoiceState({ user_id: 'user-2', channel_id: 'c-voice' })],
// Оформление приходит в снапшоте шлюза — так же, как в ReadyGuild.