Files
Website/BlazorApp/Services/DeveloperApplicationService.cs
SysAdmin c4febd7036
All checks were successful
Build and Deploy / deploy (push) Successful in 18s
fix(developers): fix application record creation and approval flow
Fix two bugs preventing developer applications from appearing in SilverDESK:

1. Application creation payload used wrong types - ticketId was parsed as
   int (GetInt32) but SilverDESK expects a Guid string, and appliedRole
   was cast to int but the DTO expects "Tester"/"Developer" strings.

2. Approval provisioning now updates the DeveloperApplication record in
   SilverDESK after Mattermost/Mailcow provisioning, setting status to
   Approved and the correct provisioning flags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:40:42 +00:00

201 lines
8.4 KiB
C#

using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using SilverLabs.Website.Models;
namespace SilverLabs.Website.Services;
public class DeveloperApplicationService
{
private readonly HttpClient _httpClient;
private readonly ILogger<DeveloperApplicationService> _logger;
public DeveloperApplicationService(HttpClient httpClient, ILogger<DeveloperApplicationService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
/// <summary>
/// Checks username availability. Returns: true = available, false = taken, null = error/unknown.
/// </summary>
public async Task<bool?> CheckUsernameAsync(string username)
{
try
{
var response = await _httpClient.GetAsync($"/api/auth/check-username/{Uri.EscapeDataString(username)}");
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Username check returned {StatusCode} for {Username}", response.StatusCode, username);
return null;
}
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
if (result.TryGetProperty("available", out var available))
return available.GetBoolean();
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking username availability for {Username}", username);
return null;
}
}
public async Task<(bool Success, string Message, string? Token)> SubmitApplicationAsync(DeveloperApplication application)
{
try
{
// 1. Register user on SilverDESK
var registerPayload = new
{
username = application.DesiredUsername,
email = application.Email,
password = application.Password,
fullName = application.FullName
};
var registerResponse = await _httpClient.PostAsJsonAsync("/api/auth/register", registerPayload);
if (!registerResponse.IsSuccessStatusCode)
{
var errorBody = await registerResponse.Content.ReadAsStringAsync();
_logger.LogError("SilverDESK registration failed: {StatusCode} - {Body}", registerResponse.StatusCode, errorBody);
var friendlyMessage = ParseRegistrationError(errorBody);
return (false, friendlyMessage, null);
}
var authResult = await registerResponse.Content.ReadFromJsonAsync<JsonElement>();
var token = authResult.GetProperty("token").GetString();
// 2. Create ticket using the user's own JWT
var ticketBody = FormatTicketBody(application);
var ticketPayload = new
{
Subject = $"[Developer Program] {application.Role} Application - {application.FullName}",
Description = ticketBody,
Priority = "Medium",
Category = "Developer Program"
};
var ticketRequest = new HttpRequestMessage(HttpMethod.Post, "/api/tickets");
ticketRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
ticketRequest.Content = JsonContent.Create(ticketPayload);
var ticketResponse = await _httpClient.SendAsync(ticketRequest);
if (!ticketResponse.IsSuccessStatusCode)
{
var errorBody = await ticketResponse.Content.ReadAsStringAsync();
_logger.LogError("Failed to create ticket: {StatusCode} - {Body}", ticketResponse.StatusCode, errorBody);
// User was created but ticket failed — still return success with a note
return (true, "Your account has been created, but we had trouble submitting your application ticket. Please log in to SilverDESK and create a support ticket.", token);
}
// 3. Create DeveloperApplication record linking user + ticket
try
{
var userId = authResult.GetProperty("user").GetProperty("id").GetString();
var ticketResult = await ticketResponse.Content.ReadFromJsonAsync<JsonElement>();
var ticketId = ticketResult.GetProperty("id").GetString();
var applicationPayload = new
{
userId,
ticketId,
fullName = application.FullName,
email = application.Email,
desiredUsername = application.DesiredUsername,
timezone = application.Timezone,
appliedRole = application.Role.ToString(),
platforms = application.Platforms,
skills = application.Skills ?? "",
motivation = application.Motivation,
status = 0, // Pending
silverDeskProvisioned = true
};
var appResponse = await _httpClient.PostAsJsonAsync("/api/developer-program/applications", applicationPayload);
if (appResponse.IsSuccessStatusCode)
{
_logger.LogInformation("DeveloperApplication record created for {Email}", application.Email);
}
else
{
var appError = await appResponse.Content.ReadAsStringAsync();
_logger.LogWarning("Failed to create DeveloperApplication record for {Email}: {StatusCode} - {Body}",
application.Email, appResponse.StatusCode, appError);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to create DeveloperApplication record for {Email} — user and ticket were created successfully",
application.Email);
}
_logger.LogInformation("Developer application submitted for {Email} as {Role} — user registered and ticket created",
application.Email, application.Role);
return (true, "Application submitted successfully! Your SilverDESK account has been created.", token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error submitting developer application for {Email}", application.Email);
return (false, "Unable to connect to the application service. Please try again later.", null);
}
}
private static string ParseRegistrationError(string errorBody)
{
try
{
var error = JsonSerializer.Deserialize<JsonElement>(errorBody);
if (error.TryGetProperty("message", out var message))
{
var msg = message.GetString() ?? "";
if (msg.Contains("Username already exists", StringComparison.OrdinalIgnoreCase))
return "That username is already taken. Please choose a different one.";
if (msg.Contains("Email already exists", StringComparison.OrdinalIgnoreCase))
return "An account with that email already exists.";
if (msg.Contains("Password", StringComparison.OrdinalIgnoreCase))
return msg;
return msg;
}
}
catch { }
return "Something went wrong creating your account. Please try again later.";
}
private static string FormatTicketBody(DeveloperApplication app)
{
var sb = new StringBuilder();
sb.AppendLine("## Developer Program Application");
sb.AppendLine();
sb.AppendLine($"**Role:** {app.Role}");
sb.AppendLine($"**Full Name:** {app.FullName}");
sb.AppendLine($"**Email:** {app.Email}");
sb.AppendLine($"**Desired Username:** {app.DesiredUsername}");
sb.AppendLine($"**Timezone:** {app.Timezone}");
sb.AppendLine();
sb.AppendLine($"**Platforms:** {string.Join(", ", app.Platforms)}");
sb.AppendLine();
if (app.Role == ApplicationRole.Developer && !string.IsNullOrWhiteSpace(app.Skills))
{
sb.AppendLine("**Skills & Experience:**");
sb.AppendLine(app.Skills);
sb.AppendLine();
}
sb.AppendLine("**Motivation:**");
sb.AppendLine(app.Motivation);
return sb.ToString();
}
}