Files
Website/BlazorApp/Services/DeveloperApplicationService.cs
SysAdmin 33b21959d8
All checks were successful
Build and Deploy / deploy (push) Successful in 40s
fix(developers): distinguish API errors from taken usernames in availability check
CheckUsernameAsync returned false (taken) on any API failure, making every
username appear taken when SilverDESK was unreachable. Now returns nullable
bool so errors show a warning instead of blocking submission.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:00:46 +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").GetInt32();
var applicationPayload = new
{
userId,
ticketId,
fullName = application.FullName,
email = application.Email,
desiredUsername = application.DesiredUsername,
timezone = application.Timezone,
appliedRole = (int)application.Role,
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();
}
}