Files
glchat/internal/server/media_limits.go
T
grendervill ef871bcd96 feat(instance): админ-панель с лимитами медиа и действиями над пользователями
- лимиты медиа живут в настройках инстанса (миграция 00016): аватары,
  оформление сервера, эмодзи, звуки и галерея; все загрузки берут предел
  оттуда, значения по умолчанию — из AGENT.md 7.7
- действия администратора: временный пароль (показывается один раз, сессии
  отзываются), выход со всех устройств, мягкое удаление пользователя
  (сообщения и аудит остаются), переименование и удаление любого сервера
- админ-панель разбита на вкладки: обзор и здоровье, лимиты, пользователи,
  серверы, аудит; смена администраторов подтверждается личностью (step-up)
- тесты: Go (действия администратора, лимит эмодзи из настроек) и Vitest
  (вкладки, сброс пароля, выход, удаление, переименование сервера)
2026-09-21 22:37:47 +03:00

79 lines
2.6 KiB
Go

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
}
}