fix(files): аватары и баннеры доступны авторизованным пользователям

Аватар нельзя было показать в списке друзей: файл без комнаты был доступен
только загрузившему. Добавлено назначение файла (`files.purpose`: attachment |
avatar | banner, миграция 00006): аватары и баннеры отдаются любому
авторизованному пользователю, вложения по-прежнему проверяются по правам
комнаты, а непривязанные загрузки остаются приватными.
This commit is contained in:
2026-09-20 01:39:04 +03:00
parent 20eed227ca
commit d02ef7527a
35 changed files with 2330 additions and 191 deletions
+38 -2
View File
@@ -11,17 +11,21 @@ import {
import {
parseChannelDelete,
parseChannelPayload,
parseDmChannelEvent,
parseGuildId,
parseGuildUpdate,
parseMessageDelete,
parseMessagePayload,
parsePinsUpdate,
parsePresenceEvent,
parseReactionEvent,
parseReadStateUpdate,
parseRelationshipEvent,
parseRoleEvent,
parseTypingStart,
parseUserUpdate,
} from '@/api/gatewayEvents';
import { useFriendsStore } from '@/stores/friends';
import { useMessagesStore } from '@/stores/messages';
import { useSessionStore } from '@/stores/session';
@@ -170,6 +174,11 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
if (channel === null) {
return;
}
// Личная беседа живёт вне серверов: у неё нет guild_id.
if (channel.type === 'dm') {
session.upsertDmChannel(channel);
return;
}
const guildId = channel.guild_id ?? session.selectedGuildId;
if (guildId !== null) {
session.upsertChannel(guildId, channel);
@@ -260,9 +269,32 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
}
return;
}
case 'PRESENCE_UPDATE':
// Присутствие других участников в Фазе 1 не отображается.
case 'PRESENCE_UPDATE': {
// Статус друга: обновляем список друзей и шапку личной беседы точечно.
const presence = parsePresenceEvent(event.d);
if (presence === null) {
return;
}
useFriendsStore.getState().applyPresence(presence.user_id, presence);
session.patchDmPresence(presence.user_id, presence.visible_status ?? presence.status);
return;
}
case 'RELATIONSHIP_UPDATE': {
// Связь появилась или пропала — перечитываем список целиком.
const relationship = parseRelationshipEvent(event.d);
if (relationship !== null) {
void useFriendsStore.getState().reload();
}
return;
}
case 'DM_CHANNEL_CREATE': {
// В событии только channel_id: список бесед забираем по REST.
const direct = parseDmChannelEvent(event.d);
if (direct !== null) {
void session.reloadDmChannels();
}
return;
}
default:
// Неизвестные события игнорируем: контракт может расширяться.
return;
@@ -324,6 +356,10 @@ function syncUrlWithSelection(): void {
if (typeof window === 'undefined') {
return;
}
// Раздел друзей не зависит от выбранного сервера — не выдёргиваем из него.
if (window.location.pathname.startsWith('/app/friends')) {
return;
}
const state = useSessionStore.getState();
const target =
state.selectedGuildId === null