using InvoiceMaster.Application.DTOs; using InvoiceMaster.Core.Entities; using InvoiceMaster.Core.Interfaces; using InvoiceMaster.Integrations.Accounting; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace InvoiceMaster.API.Controllers; [ApiController] [Route("api/v1/accounting")] [Authorize] public class AccountingController : ControllerBase { private readonly IAccountingSystemFactory _factory; private readonly IRepository _connectionRepository; private readonly IRepository _userRepository; public AccountingController( IAccountingSystemFactory factory, IRepository connectionRepository, IRepository userRepository) { _factory = factory; _connectionRepository = connectionRepository; _userRepository = userRepository; } [HttpGet("providers")] public IActionResult GetProviders() { var userId = GetUserId(); var connections = _connectionRepository.GetAllAsync().Result .Where(c => c.UserId == userId && c.IsActive) .ToList(); var providers = new[] { new ProviderInfoDto("fortnox", "Fortnox", "Swedish accounting software", true, connections.Any(c => c.Provider == "fortnox")), new ProviderInfoDto("visma", "Visma eAccounting", "Nordic accounting software", false, false), new ProviderInfoDto("hogia", "Hogia Smart", "Swedish accounting software", false, false) }; return Ok(new { success = true, data = new { providers } }); } [HttpGet("{provider}/auth/url")] public IActionResult GetAuthUrl(string provider) { var redirectUri = $"{Request.Scheme}://{Request.Host}/api/v1/accounting/{provider}/auth/callback"; var state = Guid.NewGuid().ToString("N"); string authUrl = provider.ToLower() switch { "fortnox" => $"https://apps.fortnox.se/oauth-v1/auth?client_id=&redirect_uri={Uri.EscapeDataString(redirectUri)}&scope=supplier+voucher+account&state={state}", _ => throw new NotSupportedException($"Provider '{provider}' is not supported") }; return Ok(new { success = true, data = new { provider, authorizationUrl = authUrl, state } }); } [HttpGet("{provider}/auth/callback")] public async Task AuthCallback(string provider, [FromQuery] string code, [FromQuery] string state) { var accounting = _factory.Create(provider); var result = await accounting.AuthenticateAsync(code); if (!result.Success) { return BadRequest(new { success = false, error = result.ErrorMessage }); } var userId = GetUserId(); var connection = AccountingConnection.Create( userId, provider, result.AccessToken ?? string.Empty, result.RefreshToken ?? string.Empty, result.ExpiresAt ?? DateTime.UtcNow.AddHours(1), result.Scope, result.CompanyInfo?.Name, result.CompanyInfo?.OrganisationNumber); await _connectionRepository.AddAsync(connection); return Ok(new { success = true, data = new { provider, connected = true, companyName = result.CompanyInfo?.Name, companyOrgNumber = result.CompanyInfo?.OrganisationNumber, connectedAt = connection.CreatedAt } }); } [HttpGet("connections")] public async Task GetConnections() { var userId = GetUserId(); var connections = (await _connectionRepository.GetAllAsync()) .Where(c => c.UserId == userId && c.IsActive) .Select(c => new ConnectionDto( c.Provider, true, c.CompanyName, c.CompanyOrgNumber, c.Scope?.Split(' ').ToList(), c.ExpiresAt, new ConnectionSettingsDto( c.DefaultVoucherSeries, c.DefaultAccountCode, c.AutoAttachPdf, c.AutoCreateSupplier))) .ToList(); return Ok(new { success = true, data = new { connections } }); } [HttpDelete("connections/{provider}")] public async Task Disconnect(string provider) { var userId = GetUserId(); var connections = await _connectionRepository.GetAllAsync(); var connection = connections.FirstOrDefault(c => c.UserId == userId && c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase) && c.IsActive); if (connection == null) { return NotFound(new { success = false, error = "Connection not found" }); } connection.Deactivate(); return Ok(new { success = true }); } private Guid GetUserId() { var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; return Guid.Parse(userId!); } }