feat(guilds): встроенные шаблоны серверов
- каталог шаблонов «Пустой», «Сообщество», «Игровой» (AGENT.md 7.3): роли, категории, текстовые и голосовые комнаты без сообщений - POST /guilds принимает template_id, неизвестный шаблон отклоняется; аудит фиксирует выбранный шаблон - GET /guild-templates отдаёт каталог с предпросмотром структуры - клиент: выбор шаблона в окне создания сервера - тесты: Go (структура каждого шаблона) и Vitest (выбор шаблона)
This commit is contained in:
@@ -186,6 +186,9 @@ func (s *Server) registerGuildCoreRoutes(api huma.API) {
|
||||
}, func(ctx context.Context, input *struct {
|
||||
Body struct {
|
||||
Name string `json:"name" minLength:"1" maxLength:"64"`
|
||||
// TemplateID — встроенный шаблон структуры (AGENT.md 7.3);
|
||||
// пусто — «Пустой».
|
||||
TemplateID string `json:"template_id,omitempty"`
|
||||
}
|
||||
},
|
||||
) (*guildOutput, error) {
|
||||
@@ -197,13 +200,32 @@ func (s *Server) registerGuildCoreRoutes(api huma.API) {
|
||||
if name == "" {
|
||||
return nil, humaErrorStatus(http.StatusUnprocessableEntity, "validation.failed", "guild name must not be empty")
|
||||
}
|
||||
guild, err := s.createGuild(ctx, user, name)
|
||||
guild, err := s.createGuild(ctx, user, name, input.Body.TemplateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.guildOutput(ctx, user, *guild)
|
||||
})
|
||||
|
||||
huma.Register(api, huma.Operation{
|
||||
OperationID: "listGuildTemplates",
|
||||
Method: http.MethodGet,
|
||||
Path: "/guild-templates",
|
||||
Summary: "Встроенные шаблоны серверов",
|
||||
Tags: []string{"Guilds"},
|
||||
Security: security,
|
||||
}, func(ctx context.Context, _ *struct{}) (*guildTemplateListOutput, error) {
|
||||
if _, _, err := requireUser(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output := &guildTemplateListOutput{}
|
||||
output.Body.Templates = make([]guildTemplatePayload, 0, len(guildTemplates))
|
||||
for _, template := range guildTemplates {
|
||||
output.Body.Templates = append(output.Body.Templates, guildTemplateFromCatalog(template))
|
||||
}
|
||||
return output, nil
|
||||
})
|
||||
|
||||
huma.Register(api, huma.Operation{
|
||||
OperationID: "getGuild",
|
||||
Method: http.MethodGet,
|
||||
@@ -1518,7 +1540,11 @@ func (s *Server) recordAudit(ctx context.Context, actor *store.User, guildID uin
|
||||
}
|
||||
|
||||
// createGuild создаёт сервер с ролями по умолчанию и общей комнатой (AGENT.md 6.3).
|
||||
func (s *Server) createGuild(ctx context.Context, owner *store.User, name string) (*store.Guild, error) {
|
||||
func (s *Server) createGuild(ctx context.Context, owner *store.User, name, templateID string) (*store.Guild, error) {
|
||||
template, known := findGuildTemplate(templateID)
|
||||
if !known {
|
||||
return nil, humaErrorStatus(http.StatusUnprocessableEntity, "validation.failed", "unknown guild template")
|
||||
}
|
||||
settings, err := s.store.InstanceSettings(ctx)
|
||||
if err != nil {
|
||||
return nil, humaError(err)
|
||||
@@ -1552,17 +1578,16 @@ func (s *Server) createGuild(ctx context.Context, owner *store.User, name string
|
||||
if _, err := bootstrap.SeedGuildDefaults(ctx, s.store, guild, owner); err != nil {
|
||||
return nil, humaError(err)
|
||||
}
|
||||
if _, err := s.store.CreateChannel(ctx, store.CreateChannelParams{
|
||||
GuildID: &guild.ID, Type: store.ChannelText, Name: "общий", Position: 0,
|
||||
}); err != nil {
|
||||
return nil, humaError(err)
|
||||
// Структура комнат и ролей — из выбранного шаблона (AGENT.md 7.3).
|
||||
if err := s.applyGuildTemplate(ctx, guild, template); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if guild.IsMain {
|
||||
if err := s.store.SetInstanceSetting(ctx, "main_guild_id", strconv.FormatUint(guild.ID, 10)); err != nil {
|
||||
return nil, humaError(err)
|
||||
}
|
||||
}
|
||||
s.recordAudit(ctx, owner, guild.ID, "guild.create", "guild", &guild.ID, "")
|
||||
s.recordAudit(ctx, owner, guild.ID, "guild.create", "guild", &guild.ID, "template="+template.ID)
|
||||
if bypassed {
|
||||
s.recordAudit(ctx, owner, guild.ID, "limits.bypass", "guild", &guild.ID, "instance admin bypassed guild limit")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user