Add customer communication system
This commit is contained in:
330
LittleShop/Services/BotService.cs
Normal file
330
LittleShop/Services/BotService.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LittleShop.Data;
|
||||
using LittleShop.DTOs;
|
||||
using LittleShop.Enums;
|
||||
using LittleShop.Models;
|
||||
|
||||
namespace LittleShop.Services;
|
||||
|
||||
public class BotService : IBotService
|
||||
{
|
||||
private readonly LittleShopContext _context;
|
||||
private readonly ILogger<BotService> _logger;
|
||||
|
||||
public BotService(LittleShopContext context, ILogger<BotService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BotRegistrationResponseDto> RegisterBotAsync(BotRegistrationDto dto)
|
||||
{
|
||||
_logger.LogInformation("Registering new bot: {BotName}", dto.Name);
|
||||
|
||||
var botKey = await GenerateBotKeyAsync();
|
||||
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = dto.Name,
|
||||
Description = dto.Description,
|
||||
Type = dto.Type,
|
||||
BotKey = botKey,
|
||||
Status = BotStatus.Active,
|
||||
Settings = JsonSerializer.Serialize(dto.InitialSettings),
|
||||
Version = dto.Version,
|
||||
PersonalityName = string.IsNullOrEmpty(dto.PersonalityName) ? AssignDefaultPersonality(dto.Name) : dto.PersonalityName,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
_context.Bots.Add(bot);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Bot registered successfully: {BotId}", bot.Id);
|
||||
|
||||
return new BotRegistrationResponseDto
|
||||
{
|
||||
BotId = bot.Id,
|
||||
BotKey = botKey,
|
||||
Name = bot.Name,
|
||||
Settings = dto.InitialSettings
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<BotDto?> AuthenticateBotAsync(string botKey)
|
||||
{
|
||||
var bot = await _context.Bots
|
||||
.Include(b => b.Sessions)
|
||||
.Include(b => b.Metrics)
|
||||
.FirstOrDefaultAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active);
|
||||
|
||||
if (bot == null)
|
||||
{
|
||||
_logger.LogWarning("Authentication failed for bot key: {BotKey}", botKey.Substring(0, 8) + "...");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update last seen
|
||||
bot.LastSeenAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return MapToDto(bot);
|
||||
}
|
||||
|
||||
public async Task<BotDto?> GetBotByIdAsync(Guid id)
|
||||
{
|
||||
var bot = await _context.Bots
|
||||
.Include(b => b.Sessions)
|
||||
.Include(b => b.Metrics)
|
||||
.FirstOrDefaultAsync(b => b.Id == id);
|
||||
|
||||
return bot != null ? MapToDto(bot) : null;
|
||||
}
|
||||
|
||||
public async Task<BotDto?> GetBotByKeyAsync(string botKey)
|
||||
{
|
||||
var bot = await _context.Bots
|
||||
.Include(b => b.Sessions)
|
||||
.Include(b => b.Metrics)
|
||||
.FirstOrDefaultAsync(b => b.BotKey == botKey);
|
||||
|
||||
return bot != null ? MapToDto(bot) : null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BotDto>> GetAllBotsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bots = await _context.Bots
|
||||
.Include(b => b.Sessions)
|
||||
.Include(b => b.Metrics)
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return bots.Select(MapToDto);
|
||||
}
|
||||
catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table"))
|
||||
{
|
||||
// Tables don't exist yet - return empty list
|
||||
return new List<BotDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BotDto>> GetActiveBots()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bots = await _context.Bots
|
||||
.Include(b => b.Sessions)
|
||||
.Include(b => b.Metrics)
|
||||
.Where(b => b.Status == BotStatus.Active && b.IsActive)
|
||||
.OrderByDescending(b => b.LastSeenAt)
|
||||
.ToListAsync();
|
||||
|
||||
return bots.Select(MapToDto);
|
||||
}
|
||||
catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table"))
|
||||
{
|
||||
// Tables don't exist yet - return empty list
|
||||
return new List<BotDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateBotSettingsAsync(Guid botId, UpdateBotSettingsDto dto)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return false;
|
||||
|
||||
bot.Settings = JsonSerializer.Serialize(dto.Settings);
|
||||
bot.LastConfigSyncAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Updated settings for bot {BotId}", botId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateBotStatusAsync(Guid botId, BotStatus status)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return false;
|
||||
|
||||
var oldStatus = bot.Status;
|
||||
bot.Status = status;
|
||||
bot.IsActive = status == BotStatus.Active;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Bot {BotId} status changed from {OldStatus} to {NewStatus}",
|
||||
botId, oldStatus, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> RecordHeartbeatAsync(Guid botId, BotHeartbeatDto dto)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return false;
|
||||
|
||||
bot.LastSeenAt = DateTime.UtcNow;
|
||||
bot.Version = dto.Version;
|
||||
bot.IpAddress = dto.IpAddress;
|
||||
|
||||
// Record uptime metric
|
||||
var uptimeMetric = new BotMetric
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
BotId = botId,
|
||||
MetricType = MetricType.Uptime,
|
||||
Value = 1,
|
||||
Metadata = JsonSerializer.Serialize(dto.Status),
|
||||
RecordedAt = DateTime.UtcNow,
|
||||
Category = "System",
|
||||
Description = "Heartbeat"
|
||||
};
|
||||
|
||||
_context.BotMetrics.Add(uptimeMetric);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteBotAsync(Guid botId)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return false;
|
||||
|
||||
bot.Status = BotStatus.Deleted;
|
||||
bot.IsActive = false;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Bot {BotId} marked as deleted", botId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object>> GetBotSettingsAsync(Guid botId)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return new Dictionary<string, object>();
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings)
|
||||
?? new Dictionary<string, object>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateBotKeyAsync(string botKey)
|
||||
{
|
||||
return await _context.Bots.AnyAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active);
|
||||
}
|
||||
|
||||
public Task<string> GenerateBotKeyAsync()
|
||||
{
|
||||
const string prefix = "bot_";
|
||||
const int keyLength = 32;
|
||||
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
var bytes = new byte[keyLength];
|
||||
rng.GetBytes(bytes);
|
||||
|
||||
var key = prefix + Convert.ToBase64String(bytes)
|
||||
.Replace("+", "")
|
||||
.Replace("/", "")
|
||||
.Replace("=", "")
|
||||
.Substring(0, keyLength);
|
||||
|
||||
return Task.FromResult(key);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePlatformInfoAsync(Guid botId, UpdatePlatformInfoDto dto)
|
||||
{
|
||||
var bot = await _context.Bots.FindAsync(botId);
|
||||
if (bot == null)
|
||||
return false;
|
||||
|
||||
bot.PlatformUsername = dto.PlatformUsername;
|
||||
bot.PlatformDisplayName = dto.PlatformDisplayName;
|
||||
bot.PlatformId = dto.PlatformId;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Updated platform info for bot {BotId}: @{Username} ({DisplayName})",
|
||||
botId, dto.PlatformUsername, dto.PlatformDisplayName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private BotDto MapToDto(Bot bot)
|
||||
{
|
||||
var settings = new Dictionary<string, object>();
|
||||
try
|
||||
{
|
||||
settings = JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings)
|
||||
?? new Dictionary<string, object>();
|
||||
}
|
||||
catch { }
|
||||
|
||||
var activeSessions = bot.Sessions.Count(s => !s.EndedAt.HasValue);
|
||||
var totalRevenue = bot.Sessions.Sum(s => s.TotalSpent);
|
||||
var totalOrders = bot.Sessions.Sum(s => s.OrderCount);
|
||||
|
||||
return new BotDto
|
||||
{
|
||||
Id = bot.Id,
|
||||
Name = bot.Name,
|
||||
Description = bot.Description,
|
||||
Type = bot.Type,
|
||||
Status = bot.Status,
|
||||
CreatedAt = bot.CreatedAt,
|
||||
LastSeenAt = bot.LastSeenAt,
|
||||
LastConfigSyncAt = bot.LastConfigSyncAt,
|
||||
IsActive = bot.IsActive,
|
||||
Version = bot.Version,
|
||||
IpAddress = bot.IpAddress,
|
||||
PlatformUsername = bot.PlatformUsername,
|
||||
PlatformDisplayName = bot.PlatformDisplayName,
|
||||
PlatformId = bot.PlatformId,
|
||||
PersonalityName = bot.PersonalityName,
|
||||
Settings = settings,
|
||||
TotalSessions = bot.Sessions.Count,
|
||||
ActiveSessions = activeSessions,
|
||||
TotalRevenue = totalRevenue,
|
||||
TotalOrders = totalOrders
|
||||
};
|
||||
}
|
||||
|
||||
private string AssignDefaultPersonality(string botName)
|
||||
{
|
||||
var personalities = new[] { "Alan", "Dave", "Sarah", "Mike", "Emma", "Tom" };
|
||||
|
||||
// Smart assignment based on name
|
||||
var lowerName = botName.ToLower();
|
||||
if (lowerName.Contains("alan")) return "Alan";
|
||||
if (lowerName.Contains("dave")) return "Dave";
|
||||
if (lowerName.Contains("sarah")) return "Sarah";
|
||||
if (lowerName.Contains("mike")) return "Mike";
|
||||
if (lowerName.Contains("emma")) return "Emma";
|
||||
if (lowerName.Contains("tom")) return "Tom";
|
||||
|
||||
// Random assignment if no match
|
||||
var random = new Random();
|
||||
return personalities[random.Next(personalities.Length)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user