FUll implementation plan (first draft)
This commit is contained in:
		@@ -4,6 +4,8 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@@ -340,6 +342,310 @@ internal static class CommandHandlers
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorInitAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        IReadOnlyList<string> providers,
 | 
			
		||||
        bool resume,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        var normalizedProviders = NormalizeProviders(providers);
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        if (normalizedProviders.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            payload["providers"] = normalizedProviders;
 | 
			
		||||
        }
 | 
			
		||||
        if (resume)
 | 
			
		||||
        {
 | 
			
		||||
            payload["resume"] = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor init",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["providers"] = normalizedProviders.Count,
 | 
			
		||||
                ["resume"] = resume
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("init", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorPullAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        IReadOnlyList<string> providers,
 | 
			
		||||
        DateTimeOffset? since,
 | 
			
		||||
        TimeSpan? window,
 | 
			
		||||
        bool force,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        var normalizedProviders = NormalizeProviders(providers);
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        if (normalizedProviders.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            payload["providers"] = normalizedProviders;
 | 
			
		||||
        }
 | 
			
		||||
        if (since.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            payload["since"] = since.Value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
        if (window.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            payload["window"] = window.Value.ToString("c", CultureInfo.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
        if (force)
 | 
			
		||||
        {
 | 
			
		||||
            payload["force"] = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor pull",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["providers"] = normalizedProviders.Count,
 | 
			
		||||
                ["force"] = force,
 | 
			
		||||
                ["since"] = since?.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
 | 
			
		||||
                ["window"] = window?.ToString("c", CultureInfo.InvariantCulture)
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("ingest/run", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorResumeAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        IReadOnlyList<string> providers,
 | 
			
		||||
        string? checkpoint,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        var normalizedProviders = NormalizeProviders(providers);
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        if (normalizedProviders.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            payload["providers"] = normalizedProviders;
 | 
			
		||||
        }
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(checkpoint))
 | 
			
		||||
        {
 | 
			
		||||
            payload["checkpoint"] = checkpoint.Trim();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor resume",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["providers"] = normalizedProviders.Count,
 | 
			
		||||
                ["checkpoint"] = checkpoint
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("ingest/resume", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task HandleExcititorListProvidersAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        bool includeDisabled,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        await using var scope = services.CreateAsyncScope();
 | 
			
		||||
        var client = scope.ServiceProvider.GetRequiredService<IBackendOperationsClient>();
 | 
			
		||||
        var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("excititor-list-providers");
 | 
			
		||||
        var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
 | 
			
		||||
        var previousLevel = verbosity.MinimumLevel;
 | 
			
		||||
        verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
 | 
			
		||||
        using var activity = CliActivitySource.Instance.StartActivity("cli.excititor.list-providers", ActivityKind.Client);
 | 
			
		||||
        activity?.SetTag("stellaops.cli.command", "excititor list-providers");
 | 
			
		||||
        activity?.SetTag("stellaops.cli.include_disabled", includeDisabled);
 | 
			
		||||
        using var duration = CliMetrics.MeasureCommandDuration("excititor list-providers");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var providers = await client.GetExcititorProvidersAsync(includeDisabled, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            Environment.ExitCode = 0;
 | 
			
		||||
            logger.LogInformation("Providers returned: {Count}", providers.Count);
 | 
			
		||||
 | 
			
		||||
            if (providers.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                if (AnsiConsole.Profile.Capabilities.Interactive)
 | 
			
		||||
                {
 | 
			
		||||
                    var table = new Table().Border(TableBorder.Rounded).AddColumns("Provider", "Kind", "Trust", "Enabled", "Last Ingested");
 | 
			
		||||
                    foreach (var provider in providers)
 | 
			
		||||
                    {
 | 
			
		||||
                        table.AddRow(
 | 
			
		||||
                            provider.Id,
 | 
			
		||||
                            provider.Kind,
 | 
			
		||||
                            string.IsNullOrWhiteSpace(provider.TrustTier) ? "-" : provider.TrustTier,
 | 
			
		||||
                            provider.Enabled ? "yes" : "no",
 | 
			
		||||
                            provider.LastIngestedAt?.ToString("yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture) ?? "unknown");
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    AnsiConsole.Write(table);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var provider in providers)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.LogInformation("{ProviderId} [{Kind}] Enabled={Enabled} Trust={Trust} LastIngested={LastIngested}",
 | 
			
		||||
                            provider.Id,
 | 
			
		||||
                            provider.Kind,
 | 
			
		||||
                            provider.Enabled ? "yes" : "no",
 | 
			
		||||
                            string.IsNullOrWhiteSpace(provider.TrustTier) ? "-" : provider.TrustTier,
 | 
			
		||||
                            provider.LastIngestedAt?.ToString("O", CultureInfo.InvariantCulture) ?? "unknown");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogError(ex, "Failed to list Excititor providers.");
 | 
			
		||||
            Environment.ExitCode = 1;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            verbosity.MinimumLevel = previousLevel;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorExportAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        string format,
 | 
			
		||||
        bool delta,
 | 
			
		||||
        string? scope,
 | 
			
		||||
        DateTimeOffset? since,
 | 
			
		||||
        string? provider,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
        {
 | 
			
		||||
            ["format"] = string.IsNullOrWhiteSpace(format) ? "openvex" : format.Trim(),
 | 
			
		||||
            ["delta"] = delta
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(scope))
 | 
			
		||||
        {
 | 
			
		||||
            payload["scope"] = scope.Trim();
 | 
			
		||||
        }
 | 
			
		||||
        if (since.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            payload["since"] = since.Value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(provider))
 | 
			
		||||
        {
 | 
			
		||||
            payload["provider"] = provider.Trim();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor export",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["format"] = payload["format"],
 | 
			
		||||
                ["delta"] = delta,
 | 
			
		||||
                ["scope"] = scope,
 | 
			
		||||
                ["since"] = since?.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
 | 
			
		||||
                ["provider"] = provider
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("export", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorVerifyAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        string? exportId,
 | 
			
		||||
        string? digest,
 | 
			
		||||
        string? attestationPath,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(exportId) && string.IsNullOrWhiteSpace(digest) && string.IsNullOrWhiteSpace(attestationPath))
 | 
			
		||||
        {
 | 
			
		||||
            var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("excititor-verify");
 | 
			
		||||
            logger.LogError("At least one of --export-id, --digest, or --attestation must be provided.");
 | 
			
		||||
            Environment.ExitCode = 1;
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(exportId))
 | 
			
		||||
        {
 | 
			
		||||
            payload["exportId"] = exportId.Trim();
 | 
			
		||||
        }
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(digest))
 | 
			
		||||
        {
 | 
			
		||||
            payload["digest"] = digest.Trim();
 | 
			
		||||
        }
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(attestationPath))
 | 
			
		||||
        {
 | 
			
		||||
            var fullPath = Path.GetFullPath(attestationPath);
 | 
			
		||||
            if (!File.Exists(fullPath))
 | 
			
		||||
            {
 | 
			
		||||
                var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("excititor-verify");
 | 
			
		||||
                logger.LogError("Attestation file not found at {Path}.", fullPath);
 | 
			
		||||
                Environment.ExitCode = 1;
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var bytes = File.ReadAllBytes(fullPath);
 | 
			
		||||
            payload["attestation"] = new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
            {
 | 
			
		||||
                ["fileName"] = Path.GetFileName(fullPath),
 | 
			
		||||
                ["base64"] = Convert.ToBase64String(bytes)
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor verify",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["export_id"] = exportId,
 | 
			
		||||
                ["digest"] = digest,
 | 
			
		||||
                ["attestation_path"] = attestationPath
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("verify", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task HandleExcititorReconcileAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        IReadOnlyList<string> providers,
 | 
			
		||||
        TimeSpan? maxAge,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        var normalizedProviders = NormalizeProviders(providers);
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        if (normalizedProviders.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            payload["providers"] = normalizedProviders;
 | 
			
		||||
        }
 | 
			
		||||
        if (maxAge.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            payload["maxAge"] = maxAge.Value.ToString("c", CultureInfo.InvariantCulture);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ExecuteExcititorCommandAsync(
 | 
			
		||||
            services,
 | 
			
		||||
            commandName: "excititor reconcile",
 | 
			
		||||
            verbose,
 | 
			
		||||
            new Dictionary<string, object?>
 | 
			
		||||
            {
 | 
			
		||||
                ["providers"] = normalizedProviders.Count,
 | 
			
		||||
                ["max_age"] = maxAge?.ToString("c", CultureInfo.InvariantCulture)
 | 
			
		||||
            },
 | 
			
		||||
            client => client.ExecuteExcititorOperationAsync("reconcile", HttpMethod.Post, RemoveNullValues(payload), cancellationToken),
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task HandleAuthLoginAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        StellaOpsCliOptions options,
 | 
			
		||||
@@ -1111,12 +1417,109 @@ internal static class CommandHandlers
 | 
			
		||||
        "jti"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private static async Task ExecuteExcititorCommandAsync(
 | 
			
		||||
        IServiceProvider services,
 | 
			
		||||
        string commandName,
 | 
			
		||||
        bool verbose,
 | 
			
		||||
        IDictionary<string, object?>? activityTags,
 | 
			
		||||
        Func<IBackendOperationsClient, Task<ExcititorOperationResult>> operation,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        await using var scope = services.CreateAsyncScope();
 | 
			
		||||
        var client = scope.ServiceProvider.GetRequiredService<IBackendOperationsClient>();
 | 
			
		||||
        var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(commandName.Replace(' ', '-'));
 | 
			
		||||
        var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
 | 
			
		||||
        var previousLevel = verbosity.MinimumLevel;
 | 
			
		||||
        verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
 | 
			
		||||
        using var activity = CliActivitySource.Instance.StartActivity($"cli.{commandName.Replace(' ', '.')}" , ActivityKind.Client);
 | 
			
		||||
        activity?.SetTag("stellaops.cli.command", commandName);
 | 
			
		||||
        if (activityTags is not null)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var tag in activityTags)
 | 
			
		||||
            {
 | 
			
		||||
                activity?.SetTag(tag.Key, tag.Value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        using var duration = CliMetrics.MeasureCommandDuration(commandName);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var result = await operation(client).ConfigureAwait(false);
 | 
			
		||||
            if (result.Success)
 | 
			
		||||
            {
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(result.Message))
 | 
			
		||||
                {
 | 
			
		||||
                    logger.LogInformation(result.Message);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    logger.LogInformation("Operation completed successfully.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(result.Location))
 | 
			
		||||
                {
 | 
			
		||||
                    logger.LogInformation("Location: {Location}", result.Location);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (result.Payload is JsonElement payload && payload.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.LogDebug("Response payload: {Payload}", payload.ToString());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Environment.ExitCode = 0;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                logger.LogError(string.IsNullOrWhiteSpace(result.Message) ? "Operation failed." : result.Message);
 | 
			
		||||
                Environment.ExitCode = 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.LogError(ex, "Excititor operation failed.");
 | 
			
		||||
            Environment.ExitCode = 1;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            verbosity.MinimumLevel = previousLevel;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IReadOnlyList<string> NormalizeProviders(IReadOnlyList<string> providers)
 | 
			
		||||
    {
 | 
			
		||||
        if (providers is null || providers.Count == 0)
 | 
			
		||||
        {
 | 
			
		||||
            return Array.Empty<string>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var list = new List<string>();
 | 
			
		||||
        foreach (var provider in providers)
 | 
			
		||||
        {
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(provider))
 | 
			
		||||
            {
 | 
			
		||||
                list.Add(provider.Trim());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return list.Count == 0 ? Array.Empty<string>() : list;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IDictionary<string, object?> RemoveNullValues(Dictionary<string, object?> source)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var key in source.Where(kvp => kvp.Value is null).Select(kvp => kvp.Key).ToList())
 | 
			
		||||
        {
 | 
			
		||||
            source.Remove(key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static async Task TriggerJobAsync(
 | 
			
		||||
        IBackendOperationsClient client,
 | 
			
		||||
        ILogger logger,
 | 
			
		||||
        string jobKind,
 | 
			
		||||
        IDictionary<string, object?> parameters,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
        IDictionary<string, object?> parameters,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        JobTriggerResult result = await client.TriggerJobAsync(jobKind, parameters, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        if (result.Success)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user