feat(guilds): приватные комнаты — права доступа поверх серверных
Сервер:
- store: оверрайды всех комнат сервера одним запросом (без N+1) и снятие
оверрайда с признаком «был ли он»;
- API: PUT/DELETE /guilds/{id}/channels/{cid}/overwrites/{role|user}/{tid}
(права именами через `|`, как у ролей), step-up на изменение, проверка что
роль принадлежит серверу, а участник состоит в нём;
- список комнат отдаёт permission_overwrites; после правки сбрасывается кэш
прав комнаты, пишется аудит (channel.overwrite_set/delete) и уходит событие
GUILD_CHANNELS_SYNC — видимость комнаты меняется у всех участников.
Клиент:
- редактор «Доступ к комнате»: приватность одним переключателем (запрет
VIEW_CHANNEL для @everyone), права просмотра/переписки/входа для ролей и
участников, подтверждение личности по требованию сервера;
- в настройках комнаты теперь и голосовые комнаты (фон и права), вебхуки —
только у текстовых; событие GUILD_CHANNELS_SYNC перечитывает список комнат.
Тесты: 2 Go-теста (скрытие и открытие комнаты ролями, проверка цели и прав) и
3 web-теста редактора (маски, step-up, скрытие без MANAGE_ROLES).
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
} from '@/api/gateway';
|
||||
import {
|
||||
parseChannelDelete,
|
||||
parseGuildChannelsSyncEvent,
|
||||
parseChannelPayload,
|
||||
parseDmChannelEvent,
|
||||
parseGuildEmojisUpdate,
|
||||
@@ -334,6 +335,15 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'GUILD_CHANNELS_SYNC': {
|
||||
// Права комнаты изменились: видимость может поменяться у всех
|
||||
// участников, поэтому список комнат перечитываем целиком (AGENT.md 11.6).
|
||||
const sync = parseGuildChannelsSyncEvent(event.d);
|
||||
if (sync !== null) {
|
||||
void session.reloadGuildChannels(sync.guild_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'DM_CHANNEL_CREATE': {
|
||||
// В событии только channel_id: список бесед забираем по REST.
|
||||
const direct = parseDmChannelEvent(event.d);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
import type { GuildEmoji } from '@/api/emojis';
|
||||
import { fetchChannels } from '@/api/guilds';
|
||||
import { fetchDirectChannels } from '@/api/friends';
|
||||
import type { GatewayGuild, GatewaySnapshot, GatewayUser } from '@/api/gateway';
|
||||
import type { GuildSound, SoundKind } from '@/api/sounds';
|
||||
@@ -113,6 +114,8 @@ interface SessionState {
|
||||
reloadDmChannels: () => Promise<void>;
|
||||
/** CHANNEL_DELETE: удаляет комнату и переносит выбор на первую доступную. */
|
||||
removeChannel: (guildId: string, channelId: string) => void;
|
||||
/** GUILD_CHANNELS_SYNC: перечитать видимые комнаты сервера (права изменились). */
|
||||
reloadGuildChannels: (guildId: string) => Promise<void>;
|
||||
/** Первая доступная (видимая) комната сервера — для выбора после удалений. */
|
||||
firstChannelId: (guildId: string) => string | null;
|
||||
selectGuild: (guildId: string | null) => void;
|
||||
@@ -351,6 +354,15 @@ export const useSessionStore = create<SessionState>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
reloadGuildChannels: async (guildId) => {
|
||||
try {
|
||||
const channels = await fetchChannels(guildId);
|
||||
get().setGuildChannels(guildId, channels);
|
||||
} catch {
|
||||
// Сеть подвела: список обновится при следующем изменении прав.
|
||||
}
|
||||
},
|
||||
|
||||
setGuildChannels: (guildId, channels) => {
|
||||
set({
|
||||
guilds: get().guilds.map((guild) => (guild.id === guildId ? { ...guild, channels } : guild)),
|
||||
|
||||
Reference in New Issue
Block a user