feat(developers): fix approval webhook flow and add ticket parsing service
All checks were successful
Build and Deploy / deploy (push) Successful in 16s
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:
@@ -17,10 +17,10 @@ public static class DeveloperEndpoints
|
|||||||
: Results.Problem(message, statusCode: 502);
|
: Results.Problem(message, statusCode: 502);
|
||||||
});
|
});
|
||||||
|
|
||||||
group.MapPost("/approve/{ticketId:int}", async (
|
group.MapPost("/approve/{ticketId}", async (
|
||||||
int ticketId,
|
string ticketId,
|
||||||
ApproveRequest request,
|
DeveloperTicketParsingService ticketService,
|
||||||
ProvisioningService service,
|
ProvisioningService provisioningService,
|
||||||
HttpContext context,
|
HttpContext context,
|
||||||
IConfiguration config) =>
|
IConfiguration config) =>
|
||||||
{
|
{
|
||||||
@@ -30,8 +30,18 @@ public static class DeveloperEndpoints
|
|||||||
if (string.IsNullOrEmpty(expectedKey) || apiKey != expectedKey)
|
if (string.IsNullOrEmpty(expectedKey) || apiKey != expectedKey)
|
||||||
return Results.Unauthorized();
|
return Results.Unauthorized();
|
||||||
|
|
||||||
var (success, message) = await service.ApproveApplicationAsync(
|
var ticket = await ticketService.FetchTicketAsync(ticketId);
|
||||||
ticketId, request.Username, request.Email, request.FullName);
|
if (ticket is null)
|
||||||
|
return Results.Problem("Failed to fetch ticket from SilverDESK", statusCode: 502);
|
||||||
|
|
||||||
|
var description = ticket.Value.GetProperty("description").GetString() ?? "";
|
||||||
|
var (fullName, email, desiredUsername) = ticketService.ParseApplicationFromDescription(description);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(fullName) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(desiredUsername))
|
||||||
|
return Results.Problem("Could not parse applicant details from ticket description", statusCode: 422);
|
||||||
|
|
||||||
|
var (success, message) = await provisioningService.ApproveApplicationAsync(
|
||||||
|
ticketId, desiredUsername, email, fullName);
|
||||||
|
|
||||||
return success
|
return success
|
||||||
? Results.Ok(new { message })
|
? Results.Ok(new { message })
|
||||||
@@ -39,5 +49,3 @@ public static class DeveloperEndpoints
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ApproveRequest(string Username, string Email, string FullName);
|
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ builder.Services.AddHttpClient<DeveloperApplicationService>(client =>
|
|||||||
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
|
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// HttpClient for DeveloperTicketParsingService (fetches tickets from SilverDESK)
|
||||||
|
builder.Services.AddHttpClient<DeveloperTicketParsingService>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri(builder.Configuration["SilverDesk:BaseUrl"] ?? "https://silverdesk.silverlabs.uk");
|
||||||
|
var apiKey = builder.Configuration["SilverDesk:ApiKey"];
|
||||||
|
if (!string.IsNullOrEmpty(apiKey))
|
||||||
|
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
|
||||||
|
});
|
||||||
|
|
||||||
// Named HttpClients for provisioning
|
// Named HttpClients for provisioning
|
||||||
builder.Services.AddHttpClient("SilverDesk", client =>
|
builder.Services.AddHttpClient("SilverDesk", client =>
|
||||||
{
|
{
|
||||||
|
|||||||
52
BlazorApp/Services/DeveloperTicketParsingService.cs
Normal file
52
BlazorApp/Services/DeveloperTicketParsingService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ public class ProvisioningService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool Success, string Message)> ApproveApplicationAsync(
|
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 results = new List<string>();
|
||||||
var allSuccess = true;
|
var allSuccess = true;
|
||||||
@@ -35,14 +35,8 @@ public class ProvisioningService
|
|||||||
results.Add($"Mailcow: {mailMsg}");
|
results.Add($"Mailcow: {mailMsg}");
|
||||||
if (!mailOk) allSuccess = false;
|
if (!mailOk) allSuccess = false;
|
||||||
|
|
||||||
// 4. Update SilverDESK ticket
|
|
||||||
if (allSuccess)
|
|
||||||
{
|
|
||||||
await UpdateTicketStatusAsync(ticketId, "approved", string.Join("\n", results));
|
|
||||||
}
|
|
||||||
|
|
||||||
var summary = string.Join("; ", 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);
|
return (allSuccess, summary);
|
||||||
}
|
}
|
||||||
@@ -136,21 +130,4 @@ public class ProvisioningService
|
|||||||
return (false, $"Error: {ex.Message}");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,5 @@
|
|||||||
"BaseUrl": "https://mail.silverlined.uk",
|
"BaseUrl": "https://mail.silverlined.uk",
|
||||||
"ApiKey": ""
|
"ApiKey": ""
|
||||||
},
|
},
|
||||||
"AdminApiKey": ""
|
"AdminApiKey": "aawb2MHblbfmqdhcS7Xp2/ibQOUbUE1BDoqdJOu0bjM="
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user