diff --git a/BlazorApp/Endpoints/DeveloperEndpoints.cs b/BlazorApp/Endpoints/DeveloperEndpoints.cs index 5e7df84..888b28b 100644 --- a/BlazorApp/Endpoints/DeveloperEndpoints.cs +++ b/BlazorApp/Endpoints/DeveloperEndpoints.cs @@ -43,18 +43,22 @@ public static class DeveloperEndpoints 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); + var (fullName, email, desiredUsername, role) = 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); // Generate confirmation token instead of provisioning immediately - var deployment = provisioningService.CreatePendingDeployment(desiredUsername, email, fullName, ticketId); + var deployment = provisioningService.CreatePendingDeployment(desiredUsername, email, fullName, ticketId, role); var siteBase = config["SiteBaseUrl"] ?? "https://silverlabs.uk"; var confirmUrl = $"{siteBase}/developers/confirm/{deployment.Token}"; // Send ticket reply with confirmation link + var giteaLine = string.Equals(role, "Developer", StringComparison.OrdinalIgnoreCase) + ? "\n- **Gitea**: Source code repository access" + : ""; + var replyContent = $""" Your application has been approved! To activate your accounts, please confirm your identity: @@ -64,8 +68,7 @@ public static class DeveloperEndpoints Once confirmed, the following accounts will be created for you: - **Email**: {desiredUsername}@silverlabs.uk - - **Mattermost**: Team chat access - - **Gitea**: Source code repository access + - **Mattermost**: Team chat access{giteaLine} """; var (replyOk, replyMsg) = await provisioningService.SendTicketReplyAsync(ticketId, replyContent); @@ -112,7 +115,15 @@ public static class DeveloperEndpoints // Provision all services with the user's password var (success, message) = await provisioningService.ProvisionWithPasswordAsync( - deployment.TicketId, deployment.Username, deployment.Email, deployment.FullName, request.Password); + deployment.TicketId, deployment.Username, deployment.Email, deployment.FullName, request.Password, deployment.Role); + + var isDeveloper = string.Equals(deployment.Role, "Developer", StringComparison.OrdinalIgnoreCase); + var giteaSuccessSection = isDeveloper + ? $"\n\n**Gitea** (Source Code): [git.silverlabs.uk](https://git.silverlabs.uk)" + : ""; + var giteaFailSection = isDeveloper + ? $"\n- **Gitea**: [git.silverlabs.uk](https://git.silverlabs.uk)" + : ""; // Send follow-up ticket reply with results var resultContent = success @@ -124,9 +135,7 @@ public static class DeveloperEndpoints - IMAP: `mail.silverlined.uk:993` (SSL) - SMTP: `mail.silverlined.uk:465` (SSL) - **Mattermost** (Team Chat): [ops.silverlined.uk](https://ops.silverlined.uk) - - **Gitea** (Source Code): [git.silverlabs.uk](https://git.silverlabs.uk) + **Mattermost** (Team Chat): [ops.silverlined.uk](https://ops.silverlined.uk){giteaSuccessSection} **SilverDESK** (Support & Tickets): [silverdesk.silverlabs.uk](https://silverdesk.silverlabs.uk) @@ -144,8 +153,7 @@ public static class DeveloperEndpoints Once resolved, your services will be: - **Email**: {deployment.Username}@silverlabs.uk — [mail.silverlined.uk](https://mail.silverlined.uk) - - **Mattermost**: [ops.silverlined.uk](https://ops.silverlined.uk) - - **Gitea**: [git.silverlabs.uk](https://git.silverlabs.uk) + - **Mattermost**: [ops.silverlined.uk](https://ops.silverlined.uk){giteaFailSection} """; await provisioningService.SendTicketReplyAsync(deployment.TicketId, resultContent, "close"); diff --git a/BlazorApp/Services/DeveloperTicketParsingService.cs b/BlazorApp/Services/DeveloperTicketParsingService.cs index d819cb2..e0dece1 100644 --- a/BlazorApp/Services/DeveloperTicketParsingService.cs +++ b/BlazorApp/Services/DeveloperTicketParsingService.cs @@ -35,13 +35,14 @@ public class DeveloperTicketParsingService } } - public (string? FullName, string? Email, string? DesiredUsername) ParseApplicationFromDescription(string description) + public (string? FullName, string? Email, string? DesiredUsername, string? Role) ParseApplicationFromDescription(string description) { var fullName = ExtractField(description, @"\*\*Full Name:\*\*\s*(.+)"); var email = ExtractField(description, @"\*\*Email:\*\*\s*(.+)"); var desiredUsername = ExtractField(description, @"\*\*Desired Username:\*\*\s*(.+)"); + var role = ExtractField(description, @"\*\*Role:\*\*\s*(.+)"); - return (fullName, email, desiredUsername); + return (fullName, email, desiredUsername, role); } private static string? ExtractField(string text, string pattern) diff --git a/BlazorApp/Services/ProvisioningService.cs b/BlazorApp/Services/ProvisioningService.cs index 9d6ba8c..33847be 100644 --- a/BlazorApp/Services/ProvisioningService.cs +++ b/BlazorApp/Services/ProvisioningService.cs @@ -12,6 +12,7 @@ public record PendingDeployment( string Email, string FullName, string TicketId, + string? Role, DateTime CreatedAt, DateTime ExpiresAt); @@ -35,7 +36,7 @@ public class ProvisioningService // --- Token management --- - public PendingDeployment CreatePendingDeployment(string username, string email, string fullName, string ticketId) + public PendingDeployment CreatePendingDeployment(string username, string email, string fullName, string ticketId, string? role = null) { CleanupExpiredTokens(); @@ -43,7 +44,7 @@ public class ProvisioningService .Replace("+", "-").Replace("/", "_").TrimEnd('='); var deployment = new PendingDeployment( - token, username, email, fullName, ticketId, + token, username, email, fullName, ticketId, role, DateTime.UtcNow, DateTime.UtcNow.AddHours(48)); _pendingDeployments[token] = deployment; @@ -113,7 +114,7 @@ public class ProvisioningService // --- Full provisioning with password --- public async Task<(bool Success, string Message)> ProvisionWithPasswordAsync( - string ticketId, string username, string email, string fullName, string password) + string ticketId, string username, string email, string fullName, string password, string? role = null) { var results = new List(); var allSuccess = true; @@ -123,15 +124,32 @@ public class ProvisioningService results.Add($"Mattermost: {mmMsg}"); if (!mmOk) allSuccess = false; + // 1b. Add to SilverLABS team (only if user was created) + if (mmOk) + { + var (teamOk, teamMsg) = await AddMattermostUserToTeamAsync(username); + results.Add($"Mattermost Team: {teamMsg}"); + if (!teamOk) allSuccess = false; + } + // 2. Create Mailcow mailbox var (mailOk, mailMsg) = await CreateMailcowMailboxAsync(username, fullName, password); results.Add($"Mailcow: {mailMsg}"); if (!mailOk) allSuccess = false; - // 3. Create Gitea user - var (giteaOk, giteaMsg) = await CreateGiteaUserAsync(username, email, fullName, password); - results.Add($"Gitea: {giteaMsg}"); - if (!giteaOk) allSuccess = false; + // 3. Create Gitea user (Developers only) + var giteaOk = false; + if (string.Equals(role, "Developer", StringComparison.OrdinalIgnoreCase)) + { + var (gOk, giteaMsg) = await CreateGiteaUserAsync(username, email, fullName, password); + giteaOk = gOk; + results.Add($"Gitea: {giteaMsg}"); + if (!giteaOk) allSuccess = false; + } + else + { + results.Add("Gitea: Skipped (not required for Tester role)"); + } // 4. Update the DeveloperApplication record in SilverDESK var (updateOk, updateMsg) = await UpdateApplicationStatusAsync(ticketId, mmOk, mailOk, giteaOk); @@ -318,6 +336,39 @@ public class ProvisioningService } } + private async Task<(bool Success, string Message)> AddMattermostUserToTeamAsync(string username) + { + try + { + var client = _httpClientFactory.CreateClient("Mattermost"); + + // Look up user ID by username + var userResponse = await client.GetAsync($"/api/v4/users/username/{username}"); + if (!userResponse.IsSuccessStatusCode) + return (false, $"User lookup failed ({userResponse.StatusCode})"); + + var userData = await userResponse.Content.ReadFromJsonAsync(); + var userId = userData.GetProperty("id").GetString(); + + // Add to SilverLABS team + var teamId = _configuration["Mattermost:TeamId"] ?? "ear83bc7nprzpe878ey7hxza7h"; + var payload = new { team_id = teamId, user_id = userId }; + var response = await client.PostAsJsonAsync($"/api/v4/teams/{teamId}/members", payload); + + if (response.IsSuccessStatusCode) + return (true, "Added to team"); + + var body = await response.Content.ReadAsStringAsync(); + _logger.LogError("Mattermost team join failed: {Status} {Body}", response.StatusCode, body); + return (false, $"Team join failed ({response.StatusCode})"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Mattermost team join error for {Username}", username); + return (false, $"Error: {ex.Message}"); + } + } + private async Task<(bool Success, string Message)> CreateMailcowMailboxAsync( string username, string fullName, string password) { diff --git a/BlazorApp/appsettings.json b/BlazorApp/appsettings.json index bce772f..4237b43 100644 --- a/BlazorApp/appsettings.json +++ b/BlazorApp/appsettings.json @@ -12,7 +12,8 @@ }, "Mattermost": { "BaseUrl": "https://ops.silverlined.uk", - "ApiToken": "ktmfkpxz7ffr5g1imuqg8hm58c" + "ApiToken": "ktmfkpxz7ffr5g1imuqg8hm58c", + "TeamId": "ear83bc7nprzpe878ey7hxza7h" }, "Mailcow": { "BaseUrl": "https://mail.silverlined.uk", @@ -20,7 +21,7 @@ }, "Gitea": { "BaseUrl": "https://git.silverlabs.uk", - "ApiToken": "" + "ApiToken": "70ec152b27ee12d8a2cfb7241df5735351df72cd" }, "SiteBaseUrl": "https://silverlabs.uk", "AdminApiKey": "aawb2MHblbfmqdhcS7Xp2/ibQOUbUE1BDoqdJOu0bjM="