feat(developers): fix approval webhook flow and add ticket parsing service
All checks were successful
Build and Deploy / deploy (push) Successful in 16s

Change approve endpoint from int to string ticketId to match SilverDESK
GUIDs. Remove body parameter requirement so the endpoint works as a
webhook target. Add DeveloperTicketParsingService to fetch and parse
applicant details from ticket descriptions. Remove redundant ticket
status update from ProvisioningService since SilverDESK action engine
now handles SetStatus/AddNote/AddReply steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 23:10:46 +00:00
parent a8b7cc2ffd
commit ed5d14989a
5 changed files with 80 additions and 34 deletions

View File

@@ -0,0 +1,52 @@
using System.Text.Json;
using System.Text.RegularExpressions;
namespace SilverLabs.Website.Services;
public class DeveloperTicketParsingService
{
private readonly HttpClient _httpClient;
private readonly ILogger<DeveloperTicketParsingService> _logger;
public DeveloperTicketParsingService(HttpClient httpClient, ILogger<DeveloperTicketParsingService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<JsonElement?> FetchTicketAsync(string ticketId)
{
try
{
var response = await _httpClient.GetAsync($"/api/tickets/{ticketId}");
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to fetch ticket {TicketId}: {Status}", ticketId, response.StatusCode);
return null;
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<JsonElement>(json);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching ticket {TicketId}", ticketId);
return null;
}
}
public (string? FullName, string? Email, string? DesiredUsername) ParseApplicationFromDescription(string description)
{
var fullName = ExtractField(description, @"\*\*Full Name:\*\*\s*(.+)");
var email = ExtractField(description, @"\*\*Email:\*\*\s*(.+)");
var desiredUsername = ExtractField(description, @"\*\*Desired Username:\*\*\s*(.+)");
return (fullName, email, desiredUsername);
}
private static string? ExtractField(string text, string pattern)
{
var match = Regex.Match(text, pattern);
return match.Success ? match.Groups[1].Value.Trim() : null;
}
}

View File

@@ -15,7 +15,7 @@ public class ProvisioningService
}
public async Task<(bool Success, string Message)> ApproveApplicationAsync(
int ticketId, string username, string email, string fullName)
string ticketId, string username, string email, string fullName)
{
var results = new List<string>();
var allSuccess = true;
@@ -35,14 +35,8 @@ public class ProvisioningService
results.Add($"Mailcow: {mailMsg}");
if (!mailOk) allSuccess = false;
// 4. Update SilverDESK ticket
if (allSuccess)
{
await UpdateTicketStatusAsync(ticketId, "approved", string.Join("\n", results));
}
var summary = string.Join("; ", results);
_logger.LogInformation("Provisioning for {Username}: {Summary}", username, summary);
_logger.LogInformation("Provisioning for {Username} (ticket {TicketId}): {Summary}", username, ticketId, summary);
return (allSuccess, summary);
}
@@ -136,21 +130,4 @@ public class ProvisioningService
return (false, $"Error: {ex.Message}");
}
}
private async Task UpdateTicketStatusAsync(int ticketId, string status, string note)
{
try
{
var client = _httpClientFactory.CreateClient("SilverDesk");
var payload = new { status, note = $"Application approved. Provisioning results:\n{note}" };
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
await client.PutAsync($"/api/tickets/{ticketId}", content);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update ticket {TicketId} status", ticketId);
}
}
}