package store import ( "context" "time" ) // Retention-запросы (AGENT.md 6.4, Фаза 5): чистка сессий, аудита и файлов без // ссылок. Все методы идемпотентны и работают порциями, чтобы не держать // блокировку писателя надолго. // DeleteSessionsBefore удаляет сессии, истёкшие раньше указанного времени. func (s *Store) DeleteSessionsBefore(ctx context.Context, before time.Time) (int64, error) { result, err := s.writer.ExecContext(ctx, `DELETE FROM sessions WHERE expires_at <= ?`, s.Timestamp(before)) if err != nil { return 0, mapError(err) } affected, err := result.RowsAffected() if err != nil { return 0, err } return affected, nil } // DeleteAuditBefore удаляет записи аудита старше указанного времени. func (s *Store) DeleteAuditBefore(ctx context.Context, before time.Time) (int64, error) { result, err := s.writer.ExecContext(ctx, `DELETE FROM audit_log WHERE created_at <= ?`, s.Timestamp(before)) if err != nil { return 0, mapError(err) } affected, err := result.RowsAffected() if err != nil { return 0, err } return affected, nil } // DeleteFileRecord удаляет запись о файле (содержимое удаляет вызывающий). func (s *Store) DeleteFileRecord(ctx context.Context, id uint64) error { if _, err := s.writer.ExecContext(ctx, `DELETE FROM files WHERE id = ?`, int64(id)); err != nil { return mapError(err) } return nil } // RebuildSearchIndex пересобирает индекс полнотекстового поиска (FTS5). func (s *Store) RebuildSearchIndex(ctx context.Context) error { if _, err := s.writer.ExecContext(ctx, `INSERT INTO messages_fts (messages_fts) VALUES ('rebuild')`); err != nil { return mapError(err) } return nil } // RevokeUserSessions удаляет все сессии пользователя: администратор инстанса // выкидывает его со всех устройств (AGENT.md 7.19). func (s *Store) RevokeUserSessions(ctx context.Context, userID uint64) (int64, error) { result, err := s.writer.ExecContext(ctx, `DELETE FROM sessions WHERE user_id = ?`, int64(userID)) if err != nil { return 0, mapError(err) } affected, err := result.RowsAffected() if err != nil { return 0, err } return affected, nil } // SoftDeleteUser помечает пользователя удалённым и убирает его из серверов: // сообщения остаются, чтобы история и ссылки не ломались (AGENT.md 6.4). func (s *Store) SoftDeleteUser(ctx context.Context, userID uint64) error { tx, err := s.writer.BeginTx(ctx, nil) if err != nil { return err } defer func() { _ = tx.Rollback() }() if _, err := tx.ExecContext(ctx, `UPDATE users SET deleted_at = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL`, s.Now(), s.Now(), int64(userID)); err != nil { return mapError(err) } if _, err := tx.ExecContext(ctx, `DELETE FROM guild_members WHERE user_id = ?`, int64(userID)); err != nil { return mapError(err) } if _, err := tx.ExecContext(ctx, `DELETE FROM sessions WHERE user_id = ?`, int64(userID)); err != nil { return mapError(err) } // Личные связи и блокировки удалённого пользователя больше не нужны. if _, err := tx.ExecContext(ctx, `DELETE FROM relationships WHERE user_id = ? OR target_id = ?`, int64(userID), int64(userID)); err != nil { return mapError(err) } // Глобальный бан аккаунта снимается вместе с аккаунтом: запись о бане // без пользователя только мешает разбору (`banned=true` в панели). if _, err := tx.ExecContext(ctx, `DELETE FROM instance_bans WHERE user_id = ?`, int64(userID)); err != nil { return mapError(err) } return tx.Commit() }