littleshop/LittleShop/Areas/Admin/Controllers/BotRecoveryController.cs
SilverLabs DevTeam 73e8773ea3 Configure BTCPay with external nodes via Tor
- Set up Tor container for SOCKS proxy (port 9050)
- Configured Monero wallet with remote onion node
- Bitcoin node continues syncing in background (60% complete)
- Created documentation for wallet configuration steps
- All external connections routed through Tor for privacy

BTCPay requires manual wallet configuration through web interface:
- Bitcoin: Need to add xpub/zpub for watch-only wallet
- Monero: Need to add address and view key

System ready for payment acceptance once wallets configured.
2025-09-19 12:14:39 +01:00

165 lines
5.7 KiB
C#

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using LittleShop.Services;
namespace LittleShop.Areas.Admin.Controllers;
[Area("Admin")]
[Authorize(Policy = "AdminOnly")]
public class BotRecoveryController : Controller
{
private readonly IBotContactService _contactService;
private readonly IBotService _botService;
private readonly ILogger<BotRecoveryController> _logger;
public BotRecoveryController(
IBotContactService contactService,
IBotService botService,
ILogger<BotRecoveryController> logger)
{
_contactService = contactService;
_botService = botService;
_logger = logger;
}
// GET: Admin/BotRecovery
public async Task<IActionResult> Index()
{
var allBots = await _botService.GetAllBotsAsync();
var botsWithStatus = allBots.Select(bot => new
{
Bot = bot,
Contacts = _contactService.GetBotContactsAsync(bot.Id).Result,
IsHealthy = bot.Status == Enums.BotStatus.Active &&
bot.LastSeenAt > DateTime.UtcNow.AddMinutes(-5)
}).ToList();
ViewData["BotsWithStatus"] = botsWithStatus;
return View();
}
// GET: Admin/BotRecovery/PrepareRecovery/{botId}
public async Task<IActionResult> PrepareRecovery(Guid botId)
{
var recoveryData = await _contactService.PrepareContactRecoveryAsync(botId);
return View(recoveryData);
}
// POST: Admin/BotRecovery/ExecuteRecovery
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExecuteRecovery(Guid fromBotId, Guid toBotId, bool notifyUsers = false)
{
try
{
// Migrate contacts
var success = await _contactService.MigrateContactsAsync(fromBotId, toBotId);
if (!success)
{
TempData["Error"] = "Failed to migrate contacts. Check logs for details.";
return RedirectToAction(nameof(PrepareRecovery), new { botId = fromBotId });
}
// Get the new bot info for notifications
if (notifyUsers)
{
var toBot = await _botService.GetBotByIdAsync(toBotId);
var orphanedContacts = await _contactService.GetOrphanedContactsAsync(fromBotId);
foreach (var contact in orphanedContacts)
{
await _contactService.NotifyContactOfBotChangeAsync(
contact.TelegramUserId,
toBot.PlatformUsername);
}
}
// Update bot statuses
await _botService.UpdateBotStatusAsync(fromBotId, Enums.BotStatus.Retired);
TempData["Success"] = $"Successfully migrated contacts from bot {fromBotId} to {toBotId}";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to execute bot recovery");
TempData["Error"] = "An error occurred during recovery. Please try again.";
return RedirectToAction(nameof(PrepareRecovery), new { botId = fromBotId });
}
}
// GET: Admin/BotRecovery/Backup/{botId}
public async Task<IActionResult> Backup(Guid botId)
{
var backup = await _contactService.CreateContactBackupAsync(botId);
// Return as downloadable JSON file
var json = System.Text.Json.JsonSerializer.Serialize(backup, new System.Text.Json.JsonSerializerOptions
{
WriteIndented = true
});
var bytes = System.Text.Encoding.UTF8.GetBytes(json);
return File(bytes, "application/json", $"bot-contacts-{botId}-{DateTime.UtcNow:yyyyMMdd}.json");
}
// POST: Admin/BotRecovery/RestoreBackup
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RestoreBackup(Guid toBotId, IFormFile backupFile)
{
if (backupFile == null || backupFile.Length == 0)
{
TempData["Error"] = "Please select a backup file to restore";
return RedirectToAction(nameof(Index));
}
try
{
using var stream = backupFile.OpenReadStream();
using var reader = new System.IO.StreamReader(stream);
var json = await reader.ReadToEndAsync();
var backup = System.Text.Json.JsonSerializer.Deserialize<ContactBackupDto>(json);
if (backup == null)
{
TempData["Error"] = "Invalid backup file format";
return RedirectToAction(nameof(Index));
}
var success = await _contactService.RestoreContactsFromBackupAsync(toBotId, backup);
if (success)
{
TempData["Success"] = $"Successfully restored {backup.ContactCount} contacts to bot";
}
else
{
TempData["Error"] = "Failed to restore contacts from backup";
}
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to restore backup");
TempData["Error"] = "An error occurred while restoring the backup";
return RedirectToAction(nameof(Index));
}
}
// GET: Admin/BotRecovery/ContactHistory/{telegramUserId}
public async Task<IActionResult> ContactHistory(long telegramUserId)
{
// Show all bot interactions for a specific user
ViewData["UserId"] = telegramUserId;
// Implementation would query all BotContacts for this user across all bots
return View();
}
}