Initial commit: HelpDesk ICHN - .NET 10 backend + Next.js 16 frontend

This commit is contained in:
Nurfog committed 2026-09-10 10:20:51 -03:00
commit 359f0caf85
90 files changed
+13395

No files matched your search

@@ -0,0 +1,22 @@
using HelpDesk.Models.Entities;
namespace HelpDesk.Data.Repositories;
public interface IKnowledgeBaseRepository
{
// Articles
Task<KnowledgeBaseArticle?> GetArticleByIdAsync(Guid id);
Task<IEnumerable<KnowledgeBaseArticle>> GetAllArticlesAsync();
Task<IEnumerable<KnowledgeBaseArticle>> GetPublishedArticlesAsync();
Task<IEnumerable<KnowledgeBaseArticle>> SearchArticlesAsync(string query);
Task<KnowledgeBaseArticle> CreateArticleAsync(KnowledgeBaseArticle article);
Task<KnowledgeBaseArticle?> UpdateArticleAsync(KnowledgeBaseArticle article);
Task<bool> DeleteArticleAsync(Guid id);
// Categories
Task<KnowledgeBaseCategory?> GetCategoryByIdAsync(Guid id);
Task<IEnumerable<KnowledgeBaseCategory>> GetAllCategoriesAsync();
Task<KnowledgeBaseCategory> CreateCategoryAsync(KnowledgeBaseCategory category);
Task<KnowledgeBaseCategory?> UpdateCategoryAsync(KnowledgeBaseCategory category);
Task<bool> DeleteCategoryAsync(Guid id);
}
@@ -0,0 +1,11 @@
namespace HelpDesk.Data.Repositories;
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(Guid id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
Task<int> SaveChangesAsync();
}
@@ -0,0 +1,19 @@
using HelpDesk.Models.Entities;
using HelpDesk.Models.Enums;
namespace HelpDesk.Data.Repositories;
public interface ITicketRepository
{
Task<Ticket?> GetByIdAsync(Guid id);
Task<IEnumerable<Ticket>> GetAllAsync(TicketStatus? status = null, TicketPriority? priority = null, Guid? assigneeId = null, Guid? requesterId = null, int page = 1, int pageSize = 20);
Task<int> GetCountAsync(TicketStatus? status = null, TicketPriority? priority = null, Guid? assigneeId = null, Guid? requesterId = null);
Task<Ticket> CreateAsync(Ticket ticket);
Task<Ticket?> UpdateAsync(Ticket ticket);
Task<bool> DeleteAsync(Guid id);
Task<int> GetTotalCountAsync();
Task<int> GetCountByStatusAsync(TicketStatus status);
Task<int> GetCountByPriorityAsync(TicketPriority priority);
Task<double> GetAverageResponseTimeAsync();
Task<double> GetAverageResolutionTimeAsync();
}
@@ -0,0 +1,15 @@
using HelpDesk.Models.Entities;
using HelpDesk.Models.Enums;
namespace HelpDesk.Data.Repositories;
public interface IUserRepository
{
Task<User?> GetByIdAsync(Guid id);
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetAllAsync();
Task<IEnumerable<User>> GetByRoleAsync(UserRole role);
Task<User> CreateAsync(User user);
Task<User?> UpdateAsync(User user);
Task<bool> ExistsAsync(string email);
}
@@ -0,0 +1,120 @@
using HelpDesk.Models.Entities;
using Microsoft.EntityFrameworkCore;
namespace HelpDesk.Data.Repositories;
public class KnowledgeBaseRepository : IKnowledgeBaseRepository
{
private readonly HelpDeskDbContext _context;
public KnowledgeBaseRepository(HelpDeskDbContext context)
{
_context = context;
}
// Articles
public async Task<KnowledgeBaseArticle?> GetArticleByIdAsync(Guid id)
=> await _context.KnowledgeBaseArticles
.Include(a => a.Category)
.Include(a => a.Author)
.FirstOrDefaultAsync(a => a.Id == id);
public async Task<IEnumerable<KnowledgeBaseArticle>> GetAllArticlesAsync()
=> await _context.KnowledgeBaseArticles
.Include(a => a.Category)
.Include(a => a.Author)
.OrderByDescending(a => a.CreatedAt)
.ToListAsync();
public async Task<IEnumerable<KnowledgeBaseArticle>> GetPublishedArticlesAsync()
=> await _context.KnowledgeBaseArticles
.Include(a => a.Category)
.Include(a => a.Author)
.Where(a => a.IsPublished)
.OrderByDescending(a => a.CreatedAt)
.ToListAsync();
public async Task<IEnumerable<KnowledgeBaseArticle>> SearchArticlesAsync(string query)
{
var lowerQuery = query.ToLower();
return await _context.KnowledgeBaseArticles
.Include(a => a.Category)
.Include(a => a.Author)
.Where(a => a.IsPublished && (a.Title.ToLower().Contains(lowerQuery) || a.Content.ToLower().Contains(lowerQuery)))
.OrderByDescending(a => a.ViewCount)
.ToListAsync();
}
public async Task<KnowledgeBaseArticle> CreateArticleAsync(KnowledgeBaseArticle article)
{
_context.KnowledgeBaseArticles.Add(article);
await _context.SaveChangesAsync();
return article;
}
public async Task<KnowledgeBaseArticle?> UpdateArticleAsync(KnowledgeBaseArticle article)
{
var existing = await _context.KnowledgeBaseArticles.FindAsync(article.Id);
if (existing == null) return null;
existing.Title = article.Title;
existing.Content = article.Content;
existing.CategoryId = article.CategoryId;
existing.IsPublished = article.IsPublished;
existing.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> DeleteArticleAsync(Guid id)
{
var article = await _context.KnowledgeBaseArticles.FindAsync(id);
if (article == null) return false;
_context.KnowledgeBaseArticles.Remove(article);
await _context.SaveChangesAsync();
return true;
}
// Categories
public async Task<KnowledgeBaseCategory?> GetCategoryByIdAsync(Guid id)
=> await _context.KnowledgeBaseCategories
.Include(c => c.Articles)
.FirstOrDefaultAsync(c => c.Id == id);
public async Task<IEnumerable<KnowledgeBaseCategory>> GetAllCategoriesAsync()
=> await _context.KnowledgeBaseCategories
.Include(c => c.Articles)
.OrderBy(c => c.SortOrder)
.ThenBy(c => c.Name)
.ToListAsync();
public async Task<KnowledgeBaseCategory> CreateCategoryAsync(KnowledgeBaseCategory category)
{
_context.KnowledgeBaseCategories.Add(category);
await _context.SaveChangesAsync();
return category;
}
public async Task<KnowledgeBaseCategory?> UpdateCategoryAsync(KnowledgeBaseCategory category)
{
var existing = await _context.KnowledgeBaseCategories.FindAsync(category.Id);
if (existing == null) return null;
existing.Name = category.Name;
existing.Description = category.Description;
existing.SortOrder = category.SortOrder;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> DeleteCategoryAsync(Guid id)
{
var category = await _context.KnowledgeBaseCategories.FindAsync(id);
if (category == null) return false;
_context.KnowledgeBaseCategories.Remove(category);
await _context.SaveChangesAsync();
return true;
}
}
@@ -0,0 +1,29 @@
using HelpDesk.Models.Entities;
namespace HelpDesk.Data.Repositories;
public interface ITicketCommentRepository
{
Task<TicketComment?> GetByIdAsync(Guid id);
Task<IEnumerable<TicketComment>> GetByTicketIdAsync(Guid ticketId);
Task<TicketComment> CreateAsync(TicketComment comment);
Task<bool> DeleteAsync(Guid id);
}
public interface IGroupRepository
{
Task<Group?> GetByIdAsync(Guid id);
Task<IEnumerable<Group>> GetAllAsync();
Task<Group> CreateAsync(Group group);
Task<Group?> UpdateAsync(Group group);
Task<bool> DeleteAsync(Guid id);
}
public interface ISLARepository
{
Task<SLAPolicy?> GetByIdAsync(Guid id);
Task<IEnumerable<SLAPolicy>> GetAllAsync();
Task<SLAPolicy> CreateAsync(SLAPolicy policy);
Task<SLAPolicy?> UpdateAsync(SLAPolicy policy);
Task<bool> DeleteAsync(Guid id);
}
@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore;
namespace HelpDesk.Data.Repositories;
public class Repository<T> : IRepository<T> where T : class
{
protected readonly HelpDeskDbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(HelpDeskDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public virtual async Task<T?> GetByIdAsync(Guid id)
{
return await _dbSet.FindAsync(id);
}
public virtual async Task<IEnumerable<T>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
public virtual async Task<T> AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
await SaveChangesAsync();
return entity;
}
public virtual async Task UpdateAsync(T entity)
{
_dbSet.Update(entity);
await SaveChangesAsync();
}
public virtual async Task DeleteAsync(T entity)
{
_dbSet.Remove(entity);
await SaveChangesAsync();
}
public virtual async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
}
@@ -0,0 +1,132 @@
using HelpDesk.Models.Entities;
using Microsoft.EntityFrameworkCore;
namespace HelpDesk.Data.Repositories;
public class TicketCommentRepository : ITicketCommentRepository
{
private readonly HelpDeskDbContext _context;
public TicketCommentRepository(HelpDeskDbContext context)
{
_context = context;
}
public async Task<TicketComment?> GetByIdAsync(Guid id)
=> await _context.TicketComments.FindAsync(id);
public async Task<IEnumerable<TicketComment>> GetByTicketIdAsync(Guid ticketId)
=> await _context.TicketComments
.Include(c => c.User)
.Where(c => c.TicketId == ticketId)
.OrderBy(c => c.CreatedAt)
.ToListAsync();
public async Task<TicketComment> CreateAsync(TicketComment comment)
{
_context.TicketComments.Add(comment);
await _context.SaveChangesAsync();
return comment;
}
public async Task<bool> DeleteAsync(Guid id)
{
var comment = await _context.TicketComments.FindAsync(id);
if (comment == null) return false;
_context.TicketComments.Remove(comment);
await _context.SaveChangesAsync();
return true;
}
}
public class GroupRepository : IGroupRepository
{
private readonly HelpDeskDbContext _context;
public GroupRepository(HelpDeskDbContext context)
{
_context = context;
}
public async Task<Group?> GetByIdAsync(Guid id)
=> await _context.Groups.FindAsync(id);
public async Task<IEnumerable<Group>> GetAllAsync()
=> await _context.Groups.OrderBy(g => g.Name).ToListAsync();
public async Task<Group> CreateAsync(Group group)
{
_context.Groups.Add(group);
await _context.SaveChangesAsync();
return group;
}
public async Task<Group?> UpdateAsync(Group group)
{
var existing = await _context.Groups.FindAsync(group.Id);
if (existing == null) return null;
existing.Name = group.Name;
existing.Description = group.Description;
existing.IsActive = group.IsActive;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> DeleteAsync(Guid id)
{
var group = await _context.Groups.FindAsync(id);
if (group == null) return false;
_context.Groups.Remove(group);
await _context.SaveChangesAsync();
return true;
}
}
public class SLARepository : ISLARepository
{
private readonly HelpDeskDbContext _context;
public SLARepository(HelpDeskDbContext context)
{
_context = context;
}
public async Task<SLAPolicy?> GetByIdAsync(Guid id)
=> await _context.SLAPolicies.FindAsync(id);
public async Task<IEnumerable<SLAPolicy>> GetAllAsync()
=> await _context.SLAPolicies.OrderBy(s => s.Name).ToListAsync();
public async Task<SLAPolicy> CreateAsync(SLAPolicy policy)
{
_context.SLAPolicies.Add(policy);
await _context.SaveChangesAsync();
return policy;
}
public async Task<SLAPolicy?> UpdateAsync(SLAPolicy policy)
{
var existing = await _context.SLAPolicies.FindAsync(policy.Id);
if (existing == null) return null;
existing.Name = policy.Name;
existing.Description = policy.Description;
existing.ResponseTimeHours = policy.ResponseTimeHours;
existing.ResolutionTimeHours = policy.ResolutionTimeHours;
existing.IsActive = policy.IsActive;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> DeleteAsync(Guid id)
{
var policy = await _context.SLAPolicies.FindAsync(id);
if (policy == null) return false;
_context.SLAPolicies.Remove(policy);
await _context.SaveChangesAsync();
return true;
}
}
@@ -0,0 +1,128 @@
using HelpDesk.Models.Entities;
using HelpDesk.Models.Enums;
using Microsoft.EntityFrameworkCore;
namespace HelpDesk.Data.Repositories;
public class TicketRepository : ITicketRepository
{
private readonly HelpDeskDbContext _context;
public TicketRepository(HelpDeskDbContext context)
{
_context = context;
}
private IQueryable<Ticket> GetQueryable()
=> _context.Tickets
.Include(t => t.Requester)
.Include(t => t.Assignee)
.Include(t => t.Group)
.Include(t => t.SlaPolicy)
.Include(t => t.Comments);
public async Task<Ticket?> GetByIdAsync(Guid id)
=> await GetQueryable().FirstOrDefaultAsync(t => t.Id == id);
public async Task<IEnumerable<Ticket>> GetAllAsync(TicketStatus? status, TicketPriority? priority, Guid? assigneeId, Guid? requesterId, int page = 1, int pageSize = 20)
{
var query = GetQueryable().AsQueryable();
if (status.HasValue) query = query.Where(t => t.Status == status.Value);
if (priority.HasValue) query = query.Where(t => t.Priority == priority.Value);
if (assigneeId.HasValue) query = query.Where(t => t.AssigneeId == assigneeId.Value);
if (requesterId.HasValue) query = query.Where(t => t.RequesterId == requesterId.Value);
return await query.OrderByDescending(t => t.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<int> GetCountAsync(TicketStatus? status, TicketPriority? priority, Guid? assigneeId, Guid? requesterId)
{
var query = _context.Tickets.AsQueryable();
if (status.HasValue) query = query.Where(t => t.Status == status.Value);
if (priority.HasValue) query = query.Where(t => t.Priority == priority.Value);
if (assigneeId.HasValue) query = query.Where(t => t.AssigneeId == assigneeId.Value);
if (requesterId.HasValue) query = query.Where(t => t.RequesterId == requesterId.Value);
return await query.CountAsync();
}
public async Task<Ticket> CreateAsync(Ticket ticket)
{
_context.Tickets.Add(ticket);
await _context.SaveChangesAsync();
return ticket;
}
public async Task<Ticket?> UpdateAsync(Ticket ticket)
{
var existing = await _context.Tickets.FindAsync(ticket.Id);
if (existing == null) return null;
existing.Title = ticket.Title;
existing.Description = ticket.Description;
existing.Priority = ticket.Priority;
existing.Status = ticket.Status;
existing.AssigneeId = ticket.AssigneeId;
existing.GroupId = ticket.GroupId;
existing.SlaPolicyId = ticket.SlaPolicyId;
existing.UpdatedAt = DateTime.UtcNow;
if (ticket.Status == TicketStatus.Resolved && existing.ResolvedAt == null)
existing.ResolvedAt = DateTime.UtcNow;
if (ticket.Status == TicketStatus.Closed && existing.ClosedAt == null)
existing.ClosedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> DeleteAsync(Guid id)
{
var ticket = await _context.Tickets.FindAsync(id);
if (ticket == null) return false;
_context.Tickets.Remove(ticket);
await _context.SaveChangesAsync();
return true;
}
public async Task<int> GetTotalCountAsync()
=> await _context.Tickets.CountAsync();
public async Task<int> GetCountByStatusAsync(TicketStatus status)
=> await _context.Tickets.CountAsync(t => t.Status == status);
public async Task<int> GetCountByPriorityAsync(TicketPriority priority)
=> await _context.Tickets.CountAsync(t => t.Priority == priority);
public async Task<double> GetAverageResponseTimeAsync()
{
var tickets = await _context.Tickets
.Where(t => t.Comments.Any())
.Select(t => new
{
t.CreatedAt,
FirstCommentAt = t.Comments.OrderBy(c => c.CreatedAt).Select(c => c.CreatedAt).FirstOrDefault()
})
.Where(t => t.FirstCommentAt != default(DateTime))
.ToListAsync();
if (!tickets.Any()) return 0;
return tickets.Average(t => (t.FirstCommentAt - t.CreatedAt).TotalMinutes);
}
public async Task<double> GetAverageResolutionTimeAsync()
{
var tickets = await _context.Tickets
.Where(t => t.ResolvedAt.HasValue)
.Select(t => new { t.CreatedAt, t.ResolvedAt })
.ToListAsync();
if (!tickets.Any()) return 0;
return tickets.Average(t => (t.ResolvedAt!.Value - t.CreatedAt).TotalMinutes);
}
}
@@ -0,0 +1,55 @@
using HelpDesk.Models.Entities;
using HelpDesk.Models.Enums;
using Microsoft.EntityFrameworkCore;
namespace HelpDesk.Data.Repositories;
public class UserRepository : IUserRepository
{
private readonly HelpDeskDbContext _context;
public UserRepository(HelpDeskDbContext context)
{
_context = context;
}
public async Task<User?> GetByIdAsync(Guid id)
=> await _context.Users.FirstOrDefaultAsync(u => u.Id == id);
public async Task<User?> GetByEmailAsync(string email)
=> await _context.Users.FirstOrDefaultAsync(u => u.Email == email);
public async Task<IEnumerable<User>> GetAllAsync()
=> await _context.Users.OrderBy(u => u.LastName).ToListAsync();
public async Task<IEnumerable<User>> GetByRoleAsync(UserRole role)
=> await _context.Users.Where(u => u.Role == role && u.IsActive).OrderBy(u => u.LastName).ToListAsync();
public async Task<User> CreateAsync(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user;
}
public async Task<User?> UpdateAsync(User user)
{
var existing = await _context.Users.FindAsync(user.Id);
if (existing == null) return null;
existing.FirstName = user.FirstName;
existing.LastName = user.LastName;
existing.Email = user.Email;
existing.PhoneNumber = user.PhoneNumber;
existing.AvatarUrl = user.AvatarUrl;
existing.Role = user.Role;
existing.IsActive = user.IsActive;
existing.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return existing;
}
public async Task<bool> ExistsAsync(string email)
=> await _context.Users.AnyAsync(u => u.Email == email);
}