feat(api): ручки Фаз(ы) 1 — профиль, серверы, роли, админ инстанса

REST-слой Фазы 1 на huma (OpenAPI 3.1 генерируется из кода):

- профиль: GET/PATCH /users/@me, смена пароля со step-up, публичный профиль,
  завершение онбординга (новая миграция 00003 с onboarding_completed_at);
- серверы: создание/изменение/удаление, join/leave, список серверов
  пользователя, журнал действий;
- комнаты: список с учётом прав, создание/изменение/удаление;
- участники: список с профилями и ролями, никнейм, тайм-аут, исключение;
- роли: CRUD, выдача/снятие с проверкой иерархии и запретом выдачи прав выше
  собственных;
- админ инстанса: публичная информация, настройки, серверы, пользователи,
  аудит, выдача прав администратора со step-up; обход лимитов фиксируется в
  аудите отдельной записью limits.bypass;
- движок прав: участие в сервере стало обязательным условием (IsMember),
  не участник не получает прав роли @user; калькулятор прав общий для API и
  Gateway, инвалидация кэша после изменений;
- Gateway: браузерный клиент аутентифицируется cookie на рукопожатии, IDENTIFY
  без токена использует её; события GUILD/CHANNEL/MEMBER/ROLE рассылаются из
  ручек, USER_UPDATE — адресно;
- ошибки huma отдаются в едином конверте {"error":{"code","message"}}.

Тесты: 8 сценариев API (профиль, жизненный цикл сервера и права, лимиты и
обход админом, иерархия ролей, тайм-аут, скрытие комнаты оверрайдом,
членство в движке прав, cookie-идентификация Gateway).
This commit is contained in:
2026-09-19 21:50:06 +03:00
parent 86dff94a02
commit 1b1a679827
63 changed files with 8477 additions and 187 deletions
+33 -13
View File
@@ -171,12 +171,20 @@ func (r *Runner) findOrCreateAdmin(ctx context.Context, opts Options, result *Re
return user, nil
}
// ensureMainGuildSetup гарантирует роли по умолчанию, участие владельца и
// выдачу роли «Администратор» (AGENT.md 6.3).
func (r *Runner) ensureMainGuildSetup(ctx context.Context, guild *store.Guild, owner *store.User, result *Result) error {
roles, err := r.store.ListGuildRoles(ctx, guild.ID)
// SeedResult — роли, созданные при настройке сервера.
type SeedResult struct {
AdminRoleID uint64
MemberRoleID uint64
}
// SeedGuildDefaults создаёт роли по умолчанию, добавляет владельца в сервер и
// выдаёт ему роль «Администратор» (AGENT.md 6.3). Функция идемпотентна: её
// используют и установщик, и ручка создания сервера.
func SeedGuildDefaults(ctx context.Context, st *store.Store, guild *store.Guild, owner *store.User) (*SeedResult, error) {
result := &SeedResult{}
roles, err := st.ListGuildRoles(ctx, guild.ID)
if err != nil {
return err
return nil, err
}
var adminRole, defaultRole *store.Role
for i := range roles {
@@ -188,35 +196,47 @@ func (r *Runner) ensureMainGuildSetup(ctx context.Context, guild *store.Guild, o
}
}
if adminRole == nil {
created, err := r.store.CreateRole(ctx, store.CreateRoleParams{
created, err := st.CreateRole(ctx, store.CreateRoleParams{
GuildID: guild.ID, Name: "Администратор",
Permissions: uint64(permissions.AllPermissions), Position: 100, Mentionable: true,
})
if err != nil {
return fmt.Errorf("create administrator role: %w", err)
return nil, fmt.Errorf("create administrator role: %w", err)
}
adminRole = created
}
result.AdminRoleID = adminRole.ID
if defaultRole == nil {
created, err := r.store.CreateRole(ctx, store.CreateRoleParams{
created, err := st.CreateRole(ctx, store.CreateRoleParams{
GuildID: guild.ID, Name: "Пользователь",
Permissions: uint64(permissions.DefaultUserPermissions), Position: 0,
IsDefault: true, Mentionable: true,
})
if err != nil {
return fmt.Errorf("create default role: %w", err)
return nil, fmt.Errorf("create default role: %w", err)
}
defaultRole = created
}
result.MemberRoleID = defaultRole.ID
if _, err := r.store.AddGuildMember(ctx, guild.ID, owner.ID, ""); err != nil {
return fmt.Errorf("add owner to main guild: %w", err)
if _, err := st.AddGuildMember(ctx, guild.ID, owner.ID, ""); err != nil {
return nil, fmt.Errorf("add owner to guild: %w", err)
}
if err := r.store.AssignRole(ctx, guild.ID, owner.ID, adminRole.ID); err != nil {
return fmt.Errorf("assign administrator role: %w", err)
if err := st.AssignRole(ctx, guild.ID, owner.ID, adminRole.ID); err != nil {
return nil, fmt.Errorf("assign administrator role: %w", err)
}
return result, nil
}
// ensureMainGuildSetup гарантирует роли по умолчанию, участие владельца и
// выдачу роли «Администратор» (AGENT.md 6.3).
func (r *Runner) ensureMainGuildSetup(ctx context.Context, guild *store.Guild, owner *store.User, result *Result) error {
seeded, err := SeedGuildDefaults(ctx, r.store, guild, owner)
if err != nil {
return err
}
result.AdminRoleID = seeded.AdminRoleID
result.MemberRoleID = seeded.MemberRoleID
return nil
}