package store import ( "context" "database/sql" "time" ) // SoundKind — назначение звука: саундборд или звук интерфейса (AGENT.md 7.13). type SoundKind string const ( SoundKindSoundboard SoundKind = "soundboard" SoundKindUI SoundKind = "ui" ) // GuildSound — звук сервера. type GuildSound struct { ID uint64 GuildID uint64 Name string FileID uint64 Kind SoundKind Event string Emoji string CreatorID *uint64 CreatedAt time.Time } // CreateGuildSoundParams — параметры нового звука. type CreateGuildSoundParams struct { ID uint64 GuildID uint64 Name string FileID uint64 Kind SoundKind Event string Emoji string CreatorID uint64 } const guildSoundColumns = `id, guild_id, name, file_id, kind, event, emoji, creator_id, created_at` // CreateGuildSound регистрирует звук сервера. func (s *Store) CreateGuildSound(ctx context.Context, params CreateGuildSoundParams) (*GuildSound, error) { if params.ID == 0 { params.ID = s.NextID() } if params.Kind == "" { params.Kind = SoundKindSoundboard } _, err := s.writer.ExecContext(ctx, ` INSERT INTO guild_sounds (id, guild_id, name, file_id, kind, event, emoji, creator_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, int64(params.ID), int64(params.GuildID), params.Name, int64(params.FileID), string(params.Kind), params.Event, params.Emoji, int64(params.CreatorID), s.Now()) if err != nil { return nil, mapError(err) } return s.GetGuildSound(ctx, params.ID) } func (s *Store) GetGuildSound(ctx context.Context, id uint64) (*GuildSound, error) { row := s.reader.QueryRowContext(ctx, `SELECT `+guildSoundColumns+` FROM guild_sounds WHERE id = ?`, int64(id)) return scanGuildSound(row) } // ListGuildSounds отдаёт звуки сервера: all — оба вида, иначе только указанный. func (s *Store) ListGuildSounds(ctx context.Context, guildID uint64, kind SoundKind, limit int) ([]GuildSound, error) { if limit <= 0 || limit > 200 { limit = 100 } query := `SELECT ` + guildSoundColumns + ` FROM guild_sounds WHERE guild_id = ?` args := []any{int64(guildID)} if kind != "" { query += ` AND kind = ?` args = append(args, string(kind)) } query += ` ORDER BY name LIMIT ?` args = append(args, limit) rows, err := s.reader.QueryContext(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() sounds := make([]GuildSound, 0, 16) for rows.Next() { sound, err := scanGuildSound(rows) if err != nil { return nil, err } sounds = append(sounds, *sound) } return sounds, rows.Err() } // CountGuildSounds считает звуки указанного вида (лимиты 30 + 30). func (s *Store) CountGuildSounds(ctx context.Context, guildID uint64, kind SoundKind) (int, error) { var count int err := s.reader.QueryRowContext(ctx, `SELECT COUNT(*) FROM guild_sounds WHERE guild_id = ? AND kind = ?`, int64(guildID), string(kind)).Scan(&count) return count, err } // RenameGuildSound меняет имя звука. func (s *Store) RenameGuildSound(ctx context.Context, id uint64, name string, emoji string) (*GuildSound, error) { result, err := s.writer.ExecContext(ctx, `UPDATE guild_sounds SET name = ?, emoji = ? WHERE id = ?`, name, emoji, int64(id)) if err != nil { return nil, mapError(err) } if affected, err := result.RowsAffected(); err == nil && affected == 0 { return nil, ErrNotFound } return s.GetGuildSound(ctx, id) } // DeleteGuildSound удаляет звук сервера. func (s *Store) DeleteGuildSound(ctx context.Context, id uint64) error { result, err := s.writer.ExecContext(ctx, `DELETE FROM guild_sounds WHERE id = ?`, int64(id)) if err != nil { return err } if affected, err := result.RowsAffected(); err == nil && affected == 0 { return ErrNotFound } return nil } func scanGuildSound(scanner interface{ Scan(...any) error }) (*GuildSound, error) { var ( sound GuildSound creatorID sql.NullInt64 createdAt string ) err := scanner.Scan(&sound.ID, &sound.GuildID, &sound.Name, &sound.FileID, &sound.Kind, &sound.Event, &sound.Emoji, &creatorID, &createdAt) if err != nil { return nil, mapError(err) } sound.CreatorID = optionalID(creatorID) sound.CreatedAt = parseTimestamp(createdAt) return &sound, nil }