Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions JobFlow.API/Controllers/SupportHubController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using FirebaseAdmin.Auth;
using JobFlow.API.Extensions;
using JobFlow.Business.Extensions;
using JobFlow.Business.Models.DTOs;
using JobFlow.Business.Services.ServiceInterfaces;
using JobFlow.Domain.Enums;
using JobFlow.Domain.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JobFlow.API.Controllers;

[ApiController]
[Route("api/supporthub")]
public class SupportHubController : ControllerBase
{
private readonly ISupportHubInviteService _inviteService;
private readonly ISupportHubService _supportHubService;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;

public SupportHubController(
ISupportHubService supportHubService,
ISupportHubInviteService inviteService,
IUserService userService,
IOrganizationService organizationService)
{
_supportHubService = supportHubService;
_inviteService = inviteService;
_userService = userService;
_organizationService = organizationService;
}

[HttpPost("register")]
[Authorize]
public async Task<IResult> RegisterSupportHubUser()
{
var firebaseUid = HttpContext.GetFirebaseUid();
if (string.IsNullOrWhiteSpace(firebaseUid))
{
return Results.Unauthorized();
}

var orgResult = await _organizationService.GetAllOrganizations();
if (orgResult.IsFailure)
{
return orgResult.ToProblemDetails();
}

var masterOrg = orgResult.Value
.FirstOrDefault(o => o.OrganizationType?.TypeName == "Master Account");
if (masterOrg == null)
{
return Results.Problem("Master account organization not found.");
}

var userResult = await _userService.GetUserByFirebaseUid(firebaseUid);
if (userResult.IsFailure)
{
var email = HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
var newUser = new User
{
Email = email,
FirebaseUid = firebaseUid,
OrganizationId = masterOrg.Id
};

var createResult = await _userService.UpsertUser(newUser);
if (createResult.IsFailure)
{
return createResult.ToProblemDetails();
}

await _userService.AssignRole(createResult.Value.Id, UserRoles.KatharixEmployee);
}
else
{
var existingUser = userResult.Value;
if (existingUser.OrganizationId == Guid.Empty)
{
existingUser.OrganizationId = masterOrg.Id;
var updateResult = await _userService.UpsertUser(existingUser);
if (updateResult.IsFailure)
{
return updateResult.ToProblemDetails();
}
}

await _userService.AssignRole(existingUser.Id, UserRoles.KatharixEmployee);
}

await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(
firebaseUid,
new Dictionary<string, object>
{
{ "role", UserRoles.KatharixEmployee }
});

return Results.Ok();
}

[HttpGet("tickets")]
[Authorize(Roles = $"{UserRoles.KatharixAdmin},{UserRoles.KatharixEmployee}")]
public async Task<IResult> GetTickets()
{
var result = await _supportHubService.GetTicketsAsync();
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpGet("sessions")]
[Authorize(Roles = $"{UserRoles.KatharixAdmin},{UserRoles.KatharixEmployee}")]
public async Task<IResult> GetSessions()
{
var result = await _supportHubService.GetSessionsAsync();
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("sessions/{sessionId:guid}/screen")]
[Authorize(Roles = $"{UserRoles.KatharixAdmin},{UserRoles.KatharixEmployee}")]
public async Task<IResult> StartScreenView([FromRoute] Guid sessionId)
{
var result = await _supportHubService.CreateScreenViewAsync(sessionId);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("tickets")]
[Authorize(Roles = $"{UserRoles.KatharixAdmin},{UserRoles.KatharixEmployee}")]
public async Task<IResult> CreateTicket([FromBody] SupportHubTicketCreateRequest request)
{
var createdBy = HttpContext.GetFirebaseUid();
var result = await _supportHubService.CreateTicketAsync(request, createdBy);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("sessions")]
[Authorize(Roles = $"{UserRoles.KatharixAdmin},{UserRoles.KatharixEmployee}")]
public async Task<IResult> CreateSession([FromBody] SupportHubSessionCreateRequest request)
{
var createdBy = HttpContext.GetFirebaseUid();
var result = await _supportHubService.CreateSessionAsync(request, createdBy);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("seed")]
[Authorize(Roles = UserRoles.KatharixAdmin)]
public async Task<IResult> SeedDemo([FromBody] SupportHubSeedRequest request)
{
var createdBy = HttpContext.GetFirebaseUid();
var result = await _supportHubService.SeedDemoAsync(request, createdBy);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpGet("invites")]
[Authorize(Roles = UserRoles.KatharixAdmin)]
public async Task<IResult> GetInvites()
{
var result = await _inviteService.GetActiveInvitesAsync();
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("invites")]
[Authorize(Roles = UserRoles.KatharixAdmin)]
public async Task<IResult> CreateInvite([FromBody] SupportHubInviteCreateRequest request)
{
var createdBy = HttpContext.GetFirebaseUid();
var result = await _inviteService.CreateInviteAsync(request, createdBy);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpGet("invites/validate/{code}")]
[AllowAnonymous]
public async Task<IResult> ValidateInvite([FromRoute] string code)
{
var result = await _inviteService.ValidateInviteAsync(code);
return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblemDetails();
}

[HttpPost("invites/redeem")]
[Authorize]
public async Task<IResult> RedeemInvite([FromBody] SupportHubInviteRedeemRequest request)
{
var firebaseUid = HttpContext.GetFirebaseUid();
var result = await _inviteService.RedeemInviteAsync(request.Code, firebaseUid);
if (result.IsFailure)
{
return result.ToProblemDetails();
}

if (!string.IsNullOrWhiteSpace(firebaseUid))
{
await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(
firebaseUid,
new Dictionary<string, object>
{
{ "role", result.Value.Role.ToString() }
});
}

return Results.Ok(result.Value);
}
}
17 changes: 17 additions & 0 deletions JobFlow.API/Extensions/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,21 @@ public static Guid GetUserId(this HttpContext context)

