feat(instance): админ-панель с лимитами медиа и действиями над пользователями
- лимиты медиа живут в настройках инстанса (миграция 00016): аватары, оформление сервера, эмодзи, звуки и галерея; все загрузки берут предел оттуда, значения по умолчанию — из AGENT.md 7.7 - действия администратора: временный пароль (показывается один раз, сессии отзываются), выход со всех устройств, мягкое удаление пользователя (сообщения и аудит остаются), переименование и удаление любого сервера - админ-панель разбита на вкладки: обзор и здоровье, лимиты, пользователи, серверы, аудит; смена администраторов подтверждается личностью (step-up) - тесты: Go (действия администратора, лимит эмодзи из настроек) и Vitest (вкладки, сброс пароля, выход, удаление, переименование сервера)
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Лимиты медиа (AGENT.md 7.7): значения живут в настройках инстанса и меняются
|
||||
// в админ-панели, а обработчики загрузок спрашивают их здесь.
|
||||
|
||||
// Пределы по умолчанию (AGENT.md 7.7): 5 МБ — аватары, баннеры и оформление,
|
||||
// 512 КБ — эмодзи и звуки. Администратор инстанса меняет их в админ-панели.
|
||||
const (
|
||||
maxAvatarSize int64 = 5 << 20
|
||||
maxGuildImageSize int64 = 5 << 20
|
||||
maxEmojiSize int64 = 512 << 10
|
||||
maxSoundSize int64 = 512 << 10
|
||||
maxCosmeticSize int64 = 5 << 20
|
||||
)
|
||||
|
||||
// mediaKind — вид загрузки, для которого действует свой лимит.
|
||||
type mediaKind string
|
||||
|
||||
const (
|
||||
mediaAvatar mediaKind = "avatar"
|
||||
mediaGuildImage mediaKind = "guild_image"
|
||||
mediaEmoji mediaKind = "emoji"
|
||||
mediaSound mediaKind = "sound"
|
||||
mediaCosmetic mediaKind = "cosmetic"
|
||||
)
|
||||
|
||||
// mediaLimit возвращает предел размера файла в байтах. Настройки читаются на
|
||||
// каждую загрузку: админ мог поменять лимит только что, а кэш настроек здесь не
|
||||
// нужен — запрос к базе дешёвый.
|
||||
func (s *Server) mediaLimit(ctx context.Context, kind mediaKind) int64 {
|
||||
fallback := defaultMediaLimit(kind)
|
||||
if s.store == nil {
|
||||
return fallback
|
||||
}
|
||||
settings, err := s.store.InstanceSettings(ctx)
|
||||
if err != nil {
|
||||
s.logger.WarnContext(ctx, "failed to read media limits", slog.Any("error", err))
|
||||
return fallback
|
||||
}
|
||||
switch kind {
|
||||
case mediaAvatar:
|
||||
return settings.MaxAvatarSize
|
||||
case mediaGuildImage:
|
||||
return settings.MaxGuildImageSize
|
||||
case mediaEmoji:
|
||||
return settings.MaxEmojiSize
|
||||
case mediaSound:
|
||||
return settings.MaxSoundSize
|
||||
case mediaCosmetic:
|
||||
return settings.MaxCosmeticSize
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
// defaultMediaLimit — значения по умолчанию, если настройка недоступна:
|
||||
// именованные константы рядом с обработчиками задают те же пределы.
|
||||
func defaultMediaLimit(kind mediaKind) int64 {
|
||||
switch kind {
|
||||
case mediaAvatar:
|
||||
return maxAvatarSize
|
||||
case mediaGuildImage:
|
||||
return maxGuildImageSize
|
||||
case mediaEmoji:
|
||||
return maxEmojiSize
|
||||
case mediaSound:
|
||||
return maxSoundSize
|
||||
case mediaCosmetic:
|
||||
return maxCosmeticSize
|
||||
default:
|
||||
return maxGuildImageSize
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user