205 lines
7.7 KiB
C#
205 lines
7.7 KiB
C#
using System.Security.Claims;
|
|
using HelpDesk.Data.Repositories;
|
|
using HelpDesk.Models.DTOs;
|
|
using HelpDesk.Models.Entities;
|
|
using HelpDesk.Models.Enums;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace HelpDesk.API.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class TicketsController : ControllerBase
|
|
{
|
|
private readonly ITicketRepository _ticketRepository;
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
public TicketsController(ITicketRepository ticketRepository, IUserRepository userRepository)
|
|
{
|
|
_ticketRepository = ticketRepository;
|
|
_userRepository = userRepository;
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<TicketListResponse>> GetTickets(
|
|
[FromQuery] TicketStatus? status,
|
|
[FromQuery] TicketPriority? priority,
|
|
[FromQuery] Guid? assigneeId,
|
|
[FromQuery] Guid? requesterId,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20)
|
|
{
|
|
var userId = GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var user = await _userRepository.GetByIdAsync(userId.Value);
|
|
if (user == null) return Unauthorized();
|
|
|
|
if (user.Role == UserRole.Customer)
|
|
requesterId = userId.Value;
|
|
|
|
var tickets = await _ticketRepository.GetAllAsync(status, priority, assigneeId, requesterId, page, pageSize);
|
|
var totalCount = await _ticketRepository.GetCountAsync(status, priority, assigneeId, requesterId);
|
|
|
|
return Ok(new TicketListResponse
|
|
{
|
|
Items = tickets.Select(MapToDto).ToList(),
|
|
TotalCount = totalCount,
|
|
Page = page,
|
|
PageSize = pageSize
|
|
});
|
|
}
|
|
|
|
[HttpGet("stats")]
|
|
public async Task<ActionResult<TicketStatsDto>> GetStats()
|
|
{
|
|
return Ok(new TicketStatsDto
|
|
{
|
|
TotalTickets = await _ticketRepository.GetTotalCountAsync(),
|
|
OpenTickets = await _ticketRepository.GetCountByStatusAsync(TicketStatus.Open),
|
|
InProgressTickets = await _ticketRepository.GetCountByStatusAsync(TicketStatus.InProgress),
|
|
PendingTickets = await _ticketRepository.GetCountByStatusAsync(TicketStatus.Pending),
|
|
ResolvedTickets = await _ticketRepository.GetCountByStatusAsync(TicketStatus.Resolved),
|
|
ClosedTickets = await _ticketRepository.GetCountByStatusAsync(TicketStatus.Closed)
|
|
});
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<ActionResult<TicketDto>> GetTicket(Guid id)
|
|
{
|
|
var ticket = await _ticketRepository.GetByIdAsync(id);
|
|
if (ticket == null) return NotFound(new { error = "Ticket not found." });
|
|
|
|
var userId = GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var user = await _userRepository.GetByIdAsync(userId.Value);
|
|
if (user?.Role == UserRole.Customer && ticket.RequesterId != userId.Value)
|
|
return Forbid();
|
|
|
|
return Ok(MapToDto(ticket));
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<TicketDto>> CreateTicket([FromBody] CreateTicketRequest request)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.Title))
|
|
return BadRequest(new { error = "Title is required." });
|
|
if (string.IsNullOrWhiteSpace(request.Description))
|
|
return BadRequest(new { error = "Description is required." });
|
|
|
|
var userId = GetUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var ticket = new Ticket
|
|
{
|
|
Title = request.Title,
|
|
Description = request.Description,
|
|
Priority = request.Priority,
|
|
RequesterId = userId.Value,
|
|
GroupId = request.GroupId,
|
|
SlaPolicyId = request.SlaPolicyId,
|
|
TicketNumber = $"TK-{DateTime.UtcNow:yyyyMMddHHmmss}",
|
|
Status = TicketStatus.Open,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
var created = await _ticketRepository.CreateAsync(ticket);
|
|
var full = await _ticketRepository.GetByIdAsync(created.Id);
|
|
|
|
return CreatedAtAction(nameof(GetTicket), new { id = created.Id }, MapToDto(full!));
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
public async Task<ActionResult<TicketDto>> UpdateTicket(Guid id, [FromBody] UpdateTicketRequest request)
|
|
{
|
|
var ticket = await _ticketRepository.GetByIdAsync(id);
|
|
if (ticket == null) return NotFound(new { error = "Ticket not found." });
|
|
|
|
if (!string.IsNullOrWhiteSpace(request.Title)) ticket.Title = request.Title;
|
|
if (!string.IsNullOrWhiteSpace(request.Description)) ticket.Description = request.Description;
|
|
if (request.Priority.HasValue) ticket.Priority = request.Priority.Value;
|
|
|
|
var updated = await _ticketRepository.UpdateAsync(ticket);
|
|
if (updated == null) return StatusCode(500, new { error = "Failed to update ticket." });
|
|
|
|
var full = await _ticketRepository.GetByIdAsync(id);
|
|
return Ok(MapToDto(full!));
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
[Authorize(Policy = "Admin")]
|
|
public async Task<IActionResult> DeleteTicket(Guid id)
|
|
{
|
|
var deleted = await _ticketRepository.DeleteAsync(id);
|
|
if (!deleted) return NotFound(new { error = "Ticket not found." });
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPut("{id}/status")]
|
|
[Authorize(Policy = "Agent")]
|
|
public async Task<IActionResult> UpdateStatus(Guid id, [FromBody] UpdateStatusRequest request)
|
|
{
|
|
var ticket = await _ticketRepository.GetByIdAsync(id);
|
|
if (ticket == null) return NotFound(new { error = "Ticket not found." });
|
|
|
|
ticket.Status = request.Status;
|
|
await _ticketRepository.UpdateAsync(ticket);
|
|
|
|
return Ok(new { message = "Status updated.", status = request.Status.ToString() });
|
|
}
|
|
|
|
[HttpPut("{id}/assign")]
|
|
[Authorize(Policy = "Agent")]
|
|
public async Task<IActionResult> AssignTicket(Guid id, [FromBody] AssignTicketRequest request)
|
|
{
|
|
var ticket = await _ticketRepository.GetByIdAsync(id);
|
|
if (ticket == null) return NotFound(new { error = "Ticket not found." });
|
|
|
|
if (request.AssigneeId.HasValue)
|
|
{
|
|
var assignee = await _userRepository.GetByIdAsync(request.AssigneeId.Value);
|
|
if (assignee == null) return BadRequest(new { error = "Assignee not found." });
|
|
if (assignee.Role != UserRole.Agent && assignee.Role != UserRole.Admin)
|
|
return BadRequest(new { error = "Assignee must be an agent or admin." });
|
|
}
|
|
|
|
ticket.AssigneeId = request.AssigneeId;
|
|
if (request.AssigneeId.HasValue && ticket.Status == TicketStatus.Open)
|
|
ticket.Status = TicketStatus.InProgress;
|
|
|
|
await _ticketRepository.UpdateAsync(ticket);
|
|
return Ok(new { message = "Ticket assigned.", assigneeId = request.AssigneeId });
|
|
}
|
|
|
|
private Guid? GetUserId()
|
|
{
|
|
var claim = User.FindFirst("userId") ?? User.FindFirst(ClaimTypes.NameIdentifier);
|
|
return claim != null && Guid.TryParse(claim.Value, out var id) ? id : null;
|
|
}
|
|
|
|
private static TicketDto MapToDto(Ticket t) => new()
|
|
{
|
|
Id = t.Id,
|
|
Title = t.Title,
|
|
Description = t.Description,
|
|
Status = t.Status,
|
|
Priority = t.Priority,
|
|
TicketNumber = t.TicketNumber,
|
|
RequesterId = t.RequesterId,
|
|
RequesterName = t.Requester != null ? $"{t.Requester.FirstName} {t.Requester.LastName}" : "Unknown",
|
|
AssigneeId = t.AssigneeId,
|
|
AssigneeName = t.Assignee != null ? $"{t.Assignee.FirstName} {t.Assignee.LastName}" : null,
|
|
GroupId = t.GroupId,
|
|
GroupName = t.Group?.Name,
|
|
SlaPolicyId = t.SlaPolicyId,
|
|
SlaPolicyName = t.SlaPolicy?.Name,
|
|
CreatedAt = t.CreatedAt,
|
|
UpdatedAt = t.UpdatedAt,
|
|
ResolvedAt = t.ResolvedAt,
|
|
CommentCount = t.Comments?.Count ?? 0
|
|
};
|
|
}
|