return userId;
}

public static string? GetFirebaseUid(this HttpContext context)
{
var uid = context.User.FindFirst("user_id")?.Value;
if (!string.IsNullOrWhiteSpace(uid))
{
return uid;
}

uid = context.User.FindFirst("sub")?.Value;
if (!string.IsNullOrWhiteSpace(uid))
{
return uid;
}

return context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
56 changes: 56 additions & 0 deletions JobFlow.Business/Models/DTOs/SupportHubDtos.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using JobFlow.Domain.Enums;

namespace JobFlow.Business.Models.DTOs;

public record SupportHubTicketDto(
Guid Id,
string Title,
SupportHubTicketStatus Status,
string OrganizationName,
DateTimeOffset CreatedAt);

public record SupportHubSessionDto(
Guid Id,
string OrganizationName,
string AgentName,
SupportHubSessionStatus Status,
DateTimeOffset? StartedAt);

public record SupportHubScreenResponseDto(
Guid SessionId,
string ViewerUrl);

public record SupportHubTicketCreateRequest(
Guid OrganizationId,
string Title,
string? Summary,
SupportHubTicketStatus Status);

public record SupportHubSessionCreateRequest(
Guid OrganizationId,
string AgentName,
SupportHubSessionStatus Status);

public record SupportHubSeedRequest(Guid OrganizationId);

public record SupportHubSeedResponse(int TicketsCreated, int SessionsCreated);

public record SupportHubInviteDto(
Guid Id,
string Code,
SupportHubInviteRole Role,
DateTimeOffset CreatedAt,
string? CreatedBy,
DateTimeOffset ExpiresAt,
DateTimeOffset? RedeemedAt,
string? RedeemedBy);

public record SupportHubInviteCreateRequest(
SupportHubInviteRole Role,
DateTimeOffset? ExpiresAt);

public record SupportHubInviteRedeemRequest(string Code);

public record SupportHubInviteValidationDto(
SupportHubInviteDto? Invite,
string? Error);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using JobFlow.Business.Models.DTOs;

namespace JobFlow.Business.Services.ServiceInterfaces;

public interface ISupportHubInviteService
{
Task<Result<SupportHubInviteDto>> CreateInviteAsync(SupportHubInviteCreateRequest request, string? createdBy);
Task<Result<List<SupportHubInviteDto>>> GetActiveInvitesAsync();
Task<Result<SupportHubInviteValidationDto>> ValidateInviteAsync(string code);
Task<Result<SupportHubInviteDto>> RedeemInviteAsync(string code, string? redeemedBy);
}
13 changes: 13 additions & 0 deletions JobFlow.Business/Services/ServiceInterfaces/ISupportHubService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using JobFlow.Business.Models.DTOs;

namespace JobFlow.Business.Services.ServiceInterfaces;

public interface ISupportHubService
{
Task<Result<List<SupportHubTicketDto>>> GetTicketsAsync();
Task<Result<List<SupportHubSessionDto>>> GetSessionsAsync();
Task<Result<SupportHubScreenResponseDto>> CreateScreenViewAsync(Guid sessionId);
Task<Result<SupportHubTicketDto>> CreateTicketAsync(SupportHubTicketCreateRequest request, string? createdBy);
Task<Result<SupportHubSessionDto>> CreateSessionAsync(SupportHubSessionCreateRequest request, string? createdBy);
Task<Result<SupportHubSeedResponse>> SeedDemoAsync(SupportHubSeedRequest request, string? createdBy);
}
Loading
Loading