Initial commit: HelpDesk ICHN - .NET 10 backend + Next.js 16 frontend
This commit is contained in:
commit
359f0caf85
90 files changed
+13395
No files matched your search
@@ -0,0 +1,204 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user