refactor: Phase 7-3 Complete - All API Controllers + Browser Clients
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m5s
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m5s
**API Controllers Complete:** - ClientController (GET /api/client paged, POST/PUT/DELETE) - TaxFilingController (GET upcoming, GET by client, POST/PUT/DELETE) - FaqController (GET active/all, GET by id, POST/PUT/DELETE) - AnnouncementController (GET active/all, GET by id, POST/PUT/DELETE) **Browser Clients Complete:** - IClientBrowserClient + ClientBrowserClient - ITaxFilingBrowserClient + TaxFilingBrowserClient - IFaqBrowserClient + FaqBrowserClient - IAnnouncementBrowserClient + AnnouncementBrowserClient **All Registered in Program.cs:** - BaseAddress: http://localhost:5001/taxbaik/api/ - TokenRefreshHandler attached to all clients - DI container: AddHttpClient<IXxxClient, XxxClient> **Blazor Refactored (Partial):** - ClientList.razor: ✅ IClientBrowserClient (service → API) - ClientEdit.razor: ✅ IClientBrowserClient (service → API) - TaxFilings Blazor: ⏳ Pending refactor - Faqs Blazor: ⏳ Pending refactor - Announcements Blazor: ⏳ Pending refactor **Phase 7 Status:** - API-First Foundation: ✅ 100% (all controllers + clients ready) - Blazor Refactoring: 🟡 30% (Clients done, others pending) - Phase 6 SignalR: ⏳ Deferred (ready for real-time on API-first pages) **SOLID Applied Throughout:** ✓ Single Responsibility: Each client handles one domain ✓ Open/Closed: Extend via interface, not modification ✓ Dependency Inversion: Blazor → Interfaces, not services ✓ Interface Segregation: Specialized clients per operation ✓ Liskov Substitution: All clients follow same contract **Build:** ✅ Success (0 errors, 2 warnings in Dashboard) **Pattern:** Established & repeatable for remaining Blazor pages Next: Blazor page migrations (TaxFilings, Faqs, Announcements) Then: Phase 6 SignalR for real-time notifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
namespace TaxBaik.Web.Services;
|
||||
|
||||
using System.Net.Http.Json;
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface IFaqBrowserClient
|
||||
{
|
||||
Task<IEnumerable<Faq>> GetAllAsync(CancellationToken ct = default);
|
||||
Task<Faq?> GetByIdAsync(int id, CancellationToken ct = default);
|
||||
Task<Faq?> CreateAsync(Faq faq, CancellationToken ct = default);
|
||||
Task<Faq?> UpdateAsync(int id, Faq faq, CancellationToken ct = default);
|
||||
Task<bool> DeleteAsync(int id, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public class FaqBrowserClient : IFaqBrowserClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly ILogger<FaqBrowserClient> _logger;
|
||||
|
||||
public FaqBrowserClient(HttpClient http, ILogger<FaqBrowserClient> logger)
|
||||
{
|
||||
_http = http;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Faq>> GetAllAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<FaqListResponse>("faq", cancellationToken: ct);
|
||||
return result?.Data ?? [];
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch FAQs");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Faq?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _http.GetFromJsonAsync<Faq>($"faq/{id}", cancellationToken: ct);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch FAQ {FaqId}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Faq?> CreateAsync(Faq faq, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync("faq", faq, cancellationToken: ct);
|
||||
if (!response.IsSuccessStatusCode) return null;
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(ct);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<Faq>(
|
||||
content,
|
||||
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create FAQ");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Faq?> UpdateAsync(int id, Faq faq, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PutAsJsonAsync($"faq/{id}", faq, cancellationToken: ct);
|
||||
if (!response.IsSuccessStatusCode) return null;
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(ct);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<Faq>(
|
||||
content,
|
||||
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update FAQ {FaqId}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.DeleteAsync($"faq/{id}", cancellationToken: ct);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete FAQ {FaqId}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private class FaqListResponse
|
||||
{
|
||||
public List<Faq> Data { get; set; } = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user