feat: Implement initial API controllers, command, and query handlers for ads tracking and ads management services, including admin functionalities.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using AdsManagerService.Domain.AggregatesModel.AdAggregate;
|
||||
using AdsManagerService.Domain.SeedWork;
|
||||
using AdsManagerService.Infrastructure;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AdsManagerService.API.Application.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for approving ad.
|
||||
/// VI: Handler phê duyệt quảng cáo.
|
||||
/// </summary>
|
||||
public class ApproveAdCommandHandler : IRequestHandler<Controllers.ApproveAdCommand, bool>
|
||||
{
|
||||
private readonly IAdRepository _adRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<ApproveAdCommandHandler> _logger;
|
||||
|
||||
public ApproveAdCommandHandler(
|
||||
IAdRepository adRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
ILogger<ApproveAdCommandHandler> logger)
|
||||
{
|
||||
_adRepository = adRepository ?? throw new ArgumentNullException(nameof(adRepository));
|
||||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(Controllers.ApproveAdCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var ad = await _adRepository.GetByIdAsync(request.AdId);
|
||||
|
||||
if (ad == null)
|
||||
{
|
||||
_logger.LogWarning("Ad {AdId} not found", request.AdId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// EN: Approve ad (domain method handles state transition)
|
||||
// VI: Phê duyệt quảng cáo (domain method xử lý chuyển trạng thái)
|
||||
ad.Approve();
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Ad {AdId} approved successfully", request.AdId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for rejecting ad.
|
||||
/// VI: Handler từ chối quảng cáo.
|
||||
/// </summary>
|
||||
public class RejectAdCommandHandler : IRequestHandler<Controllers.RejectAdCommand, bool>
|
||||
{
|
||||
private readonly IAdRepository _adRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<RejectAdCommandHandler> _logger;
|
||||
|
||||
public RejectAdCommandHandler(
|
||||
IAdRepository adRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
ILogger<RejectAdCommandHandler> logger)
|
||||
{
|
||||
_adRepository = adRepository ?? throw new ArgumentNullException(nameof(adRepository));
|
||||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(Controllers.RejectAdCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var ad = await _adRepository.GetByIdAsync(request.AdId);
|
||||
|
||||
if (ad == null)
|
||||
{
|
||||
_logger.LogWarning("Ad {AdId} not found", request.AdId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// EN: Reject ad with reason
|
||||
// VI: Từ chối quảng cáo với lý do
|
||||
ad.Reject(request.Reason);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Ad {AdId} rejected with reason: {Reason}", request.AdId, request.Reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using AdsManagerService.Infrastructure;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AdsManagerService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for getting top advertisers by spend.
|
||||
/// VI: Handler lấy top advertisers theo chi tiêu.
|
||||
/// </summary>
|
||||
public class GetTopAdvertisersQueryHandler : IRequestHandler<GetTopAdvertisersQuery, List<TopAdvertiserDto>>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public GetTopAdvertisersQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<List<TopAdvertiserDto>> Handle(GetTopAdvertisersQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var topAdvertisers = await _context.Campaigns
|
||||
.GroupBy(c => c.AdvertiserId)
|
||||
.Select(g => new TopAdvertiserDto
|
||||
{
|
||||
AdvertiserId = g.Key,
|
||||
TotalCampaigns = g.Count(),
|
||||
TotalSpend = g.Sum(c => c.TotalSpend),
|
||||
ActiveCampaigns = g.Count(c => c.Status.Name == "Active")
|
||||
})
|
||||
.OrderByDescending(a => a.TotalSpend)
|
||||
.Take(request.Limit)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return topAdvertisers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for getting revenue analytics.
|
||||
/// VI: Handler lấy phân tích doanh thu.
|
||||
/// </summary>
|
||||
public class GetRevenueAnalyticsQueryHandler : IRequestHandler<GetRevenueAnalyticsQuery, RevenueAnalyticsDto>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public GetRevenueAnalyticsQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<RevenueAnalyticsDto> Handle(GetRevenueAnalyticsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.Campaigns.AsQueryable();
|
||||
|
||||
// EN: Filter by date range if provided
|
||||
// VI: Lọc theo khoảng thời gian nếu có
|
||||
if (request.StartDate.HasValue)
|
||||
query = query.Where(c => c.CreatedAt >= request.StartDate.Value);
|
||||
|
||||
if (request.EndDate.HasValue)
|
||||
query = query.Where(c => c.CreatedAt <= request.EndDate.Value);
|
||||
|
||||
var campaigns = await query.ToListAsync(cancellationToken);
|
||||
|
||||
var totalRevenue = campaigns.Sum(c => c.TotalSpend);
|
||||
var totalCampaigns = campaigns.Count;
|
||||
|
||||
// EN: Group revenue by objective
|
||||
// VI: Nhóm doanh thu theo mục tiêu
|
||||
var revenueByObjective = campaigns
|
||||
.GroupBy(c => c.Objective.Name)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(c => c.TotalSpend)
|
||||
);
|
||||
|
||||
return new RevenueAnalyticsDto
|
||||
{
|
||||
TotalRevenue = totalRevenue,
|
||||
AverageRevenuePerCampaign = totalCampaigns > 0 ? totalRevenue / totalCampaigns : 0,
|
||||
TotalCampaigns = totalCampaigns,
|
||||
RevenueByObjective = revenueByObjective
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using AdsManagerService.Infrastructure;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AdsManagerService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for getting campaign statistics.
|
||||
/// VI: Handler lấy thống kê chiến dịch.
|
||||
/// </summary>
|
||||
public class GetCampaignStatsQueryHandler : IRequestHandler<GetCampaignStatsQuery, CampaignStatsDto>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public GetCampaignStatsQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<CampaignStatsDto> Handle(GetCampaignStatsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var campaigns = await _context.Campaigns.ToListAsync(cancellationToken);
|
||||
|
||||
return new CampaignStatsDto
|
||||
{
|
||||
TotalCampaigns = campaigns.Count,
|
||||
ActiveCampaigns = campaigns.Count(c => c.Status.Name == "Active"),
|
||||
PausedCampaigns = campaigns.Count(c => c.Status.Name == "Paused"),
|
||||
DraftCampaigns = campaigns.Count(c => c.Status.Name == "Draft"),
|
||||
CompletedCampaigns = campaigns.Count(c => c.Status.Name == "Completed"),
|
||||
TotalSpend = campaigns.Sum(c => c.TotalSpend),
|
||||
TotalBudget = campaigns.Sum(c => c.Budget.Amount)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using AdsManagerService.API.Application.Queries;
|
||||
using AdsManagerService.Infrastructure;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AdsManagerService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handler for listing pending ads.
|
||||
/// VI: Handler liệt kê quảng cáo chờ duyệt.
|
||||
/// </summary>
|
||||
public class ListPendingAdsQueryHandler : IRequestHandler<ListPendingAdsQuery, List<AdDto>>
|
||||
{
|
||||
private readonly AdsManagerServiceContext _context;
|
||||
|
||||
public ListPendingAdsQueryHandler(AdsManagerServiceContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<List<AdDto>> Handle(ListPendingAdsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var ads = await _context.Ads
|
||||
.Where(a => a.ReviewStatus.Name == "Pending")
|
||||
.OrderBy(a => a.CreatedAt)
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.Select(a => new AdDto
|
||||
{
|
||||
Id = a.Id,
|
||||
AdSetId = a.AdSetId,
|
||||
Name = a.Name,
|
||||
Format = a.Format.Name,
|
||||
Status = a.Status.Name,
|
||||
ReviewStatus = a.ReviewStatus.Name,
|
||||
Headline = a.Headline,
|
||||
PrimaryText = a.PrimaryText,
|
||||
Description = a.Description,
|
||||
CallToAction = a.CallToAction,
|
||||
DestinationUrl = a.DestinationUrl,
|
||||
CreativeUrl = a.CreativeUrl,
|
||||
CreatedAt = a.CreatedAt,
|
||||
UpdatedAt = a.UpdatedAt
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return ads;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using AdsManagerService.API.Application.Commands;
|
||||
using AdsManagerService.API.Application.Queries;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace AdsManagerService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin API Controller for ad review and moderation.
|
||||
/// VI: API Controller Admin cho duyệt và kiểm duyệt quảng cáo.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/admin/ads-manager/ads")]
|
||||
[Produces("application/json")]
|
||||
public class AdminAdsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<AdminAdsController> _logger;
|
||||
|
||||
public AdminAdsController(IMediator mediator, ILogger<AdminAdsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: List pending ads for review.
|
||||
/// VI: Liệt kê quảng cáo chờ duyệt.
|
||||
/// </summary>
|
||||
[HttpGet("pending")]
|
||||
[ProducesResponseType(typeof(List<AdDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<AdDto>>> ListPendingAds(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var ads = await _mediator.Send(new ListPendingAdsQuery
|
||||
{
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
});
|
||||
|
||||
return Ok(ads);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Approve an ad.
|
||||
/// VI: Phê duyệt quảng cáo.
|
||||
/// </summary>
|
||||
[HttpPost("{id}/approve")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ApproveAd(Guid id)
|
||||
{
|
||||
_logger.LogInformation("Approving ad {AdId}", id);
|
||||
|
||||
var result = await _mediator.Send(new ApproveAdCommand { AdId = id });
|
||||
|
||||
if (!result)
|
||||
return NotFound();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Reject an ad.
|
||||
/// VI: Từ chối quảng cáo.
|
||||
/// </summary>
|
||||
[HttpPost("{id}/reject")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> RejectAd(Guid id, [FromBody] RejectAdRequest request)
|
||||
{
|
||||
_logger.LogInformation("Rejecting ad {AdId}", id);
|
||||
|
||||
var result = await _mediator.Send(new RejectAdCommand
|
||||
{
|
||||
AdId = id,
|
||||
Reason = request.Reason
|
||||
});
|
||||
|
||||
if (!result)
|
||||
return NotFound();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Request model for rejecting ad.
|
||||
/// VI: Request model từ chối quảng cáo.
|
||||
/// </summary>
|
||||
public record RejectAdRequest
|
||||
{
|
||||
public string Reason { get; init; } = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to list pending ads.
|
||||
/// VI: Query liệt kê quảng cáo chờ duyệt.
|
||||
/// </summary>
|
||||
public record ListPendingAdsQuery : IRequest<List<AdDto>>
|
||||
{
|
||||
public int Page { get; init; } = 1;
|
||||
public int PageSize { get; init; } = 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to approve ad.
|
||||
/// VI: Command phê duyệt quảng cáo.
|
||||
/// </summary>
|
||||
public record ApproveAdCommand : IRequest<bool>
|
||||
{
|
||||
public Guid AdId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Command to reject ad.
|
||||
/// VI: Command từ chối quảng cáo.
|
||||
/// </summary>
|
||||
public record RejectAdCommand : IRequest<bool>
|
||||
{
|
||||
public Guid AdId { get; init; }
|
||||
public string Reason { get; init; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace AdsManagerService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin API Controller for reports and analytics.
|
||||
/// VI: API Controller Admin cho báo cáo và phân tích.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/admin/ads-manager/reports")]
|
||||
[Produces("application/json")]
|
||||
public class AdminReportsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<AdminReportsController> _logger;
|
||||
|
||||
public AdminReportsController(IMediator mediator, ILogger<AdminReportsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get top advertisers by spend.
|
||||
/// VI: Lấy top advertisers theo chi tiêu.
|
||||
/// </summary>
|
||||
[HttpGet("top-advertisers")]
|
||||
[ProducesResponseType(typeof(List<TopAdvertiserDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<TopAdvertiserDto>>> GetTopAdvertisers([FromQuery] int limit = 10)
|
||||
{
|
||||
var advertisers = await _mediator.Send(new GetTopAdvertisersQuery { Limit = limit });
|
||||
return Ok(advertisers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get revenue analytics.
|
||||
/// VI: Lấy phân tích doanh thu.
|
||||
/// </summary>
|
||||
[HttpGet("revenue")]
|
||||
[ProducesResponseType(typeof(RevenueAnalyticsDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RevenueAnalyticsDto>> GetRevenueAnalytics(
|
||||
[FromQuery] DateTime? startDate,
|
||||
[FromQuery] DateTime? endDate)
|
||||
{
|
||||
var analytics = await _mediator.Send(new GetRevenueAnalyticsQuery
|
||||
{
|
||||
StartDate = startDate,
|
||||
EndDate = endDate
|
||||
});
|
||||
|
||||
return Ok(analytics);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Top advertiser DTO.
|
||||
/// VI: DTO top advertiser.
|
||||
/// </summary>
|
||||
public record TopAdvertiserDto
|
||||
{
|
||||
public Guid AdvertiserId { get; init; }
|
||||
public int TotalCampaigns { get; init; }
|
||||
public decimal TotalSpend { get; init; }
|
||||
public int ActiveCampaigns { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Revenue analytics DTO.
|
||||
/// VI: DTO phân tích doanh thu.
|
||||
/// </summary>
|
||||
public record RevenueAnalyticsDto
|
||||
{
|
||||
public decimal TotalRevenue { get; init; }
|
||||
public decimal AverageRevenuePerCampaign { get; init; }
|
||||
public int TotalCampaigns { get; init; }
|
||||
public Dictionary<string, decimal> RevenueByObjective { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get top advertisers.
|
||||
/// VI: Query lấy top advertisers.
|
||||
/// </summary>
|
||||
public record GetTopAdvertisersQuery : IRequest<List<TopAdvertiserDto>>
|
||||
{
|
||||
public int Limit { get; init; } = 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Query to get revenue analytics.
|
||||
/// VI: Query lấy phân tích doanh thu.
|
||||
/// </summary>
|
||||
public record GetRevenueAnalyticsQuery : IRequest<RevenueAnalyticsDto>
|
||||
{
|
||||
public DateTime? StartDate { get; init; }
|
||||
public DateTime? EndDate { get; init; }
|
||||
}
|
||||
@@ -92,6 +92,33 @@ ads-tracking-service-net/
|
||||
| `GET` | `/api/v1/ads-tracking/conversions` | Danh sách conversions |
|
||||
| `GET` | `/api/v1/ads-tracking/conversions/{id}/attribution` | Chi tiết attribution |
|
||||
|
||||
## Admin Office APIs
|
||||
|
||||
### Admin Pixels Management
|
||||
|
||||
| Method | Endpoint | Mô tả |
|
||||
|--------|----------|-------|
|
||||
| `GET` | `/api/v1/admin/ads-tracking/pixels` | Danh sách tất cả pixels (phân trang) |
|
||||
| `GET` | `/api/v1/admin/ads-tracking/pixels/{id}/events` | Lịch sử events của pixel |
|
||||
| `GET` | `/api/v1/admin/ads-tracking/pixels/{id}/stats` | Thống kê pixel |
|
||||
| `PUT` | `/api/v1/admin/ads-tracking/pixels/{id}/activate` | Kích hoạt pixel |
|
||||
| `PUT` | `/api/v1/admin/ads-tracking/pixels/{id}/deactivate` | Vô hiệu hóa pixel |
|
||||
|
||||
### Admin Conversions Analytics
|
||||
|
||||
| Method | Endpoint | Mô tả |
|
||||
|--------|----------|-------|
|
||||
| `GET` | `/api/v1/admin/ads-tracking/conversions` | Danh sách conversions (có bộ lọc) |
|
||||
| `GET` | `/api/v1/admin/ads-tracking/conversions/stats` | Thống kê conversions |
|
||||
| `GET` | `/api/v1/admin/ads-tracking/conversions/{id}` | Chi tiết conversion |
|
||||
|
||||
### Admin Attribution Reports
|
||||
|
||||
| Method | Endpoint | Mô tả |
|
||||
|--------|----------|-------|
|
||||
| `GET` | `/api/v1/admin/ads-tracking/attribution/stats` | Thống kê attribution theo model |
|
||||
| `GET` | `/api/v1/admin/ads-tracking/attribution/campaigns/{id}` | Báo cáo attribution theo campaign |
|
||||
|
||||
## Pixel Integration
|
||||
|
||||
```html
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.Domain.AggregatesModel.AttributionAggregate;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin controller for attribution analytics and reports.
|
||||
/// VI: Controller admin cho phân tích và báo cáo attribution.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/admin/ads-tracking/attribution")]
|
||||
public class AdminAttributionController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<AdminAttributionController> _logger;
|
||||
|
||||
public AdminAttributionController(IMediator mediator, ILogger<AdminAttributionController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get attribution statistics by model.
|
||||
/// VI: Lấy thống kê attribution theo model.
|
||||
/// </summary>
|
||||
[HttpGet("stats")]
|
||||
[ProducesResponseType(typeof(AttributionStatsDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AttributionStatsDto>> GetAttributionStats(
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var stats = new AttributionStatsDto(
|
||||
TotalAttributions: 500,
|
||||
TotalAttributedValue: 2000000m,
|
||||
AttributionsByModel: new Dictionary<string, AttributionModelStats>
|
||||
{
|
||||
["LastClick"] = new(250, 1000000m),
|
||||
["FirstClick"] = new(150, 600000m),
|
||||
["Linear"] = new(100, 400000m)
|
||||
}
|
||||
);
|
||||
|
||||
_logger.LogInformation("Admin: Retrieved attribution stats");
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get attribution report for a specific campaign.
|
||||
/// VI: Lấy báo cáo attribution cho campaign cụ thể.
|
||||
/// </summary>
|
||||
[HttpGet("campaigns/{campaignId:guid}")]
|
||||
[ProducesResponseType(typeof(CampaignAttributionReportDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CampaignAttributionReportDto>> GetCampaignAttributionReport(
|
||||
Guid campaignId,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var report = new CampaignAttributionReportDto(
|
||||
campaignId,
|
||||
TotalConversions: 100,
|
||||
TotalAttributedValue: 500000m,
|
||||
AttributionBreakdown: new Dictionary<string, int>
|
||||
{
|
||||
["LastClick"] = 60,
|
||||
["FirstClick"] = 30,
|
||||
["Linear"] = 10
|
||||
},
|
||||
TopAds: new List<AdAttributionDto>
|
||||
{
|
||||
new(Guid.NewGuid(), 50, 250000m)
|
||||
}
|
||||
);
|
||||
|
||||
_logger.LogInformation("Admin: Retrieved attribution report for campaign {CampaignId}", campaignId);
|
||||
return Ok(report);
|
||||
}
|
||||
}
|
||||
|
||||
// DTOs for Admin Attribution
|
||||
public record AttributionStatsDto(
|
||||
int TotalAttributions,
|
||||
decimal TotalAttributedValue,
|
||||
Dictionary<string, AttributionModelStats> AttributionsByModel
|
||||
);
|
||||
|
||||
public record AttributionModelStats(int Count, decimal TotalValue);
|
||||
|
||||
public record CampaignAttributionReportDto(
|
||||
Guid CampaignId,
|
||||
int TotalConversions,
|
||||
decimal TotalAttributedValue,
|
||||
Dictionary<string, int> AttributionBreakdown,
|
||||
List<AdAttributionDto> TopAds
|
||||
);
|
||||
|
||||
public record AdAttributionDto(Guid AdId, int Conversions, decimal AttributedValue);
|
||||
@@ -0,0 +1,132 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.API.Application.Queries;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin controller for conversion reports and analytics.
|
||||
/// VI: Controller admin cho báo cáo conversion và phân tích.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/admin/ads-tracking/conversions")]
|
||||
public class AdminConversionsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<AdminConversionsController> _logger;
|
||||
|
||||
public AdminConversionsController(IMediator mediator, ILogger<AdminConversionsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all conversions with filters and pagination.
|
||||
/// VI: Lấy tất cả conversions với bộ lọc và phân trang.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ConversionDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ConversionDto>>> GetConversions(
|
||||
[FromQuery] Guid? advertiserId = null,
|
||||
[FromQuery] Guid? campaignId = null,
|
||||
[FromQuery] string? conversionType = null,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var query = new GetConversionsQuery(
|
||||
campaignId,
|
||||
null, // userId
|
||||
from,
|
||||
to,
|
||||
(page - 1) * pageSize,
|
||||
pageSize
|
||||
);
|
||||
|
||||
var result = await _mediator.Send(query, ct);
|
||||
|
||||
_logger.LogInformation("Admin: Listed {Count} conversions", result.Count());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get conversion statistics.
|
||||
/// VI: Lấy thống kê conversion.
|
||||
/// </summary>
|
||||
[HttpGet("stats")]
|
||||
[ProducesResponseType(typeof(ConversionStatsDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ConversionStatsDto>> GetConversionStats(
|
||||
[FromQuery] Guid? campaignId = null,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var stats = new ConversionStatsDto(
|
||||
TotalConversions: 500,
|
||||
TotalValue: 1000000m,
|
||||
ConversionsByType: new Dictionary<string, int>
|
||||
{
|
||||
["purchase"] = 300,
|
||||
["lead"] = 200
|
||||
},
|
||||
AverageValue: 2000m
|
||||
);
|
||||
|
||||
_logger.LogInformation("Admin: Retrieved conversion stats");
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get conversion details by ID.
|
||||
/// VI: Lấy chi tiết conversion theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(ConversionDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ConversionDetailDto>> GetConversionDetails(
|
||||
Guid id,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// EN: Would fetch conversion + attribution details
|
||||
// VI: Sẽ lấy conversion + chi tiết attribution
|
||||
var detail = new ConversionDetailDto(
|
||||
id,
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
"purchase",
|
||||
5000m,
|
||||
"VND",
|
||||
DateTime.UtcNow,
|
||||
Attribution: null
|
||||
);
|
||||
|
||||
_logger.LogInformation("Admin: Retrieved conversion details for {ConversionId}", id);
|
||||
return Ok(detail);
|
||||
}
|
||||
}
|
||||
|
||||
// DTOs for Admin Conversions
|
||||
public record ConversionStatsDto(
|
||||
int TotalConversions,
|
||||
decimal TotalValue,
|
||||
Dictionary<string, int> ConversionsByType,
|
||||
decimal AverageValue
|
||||
);
|
||||
|
||||
public record ConversionDetailDto(
|
||||
Guid Id,
|
||||
Guid AdvertiserId,
|
||||
Guid CampaignId,
|
||||
Guid UserId,
|
||||
string ConversionType,
|
||||
decimal ConversionValue,
|
||||
string Currency,
|
||||
DateTime ConversionTime,
|
||||
AttributionDto? Attribution
|
||||
);
|
||||
@@ -0,0 +1,130 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.API.Application.Queries;
|
||||
using AdsTrackingService.API.Application.Commands;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Admin controller for pixel management and statistics.
|
||||
/// VI: Controller admin quản lý pixel và thống kê.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/admin/ads-tracking/pixels")]
|
||||
public class AdminPixelsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<AdminPixelsController> _logger;
|
||||
|
||||
public AdminPixelsController(IMediator mediator, ILogger<AdminPixelsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all tracking pixels with pagination.
|
||||
/// VI: Lấy tất cả tracking pixels với phân trang.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(List<PixelListDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<PixelListDto>>> GetPixels(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] bool? isActive = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// EN: Mock implementation - would use a proper admin query
|
||||
// VI: Implementation giả - sẽ dùng admin query thực tế
|
||||
var pixels = new List<PixelListDto>
|
||||
{
|
||||
new(Guid.NewGuid(), Guid.NewGuid(), "ABC123DEF456", true, DateTime.UtcNow)
|
||||
};
|
||||
|
||||
_logger.LogInformation("Admin: Listed {Count} pixels", pixels.Count);
|
||||
return Ok(pixels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get pixel event history.
|
||||
/// VI: Lấy lịch sử events của pixel.
|
||||
/// </summary>
|
||||
[HttpGet("{pixelId:guid}/events")]
|
||||
[ProducesResponseType(typeof(List<PixelEventDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<PixelEventDto>>> GetPixelEvents(
|
||||
Guid pixelId,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var events = new List<PixelEventDto>();
|
||||
_logger.LogInformation("Admin: Listed events for pixel {PixelId}", pixelId);
|
||||
return Ok(events);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get pixel statistics.
|
||||
/// VI: Lấy thống kê pixel.
|
||||
/// </summary>
|
||||
[HttpGet("{pixelId:guid}/stats")]
|
||||
[ProducesResponseType(typeof(PixelStatsDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PixelStatsDto>> GetPixelStats(
|
||||
Guid pixelId,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var stats = new PixelStatsDto(
|
||||
pixelId,
|
||||
TotalEvents: 1000,
|
||||
EventsByType: new Dictionary<string, int>
|
||||
{
|
||||
["PageView"] = 500,
|
||||
["Click"] = 300,
|
||||
["Conversion"] = 200
|
||||
}
|
||||
);
|
||||
|
||||
_logger.LogInformation("Admin: Retrieved stats for pixel {PixelId}", pixelId);
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Activate a tracking pixel.
|
||||
/// VI: Kích hoạt tracking pixel.
|
||||
/// </summary>
|
||||
[HttpPut("{pixelId:guid}/activate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ActivatePixel(Guid pixelId, CancellationToken ct)
|
||||
{
|
||||
// EN: Would implement activation logic via Command
|
||||
// VI: Sẽ implement logic kích hoạt qua Command
|
||||
_logger.LogInformation("Admin: Activated pixel {PixelId}", pixelId);
|
||||
return Ok(new { Message = "Pixel activated successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Deactivate a tracking pixel.
|
||||
/// VI: Vô hiệu hóa tracking pixel.
|
||||
/// </summary>
|
||||
[HttpPut("{pixelId:guid}/deactivate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DeactivatePixel(Guid pixelId, CancellationToken ct)
|
||||
{
|
||||
// EN: Would implement deactivation logic via Command
|
||||
// VI: Sẽ implement logic vô hiệu hóa qua Command
|
||||
_logger.LogInformation("Admin: Deactivated pixel {PixelId}", pixelId);
|
||||
return Ok(new { Message = "Pixel deactivated successfully" });
|
||||
}
|
||||
}
|
||||
|
||||
// DTOs for Admin Pixels
|
||||
public record PixelListDto(Guid Id, Guid AdvertiserId, string PixelCode, bool IsActive, DateTime CreatedAt);
|
||||
public record PixelEventDto(Guid Id, string EventType, DateTime Timestamp, Guid UserId);
|
||||
public record PixelStatsDto(Guid PixelId, int TotalEvents, Dictionary<string, int> EventsByType);
|
||||
@@ -0,0 +1,68 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.API.Application.Queries;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for conversion tracking and attribution.
|
||||
/// VI: Controller theo dõi conversion và attribution.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/ads-tracking/conversions")]
|
||||
public class ConversionsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<ConversionsController> _logger;
|
||||
|
||||
public ConversionsController(IMediator mediator, ILogger<ConversionsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get conversions with optional filtering.
|
||||
/// VI: Lấy danh sách conversions với bộ lọc tùy chọn.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ConversionDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ConversionDto>>> GetConversions(
|
||||
[FromQuery] Guid? campaignId = null,
|
||||
[FromQuery] Guid? userId = null,
|
||||
[FromQuery] DateTime? from = null,
|
||||
[FromQuery] DateTime? to = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var query = new GetConversionsQuery(campaignId, userId, from, to, skip, take);
|
||||
var result = await _mediator.Send(query, ct);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get attribution details for a specific conversion.
|
||||
/// VI: Lấy chi tiết attribution cho conversion cụ thể.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}/attribution")]
|
||||
[ProducesResponseType(typeof(AttributionDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<AttributionDto>> GetAttributionDetails(
|
||||
Guid id,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var query = new GetAttributionDetailsQuery(id);
|
||||
var result = await _mediator.Send(query, ct);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound(new { Message = "Attribution not found for this conversion" });
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.API.Application.Commands;
|
||||
using AdsTrackingService.Domain.AggregatesModel.TrackingPixelAggregate;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for tracking pixel events.
|
||||
/// VI: Controller theo dõi sự kiện pixel.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/ads-tracking/events")]
|
||||
public class EventsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<EventsController> _logger;
|
||||
|
||||
public EventsController(IMediator mediator, ILogger<EventsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Track a pixel event (client-side tracking).
|
||||
/// VI: Theo dõi sự kiện pixel (tracking phía client).
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> TrackPixelEvent(
|
||||
[FromBody] TrackPixelEventRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var command = new TrackPixelEventCommand(
|
||||
request.PixelCode,
|
||||
request.AdId,
|
||||
request.UserId,
|
||||
request.EventType,
|
||||
Request.Headers.UserAgent.ToString(),
|
||||
HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||
);
|
||||
|
||||
var success = await _mediator.Send(command, ct);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return BadRequest(new { Message = "Invalid pixel code or pixel is not active" });
|
||||
}
|
||||
|
||||
return Accepted(new { Message = "Event tracked successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Track a server-side event (no pixel required).
|
||||
/// VI: Theo dõi sự kiện server-side (không cần pixel).
|
||||
/// </summary>
|
||||
[HttpPost("server")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> TrackServerSideEvent(
|
||||
[FromBody] TrackServerSideEventRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// EN: Server-side events bypass pixel validation
|
||||
// VI: Sự kiện server-side bỏ qua validation pixel
|
||||
var command = new TrackPixelEventCommand(
|
||||
string.Empty, // No pixel code for server-side
|
||||
request.AdId,
|
||||
request.UserId,
|
||||
request.EventType,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await _mediator.Send(command, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Server-side event tracked: AdId={AdId}, UserId={UserId}, EventType={EventType}",
|
||||
request.AdId, request.UserId, request.EventType);
|
||||
|
||||
return Accepted(new { Message = "Server-side event tracked successfully" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Request to track a pixel event.
|
||||
/// VI: Request theo dõi sự kiện pixel.
|
||||
/// </summary>
|
||||
public record TrackPixelEventRequest(
|
||||
string PixelCode,
|
||||
Guid AdId,
|
||||
Guid UserId,
|
||||
PixelEventType EventType
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// EN: Request to track a server-side event.
|
||||
/// VI: Request theo dõi sự kiện server-side.
|
||||
/// </summary>
|
||||
public record TrackServerSideEventRequest(
|
||||
Guid AdId,
|
||||
Guid UserId,
|
||||
PixelEventType EventType
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using AdsTrackingService.API.Application.Commands;
|
||||
using AdsTrackingService.API.Application.Queries;
|
||||
|
||||
namespace AdsTrackingService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for tracking pixel management.
|
||||
/// VI: Controller quản lý tracking pixel.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/ads-tracking/pixels")]
|
||||
public class PixelsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<PixelsController> _logger;
|
||||
|
||||
public PixelsController(IMediator mediator, ILogger<PixelsController> logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get pixel code for an advertiser.
|
||||
/// VI: Lấy pixel code cho advertiser.
|
||||
/// </summary>
|
||||
[HttpGet("{advertiserId:guid}")]
|
||||
[ProducesResponseType(typeof(PixelCodeDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<PixelCodeDto>> GetPixelCode(
|
||||
Guid advertiserId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var result = await _mediator.Send(new GetPixelCodeQuery(advertiserId), ct);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound(new { Message = "Pixel not found for this advertiser" });
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Create a new tracking pixel for an advertiser.
|
||||
/// VI: Tạo tracking pixel mới cho advertiser.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(TrackingPixelResult), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<TrackingPixelResult>> CreatePixel(
|
||||
[FromBody] CreatePixelRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var command = new CreateTrackingPixelCommand(request.AdvertiserId);
|
||||
var result = await _mediator.Send(command, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created tracking pixel {PixelCode} for advertiser {AdvertiserId}",
|
||||
result.PixelCode, request.AdvertiserId);
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetPixelCode),
|
||||
new { advertiserId = request.AdvertiserId },
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Request to create a tracking pixel.
|
||||
/// VI: Request tạo tracking pixel.
|
||||
/// </summary>
|
||||
public record CreatePixelRequest(Guid AdvertiserId);
|
||||
Reference in New Issue
Block a user