diff --git a/EventPlatform.Application/Contracts/Dtos/AdminDashboardSummaryDto.cs b/EventPlatform.Application/Contracts/Dtos/AdminDashboardSummaryDto.cs new file mode 100644 index 0000000..a8fc6d0 --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/AdminDashboardSummaryDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class AdminDashboardSummaryDto + { + public int TotalEvents { get; set; } + public int PendingEvents { get; set; } + public int TotalUsers { get; set; } + public int TotalTransactions { get; set; } + public decimal TotalRevenue { get; set; } + } +} diff --git a/EventPlatform.Application/Contracts/Dtos/AdminEventDto.cs b/EventPlatform.Application/Contracts/Dtos/AdminEventDto.cs new file mode 100644 index 0000000..191ffac --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/AdminEventDto.cs @@ -0,0 +1,19 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class AdminEventDto + { + public int EventId { get; set; } + public string Title { get; set; } = string.Empty; + public string EventType { get; set; } = string.Empty; + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public string EventStatus { get; set; } = string.Empty; + public string? Location { get; set; } + public string? OnlineUrl { get; set; } + public DateTime? CreatedAt { get; set; } + public string SubmittedByName { get; set; } = string.Empty; + public string SubmittedByEmail { get; set; } = string.Empty; + } +} diff --git a/EventPlatform.Application/Contracts/Dtos/AdminTransactionDto.cs b/EventPlatform.Application/Contracts/Dtos/AdminTransactionDto.cs new file mode 100644 index 0000000..0f5e28d --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/AdminTransactionDto.cs @@ -0,0 +1,17 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class AdminTransactionDto + { + public int TransactionId { get; set; } + public decimal Amount { get; set; } + public string PaymentStatus { get; set; } = string.Empty; + public string? PaymentGateway { get; set; } + public string? GatewayTransactionId { get; set; } + public DateTime? TransactionDate { get; set; } + public string BuyerName { get; set; } = string.Empty; + public string BuyerEmail { get; set; } = string.Empty; + public string EventTitle { get; set; } = string.Empty; + } +} diff --git a/EventPlatform.Application/Contracts/Dtos/AdminUserDto.cs b/EventPlatform.Application/Contracts/Dtos/AdminUserDto.cs new file mode 100644 index 0000000..8d30a0d --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/AdminUserDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class AdminUserDto + { + public Guid UserId { get; set; } + public string FullName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Role { get; set; } = string.Empty; + public string AccountStatus { get; set; } = string.Empty; + public DateTime? CreatedAt { get; set; } + public int TotalRegistrations { get; set; } + } +} diff --git a/EventPlatform.Application/Contracts/Dtos/JoinMeetingInfoDto.cs b/EventPlatform.Application/Contracts/Dtos/JoinMeetingInfoDto.cs new file mode 100644 index 0000000..db68f6f --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/JoinMeetingInfoDto.cs @@ -0,0 +1,18 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class JoinMeetingInfoDto + { + public Guid RegistrationId { get; set; } + public Guid UserId { get; set; } + public int EventId { get; set; } + public string Title { get; set; } = string.Empty; + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public string RoomId { get; set; } = string.Empty; + public string Role { get; set; } + public string UserName { get; set; } = string.Empty; + public string UserIdentifier { get; set; } = string.Empty; + } +} diff --git a/EventPlatform.Application/Contracts/Dtos/UserRegistrationDto.cs b/EventPlatform.Application/Contracts/Dtos/UserRegistrationDto.cs new file mode 100644 index 0000000..c910333 --- /dev/null +++ b/EventPlatform.Application/Contracts/Dtos/UserRegistrationDto.cs @@ -0,0 +1,25 @@ +using System; + +namespace EventPlatform.Application.Contracts.Dtos +{ + public class UserRegistrationDto + { + public Guid RegistrationId { get; set; } + public Guid UserId { get; set; } + public int EventId { get; set; } + public string Title { get; set; } = string.Empty; + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public string EventType { get; set; } = string.Empty; + public string? Location { get; set; } + public string? OnlineUrl { get; set; } + public string EventStatus { get; set; } = string.Empty; + public bool IsOnline { get; set; } + public int TicketTypeId { get; set; } + public string TicketTypeName { get; set; } = string.Empty; + public decimal TicketPrice { get; set; } + public DateTime? RegistrationDate { get; set; } + public string UniqueToken { get; set; } = string.Empty; + public DateTime? CheckInTime { get; set; } + } +} diff --git a/EventPlatform.Application/Contracts/Requests/Admin/RejectEventRequest.cs b/EventPlatform.Application/Contracts/Requests/Admin/RejectEventRequest.cs new file mode 100644 index 0000000..cbba050 --- /dev/null +++ b/EventPlatform.Application/Contracts/Requests/Admin/RejectEventRequest.cs @@ -0,0 +1,7 @@ +namespace EventPlatform.Application.Contracts.Requests.Admin +{ + public class RejectEventRequest + { + public string? Reason { get; set; } + } +} diff --git a/EventPlatform.Application/Services/Interfaces/Admin/IAdminService.cs b/EventPlatform.Application/Services/Interfaces/Admin/IAdminService.cs new file mode 100644 index 0000000..89528c4 --- /dev/null +++ b/EventPlatform.Application/Services/Interfaces/Admin/IAdminService.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; +using EventPlatform.Application.Contracts.Dtos; + +namespace EventPlatform.Application.Services.Interfaces.Admin +{ + public interface IAdminService + { + Task GetDashboardSummaryAsync(); + Task> GetEventsAsync(string? status, string? keyword, int pageNumber, int pageSize); + Task UpdateEventStatusAsync(int eventId, string newStatus); + Task> GetTransactionsAsync(string? status, int pageNumber, int pageSize); + Task> GetUsersAsync(string? role, string? accountStatus, int pageNumber, int pageSize); + Task UpdateUserStatusAsync(Guid userId, string newStatus); + } +} diff --git a/EventPlatform.Application/Services/Interfaces/Event/IRegistrationService.cs b/EventPlatform.Application/Services/Interfaces/Event/IRegistrationService.cs index 5d17500..52ab028 100644 --- a/EventPlatform.Application/Services/Interfaces/Event/IRegistrationService.cs +++ b/EventPlatform.Application/Services/Interfaces/Event/IRegistrationService.cs @@ -1,4 +1,5 @@ -using EventPlatform.Application.Contracts.Requests; +using EventPlatform.Application.Contracts.Dtos; +using EventPlatform.Application.Contracts.Requests; using EventPlatform.Application.Contracts.Responses; using System; using System.Collections.Generic; @@ -11,6 +12,8 @@ namespace EventPlatform.Application.Services.Interfaces.Event public interface IRegistrationService { Task CreateBookingAsync(CreateBookingRequest request); + Task> GetMyRegistrationsAsync(); + Task GetJoinMeetingInfoAsync(Guid registrationId); Task CancelBookingAsync(CancelBookingRequest request); } } diff --git a/EventPlatform.Infrastructure/Services/Admin/AdminService.cs b/EventPlatform.Infrastructure/Services/Admin/AdminService.cs new file mode 100644 index 0000000..2d57ed6 --- /dev/null +++ b/EventPlatform.Infrastructure/Services/Admin/AdminService.cs @@ -0,0 +1,219 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EventPlatform.Application.Contracts.Dtos; +using EventPlatform.Application.Services.Interfaces.Admin; +using EventPlatform.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace EventPlatform.Infrastructure.Services.Admin +{ + public class AdminService : IAdminService + { + private readonly ApplicationDbContext _context; + + public AdminService(ApplicationDbContext context) + { + _context = context; + } + + public async Task GetDashboardSummaryAsync() + { + var totalEvents = await _context.Events.CountAsync(); + var pendingEvents = await _context.Events.CountAsync(e => e.EventStatus == "Pending"); + var totalUsers = await _context.Users.CountAsync(); + var totalTransactions = await _context.Transactions.CountAsync(); + var totalRevenue = await _context.Transactions + .Where(t => t.PaymentStatus == "Success" || t.PaymentStatus == "Completed") + .SumAsync(t => (decimal?)t.Amount) ?? 0m; + + return new AdminDashboardSummaryDto + { + TotalEvents = totalEvents, + PendingEvents = pendingEvents, + TotalUsers = totalUsers, + TotalTransactions = totalTransactions, + TotalRevenue = totalRevenue + }; + } + + public async Task> GetEventsAsync(string? status, string? keyword, int pageNumber, int pageSize) + { + pageNumber = Math.Max(1, pageNumber); + pageSize = Math.Clamp(pageSize, 1, 100); + + var query = _context.Events + .AsNoTracking() + .Include(e => e.CreatedByUser) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(status)) + { + query = query.Where(e => e.EventStatus == status); + } + + if (!string.IsNullOrWhiteSpace(keyword)) + { + keyword = keyword.Trim(); + query = query.Where(e => e.Title.Contains(keyword) || e.Description.Contains(keyword)); + } + + var totalCount = await query.CountAsync(); + var items = await query + .OrderByDescending(e => e.CreatedAt ?? e.StartTime) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(e => new AdminEventDto + { + EventId = e.EventId, + Title = e.Title, + EventType = e.EventType, + StartTime = e.StartTime, + EndTime = e.EndTime, + EventStatus = e.EventStatus, + Location = e.Location, + OnlineUrl = e.OnlineUrl, + CreatedAt = e.CreatedAt, + SubmittedByName = e.CreatedByUser.FullName, + SubmittedByEmail = e.CreatedByUser.Email + }) + .ToListAsync(); + + return new PaginatedResult + { + Items = items, + PageNumber = pageNumber, + PageSize = pageSize, + TotalCount = totalCount + }; + } + + public async Task UpdateEventStatusAsync(int eventId, string newStatus) + { + var eventEntity = await _context.Events.FindAsync(eventId); + if (eventEntity == null) + { + return false; + } + + if (!string.Equals(eventEntity.EventStatus, newStatus, StringComparison.OrdinalIgnoreCase)) + { + eventEntity.EventStatus = newStatus; + eventEntity.UpdatedAt = DateTime.UtcNow; + _context.Events.Update(eventEntity); + await _context.SaveChangesAsync(); + } + + return true; + } + + public async Task> GetTransactionsAsync(string? status, int pageNumber, int pageSize) + { + pageNumber = Math.Max(1, pageNumber); + pageSize = Math.Clamp(pageSize, 1, 100); + + var query = _context.Transactions + .AsNoTracking() + .Include(t => t.Registration) + .ThenInclude(r => r.User) + .Include(t => t.Registration) + .ThenInclude(r => r.TicketType) + .ThenInclude(tt => tt.Event) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(status)) + { + query = query.Where(t => t.PaymentStatus == status); + } + + var totalCount = await query.CountAsync(); + var items = await query + .OrderByDescending(t => t.TransactionDate ?? DateTime.MinValue) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(t => new AdminTransactionDto + { + TransactionId = t.TransactionId, + Amount = t.Amount, + PaymentStatus = t.PaymentStatus, + PaymentGateway = t.PaymentGateway, + GatewayTransactionId = t.GatewayTransactionId, + TransactionDate = t.TransactionDate, + BuyerName = t.Registration.User.FullName, + BuyerEmail = t.Registration.User.Email, + EventTitle = t.Registration.TicketType.Event.Title + }) + .ToListAsync(); + + return new PaginatedResult + { + Items = items, + PageNumber = pageNumber, + PageSize = pageSize, + TotalCount = totalCount + }; + } + + public async Task> GetUsersAsync(string? role, string? accountStatus, int pageNumber, int pageSize) + { + pageNumber = Math.Max(1, pageNumber); + pageSize = Math.Clamp(pageSize, 1, 100); + + var query = _context.Users.AsNoTracking().AsQueryable(); + + if (!string.IsNullOrWhiteSpace(role)) + { + query = query.Where(u => u.Role == role); + } + + if (!string.IsNullOrWhiteSpace(accountStatus)) + { + query = query.Where(u => u.AccountStatus == accountStatus); + } + + var totalCount = await query.CountAsync(); + var items = await query + .OrderByDescending(u => u.CreatedAt) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(u => new AdminUserDto + { + UserId = u.UserId, + FullName = u.FullName, + Email = u.Email, + Role = u.Role, + AccountStatus = u.AccountStatus, + CreatedAt = u.CreatedAt, + TotalRegistrations = u.Registrations.Count + }) + .ToListAsync(); + + return new PaginatedResult + { + Items = items, + PageNumber = pageNumber, + PageSize = pageSize, + TotalCount = totalCount + }; + } + + public async Task UpdateUserStatusAsync(Guid userId, string newStatus) + { + var user = await _context.Users.FindAsync(userId); + if (user == null) + { + return false; + } + + if (!string.Equals(user.AccountStatus, newStatus, StringComparison.OrdinalIgnoreCase)) + { + user.AccountStatus = newStatus; + user.UpdatedAt = DateTime.UtcNow; + _context.Users.Update(user); + await _context.SaveChangesAsync(); + } + + return true; + } + } +} diff --git a/EventPlatform.Infrastructure/Services/Event/RegistrationService.cs b/EventPlatform.Infrastructure/Services/Event/RegistrationService.cs index e5c08fc..a3465a6 100644 --- a/EventPlatform.Infrastructure/Services/Event/RegistrationService.cs +++ b/EventPlatform.Infrastructure/Services/Event/RegistrationService.cs @@ -1,4 +1,5 @@ -using EventPlatform.Application.Contracts.Requests; +using EventPlatform.Application.Contracts.Dtos; +using EventPlatform.Application.Contracts.Requests; using EventPlatform.Application.Contracts.Responses; using EventPlatform.Application.Services.Interfaces; using EventPlatform.Application.Services.Interfaces.Event; @@ -10,12 +11,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace EventPlatform.Infrastructure.Services.Event { public class RegistrationService : IRegistrationService { - private readonly ApplicationDbContext _context; + private readonly ApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; public RegistrationService(ApplicationDbContext context, ICurrentUserService currentUserService) @@ -72,7 +74,7 @@ public async Task CreateBookingAsync(CreateBookingRequest reque UserId = userId.Value, TicketTypeId = ticketType.TicketTypeId, RegistrationDate = DateTime.UtcNow, - UniqueToken = Guid.NewGuid().ToString("N").Substring(0, 12).ToUpper(), + UniqueToken = Guid.NewGuid().ToString("N").Substring(0, 12).ToUpper(), CheckInTime = null }); } @@ -159,5 +161,87 @@ public async Task CancelBookingAsync(CancelBookingRequest request) } } } + + public async Task> GetMyRegistrationsAsync() + { + var userId = _currentUserService.GetCurrentUserId(); + if (userId == null) + { + throw new UnauthorizedAccessException("Bạn cần đăng nhập để xem vé đã đặt."); + } + + var registrations = await _context.Registrations + .AsNoTracking() + .Include(r => r.TicketType) + .ThenInclude(tt => tt.Event) + .Include(u => u.User) + .Where(r => r.UserId == userId.Value) + .OrderByDescending(r => r.RegistrationDate ?? r.TicketType.Event.StartTime) + .ToListAsync(); + + return registrations.Select(r => new UserRegistrationDto + { + RegistrationId = r.RegistrationId, + UserId = r.UserId, + TicketTypeId = r.TicketTypeId, + TicketTypeName = r.TicketType.Name, + TicketPrice = r.TicketType.Price, + RegistrationDate = r.RegistrationDate, + UniqueToken = r.UniqueToken, + CheckInTime = r.CheckInTime, + EventId = r.TicketType.Event.EventId, + Title = r.TicketType.Event.Title, + StartTime = r.TicketType.Event.StartTime, + EndTime = r.TicketType.Event.EndTime, + EventType = r.TicketType.Event.EventType, + Location = r.TicketType.Event.Location, + OnlineUrl = r.TicketType.Event.OnlineUrl, + EventStatus = r.TicketType.Event.EventStatus, + IsOnline = !string.IsNullOrWhiteSpace(r.TicketType.Event.OnlineUrl) + }); + } + + public async Task GetJoinMeetingInfoAsync(Guid registrationId) + { + var currentUserId = _currentUserService.GetCurrentUserId(); + if (currentUserId == null) + { + throw new UnauthorizedAccessException("Bạn cần đăng nhập để tham gia sự kiện."); + } + + var registration = await _context.Registrations + .Include(r => r.TicketType) + .ThenInclude(tt => tt.Event) + .FirstOrDefaultAsync(r => r.RegistrationId == registrationId && r.UserId == currentUserId.Value); + + if (registration == null) + { + throw new KeyNotFoundException("Không tìm thấy vé hợp lệ cho tài khoản hiện tại."); + } + + var eventEntity = registration.TicketType.Event; + if (eventEntity == null || string.IsNullOrWhiteSpace(eventEntity.OnlineUrl)) + { + throw new InvalidOperationException("Sự kiện này không hỗ trợ tham gia trực tuyến."); + } + + var roomId = $"event-{eventEntity.EventId}"; + var userName = _currentUserService.GetCurrentUserEmail() ?? "User"; + var userIdentifier = registration.RegistrationId.ToString("N"); + + return new JoinMeetingInfoDto + { + RegistrationId = registration.RegistrationId, + UserId = registration.UserId, + EventId = eventEntity.EventId, + Title = eventEntity.Title, + StartTime = eventEntity.StartTime, + EndTime = eventEntity.EndTime, + RoomId = roomId, + Role = "Host", + UserName = userName, + UserIdentifier = userIdentifier + }; + } } } diff --git a/EventPlatform.WebApi/Controllers/AdminController.cs b/EventPlatform.WebApi/Controllers/AdminController.cs new file mode 100644 index 0000000..8cd73a2 --- /dev/null +++ b/EventPlatform.WebApi/Controllers/AdminController.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading.Tasks; +using EventPlatform.Application.Contracts.Dtos; +using EventPlatform.Application.Contracts.Requests.Admin; +using EventPlatform.Application.Services.Interfaces.Admin; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace EventPlatform.WebApi.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class AdminController : ControllerBase + { + private readonly IAdminService _adminService; + + public AdminController(IAdminService adminService) + { + _adminService = adminService; + } + + [HttpGet("dashboard/summary")] + public async Task> GetDashboardSummary() + { + var summary = await _adminService.GetDashboardSummaryAsync(); + return Ok(summary); + } + + [HttpGet("events")] + public async Task>> GetEvents([FromQuery] string? status, [FromQuery] string? keyword, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20) + { + var events = await _adminService.GetEventsAsync(status, keyword, pageNumber, pageSize); + return Ok(events); + } + + [HttpPost("events/{eventId:int}/approve")] + public async Task ApproveEvent(int eventId) + { + var updated = await _adminService.UpdateEventStatusAsync(eventId, "Approved"); + if (!updated) + { + return NotFound(new { message = "Event not found." }); + } + + return NoContent(); + } + + [HttpPost("events/{eventId:int}/reject")] + public async Task RejectEvent(int eventId, [FromBody] RejectEventRequest request) + { + var updated = await _adminService.UpdateEventStatusAsync(eventId, "Rejected"); + if (!updated) + { + return NotFound(new { message = "Event not found." }); + } + + return NoContent(); + } + + [HttpGet("transactions")] + public async Task>> GetTransactions([FromQuery] string? status, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20) + { + var transactions = await _adminService.GetTransactionsAsync(status, pageNumber, pageSize); + return Ok(transactions); + } + + [HttpGet("users")] + public async Task>> GetUsers([FromQuery] string? role, [FromQuery] string? accountStatus, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20) + { + var users = await _adminService.GetUsersAsync(role, accountStatus, pageNumber, pageSize); + return Ok(users); + } + + [HttpPost("users/{userId:guid}/ban")] + public async Task BanUser(Guid userId) + { + var updated = await _adminService.UpdateUserStatusAsync(userId, "Banned"); + if (!updated) + { + return NotFound(new { message = "User not found." }); + } + + return NoContent(); + } + + [HttpPost("users/{userId:guid}/unban")] + public async Task UnbanUser(Guid userId) + { + var updated = await _adminService.UpdateUserStatusAsync(userId, "Active"); + if (!updated) + { + return NotFound(new { message = "User not found." }); + } + + return NoContent(); + } + } +} diff --git a/EventPlatform.WebApi/Controllers/RegistrationsController.cs b/EventPlatform.WebApi/Controllers/RegistrationsController.cs index 0e63fca..48a1619 100644 --- a/EventPlatform.WebApi/Controllers/RegistrationsController.cs +++ b/EventPlatform.WebApi/Controllers/RegistrationsController.cs @@ -6,9 +6,9 @@ namespace EventPlatform.WebApi.Controllers { - [Route("api/[controller]")] + [Route("api/[controller]")] [ApiController] - [Authorize] + [Authorize] public class RegistrationsController : ControllerBase { private readonly IRegistrationService _registrationService; @@ -18,7 +18,7 @@ public RegistrationsController(IRegistrationService registrationService) _registrationService = registrationService; } - [HttpPost] + [HttpPost] public async Task CreateBooking([FromBody] CreateBookingRequest request) { try @@ -57,5 +57,49 @@ public async Task CancelBooking([FromBody] CancelBookingRequest r return BadRequest(new { message = ex.Message }); } } + + [HttpGet("mine")] + public async Task GetMyRegistrations() + { + try + { + var result = await _registrationService.GetMyRegistrationsAsync(); + return Ok(result); + } + catch (UnauthorizedAccessException ex) + { + return Unauthorized(new { message = ex.Message }); + } + catch (Exception ex) + { + return StatusCode(500, new { message = "Lỗi hệ thống: " + ex.Message }); + } + } + + [HttpGet("{registrationId:guid}/join-info")] + public async Task GetJoinInfo(Guid registrationId) + { + try + { + var result = await _registrationService.GetJoinMeetingInfoAsync(registrationId); + return Ok(result); + } + catch (UnauthorizedAccessException ex) + { + return Unauthorized(new { message = ex.Message }); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + catch (Exception ex) + { + return StatusCode(500, new { message = "Lỗi hệ thống: " + ex.Message }); + } + } } }